Author: nunofsantos
Date: 2007-10-08 11:51:14 -0400 (Mon, 08 Oct 2007)
New Revision: 967
Added:
mgmt/COPYING
mgmt/LICENSE
mgmt/bin/
mgmt/bin/cumindev
mgmt/bin/cumindev-etags
mgmt/cumin-test-0/
mgmt/cumin-test-0/bin
mgmt/cumin-test-0/lib
mgmt/cumin-test-0/python
mgmt/cumin-test-0/resources
mgmt/cumin/
mgmt/cumin/bin/
mgmt/cumin/bin/cumin-test
mgmt/cumin/python/
mgmt/cumin/python/cumin/
mgmt/cumin/python/cumin/__init__.py
mgmt/cumin/python/cumin/cluster.py
mgmt/cumin/python/cumin/cluster.strings
mgmt/cumin/python/cumin/demo.py
mgmt/cumin/python/cumin/exchange.py
mgmt/cumin/python/cumin/exchange.strings
mgmt/cumin/python/cumin/model.py
mgmt/cumin/python/cumin/page.py
mgmt/cumin/python/cumin/page.strings
mgmt/cumin/python/cumin/queue.py
mgmt/cumin/python/cumin/queue.strings
mgmt/cumin/python/cumin/realm.py
mgmt/cumin/python/cumin/realm.strings
mgmt/cumin/python/cumin/server.py
mgmt/cumin/python/cumin/server.strings
mgmt/cumin/python/cumin/virtualhost.py
mgmt/cumin/python/cumin/virtualhost.strings
mgmt/cumin/python/cumin/widgets.py
mgmt/cumin/python/cumin/widgets.strings
mgmt/cumin/python/wooly/
mgmt/cumin/python/wooly/__init__.py
mgmt/cumin/python/wooly/debug.py
mgmt/cumin/python/wooly/devel.py
mgmt/cumin/python/wooly/forms.py
mgmt/cumin/python/wooly/forms.strings
mgmt/cumin/python/wooly/model.py
mgmt/cumin/python/wooly/pages.py
mgmt/cumin/python/wooly/parameters.py
mgmt/cumin/python/wooly/resources.py
mgmt/cumin/python/wooly/server.py
mgmt/cumin/python/wooly/widgets.py
mgmt/cumin/python/wooly/widgets.strings
mgmt/cumin/resources/
mgmt/cumin/resources/ajax-test.html
mgmt/cumin/resources/ajax.js
mgmt/cumin/resources/exchange-20.png
mgmt/cumin/resources/exchange-36.png
mgmt/cumin/resources/exchange.svg
mgmt/cumin/resources/logo.png
mgmt/cumin/resources/logo.svg
mgmt/cumin/resources/object-20.png
mgmt/cumin/resources/object-36.png
mgmt/cumin/resources/object.svg
mgmt/cumin/resources/purple.png
mgmt/cumin/resources/queue-20.png
mgmt/cumin/resources/queue-36.png
mgmt/cumin/resources/queue.svg
mgmt/cumin/resources/radio-button-checked.png
mgmt/cumin/resources/radio-button.png
mgmt/cumin/resources/radio-buttons.svg
mgmt/cumin/resources/wooly.js
mgmt/etc/
mgmt/etc/cumindev.el
mgmt/etc/cumindev.profile
mgmt/lib/
mgmt/misc/
mgmt/misc/templates.py
mgmt/notes/
mgmt/notes/Errors
mgmt/notes/InterfaceQuestions
mgmt/notes/ProtocolQuestions
mgmt/notes/Todo
mgmt/notes/WoolyOverview
mgmt/notes/firebug-exception-bug.js
mgmt/notes/firebug-swallows-exceptions.html
Log:
initial import of cumin code
Added: mgmt/COPYING
===================================================================
--- mgmt/COPYING (rev 0)
+++ mgmt/COPYING 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,15 @@
+Copyright (C) 2007 Red Hat Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Added: mgmt/LICENSE
===================================================================
--- mgmt/LICENSE (rev 0)
+++ mgmt/LICENSE 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,280 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
Added: mgmt/bin/cumindev
===================================================================
--- mgmt/bin/cumindev (rev 0)
+++ mgmt/bin/cumindev 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+if [ -z "$CUMINDEV_HOME" ]; then
+ export CUMINDEV_HOME="${HOME}/cumindev"
+fi
+
+echo "CUMINDEV_HOME is ${CUMINDEV_HOME}"
+
+source "${CUMINDEV_HOME}/etc/cumindev.profile"
+
+cumindev-etags
+
+exec emacs -nw -l "${CUMINDEV_HOME}/etc/cumindev.el"
Property changes on: mgmt/bin/cumindev
___________________________________________________________________
Name: svn:executable
+ *
Added: mgmt/bin/cumindev-etags
===================================================================
--- mgmt/bin/cumindev-etags (rev 0)
+++ mgmt/bin/cumindev-etags 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+output="${CUMINDEV_HOME}/etc/cumindev.tags"
+
+find "$CUMINDEV_HOME" -name \*.py -print | etags --output="$output"
-
+find "$CUMINDEV_HOME" -name \*.strings -print \
+ | etags --append --output="$output" --regex='/^\[.*\][ \t]*$/\1/'
-
Property changes on: mgmt/bin/cumindev-etags
___________________________________________________________________
Name: svn:executable
+ *
Added: mgmt/cumin/bin/cumin-test
===================================================================
--- mgmt/cumin/bin/cumin-test (rev 0)
+++ mgmt/cumin/bin/cumin-test 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+import sys
+from cumin import CuminServer
+
+def load_args(argv):
+ args = dict()
+ key = None
+
+ for arg in sys.argv:
+ if arg.startswith("--"):
+ key = arg[2:]
+ elif key:
+ args[key] = arg
+ key = None
+
+ if key:
+ args[key] = None
+
+ return args
+
+if __name__ == "__main__":
+ args = load_args(sys.argv)
+
+ port = int(args.get("port", 9090))
+
+ if "profile" in args:
+ import profile, pstats
+
+ try:
+ profile.run("CuminServer().run()", "cumin-test-stats")
+ raise KeyboardInterrupt()
+ except KeyboardInterrupt:
+ stats = pstats.Stats("cumin-test-stats")
+
+ stats.sort_stats("cumulative").print_stats(15)
+ stats.sort_stats("time").print_stats(15)
+
+ stats.strip_dirs()
+
+ #stats.print_callers("interpolate")
+ #stats.print_callees("interpolate")
+ else:
+ CuminServer(port).run()
Property changes on: mgmt/cumin/bin/cumin-test
___________________________________________________________________
Name: svn:executable
+ *
Added: mgmt/cumin/python/cumin/__init__.py
===================================================================
--- mgmt/cumin/python/cumin/__init__.py (rev 0)
+++ mgmt/cumin/python/cumin/__init__.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,78 @@
+import sys, os
+
+from random import randint
+from wooly import Application, Session, Page
+from wooly.pages import CssPage, ResourcePage
+from wooly.server import WebServer
+from wooly.devel import DevelPage
+from wooly.parameters import IntegerParameter
+
+from model import CuminModel
+from demo import DemoData
+from page import CuminPage
+from queue import QueueXmlPage
+
+class Cumin(Application):
+ def __init__(self, model):
+ super(Cumin, self).__init__()
+
+ try:
+ self.home = os.environ["CUMIN_HOME"]
+ except KeyError:
+ sys.exit(1)
+
+ self.add_resource_dir(os.path.join(self.home, "resources"))
+
+ self.model = model
+
+ self.cumin_page = CuminPage(self, "cumin.html")
+ self.set_default_page(self.cumin_page)
+
+ self.add_page(CssPage(self, "cumin.css"))
+ self.add_page(ResourcePage(self, "resource"))
+ self.add_page(CountPage(self, "count"))
+ self.add_page(RandomIntegerPage(self, "randint"))
+ self.add_page(DevelPage(self, "devel.html"))
+ self.add_page(QueueXmlPage(self, "queue.xml"))
+
+class RandomIntegerPage(Page):
+ def __init__(self, app, name):
+ super(RandomIntegerPage, self).__init__(app, name)
+
+ self.min = IntegerParameter(app, "min");
+ self.add_parameter(self.min);
+
+ self.max = IntegerParameter(app, "max");
+ self.add_parameter(self.max);
+
+ def get_content_type(self, session):
+ return Page.xml_content_type
+
+ def do_render(self, session, object):
+ int = randint(self.min.get(session), self.max.get(session))
+ return "%s<integer>%i</integer>" %
(Page.xml_1_0_declaration, int)
+
+class CountPage(Page):
+ def __init__(self, app, name):
+ super(CountPage, self).__init__(app, name)
+
+ self.count = 0
+
+ def get_content_type(self, session):
+ return Page.xml_content_type
+
+ def do_render(self, session, object):
+ self.count += 1
+ return "<count>%i</count>" % self.count
+
+class CuminServer(WebServer):
+ def __init__(self, port=9090):
+ model = CuminModel()
+
+ data = DemoData(model)
+ data.load()
+ data.start_updates()
+
+ app = Cumin(model)
+
+ super(CuminServer, self).__init__(app, port)
Added: mgmt/cumin/python/cumin/cluster.py
===================================================================
--- mgmt/cumin/python/cumin/cluster.py (rev 0)
+++ mgmt/cumin/python/cumin/cluster.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,75 @@
+from wooly import *
+from wooly.widgets import *
+
+from server import *
+from queue import *
+from realm import *
+from exchange import *
+from widgets import *
+
+strings = StringCatalog(__file__)
+
+class ClusterSet(ItemSet):
+ def render_title(self, session, model):
+ return "Clusters (%i)" % len(model.get_clusters())
+
+ def get_items(self, session, model):
+ return sorted(model.get_clusters())
+
+ def render_item_href(self, session, cluster):
+ branch = session.branch()
+ self.page().show_cluster(branch, cluster).show_view(branch)
+ return branch.marshal()
+
+ def render_item_name(self, session, cluster):
+ return cluster.name
+
+class ClusterParameter(Parameter):
+ def do_unmarshal(self, string):
+ return self.app.model.get_cluster(int(string))
+
+ def do_marshal(self, cluster):
+ return str(cluster.id)
+
+class ClusterFrame(CuminFrame):
+ def __init__(self, app, name):
+ super(ClusterFrame, self).__init__(app, name)
+
+ self.param = ClusterParameter(app, "id")
+ self.add_parameter(self.param)
+ self.set_object_attribute(self.param)
+
+ self.view = ClusterView(app, "view")
+ self.add_child(self.view)
+
+ def set_cluster(self, session, cluster):
+ self.param.set(session, cluster)
+
+ def show_view(self, session):
+ return self.show_mode(session, self.view)
+
+ def render_href(self, session, cluster):
+ branch = session.branch()
+ return branch.marshal()
+
+ def render_title(self, session, cluster):
+ return "Cluster '%s'" % cluster.name
+
+class ClusterView(Widget):
+ def __init__(self, app, name):
+ super(ClusterView, self).__init__(app, name)
+
+ self.tabs = TabSet(app, "tabs")
+ self.add_child(self.tabs)
+
+ self.tabs.add_child(self.Servers(app, "servers"))
+
+ def render_title(self, session, cluster):
+ return "Cluster '%s'" % cluster.name
+
+ class Servers(ServerSet):
+ def render_title(self, session, cluster):
+ return "Servers (%i)" % len(cluster.server_items())
+
+ def get_items(self, session, cluster):
+ return sorted(cluster.server_items())
Added: mgmt/cumin/python/cumin/cluster.strings
===================================================================
--- mgmt/cumin/python/cumin/cluster.strings (rev 0)
+++ mgmt/cumin/python/cumin/cluster.strings 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,18 @@
+[ClusterSet.html]
+<table class="ClusterSet mobjects">
+ <tr>
+ <th>Name</th>
+ </tr>
+ {items}
+</table>
+
+[ClusterSet.item_html]
+<tr>
+ <td><a href="{item_href}">{item_name}</a></td>
+</tr>
+
+[ClusterView.html]
+<div class="oblock">
+ <h1>{title}</h1>
+ {tabs}
+</div>
Added: mgmt/cumin/python/cumin/demo.py
===================================================================
--- mgmt/cumin/python/cumin/demo.py (rev 0)
+++ mgmt/cumin/python/cumin/demo.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,115 @@
+from time import sleep
+from threading import Thread
+from random import sample, random, randint
+
+from model import *
+
+class DemoData(object):
+ def __init__(self, model):
+ self.model = model
+
+ def load(self):
+ # XXX need some locking in here
+
+ sgroups = dict()
+
+ for name in ("Geography", "Department"):
+ sgroup = ServerGroup(self.model)
+ sgroup.name = name
+ sgroups[name] = sgroup
+
+ for name in ("West Coast", "East Coast"):
+ sgroup = ServerGroup(self.model)
+ sgroup.name = name
+ sgroups["Geography"].add_child(sgroup)
+ sgroups[name] = sgroup
+
+ for name in ("Marketing", "Sales"):
+ sgroup = ServerGroup(self.model)
+ sgroup.name = name
+ sgroups["Department"].add_child(sgroup)
+ sgroups[name] = sgroup
+
+ clusters = list()
+
+ for cluster_count in range(3):
+ cluster = Cluster(self.model)
+ cluster.name = "cluster" + str(cluster_count)
+ clusters.append(cluster)
+
+ for server_count in range(12):
+ server = Server(self.model)
+ server.name = "server" + str(server_count)
+ server.set_cluster(clusters[server_count % 3])
+
+ vhost = VirtualHost(self.model)
+ vhost.name = "default"
+ server.add_virtual_host(vhost)
+ server.default_virtual_host = vhost
+
+ for name in ("test", "devel"):
+ vhost = VirtualHost(self.model)
+ vhost.name = name
+ server.add_virtual_host(vhost)
+
+ for vhost in server.virtual_host_items():
+ for name in ("realm0", "realm1",
"realm2"):
+ realm = Realm(self.model)
+ realm.name = name
+ vhost.add_realm(realm)
+
+ for name in ("amq.direct", "amq.fanout",
+ "amq.topic", "amq.match"):
+ exchange = Exchange(self.model)
+ exchange.name = name
+ vhost.add_exchange(exchange)
+
+ for queue_count in range(10):
+ queue = Queue(self.model)
+ queue.name = "queue" + str(queue_count)
+ vhost.add_queue(queue)
+
+ def start_updates(self):
+ thread = UpdateThread(self.model)
+ thread.start()
+
+class UpdateThread(Thread):
+ def __init__(self, model):
+ super(UpdateThread, self).__init__()
+
+ self.model = model
+ self.setDaemon(True)
+
+ def run(self):
+ while True:
+ sleep(1)
+
+ for server in self.model.get_servers():
+ for vhost in server.virtual_host_items():
+ for queue in vhost.queue_items():
+ queue.lock()
+ try:
+ queue.message_count += 1
+
+ if random() < 0.01:
+ queue.error_count += 1
+
+ if random() < 0.01:
+ queue.warning_count += 1
+ finally:
+ queue.unlock()
+
+if __name__ == "__main__":
+ import sys
+
+ model = CuminModel()
+
+ data = DemoData(model)
+ data.load()
+
+ sys.stdout.write("<?xml version=\"1.0\"?><model>")
+
+ for server in model.get_servers():
+ server.write_xml(sys.stdout)
+
+ sys.stdout.write("</model>")
Added: mgmt/cumin/python/cumin/exchange.py
===================================================================
--- mgmt/cumin/python/cumin/exchange.py (rev 0)
+++ mgmt/cumin/python/cumin/exchange.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,277 @@
+from wooly import *
+from wooly.widgets import *
+from wooly.forms import *
+from wooly.resources import *
+
+from model import *
+from widgets import *
+
+strings = StringCatalog(__file__)
+
+class ExchangeParameter(Parameter):
+ def do_unmarshal(self, string):
+ return self.app.model.get_exchange(int(string))
+
+ def do_marshal(self, exchange):
+ return str(exchange.id)
+
+class ExchangeInputSet(RadioInputSet):
+ def __init__(self, app, name, form):
+ super(ExchangeInputSet, self).__init__(app, name, form)
+
+ param = ExchangeParameter(app, "param")
+ self.add_parameter(param)
+ self.set_parameter(param)
+
+ def get_items(self, session, vhost):
+ return sorted(vhost.exchange_items())
+
+ def render_item_value(self, session, exchange):
+ return exchange.id
+
+ def render_item_content(self, session, exchange):
+ return exchange.name
+
+ def render_item_checked_attr(self, session, exchange):
+ return exchange is self.param.get(session) and
"checked=\"checked\""
+
+class ExchangeSet(ItemSet):
+ def render_title(self, session, vhost):
+ return "Exchanges (%s)" % len(vhost.exchange_items())
+
+ def get_items(self, session, vhost):
+ return sorted(vhost.exchange_items())
+
+ def render_item_href(self, session, exchange):
+ branch = session.branch()
+ self.page().show_exchange(branch, exchange).show_view(branch)
+ return branch.marshal()
+
+ def render_item_name(self, session, exchange):
+ return exchange.name
+
+ def render_item_flags(self, session, exchange):
+ flags = list()
+ return ", ".join(flags)
+
+ def render_item_config(self, session, exchange):
+ return "%i bindings" % len(exchange.binding_items())
+
+ def render_item_status(self, session, exchange):
+ return "2 errors"
+
+class ExchangeFrame(CuminFrame):
+ def __init__(self, app, name):
+ super(ExchangeFrame, self).__init__(app, name)
+
+ self.param = ExchangeParameter(app, "id")
+ self.add_parameter(self.param)
+ self.set_object_attribute(self.param)
+
+ self.view = ExchangeView(app, "view")
+ self.add_child(self.view)
+
+ self.edit = ExchangeEdit(app, "edit")
+ self.add_child(self.edit)
+
+ self.remove = ExchangeRemove(app, "remove")
+ self.add_child(self.remove)
+
+ def set_exchange(self, session, exchange):
+ return self.param.set(session, exchange)
+
+ def show_view(self, session):
+ return self.show_mode(session, self.view)
+
+ def show_edit(self, session):
+ return self.show_mode(session, self.edit)
+
+ def show_remove(self, session):
+ return self.show_mode(session, self.remove)
+
+ def render_href(self, session, exchange):
+ branch = session.branch()
+ self.show_view(branch)
+ return branch.marshal()
+
+ def render_title(self, session, exchange):
+ return "Exchange '%s'" % exchange.name
+
+class ExchangeView(Widget):
+ def __init__(self, app, name):
+ super(ExchangeView, self).__init__(app, name)
+
+ self.tabs = TabSet(app, "tabs")
+ self.add_child(self.tabs)
+
+ self.tabs.add_child(ExchangeBindingSet(app, "bindings"))
+ self.tabs.add_child(self.ExchangeLog(app, "log"))
+
+ class ExchangeLog(Widget):
+ def render_title(self, session, exchange):
+ return "Log Messages"
+
+ def render_title(self, session, exchange):
+ return "Exchange '%s'" % exchange.name
+
+ def render_exchange_name(self, session, exchange):
+ return exchange.name
+
+ def render_type(self, session, exchange):
+ if exchange.type == "direct":
+ return "Direct"
+ elif exchange.type == "topic":
+ return "Topic"
+ elif exchange.type == "fanout":
+ return "Fan Out"
+ else:
+ raise Exception()
+
+ def render_edit_exchange_href(self, session, exchange):
+ branch = session.branch()
+ self.page().show_exchange(branch, exchange).show_edit(branch)
+ return branch.marshal()
+
+ def render_remove_exchange_href(self, session, exchange):
+ branch = session.branch()
+ self.page().show_exchange(branch, exchange).show_remove(branch)
+ return branch.marshal()
+
+class ExchangeBindingSet(ItemSet):
+ def render_title(self, session, exchange):
+ return "Bindings (%i)" % len(exchange.binding_items())
+
+ def get_items(self, session, exchange):
+ return sorted(exchange.binding_items())
+
+ def render_item_href(self, session, binding):
+ branch = session.branch()
+ self.page().show_queue(branch, binding.queue)
+ return branch.marshal()
+
+ def render_item_name(self, session, binding):
+ return binding.get_queue().name
+
+ def render_item_routing_key(self, session, binding):
+ return binding.routing_key
+
+class ExchangeForm(CuminForm):
+ def __init__(self, app, name):
+ super(ExchangeForm, self).__init__(app, name)
+
+ self.exchange_name = TextInput(app, "exchange_name", self)
+ self.add_child(self.exchange_name)
+
+ self.type = Parameter(app, "type")
+ self.type.set_default("direct")
+ self.add_parameter(self.type)
+
+ self.direct = RadioInput(app, "direct", self)
+ self.direct.set_parameter(self.type)
+ self.direct.set_value("direct")
+ self.add_child(self.direct)
+
+ self.topic = RadioInput(app, "topic", self)
+ self.topic.set_parameter(self.type)
+ self.topic.set_value("topic")
+ self.add_child(self.topic)
+
+ self.fanout = RadioInput(app, "fanout", self)
+ self.fanout.set_parameter(self.type)
+ self.fanout.set_value("fanout")
+ self.add_child(self.fanout)
+
+ def validate(self, session):
+ valid = True
+
+ name = self.exchange_name.get(session)
+
+ if name == "":
+ valid = False
+ self.exchange_name.add_error(session, """
+ The exchange name is empty; it is required
+ """)
+ elif " " in name:
+ valid = False
+ self.exchange_name.add_error(session, """
+ The exchange name is invalid; allowed characters are
+ letters, digits, ".", and "_"
+ """)
+
+ return valid
+
+class ExchangeAdd(ExchangeForm):
+ def on_cancel(self, session, vhost):
+ branch = session.branch()
+ self.page().show_virtual_host(branch, vhost).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def on_submit(self, session, vhost):
+ if self.validate(session):
+ exchange = Exchange(self.app.model)
+
+ exchange.lock()
+ try:
+ exchange.name = self.exchange_name.get(session)
+ exchange.type = self.type.get(session)
+ finally:
+ exchange.unlock()
+
+ vhost.add_exchange(exchange)
+
+ branch = session.branch()
+ self.page().show_exchange(branch, exchange).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def render_title(self, session, vhost):
+ return "Add Exchange to Virtual Host '%s'" % vhost.name
+
+class ExchangeEdit(ExchangeForm):
+ def on_cancel(self, session, exchange):
+ branch = session.branch()
+ self.page().show_exchange(branch, exchange).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def on_submit(self, session, exchange):
+ if self.validate(session):
+ exchange.lock()
+ try:
+ exchange.name = self.exchange_name.get(session)
+ exchange.type = self.type.get(session)
+ finally:
+ exchange.unlock()
+
+ branch = session.branch()
+ self.page().show_exchange(branch, exchange).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def on_display(self, session, exchange):
+ self.exchange_name.set(session, exchange.name)
+ self.type.set(session, exchange.type)
+
+ def render_title(self, session, exchange):
+ return "Edit Exchange '%s'" % exchange.name
+
+class ExchangeRemove(CuminConfirmForm):
+ def on_confirm(self, session, exchange):
+ vhost = exchange.get_virtual_host()
+
+ exchange.remove()
+
+ branch = session.branch()
+ self.page().show_virtual_host(branch, vhost).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def on_cancel(self, session, exchange):
+ branch = session.branch()
+ self.page().show_exchange(branch, exchange).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def render_title(self, session, exchange):
+ return "Remove Exchange '%s'" % exchange.name
+
+ def render_confirm_content(self, session, exchange):
+ return "Yes, Remove Exchange '%s'" % exchange.name
+
+ def render_cancel_content(self, session, exchange):
+ return "No, Cancel"
Added: mgmt/cumin/python/cumin/exchange.strings
===================================================================
--- mgmt/cumin/python/cumin/exchange.strings (rev 0)
+++ mgmt/cumin/python/cumin/exchange.strings 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,105 @@
+[ExchangeInputSet.item_html]
+<div class="field">
+ <input type="radio" name="{name}" value="{item_value}"
tabindex="{tabindex}" {item_checked_attr}/>
+ {item_content}
+</div>
+
+[ExchangeSet.css]
+ul.ExchangeSet li:before {
+ content: url(resource?name=exchange-20.png);
+ vertical-align: -30%;
+ padding: 0 0.25em;
+}
+
+[ExchangeSet.html]
+<table class="ExchangeSet mobjects">
+ <tr>
+ <th>Name</th>
+ <th>Configuration</th>
+ <th>Status</th>
+ </tr>
+{items}
+</table>
+
+[ExchangeSet.item_html]
+<tr>
+ <td><a href="{item_href}">{item_name}</a></td>
+ <td>{item_config}</td>
+ <td>{item_status}</td>
+</tr>
+
+[ExchangeForm.html]
+<form id="{id}" class="ExchangeForm mform" method="post"
action="?">
+ <div class="head">
+ <h1>{title}</h1>
+ </div>
+ <div class="body">
+ <span class="legend">Name</span>
+ <fieldset>
+ <div class="field">{exchange_name}</div>
+ </fieldset>
+ <span class="legend">Type</span>
+ <fieldset>
+ <div class="field">
+ {direct}
+ <em>Direct:</em> Route messages to queues by queue name
+ </div>
+ <div class="field">
+ {topic}
+ <em>Topic:</em> Route messages to queues by topic keyword match
+ </div>
+ <div class="field">
+ {fanout}
+ <em>Fan Out:</em> Lorem ipsum gloria dei ipso facto ad nauseum
+ </div>
+ </fieldset>
+{hidden_inputs}
+ </div>
+ <div class="foot">
+ <div style="display: block; float:
left;"><button>Help</help></div>
+{cancel}
+{submit}
+ </div>
+</form>
+<script defer="defer">
+(function() {
+ // elements[0] is a fieldset, at least in firefox
+ var elem = wooly.doc().elem("{id}").node.elements[1];
+ elem.focus();
+ elem.select();
+}())
+</script>
+
+[ExchangeView.html]
+<div class="ExchangeView oblock">
+ <h1><img src="resource?name=exchange-36.png"> {title}</h1>
+
+ <dl class="properties">
+ <dt>Exchange Name</dt><dd>{exchange_name}</dd>
+ <dt>Type</dt><dd>{type}</dd>
+ </dl>
+
+ <ul class="actions">
+ <li><a href="{edit_exchange_href}">Edit
Exchange</a></li>
+ <li><a href="{remove_exchange_href}">Remove
Exchange</a></li>
+ </ul>
+
+ {tabs}
+</div>
+
+[ExchangeBindingSet.html]
+<table class="ExchangeBindingSet mobjects">
+ <tr>
+ <th>Queue</th>
+ <th>Routing Key</th>
+ <th></th>
+ </tr>
+{items}
+</table>
+
+[ExchangeBindingSet.item_html]
+<tr>
+ <td><a href="{item_href}">{item_name}</a></td>
+ <td>{item_routing_key}</td>
+ <td><a class="action"
href="">Remove</a></td>
+</tr>
Added: mgmt/cumin/python/cumin/model.py
===================================================================
--- mgmt/cumin/python/cumin/model.py (rev 0)
+++ mgmt/cumin/python/cumin/model.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,282 @@
+from wooly import *
+from wooly.model import *
+
+class CuminModel(Model):
+ def __init__(self):
+ super(CuminModel, self).__init__()
+
+ self.cluster = ModelClass(self, "cluster")
+ self.server = ModelClass(self, "server")
+ self.server_group = ModelClass(self, "server_group")
+ self.virtual_host = ModelClass(self, "virtual_host")
+ self.virtual_host_group = ModelClass(self, "virtual_host_group")
+ self.queue = ModelClass(self, "queue")
+ self.exchange = ModelClass(self, "exchange")
+ self.realm = ModelClass(self, "realm")
+ self.binding = ModelClass(self, "binding")
+
+ assoc = ModelAssociation(self, "cluster_to_servers")
+ assoc.add_endpoint(self.server, "cluster", "0..1")
+ assoc.add_endpoint(self.cluster, "server", "0..n")
+
+ assoc = ModelAssociation(self, "server_to_virtual_hosts")
+ assoc.add_endpoint(self.server, "virtual_host", "0..n")
+ assoc.add_endpoint(self.virtual_host, "server", "0..1")
+
+ assoc = ModelAssociation(self, "server_groups_to_servers")
+ assoc.add_endpoint(self.server, "server_group", "0..n")
+ assoc.add_endpoint(self.server_group, "server", "0..n")
+
+ assoc = ModelAssociation(self, "server_groups_to_server_groups")
+ assoc.add_endpoint(self.server_group, "parent", "0..n")
+ assoc.add_endpoint(self.server_group, "child", "0..n")
+
+ assoc = ModelAssociation(self, "virtual_host_groups_to_virtual_hosts")
+ assoc.add_endpoint(self.virtual_host, "virtual_host_group",
"0..n")
+ assoc.add_endpoint(self.virtual_host_group, "virtual_host",
"0..n")
+
+ assoc = ModelAssociation \
+ (self, "virtual_host_groups_to_virtual_host_groups")
+ assoc.add_endpoint(self.virtual_host_group, "parent",
"0..n")
+ assoc.add_endpoint(self.virtual_host_group, "child", "0..n")
+
+ assoc = ModelAssociation(self, "virtual_host_to_queues")
+ assoc.add_endpoint(self.virtual_host, "queue", "0..n")
+ assoc.add_endpoint(self.queue, "virtual_host", "0..1")
+
+ assoc = ModelAssociation(self, "virtual_host_to_exchanges")
+ assoc.add_endpoint(self.virtual_host, "exchange", "0..n")
+ assoc.add_endpoint(self.exchange, "virtual_host", "0..1")
+
+ assoc = ModelAssociation(self, "virtual_host_to_realms")
+ assoc.add_endpoint(self.virtual_host, "realm", "0..n")
+ assoc.add_endpoint(self.realm, "virtual_host", "0..1")
+
+ assoc = ModelAssociation(self, "realms_to_queues")
+ assoc.add_endpoint(self.realm, "queue", "0..n")
+ assoc.add_endpoint(self.queue, "realm", "0..n")
+
+ assoc = ModelAssociation(self, "realms_to_exchanges")
+ assoc.add_endpoint(self.realm, "exchange", "0..n")
+ assoc.add_endpoint(self.exchange, "realm", "0..n")
+
+ assoc = ModelAssociation(self, "queue_to_bindings")
+ assoc.add_endpoint(self.queue, "binding", "0..n")
+ assoc.add_endpoint(self.binding, "queue", "0..1")
+
+ assoc = ModelAssociation(self, "exchange_to_bindings")
+ assoc.add_endpoint(self.exchange, "binding", "0..n")
+ assoc.add_endpoint(self.binding, "exchange", "0..1")
+
+ def get_cluster(self, id):
+ return self.get_index(self.cluster).get(id)
+
+ def get_clusters(self):
+ return self.get_index(self.cluster).values()
+
+ def get_server(self, id):
+ return self.get_index(self.server).get(id)
+
+ def get_servers(self):
+ return self.get_index(self.server).values()
+
+ def get_server_group(self, id):
+ return self.get_index(self.server_group).get(id)
+
+ def get_server_groups(self):
+ return self.get_index(self.server_group).values()
+
+ def get_virtual_host(self, id):
+ return self.get_index(self.virtual_host).get(id)
+
+ def get_queue(self, id):
+ return self.get_index(self.queue).get(id)
+
+ def get_exchange(self, id):
+ return self.get_index(self.exchange).get(id)
+
+ def get_realm(self, id):
+ return self.get_index(self.realm).get(id)
+
+class Cluster(ModelObject):
+ def __init__(self, model):
+ super(Cluster, self).__init__(model, model.cluster)
+
+ self.name = None
+
+class Server(ModelObject):
+ def __init__(self, model):
+ super(Server, self).__init__(model, model.server)
+
+ self.name = None
+ self.default_virtual_host = None
+
+ def write_xml(self, writer):
+ writer.write("<server id=\"server-%i\">" % self.id)
+ writer.write("<name>" + self.name + "</name>")
+ writer.write("<default-virtual-host
ref=\"virtual-host-%i\"/>" \
+ % self.default_virtual_host.id)
+
+ for vhost in self.virtual_host_items():
+ vhost.write_xml(writer)
+
+ writer.write("</server>")
+
+class ServerGroup(ModelObject):
+ def __init__(self, model):
+ super(ServerGroup, self).__init__(model, model.server_group)
+
+ self.name = None
+
+class VirtualHost(ModelObject):
+ def __init__(self, model):
+ super(VirtualHost, self).__init__(model, model.virtual_host)
+
+ self.name = None
+
+ # XXX do this via associations? XXX this will leak a ref if
+ # the default exchange is removed
+
+ self.default_exchange = Exchange(model)
+ self.default_exchange.name = "default"
+ self.add_exchange(self.default_exchange)
+
+ def add_queue(self, queue):
+ self.do_add_queue(queue)
+
+ # Default binding
+
+ binding = Binding(self.model)
+ binding.routing_key = queue.name
+ binding.set_queue(queue)
+ binding.set_exchange(self.default_exchange)
+
+ def write_xml(self, writer):
+ writer.write("<virtual-host id=\"virtual-host-%i\">" %
self.id)
+ writer.write("<name>%s</name>" % self.name)
+ writer.write("<default-exchange ref=\"exchange-%i\"/>"
\
+ % self.default_exchange.id)
+
+ for queue in self.queue_items():
+ queue.write_xml(writer)
+
+ for exchange in self.exchange_items():
+ exchange.write_xml(writer)
+
+ for realm in self.realm_items():
+ realm.write_xml(writer)
+
+ writer.write("</virtual-host>")
+
+class VirtualHostGroup(ModelObject):
+ def __init__(self, model):
+ super(VirtualHostGroup, self).__init__(model, model.virtual_host_group)
+
+ self.name = None
+
+class Realm(ModelObject):
+ def __init__(self, model):
+ model.lock()
+
+ super(Realm, self).__init__(model, model.realm)
+
+ self.name = None
+
+ model.unlock()
+
+ def write_xml(self, writer):
+ writer.write("<realm id=\"realm-%i\">" % self.id)
+ writer.write("<name>%s</name>" % self.name)
+
+ for queue in self.queue_items():
+ writer.write("<queue ref=\"queue-%i\"/>" %
queue.id)
+
+ for exchange in self.exchange_items():
+ writer.write("<exchange ref=\"exchange-%i\"/>" %
exchange.id)
+
+ writer.write("</realm>")
+
+class Queue(ModelObject):
+ def __init__(self, model):
+ super(Queue, self).__init__(model, model.queue)
+
+ self.name = None
+ self.is_passive = False
+ self.is_durable = True
+ self.is_exclusive = False
+ self.is_auto_delete = False
+ self.latency_priority = "m" # h, m, or l
+
+ self.message_count = 41
+ self.error_count = 0
+ self.warning_count = 0
+
+ def remove(self):
+ for binding in self.binding_items().copy():
+ binding.remove()
+
+ super(Queue, self).remove()
+
+ def purge(self):
+ pass
+
+ def write_xml(self, writer):
+ writer.write("<queue id=\"queue-%i\">" % self.id)
+ writer.write("<name>%s</name>" % self.name)
+ writer.write("<latency-priority>%s</latency-priority>" \
+ % self.latency_priority)
+ writer.write("<message-count>%i</message-count>" %
self.message_count)
+ writer.write("<error-count>%i</error-count>" %
self.error_count)
+ writer.write("<warning-count>%i</warning-count>" %
self.warning_count)
+
+ for realm in self.realm_items():
+ writer.write("<realm ref=\"realm-%i\"/>" %
realm.id)
+
+ for binding in self.binding_items():
+ binding.write_xml(writer)
+
+ writer.write("</queue>")
+
+class Exchange(ModelObject):
+ def __init__(self, model):
+ super(Exchange, self).__init__(model, model.exchange)
+
+ self.name = None
+ self.type = "direct" # in ("direct", "topic",
"fanout")
+ self.is_passive = False
+ self.is_durable = True
+ self.is_auto_delete = False
+ self.is_internal = False
+
+ def remove(self):
+ for binding in self.binding_items().copy():
+ binding.remove()
+
+ super(Exchange, self).remove()
+
+ def write_xml(self, writer):
+ writer.write("<exchange id=\"exchange-%i\">" %
self.id)
+ writer.write("<name>%s</name>" % self.name)
+ #writer.write("<error-count>%i</error-count>" %
self.error_count)
+ #writer.write("<warning-count>%i</warning-count>" %
self.warning_count)
+
+ for realm in self.realm_items():
+ writer.write("<realm ref=\"realm-%i\"/>" %
realm.id)
+
+ for binding in self.binding_items():
+ binding.write_xml(writer)
+
+ writer.write("</exchange>")
+
+class Binding(ModelObject):
+ def __init__(self, model):
+ super(Binding, self).__init__(model, model.binding)
+
+ self.routing_key = None
+
+ def write_xml(self, writer):
+ writer.write("<binding id=\"binding-%i\">" % self.id)
+ writer.write("<exchange ref=\"exchange-%i\"/>" %
self.exchange.id)
+ writer.write("<queue ref=\"queue-%i\"/>" %
self.queue.id)
+ writer.write("<routing-key>%s</routing-key>" %
self.routing_key)
+ writer.write("</binding>")
Added: mgmt/cumin/python/cumin/page.py
===================================================================
--- mgmt/cumin/python/cumin/page.py (rev 0)
+++ mgmt/cumin/python/cumin/page.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,143 @@
+from wooly import *
+from wooly.debug import *
+from wooly.widgets import *
+from wooly.resources import *
+
+from server import *
+from cluster import *
+from widgets import *
+
+strings = StringCatalog(__file__)
+
+class CuminPage(Page):
+ def __init__(self, app, name):
+ super(CuminPage, self).__init__(app, name)
+
+ self.frames = self.FramesAttribute(app, "frames")
+ self.add_attribute(self.frames)
+
+ self.modal = Attribute(app, "modal")
+ self.add_attribute(self.modal)
+
+ self.citem = self.ContextItem(app, "citem")
+ self.add_child(self.citem)
+
+ self.main = MainFrame(app, "main")
+ self.add_child(self.main)
+
+ class FramesAttribute(Attribute):
+ def get_default(self, session):
+ return list()
+
+ def set_modal(self, session, modal):
+ self.modal.set(session, modal)
+
+ def save_session(self, session):
+ self.app.sessions.append(session)
+
+ def show_server(self, session, server):
+ return self.main.show_server(session, server)
+
+ def show_cluster(self, session, cluster):
+ return self.main.show_cluster(session, cluster)
+
+ def show_virtual_host(self, session, vhost):
+ frame = self.show_server(session, vhost.server)
+ return frame.show_virtual_host(session, vhost)
+
+ def show_queue(self, session, queue):
+ frame = self.show_virtual_host(session, queue.virtual_host)
+ return frame.show_queue(session, queue)
+
+ def show_exchange(self, session, exchange):
+ frame = self.show_virtual_host(session, exchange.virtual_host)
+ return frame.show_exchange(session, exchange)
+
+ def render_title(self, session, object):
+ return "Cumin"
+
+ def render_class(self, session, object):
+ return self.modal.get(session) and "modal"
+
+ def render_content(self, session, object):
+ return self.main.render(session, object)
+
+ def get_frames(self, session):
+ return self.frames.get(session)
+
+ def render_context_items(self, session, object):
+ writer = Writer()
+
+ for frame in self.get_frames(session):
+ writer.write(self.citem.render(session, frame))
+
+ return writer.to_string()
+
+ # XXX use a child template instead
+ class ContextItem(Widget):
+ def render_href(self, session, frame):
+ return frame.render_href(session, frame.get_object(session))
+
+ def render_content(self, session, frame):
+ return frame.render_title(session, frame.get_object(session))
+
+class MainFrame(CuminFrame):
+ def __init__(self, app, name):
+ super(MainFrame, self).__init__(app, name)
+
+ self.view = MainView(app, "view")
+ self.add_child(self.view)
+
+ self.server = ServerFrame(app, "server")
+ self.add_child(self.server)
+
+ self.cluster = ClusterFrame(app, "cluster")
+ self.add_child(self.cluster)
+
+ def get_object(self, session):
+ return self.app.model
+
+ def show_view(self, session):
+ return self.show_mode(session, self.view)
+
+ def show_server(self, session, server):
+ self.server.set_server(session, server)
+ return self.show_mode(session, self.server)
+
+ def show_cluster(self, session, cluster):
+ self.cluster.set_cluster(session, cluster)
+ return self.show_mode(session, self.cluster)
+
+ def render_href(self, session, model):
+ branch = session.branch()
+ self.show_view(branch)
+ return branch.marshal()
+
+ def render_title(self, session, model):
+ return "<img src=\"resource?name=logo.png\"/>"
+
+class MainView(Widget):
+ def __init__(self, app, name):
+ super(MainView, self).__init__(app, name)
+
+ self.tabs = TabSet(app, "tabs")
+ self.add_child(self.tabs)
+
+ self.tabs.add_child(self.ServerTab(app, "servers"))
+ self.tabs.add_child(ClusterSet(app, "clusters"))
+
+ def render_title(self, session, model):
+ return "Red Hat Messaging"
+
+ class ServerTab(TabSet):
+ def __init__(self, app, name):
+ super(MainView.ServerTab, self).__init__(app, name)
+
+ self.servers = ServerSet(app, "servers")
+ self.add_child(self.servers)
+
+ self.groups = ServerGroupTree(app, "groups")
+ self.add_child(self.groups)
+
+ def render_title(self, session, model):
+ return "Servers (%i)" % len(model.get_servers())
Added: mgmt/cumin/python/cumin/page.strings
===================================================================
--- mgmt/cumin/python/cumin/page.strings (rev 0)
+++ mgmt/cumin/python/cumin/page.strings 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,310 @@
+[CuminPage.css]
+body {
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ font-size: 0.9em;
+}
+
+body.modal #head {
+ opacity: 0.2;
+}
+
+body.modal {
+ background-color: #f7f7f7;
+}
+
+img {
+ border: none;
+}
+
+* {
+ text-decoration: none;
+}
+
+a {
+ color: #06c;
+}
+
+#head, #foot {
+ padding: 0.5em 0.75em 0.4em 0.75em;
+}
+
+#head {
+ background-color: #564979;
+}
+
+#body {
+ padding: 1em;
+}
+
+#logo {
+ vertical-align: -15%;
+}
+
+h1, h2 {
+ margin: 0;
+}
+
+h1 {
+ font-size: 1.1em;
+}
+
+h1 img {
+ vertical-align: -50%;
+ margin: 0 0.5em 0 0;
+}
+
+h2 {
+ font-size: 1em;
+ font-weight: normal;
+}
+
+.oblock {
+ padding: 0;
+ background-color: white;
+}
+
+.iblock {
+ margin: 0;
+ padding: 0 1em;
+}
+
+ul#context {
+ display: inline;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+ul#context li {
+ display: inline;
+}
+
+ul#context li:after {
+ content: " > ";
+ font-weight: bold;
+ font-size: 0.8em;
+ color: #fff;
+}
+
+ul#context li:last-child:after {
+ content: "";
+}
+
+ul#context li a {
+ color: #ff9f00;
+}
+
+ul#context li:first-child a {
+ vertical-align: -15%;
+}
+
+ul#context li:last-child a {
+ color: #fff;
+}
+
+ul.actions {
+ padding: 0;
+ margin: 1em 0;
+ list-style: none;
+}
+
+dl.properties {
+ margin: 1em 0;
+ width: 25em;
+}
+
+dl.properties dt, dd {
+ border-top: 1px dotted #ddd;
+ padding: 0.25em 0.5em;
+}
+
+dl.properties dt {
+ width: 10em;
+ float: left;
+ background-color: #f7f7f7;
+ margin-right: 0.5em;
+}
+
+ul.mobjects {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+ul.mobjects li {
+ margin: 0;
+ border-top: 1px solid #ccc;
+ padding: 0.5em 0;
+}
+
+ul.mobjects li:first-child {
+ margin: 0;
+ border: none;
+}
+
+ul.mobjects li a.action {
+ float: right;
+}
+
+a.action:before, ul.actions li:before {
+ content: "\00BB \0020";
+ font-weight: bold;
+ color: #dc9f2e;
+}
+
+ul.mobjects .flags {
+ font-size: small;
+ font-style: italic;
+}
+
+ul.mobjects .config {
+ padding: 0 0 0 2em;
+}
+
+ul.mobjects .status {
+ padding: 0 0 0 2em;
+ color: #936;
+}
+
+table.mobjects {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 0;
+}
+
+table.mobjects tr {
+ border-top: 1px dotted #ccc;
+ vertical-align: top;
+}
+
+table.mobjects td {
+ padding: 0.5em;
+}
+
+table.mobjects th {
+ padding: 0.25em 0.5em;
+}
+
+table.mobjects th {
+ text-align: left;
+ font-weight: normal;
+ background-color: #f7f7f7;
+}
+
+form.mform {
+ width: 50em;
+ border: 1px solid #ddd;
+ background-color: #fff;
+}
+
+form.mform fieldset {
+ border: none;
+ padding: 0.75em;
+}
+
+form.mform .legend {
+ font-weight: bold;
+}
+
+form.mform .head, .mform .body, .mform .foot {
+ padding: 0.5em 0.75em;
+ margin: 0;
+}
+
+form.mform .head {
+ font-weight: bold;
+ color: white;
+ background-color: #564979;
+}
+
+form.mform .foot {
+ text-align: right;
+ border-top: 1px solid #ddd;
+}
+
+form.mform .field {
+ margin: 0.25em 0;
+}
+
+form.mform .field input {
+ border-style: groove;
+}
+
+form.mform ul.errors {
+ list-style: none;
+ display: block;
+ float: right;
+ color: red;
+ padding: 0.25em 0.5em;
+ border: 1px solid red;
+ margin: 0 0.5em;
+ max-width: 20em;
+}
+
+form.mform button {
+ border-style: groove;
+ padding: 0.25em 0.5em;
+ margin: 0.5em;
+}
+
+ul.radiotabs {
+ list-style: none;
+ margin: 0.25em 0 1em 0;
+ padding: 0;
+}
+
+ul.radiotabs li {
+ display: inline;
+ margin: 0 1em 0 0;
+}
+
+ul.radiotabs li a:before {
+ content: url(resource?name=radio-button.png);
+ margin: 0 0.5em 0 0;
+ vertical-align: -15%;
+}
+
+ul.radiotabs li a.selected {
+ color: black;
+}
+
+ul.radiotabs li a.selected:before {
+ content: url(resource?name=radio-button-checked.png);
+}
+
+[CuminPage.html]
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html
xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
+ <head>
+ <title>{title}</title>
+ <link rel="stylesheet" type="text/css"
href="cumin.css"/>
+ <script src="resource?name=wooly.js"> </script>
+ </head>
+ <body class="{class}">
+ <div id="head"><ul
id="context">{context_items}</ul></div>
+ <div id="body">{content}</div>
+ <div id="foot">
+ </div>
+ </body>
+</html>
+
+[ContextItem.html]
+<li><a href="{href}">{content}</a></li>
+
+[MainView.html]
+<div class="oblock">
+ <h1>{title}</h1>
+ <ul class="actions">
+ <li><a href="">Add Server</a></li>
+ <li><a href="">Add Server Group</a></li>
+ <li><a href="">Add Cluster</a></li>
+ </ul>
+ {tabs}
+</div>
+
+[ServerTab.html]
+<ul class="ServerTab radiotabs tabs">{tabs}</ul>
+<div class="ServerTab mode">{mode}</div>
Added: mgmt/cumin/python/cumin/queue.py
===================================================================
--- mgmt/cumin/python/cumin/queue.py (rev 0)
+++ mgmt/cumin/python/cumin/queue.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,416 @@
+from wooly import *
+from wooly.widgets import *
+from wooly.forms import *
+from wooly.resources import *
+
+from model import *
+from widgets import *
+from exchange import ExchangeInputSet
+
+strings = StringCatalog(__file__)
+
+class QueueXmlPage(Page):
+ def __init__(self, app, name):
+ super(QueueXmlPage, self).__init__(app, name)
+
+ self.queue = QueueParameter(app, "id")
+ self.add_parameter(self.queue)
+
+ def get_content_type(self, session):
+ return Page.xml_content_type
+
+ def do_render(self, session, object):
+ writer = Writer()
+
+ writer.write(Page.xml_1_0_declaration)
+ self.queue.get(session).write_xml(writer)
+
+ return writer.to_string()
+
+class QueueSet(ItemSet):
+ def render_title(self, session, vhost):
+ return "Queues (%s)" % len(vhost.queue_items())
+
+ def get_items(self, session, vhost):
+ return sorted(vhost.queue_items())
+
+ def render_item_href(self, session, queue):
+ branch = session.branch()
+ self.page().show_queue(branch, queue).show_view(branch)
+ return branch.marshal()
+
+ def render_item_name(self, session, queue):
+ return queue.name
+
+ def render_item_flags(self, session, queue):
+ flags = list()
+
+ if queue.is_durable:
+ flags.append("Durable")
+
+ if queue.is_auto_delete:
+ flags.append("Auto Delete")
+
+ return ", ".join(flags)
+
+ def render_item_config(self, session, queue):
+ bindings = list()
+
+ for binding in sorted(queue.binding_items()):
+ name = binding.get_exchange().name
+ key = binding.routing_key
+
+ branch = session.branch()
+ self.page().show_exchange(branch, binding.get_exchange())
+ href = branch.marshal()
+
+ bindings.append("<a href=\"%s\">exchange
'%s'</a> with key '%s'"
+ % (href, name, key))
+
+ return ", ".join(bindings)
+
+ def render_item_status(self, session, queue):
+ return "%i messages in queue<br/>%i errors, %i warnings" \
+ % (queue.message_count, queue.error_count, queue.warning_count)
+
+class QueueParameter(Parameter):
+ def do_unmarshal(self, string):
+ return self.app.model.get_queue(int(string))
+
+ def do_marshal(self, queue):
+ return str(queue.id)
+
+class QueueFrame(CuminFrame):
+ def __init__(self, app, name):
+ super(QueueFrame, self).__init__(app, name)
+
+ self.param = QueueParameter(app, "id")
+ self.add_parameter(self.param)
+ self.set_object_attribute(self.param)
+
+ self.view = QueueView(app, "view")
+ self.add_child(self.view)
+
+ self.edit = QueueEdit(app, "edit")
+ self.add_child(self.edit)
+
+ self.remove = QueueRemove(app, "remove")
+ self.add_child(self.remove)
+
+ self.binding_add = QueueBindingAdd(app, "binding_add")
+ self.add_child(self.binding_add)
+
+ def set_queue(self, session, queue):
+ return self.param.set(session, queue)
+
+ def show_view(self, session):
+ return self.show_mode(session, self.view)
+
+ def show_edit(self, session):
+ return self.show_mode(session, self.edit)
+
+ def show_remove(self, session):
+ return self.show_mode(session, self.remove)
+
+ def show_binding_add(self, session):
+ return self.show_mode(session, self.binding_add)
+
+ def render_href(self, session, queue):
+ branch = session.branch()
+ self.show_view(branch)
+ return branch.marshal()
+
+ def render_title(self, session, queue):
+ return "Queue '%s'" % queue.name
+
+class QueueStatus(Widget):
+ def render_class(self, session, queue):
+ if queue.error_count:
+ return "QueueStatus red"
+ elif queue.warning_count:
+ return "QueueStatus yellow"
+ else:
+ return "QueueStatus green"
+
+ def render_url(self, session, queue):
+ return "queue.xml?id=%i" % queue.id
+
+ def render_message_info(self, session, queue):
+ return "%i %s in queue" % \
+ (queue.message_count,
+ queue.message_count == 1 and "message" or
"messages")
+
+ def render_error_info(self, session, queue):
+ return "%i %s, %i %s" % \
+ (queue.error_count,
+ queue.error_count == 1 and "error" or "errors",
+ queue.warning_count,
+ queue.warning_count == 1 and "warning" or
"warnings")
+
+class QueueView(Widget):
+ def __init__(self, app, name):
+ super(QueueView, self).__init__(app, name)
+
+ self.status = QueueStatus(app, "status")
+ self.add_child(self.status)
+
+ self.tabs = TabSet(app, "tabs")
+ self.add_child(self.tabs)
+
+ self.tabs.add_child(QueueBindingSet(app, "bindings"))
+ self.tabs.add_child(self.QueueLog(app, "log"))
+
+ class QueueLog(Widget):
+ def render_title(self, session, queue):
+ return "Log Messages"
+
+ def render_title(self, session, queue):
+ return "Queue '%s'" % queue.name
+
+ def render_queue_name(self, session, queue):
+ return queue.name
+
+ def render_latency_tuning(self, session, queue):
+ if queue.latency_priority == "h":
+ return "Lower Latency"
+ elif queue.latency_priority == "m":
+ return "Balanced"
+ elif queue.latency_priority == "l":
+ return "Higher Throughput"
+ else:
+ raise Exception()
+
+ def render_edit_queue_href(self, session, queue):
+ branch = session.branch()
+ self.page().show_queue(branch, queue).show_edit(branch)
+ return branch.marshal()
+
+ def render_remove_queue_href(self, session, queue):
+ branch = session.branch()
+ self.page().show_queue(branch, queue).show_remove(branch)
+ return branch.marshal()
+
+ def render_add_binding_href(self, session, queue):
+ branch = session.branch()
+ self.page().show_queue(branch, queue).show_binding_add(branch)
+ return branch.marshal()
+
+class QueueBindingSet(ItemSet):
+ def render_title(self, session, queue):
+ return "Bindings (%i)" % len(queue.binding_items())
+
+ def get_items(self, session, queue):
+ return sorted(queue.binding_items())
+
+ def render_item_href(self, session, binding):
+ branch = session.branch()
+ self.page().show_exchange(branch, binding.get_exchange())
+ return branch.marshal()
+
+ def render_item_remove_href(self, session, binding):
+ branch = session.branch()
+ return branch.marshal()
+
+ def render_item_name(self, session, binding):
+ return binding.get_exchange().name
+
+ def render_item_routing_key(self, session, binding):
+ return binding.routing_key
+
+class QueueForm(CuminForm):
+ def __init__(self, app, name):
+ super(QueueForm, self).__init__(app, name)
+
+ self.queue_name = TextInput(app, "queue_name", self)
+ self.add_child(self.queue_name)
+
+ # XXX Convert tuning stuff into single subwidget
+
+ self.latency_priority = Parameter(app, "tuning")
+ self.latency_priority.set_default("m")
+ self.add_parameter(self.latency_priority)
+
+ self.latency = RadioInput(app, "latency", self)
+ self.latency.set_parameter(self.latency_priority)
+ self.latency.set_value("h")
+ self.add_child(self.latency)
+
+ self.balanced = RadioInput(app, "balanced", self)
+ self.balanced.set_parameter(self.latency_priority)
+ self.balanced.set_value("m")
+ self.add_child(self.balanced)
+
+ self.throughput = RadioInput(app, "throughput", self)
+ self.throughput.set_parameter(self.latency_priority)
+ self.throughput.set_value("l")
+ self.add_child(self.throughput)
+
+ def validate(self, session):
+ valid = True
+
+ name = self.queue_name.get(session)
+
+ if name == "":
+ valid = False
+ self.queue_name.add_error(session, """
+ The queue name is empty; it is required
+ """)
+ elif " " in name:
+ valid = False
+ self.queue_name.add_error(session, """
+ The queue name is invalid; allowed characters are
+ letters, digits, ".", and "_"
+ """)
+
+ return valid
+
+class QueueAdd(QueueForm):
+ def on_cancel(self, session, vhost):
+ branch = session.branch()
+ self.page().show_virtual_host(branch, vhost).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def on_submit(self, session, vhost):
+ if self.validate(session):
+ queue = Queue(self.app.model)
+
+ queue.lock()
+ try:
+ queue.name = self.queue_name.get(session)
+ queue.latency_priority = self.latency_priority.get(session)
+ finally:
+ queue.unlock()
+
+ vhost.add_queue(queue)
+
+ branch = session.branch()
+ self.page().show_queue(branch, queue).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def render_title(self, session, vhost):
+ return "Add Queue to Virtual Host '%s'" % vhost.name
+
+class QueueEdit(QueueForm):
+ def on_cancel(self, session, queue):
+ branch = session.branch()
+ self.page().show_queue(branch, queue).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def on_submit(self, session, queue):
+ if self.validate(session):
+ queue.lock()
+ try:
+ queue.name = self.queue_name.get(session)
+ queue.latency_priority = self.latency_priority.get(session)
+ finally:
+ queue.unlock()
+
+ branch = session.branch()
+ self.page().show_queue(branch, queue).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def on_display(self, session, queue):
+ self.queue_name.set(session, queue.name)
+ self.latency_priority.set(session, queue.latency_priority)
+
+ def render_title(self, session, queue):
+ return "Edit Queue '%s'" % queue.name
+
+class QueueRemove(CuminConfirmForm):
+ def on_confirm(self, session, queue):
+ vhost = queue.get_virtual_host()
+
+ queue.remove()
+
+ branch = session.branch()
+ self.page().show_virtual_host(branch, vhost).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def on_cancel(self, session, queue):
+ branch = session.branch()
+ self.page().show_queue(branch, queue).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def render_title(self, session, queue):
+ return "Remove Queue '%s'" % queue.name
+
+ def render_confirm_content(self, session, queue):
+ return "Yes, Remove Queue '%s'" % queue.name
+
+ def render_cancel_content(self, session, queue):
+ return "No, Cancel"
+
+class QueueBindingAdd(CuminForm):
+ def __init__(self, app, name):
+ super(QueueBindingAdd, self).__init__(app, name)
+
+ self.exchanges = self.Exchanges(app, "exchanges", self)
+ self.add_child(self.exchanges)
+
+ self.routing_key = TextInput(app, "routing_key", self)
+ self.add_child(self.routing_key)
+
+ def render_title(self, session, queue):
+ return "Add Binding to Queue '%s'" % queue.name
+
+ def on_cancel(self, session, queue):
+ branch = session.branch()
+ self.page().show_queue(branch, queue).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def validate(self, session):
+ valid = True
+
+ if not self.routing_key.get(session):
+ valid = False
+ self.routing_key.add_error(session, """
+ The routing key is empty; it is required
+ """)
+
+ if not self.exchanges.get(session):
+ valid = False
+ self.exchanges.add_error(session, """
+ No exchange selected; it is required
+ """)
+
+ return valid
+
+ def on_submit(self, session, queue):
+ if self.validate(session):
+ binding = Binding(self.app.model)
+
+ binding.lock()
+ try:
+ binding.routing_key = self.routing_key.get(session)
+ binding.set_queue(queue)
+ binding.set_exchange(self.exchanges.get(session))
+ finally:
+ binding.unlock()
+
+ branch = session.branch()
+ self.page().show_queue(branch, queue).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ class Exchanges(ExchangeInputSet):
+ def get_items(self, session, queue):
+ return sorted(queue.virtual_host.exchange_items())
+
+class QueueBindingRemove(CuminConfirmForm):
+ def on_confirm(self, session, binding):
+ branch = session.branch()
+ self.page().show_queue(branch, binding.get_queue()).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def on_cancel(self, session, binding):
+ branch = session.branch()
+ self.page().show_queue(branch, binding.get_queue()).show_view(branch)
+ session.set_redirect(branch.marshal())
+
+ def render_title(self, session, binding):
+ return "Remove Binding"
+
+ def render_confirm_content(self, session, binding):
+ return "Yes, Remove Binding"
+
+ def render_cancel_content(self, session, binding):
+ return "No, Cancel"
Added: mgmt/cumin/python/cumin/queue.strings
===================================================================
--- mgmt/cumin/python/cumin/queue.strings (rev 0)
+++ mgmt/cumin/python/cumin/queue.strings 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,199 @@
+[QueueSet.css]
+ul.QueueSet li:before {
+ content: url(resource?name=queue-20.png);
+ vertical-align: -30%;
+ padding: 0 0.25em;
+}
+
+[QueueSet.html]
+<table class="QueueSet mobjects">
+<tr>
+ <th>Name</th>
+ <th>Exchanges</th>
+ <th>Status</th>
+</tr>
+{items}
+</table>
+
+[QueueSet.item_html]
+<tr>
+ <td><a href="{item_href}">{item_name}</a></td>
+ <td>{item_config}</td>
+ <td>{item_status}</td>
+</tr>
+
+[QueueForm.html]
+<form id="{id}" class="QueueForm mform" method="post"
action="?">
+ <div class="head">
+ <h1>{title}</h1>
+ </div>
+ <div class="body">
+ <span class="legend">Name</span>
+ <fieldset>
+ <div class="field">{queue_name}</div>
+ </fieldset>
+ <span class="legend">Latency Tuning</span>
+ <fieldset>
+ <div class="field">
+ {latency}
+ <em>Lower Latency:</em> Tune for shorter delays, with reduced volume
+ </div>
+ <div class="field">
+ {balanced}
+ <em>Balanced</em>
+ </div>
+ <div class="field">
+ {throughput}
+ <em>Higher Throughput:</em> Tune for increased volume, with longer
+ delays
+ </div>
+ </fieldset>
+{hidden_inputs}
+ </div>
+ <div class="foot">
+ <div style="display: block; float:
left;"><button>Help</button></div>
+{cancel}
+{submit}
+ </div>
+</form>
+<script defer="defer">
+var id = "{id}";
+(function() {
+ // XXX elements[0] is a fieldset, at least in firefox
+ var elem = wooly.doc().elem(id).node.elements[1];
+ elem.focus();
+ elem.select();
+}())
+</script>
+
+[QueueStatus.css]
+.QueueStatus {
+ float: right;
+ margin: 1em;
+ padding: 0.75em 1em;
+ width: 15em;
+}
+
+.QueueStatus h2 {
+ font-weight: bold;
+ margin: 0 0 0.5em 0;
+}
+
+.QueueStatus.red {
+ border: 1px solid #c99;
+ background-color: #fcc;
+}
+
+.QueueStatus.yellow {
+ border: 1px solid #cc9;
+ background-color: #ffc;
+}
+
+.QueueStatus.green {
+ border: 1px solid #9c9;
+ background-color: #cfc;
+}
+
+[QueueStatus.html]
+<script defer="defer">
+(function() {
+ var updateStatus = function(xml, elem) {
+ var mcount = xml.elems("message-count").next().text().get();
+ var messages = mcount + " " + (mcount == "1" &&
"message" || "messages");
+
+ var ecount = xml.elems("error-count").next().text().get();
+ var errors = ecount + " " + (ecount == "1" &&
"error" || "errors");
+
+ var wcount = xml.elems("warning-count").next().text().get();
+ var warnings = wcount + " " + (wcount == "1" &&
"warning" || "warnings");
+
+ if (ecount != "0") {
+ elem.node.className = "QueueStatus red";
+ } else if (wcount != "0") {
+ elem.node.className = "QueueStatus yellow";
+ } else {
+ elem.node.className = "QueueStatus green";
+ }
+
+ var divs = elem.elems("div");
+ divs.next().set(messages + " in queue");
+ divs.next().set(errors + ", " + warnings);
+ }
+
+ wooly.setIntervalUpdate("{id}", "{url}", updateStatus, 3000);
+}())
+</script>
+<div class="{class}" id="{id}">
+ <h2>Queue Status</h2>
+
+ <div>{message_info}</div>
+ <div>{error_info}</div>
+</div>
+
+[QueueView.html]
+<div class="QueueView oblock">
+ {status}
+
+ <h1><img src="resource?name=queue-36.png"> {title}</h1>
+
+ <dl class="properties">
+ <dt>Queue Name</dt><dd>{queue_name}</dd>
+ <dt>Latency Tuning</dt><dd>{latency_tuning}</dd>
+ </dl>
+
+ <ul class="actions">
+ <li><a href="{edit_queue_href}">Edit
Queue</a></li>
+ <li><a href="{remove_queue_href}">Remove
Queue</a></li>
+ <li><a href="{add_binding_href}">Add
Binding</a></li>
+ </ul>
+
+ {tabs}
+</div>
+
+[QueueBindingSet.html]
+<table class="QueueBindingSet mobjects">
+ <tr>
+ <th>Exchange</th>
+ <th>Routing Key</th>
+ <th></th>
+ </tr>
+{items}
+</table>
+
+[QueueBindingSet.item_html]
+<tr>
+ <td><a href="{item_href}">exchange
'{item_name}'</a></td>
+ <td>{item_routing_key}</td>
+ <td><a class="action"
href="{item_remove_href}">Remove</a></td>
+</tr>
+
+[QueueBindingAdd.html]
+<form id="{id}" class="QueueBindingAdd mform"
method="post" action="?">
+ <div class="head">
+ <h1>{title}</h1>
+ </div>
+ <div class="body">
+ <span class="legend">Exchange</span>
+ <fieldset>{exchanges}</fieldset>
+ <span class="legend">Routing Key</span>
+ <fieldset>
+ <div class="field">{routing_key}</div>
+ </fieldset>
+{hidden_inputs}
+ </div>
+ <div class="foot">
+ <div style="display: block; float:
left;"><button>Help</button></div>
+{cancel}
+{submit}
+ </div>
+</form>
+<script defer="defer">
+var id = "{id}";
+(function() {
+ // XXX elements[0] is a fieldset, at least in firefox
+ var elem = wooly.doc().elem(id).node.elements[1];
+ elem.focus();
+ elem.select();
+}())
+</script>
+
Added: mgmt/cumin/python/cumin/realm.py
===================================================================
--- mgmt/cumin/python/cumin/realm.py (rev 0)
+++ mgmt/cumin/python/cumin/realm.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,56 @@
+from wooly import *
+from wooly.widgets import *
+from wooly.forms import *
+from wooly.resources import *
+
+from model import *
+from widgets import *
+
+strings = StringCatalog(__file__)
+
+class RealmSet(ItemSet):
+ def render_title(self, session, vhost):
+ return "Realms (%i)" % len(vhost.realm_items())
+
+ def get_items(self, session, vhost):
+ return sorted(vhost.realm_items())
+
+ def render_item_name(self, session, realm):
+ return realm.name
+
+class RealmParameter(Parameter):
+ def do_unmarshal(self, string):
+ return self.app.model.get_realm(int(string))
+
+ def do_marshal(self, queue):
+ return str(realm.id)
+
+class RealmInputSet(CheckboxInputSet):
+ def __init__(self, app, name, form):
+ super(RealmInputSet, self).__init__(app, name, form)
+
+ param = ListParameter(app, "param", RealmParameter(app,
"item"))
+ self.add_parameter(param)
+ self.set_parameter(param)
+
+ def get_items(self, session, vhost):
+ return sorted(vhost.realm_items())
+
+ # XXX just parked here
+ def do_process(self, session, queue):
+ for realm in self.get(session):
+ if realm not in queue.realm_items():
+ queue.add_realm(realm)
+
+ for realm in list(queue.realm_items()):
+ if realm not in self.get(session):
+ queue.remove_realm(realm)
+
+ def render_item_value(self, session, realm):
+ return realm.id
+
+ def render_item_content(self, session, realm):
+ return realm.name
+
+ def render_item_checked_attr(self, session, realm):
+ return realm in self.param.get(session) and
"checked=\"checked\""
Added: mgmt/cumin/python/cumin/realm.strings
===================================================================
--- mgmt/cumin/python/cumin/realm.strings (rev 0)
+++ mgmt/cumin/python/cumin/realm.strings 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,13 @@
+[RealmInputSet.item_html]
+<div class="field">
+ <input type="checkbox" name="{name}"
value="{item_value}" tabindex="{tabindex}" {item_checked_attr}/>
+ {item_content}
+</div>
+
+[RealmSet.html]
+<ul class="RealmSet mobjects">{items}</ul>
+
+[RealmSet.item_html]
+<li>
+ <strong><a
href="{item_href}">{item_name}</a></strong>
+</li>
Added: mgmt/cumin/python/cumin/server.py
===================================================================
--- mgmt/cumin/python/cumin/server.py (rev 0)
+++ mgmt/cumin/python/cumin/server.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,146 @@
+from wooly import *
+from wooly.widgets import *
+
+from virtualhost import *
+from widgets import *
+
+strings = StringCatalog(__file__)
+
+class ServerSet(ItemSet):
+ def render_title(self, session, model):
+ return "Servers (%i)" % len(model.get_servers())
+
+ def get_items(self, session, model):
+ return sorted(model.get_servers())
+
+ def render_item_href(self, session, server):
+ branch = session.branch()
+ self.page().show_server(branch, server).show_view(branch)
+ return branch.marshal()
+
+ def render_item_name(self, session, server):
+ return server.name
+
+ def render_item_cluster_name(self, session, server):
+ cluster = server.get_cluster()
+
+ if cluster:
+ return cluster.name
+
+ def render_item_cluster_href(self, session, server):
+ cluster = server.get_cluster()
+
+ if cluster:
+ branch = session.branch()
+ self.page().show_cluster(branch, cluster).show_view(branch)
+ return branch.marshal()
+
+class ServerParameter(Parameter):
+ def do_unmarshal(self, string):
+ return self.app.model.get_server(int(string))
+
+ def do_marshal(self, server):
+ return str(server.id)
+
+class ServerFrame(CuminFrame):
+ def __init__(self, app, name):
+ super(ServerFrame, self).__init__(app, name)
+
+ self.param = ServerParameter(app, "id")
+ self.add_parameter(self.param)
+ self.set_object_attribute(self.param)
+
+ self.view = ServerView(app, "view")
+ self.add_child(self.view)
+
+ self.vhost = VirtualHostFrame(app, "vhost")
+ self.add_child(self.vhost)
+
+ def set_server(self, session, server):
+ self.param.set(session, server)
+
+ def show_view(self, session):
+ return self.show_mode(session, self.view)
+
+ def show_virtual_host(self, session, vhost):
+ self.vhost.set_virtual_host(session, vhost)
+ return self.show_mode(session, self.vhost)
+
+ def show_queue(self, session, queue):
+ vhost = self.show_virtual_host(session, queue.virtual_host)
+ return vhost.show_queue(session, queue)
+
+ def show_exchange(self, session, exchange):
+ vhost = self.show_virtual_host(session, exchange.virtual_host)
+ return vhost.show_exchange(session, exchange)
+
+ def render_href(self, session, server):
+ branch = session.branch()
+ self.show_mode(branch, self.view)
+ return branch.marshal()
+
+ def render_title(self, session, server):
+ return "Server '%s'" % server.name
+
+class ServerView(Widget):
+ def __init__(self, app, name):
+ super(ServerView, self).__init__(app, name)
+
+ self.tabs = TabSet(app, "tabs")
+ self.add_child(self.tabs)
+
+ self.vhosts = VirtualHostSet(app, "virtual_hosts")
+ self.tabs.add_child(self.vhosts)
+
+ self.log = self.ServerLog(app, "log")
+ self.tabs.add_child(self.log)
+
+ def render_title(self, session, server):
+ return "Server '%s'" % server.name
+
+ class ServerLog(Widget):
+ def render_title(self, session, server):
+ return "Log Messages"
+
+class ServerGroupTree(Widget):
+ def __init__(self, app, name):
+ super(ServerGroupTree, self).__init__(app, name)
+
+ self.item_tmpl = Template(self, "item_html")
+
+ def render_title(self, session, model):
+ return "Server Groups (%i)" % len(model.get_server_groups())
+
+ def get_root_items(self, session, model):
+ roots = list()
+
+ for group in model.get_server_groups():
+ if not group.parent_items():
+ roots.append(group)
+
+ return roots
+
+ def get_child_items(self, session, group):
+ return group.child_items()
+
+ def render_root_items(self, session, model):
+ roots = self.get_root_items(session, model)
+
+ if roots:
+ writer = Writer()
+
+ for root in roots:
+ self.item_tmpl.render(session, root, writer)
+
+ return writer.to_string()
+
+ def render_child_items(self, session, object):
+ writer = Writer()
+
+ for child in self.get_child_items(session, object):
+ self.item_tmpl.render(session, child, writer)
+
+ return writer.to_string()
+
+ def render_item_name(self, session, group):
+ return group.name
Added: mgmt/cumin/python/cumin/server.strings
===================================================================
--- mgmt/cumin/python/cumin/server.strings (rev 0)
+++ mgmt/cumin/python/cumin/server.strings 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,44 @@
+[ServerSet.html]
+<table class="ServerSet mobjects">
+ <tr>
+ <th>Name</th>
+ <th>Cluster</th>
+ <th>Status</th>
+ </tr>
+{items}
+</table>
+
+[ServerSet.item_html]
+<tr>
+ <td><a href="{item_href}">{item_name}</a></td>
+ <td><a
href="{item_cluster_href}">{item_cluster_name}</a></td>
+ <td>0 errors, 0 warnings</td>
+</tr>
+
+[ServerView.html]
+<div class="oblock">
+ <h1>{title}</h1>
+
+ <ul class="actions">
+ <li><a href="">Shutdown</a></li>
+ </ul>
+ {tabs}
+</div>
+
+[ServerGroupTree.css]
+ul.ServerGroupTree, ul.ServerGroupTree ul {
+ list-style: square;
+ color: #ccc;
+ padding: 0 0 0 2em;
+}
+
+ul.ServerGroupTree {
+ padding: 0 0 0 1em;
+}
+
+[ServerGroupTree.html]
+<ul class="ServerGroupTree">{root_items}</ul>
+
+[ServerGroupTree.item_html]
+<li><a href="">{item_name}</a></li>
+<ul>{child_items}</ul>
Added: mgmt/cumin/python/cumin/virtualhost.py
===================================================================
--- mgmt/cumin/python/cumin/virtualhost.py (rev 0)
+++ mgmt/cumin/python/cumin/virtualhost.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,117 @@
+from wooly import *
+from wooly.widgets import *
+
+from queue import *
+from realm import *
+from exchange import *
+from widgets import *
+
+strings = StringCatalog(__file__)
+
+class VirtualHostSet(ItemSet):
+ def render_title(self, session, server):
+ return "Virtual Hosts (%i)" % len(server.virtual_host_items())
+
+ def get_items(self, session, server):
+ return sorted(server.virtual_host_items())
+
+ def render_item_href(self, session, vhost):
+ branch = session.branch()
+ self.page().show_virtual_host(branch, vhost)
+ return branch.marshal()
+
+ def render_item_name(self, session, vhost):
+ return vhost.name
+
+class VirtualHostFrame(CuminFrame):
+ def __init__(self, app, name):
+ super(VirtualHostFrame, self).__init__(app, name)
+
+ self.param = self.VirtualHostParameter(app, "id")
+ self.add_parameter(self.param)
+ self.set_object_attribute(self.param)
+
+ self.view = VirtualHostView(app, "view")
+ self.add_child(self.view)
+
+ self.queue_add = QueueAdd(app, "queue_add")
+ self.add_child(self.queue_add)
+
+ self.queue = QueueFrame(app, "queue")
+ self.add_child(self.queue)
+
+ self.exchange_add = ExchangeAdd(app, "exchange_add")
+ self.add_child(self.exchange_add)
+
+ self.exchange = ExchangeFrame(app, "exchange")
+ self.add_child(self.exchange)
+
+ class VirtualHostParameter(Parameter):
+ def do_unmarshal(self, string):
+ return self.app.model.get_virtual_host(int(string))
+
+ def do_marshal(self, vhost):
+ return str(vhost.id)
+
+ def set_virtual_host(self, session, vhost):
+ return self.param.set(session, vhost)
+
+ def show_view(self, session):
+ return self.show_mode(session, self.view)
+
+ def show_queue_add(self, session):
+ return self.show_mode(session, self.queue_add)
+
+ def show_queue(self, session, queue):
+ self.queue.set_queue(session, queue)
+
+ return self.show_mode(session, self.queue)
+
+ def show_exchange_add(self, session):
+ return self.show_mode(session, self.exchange_add)
+
+ def show_exchange(self, session, exchange):
+ self.exchange.set_exchange(session, exchange)
+
+ return self.show_mode(session, self.exchange)
+
+ def render_href(self, session, vhost):
+ branch = session.branch()
+ self.show_view(branch)
+ return branch.marshal()
+
+ def render_title(self, session, vhost):
+ return "Virtual Host '%s'" % vhost.name
+
+class VirtualHostView(Widget):
+ def __init__(self, app, name):
+ super(VirtualHostView, self).__init__(app, name)
+
+ self.tabs = TabSet(app, "tabs")
+ self.add_child(self.tabs)
+
+ self.queues = QueueSet(app, "queues")
+ self.tabs.add_child(self.queues)
+
+ self.exchanges = ExchangeSet(app, "exchanges")
+ self.tabs.add_child(self.exchanges)
+
+ self.log = self.VirtualHostLog(app, "log")
+ self.tabs.add_child(self.log)
+
+ def render_title(self, session, vhost):
+ return "Virtual Host '%s'" % vhost.name
+
+ def render_add_queue_href(self, session, vhost):
+ branch = session.branch()
+ self.page().show_virtual_host(branch, vhost).show_queue_add(branch)
+ return branch.marshal()
+
+ def render_add_exchange_href(self, session, vhost):
+ branch = session.branch()
+ self.page().show_virtual_host(branch, vhost).show_exchange_add(branch)
+ return branch.marshal()
+
+ class VirtualHostLog(Widget):
+ def render_title(self, session, vhost):
+ return "Log Messages"
Added: mgmt/cumin/python/cumin/virtualhost.strings
===================================================================
--- mgmt/cumin/python/cumin/virtualhost.strings (rev 0)
+++ mgmt/cumin/python/cumin/virtualhost.strings 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,26 @@
+[VirtualHostSet.html]
+<table class="VirtualHostSet mobjects">
+ <tr>
+ <th>Name</th>
+ <th>Configuration</th>
+ <th>Status</th>
+ </tr>
+{items}
+</table>
+
+[VirtualHostSet.item_html]
+<tr>
+ <td><a href="{item_href}">{item_name}</a></td>
+ <td>10 queues, 5 exchanges</td>
+ <td>2 errors, 10 warnings</td>
+</tr>
+
+[VirtualHostView.html]
+<div class="oblock">
+ <h1>{title}</h1>
+ <ul class="actions">
+ <li><a href="{add_queue_href}">Add Queue</a></li>
+ <li><a href="{add_exchange_href}">Add
Exchange</a></li>
+ </ul>
+{tabs}
+</div>
Added: mgmt/cumin/python/cumin/widgets.py
===================================================================
--- mgmt/cumin/python/cumin/widgets.py (rev 0)
+++ mgmt/cumin/python/cumin/widgets.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,102 @@
+from wooly import *
+from wooly.widgets import *
+from wooly.forms import *
+
+strings = StringCatalog(__file__)
+
+class CuminFrame(Frame, ModeSet):
+ def do_process(self, session, object):
+ self.page().get_frames(session).append(self)
+
+ super(CuminFrame, self).do_process(session, object)
+
+class CuminForm(Form):
+ def __init__(self, app, name):
+ super(CuminForm, self).__init__(app, name)
+
+ self.cancel = self.Cancel(app, "cancel", self)
+ self.cancel.set_tab_index(201)
+ self.add_child(self.cancel)
+
+ self.submit = self.Submit(app, "submit", self)
+ self.submit.set_tab_index(200)
+ self.add_child(self.submit)
+
+ def do_process(self, session, object):
+ self.page().set_modal(session, True)
+
+ if self.cancel.get(session):
+ self.cancel.set(session, False)
+
+ self.on_cancel(session, object)
+ elif self.submit.get(session):
+ self.submit.set(session, False)
+
+ self.on_submit(session, object)
+ else:
+ self.on_display(session, object)
+
+ def on_cancel(self, session, object):
+ pass
+
+ def on_submit(self, session, object):
+ pass
+
+ def on_display(self, session, object):
+ pass
+
+ class Cancel(FormButton):
+ def render_content(self, session, object):
+ return "Cancel"
+
+ class Submit(FormButton):
+ def render_content(self, session, object):
+ return "Submit"
+
+class CuminConfirmForm(Form):
+ def __init__(self, app, name):
+ super(CuminConfirmForm, self).__init__(app, name)
+
+ self.confirm = self.ConfirmButton(app, "confirm", self)
+ self.confirm.set_tab_index(101)
+ self.add_child(self.confirm)
+
+ self.cancel = self.CancelButton(app, "cancel", self)
+ self.add_child(self.cancel)
+
+ def do_process(self, session, object):
+ self.page().set_modal(session, True)
+
+ if self.confirm.get(session):
+ self.confirm.set(session, False)
+
+ self.on_confirm(session, object)
+ elif self.cancel.get(session):
+ self.cancel.set(session, False)
+
+ self.on_cancel(session, object)
+ else:
+ self.on_display(session, object)
+
+ def on_cancel(self, session, object):
+ pass
+
+ def on_confirm(self, session, object):
+ pass
+
+ def on_display(self, session, object):
+ pass
+
+ def render_confirm_content(self, session, object):
+ return "Confirm"
+
+ def render_cancel_content(self, session, object):
+ return "Cancel"
+
+ class ConfirmButton(FormButton):
+ def render_content(self, session, object):
+ return self.parent.render_confirm_content(session, object)
+
+ class CancelButton(FormButton):
+ def render_content(self, session, object):
+ return self.parent.render_cancel_content(session, object)
Added: mgmt/cumin/python/cumin/widgets.strings
===================================================================
--- mgmt/cumin/python/cumin/widgets.strings (rev 0)
+++ mgmt/cumin/python/cumin/widgets.strings 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,14 @@
+[CuminConfirmForm.html]
+<form id="{id}" class="QueueForm mform" method="post"
action="?">
+ <div class="head">
+ <h1>{title}</h1>
+ </div>
+ <div class="body">
+ <div>{confirm}</div>
+ <div>{cancel}</div>
+{hidden_inputs}
+ </div>
+</form>
+<script>
+wooly.doc().elem("{id}").node.elements[1].focus();
+</script>
Added: mgmt/cumin/python/wooly/__init__.py
===================================================================
--- mgmt/cumin/python/wooly/__init__.py (rev 0)
+++ mgmt/cumin/python/wooly/__init__.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,639 @@
+import sys, os
+from cStringIO import StringIO
+from urllib import quote_plus, unquote_plus
+from copy import copy
+from time import time
+from datetime import datetime
+
+from resources import ResourceFinder, StringCatalog
+
+class Widget(object):
+ html = "{content}"
+
+ def __init__(self, app, name):
+ self.app = app
+ self.name = name
+ self.parent = None
+ self.children = list()
+ self.attributes = list()
+ self.parameters = list()
+ self.template = Template(self, "html")
+
+ self.cached_path = None
+ self.cached_page = None
+ self.child_index = None
+
+ app.add_widget(self)
+
+ self.strings = None
+
+ def path(self):
+ if not self.cached_path:
+ if not self.parent:
+ self.cached_path = ""
+ elif not self.parent.parent:
+ self.cached_path = self.name;
+ else:
+ self.cached_path = self.parent.path() + "." + self.name
+
+ return self.cached_path
+
+ def page(self):
+ if not self.cached_page:
+ if not self.parent:
+ self.cached_page = self
+ else:
+ self.cached_page = self.parent.page()
+
+ return self.cached_page
+
+ def add_child(self, widget):
+ self.children.append(widget)
+ widget.parent = self
+
+ def get_child(self, name):
+ if not self.child_index:
+ self.child_index = dict()
+
+ for child in self.children:
+ self.child_index[child.name] = child
+
+ return self.child_index.get(name, None)
+
+ def add_attribute(self, attribute):
+ self.attributes.append(attribute)
+ attribute.widget = self
+
+ def add_parameter(self, parameter):
+ self.parameters.append(parameter)
+ parameter.widget = self
+
+ def get_string(self, key):
+ for cls in self.__class__.__mro__:
+ str = None
+ module = sys.modules[cls.__module__]
+
+ strs = module.__dict__.get("strings")
+
+ if strs:
+ str = strs.get(cls.__name__ + "." + key)
+
+ if str:
+ return str
+
+ str = cls.__dict__.get(key)
+
+ if str:
+ return str
+
+ def scope(self, session, params):
+ params.update(self.parameters)
+
+ for child in self.children:
+ child.scope(session, params)
+
+ def process(self, session, object):
+ if session.debug:
+ call = WidgetCall(session.process_stack, self, session, object)
+ call.open()
+
+ self.do_process(session, object)
+
+ if session.debug:
+ call.close()
+
+ def do_process(self, session, object):
+ for child in self.children:
+ child.process(session, object)
+
+ def render(self, session, object):
+ if session.debug:
+ call = WidgetCall(session.render_stack, self, session, object)
+ call.open()
+
+ string = self.do_render(session, object)
+
+ if session.debug:
+ call.close()
+
+ return string
+
+ def do_render(self, session, object):
+ writer = Writer()
+
+ self.template.render(session, object, writer)
+
+ return writer.to_string()
+
+ def render_id(self, session, object):
+ return self.path()
+
+ def render_class(self, session, object):
+ return None
+
+ def render_href(self, session, object):
+ return None
+
+ def render_title(self, session, object):
+ return None
+
+ def render_content(self, session, object):
+ writer = Writer()
+
+ for child in self.children:
+ writer.write(child.render(session, object))
+
+ return writer.to_string()
+
+ def __str__(self):
+ return "%s '%s'" % (self.__class__.__name__, self.path())
+
+class Page(Widget):
+ xml_content_type = "text/xml"
+ html_content_type = "text/html"
+ xml_1_0_declaration = """<?xml
version="1.0"?>"""
+ xhtml_1_1_doctype = """<!DOCTYPE html PUBLIC "-//W3C//DTD
XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">"""
+ xhtml_namespace = "http://www.w3.org/1999/xhtml"
+
+ def __init__(self, app, name):
+ super(Page, self).__init__(app, name)
+
+ def get_last_modified(self, session):
+ return datetime.utcnow()
+
+ def get_content_type(self, session):
+ return "text/html"
+
+ def save_session(self, session):
+ pass
+
+class Frame(Widget):
+ def __init__(self, app, name):
+ super(Frame, self).__init__(app, name)
+
+ self.object = None
+
+ def set_object_attribute(self, attribute):
+ self.object = attribute
+
+ def get_object(self, session):
+ if self.object:
+ return self.object.get(session)
+
+ def do_process(self, session, object):
+ new_object = self.get_object(session)
+ super(Frame, self).do_process(session, new_object)
+
+ def do_render(self, session, object):
+ new_object = self.get_object(session)
+ return super(Frame, self).do_render(session, new_object)
+
+class Application(object):
+ def __init__(self):
+ self.pages = dict()
+ self.default_page = None
+
+ # Registration lists XXX get rid of?
+ self.widgets = list()
+ self.parameters = list()
+
+ self.finder = ResourceFinder()
+ self.cached_css = None
+
+ self.sessions = list()
+
+ def add_page(self, page):
+ if page.parent:
+ raise Exception("Page '%s' is not a root widget" %
page.name)
+
+ self.pages[page.name] = page
+
+ def get_page(self, name):
+ return self.pages.get(name, self.default_page)
+
+ def set_default_page(self, page):
+ self.default_page = page
+
+ def add_widget(self, widget):
+ self.widgets.append(widget)
+
+ def get_widget(self, key):
+ # XXX not fast
+ for widget in self.widgets:
+ if widget.path() == key:
+ return widget
+
+ def add_parameter(self, param):
+ self.parameters.append(param)
+
+ def get_parameter(self, key):
+ # XXX not fast
+ for param in self.parameters:
+ # XXX I'm not sure the param.widget test is what I want
+ if param.widget and param.path() == key:
+ return param
+
+ def add_resource_dir(self, dir):
+ self.finder.add_dir(dir)
+
+ def get_resource(self, name):
+ return self.finder.find(name)
+
+ def get_css(self):
+ if not self.cached_css:
+ writer = Writer()
+
+ for widget in self.widgets:
+ css = widget.get_string("css")
+
+ if css:
+ writer.write(css)
+ writer.write("\r\n") # HTTP newline
+
+ self.cached_css = writer.to_string()
+
+ return self.cached_css
+
+ def clear_caches(self):
+ self.cached_css = None
+
+class Attribute(object):
+ def __init__(self, app, name):
+ self.app = app
+ self.name = name
+ self.widget = None
+ self.default = None
+ self.is_required = True
+
+ def path(self):
+ if not self.widget:
+ raise Exception("Parameter has no widget")
+
+ if not self.widget.parent:
+ return self.name
+ else:
+ path = self.widget.path() + "." + self.name
+
+ return path
+
+ def set_required(self, is_required):
+ self.is_required = is_required
+
+ def validate(self, session):
+ value = self.get(session)
+
+ if value == None and self.is_required:
+ raise Exception("%s not set" % str(self))
+
+ def get(self, session):
+ value = session.get(self.path())
+
+ # Use strict test because empty collections are False
+ if value == None:
+ default = self.get_default(session)
+
+ if default != None:
+ value = self.set(session, default)
+
+ return value
+
+ def add(self, session, value):
+ self.set(session, value)
+
+ def set(self, session, value):
+ return session.set(self.path(), value)
+
+ def get_default(self, session):
+ return self.default
+
+ def set_default(self, default):
+ self.default = default
+
+ def __str__(self):
+ return "%s '%s'" % (self.__class__.__name__, self.path())
+
+class Parameter(Attribute):
+ def __init__(self, app, name):
+ super(Parameter, self).__init__(app, name)
+
+ app.add_parameter(self)
+
+ def marshal(self, object):
+ if object == None:
+ string = ""
+ else:
+ string = self.do_marshal(object)
+
+ return string
+
+ def do_marshal(self, object):
+ return str(object)
+
+ def unmarshal(self, string):
+ return self.do_unmarshal(string)
+
+ def do_unmarshal(self, string):
+ return string
+
+class Session(object):
+ def __init__(self, app, trunk=None):
+ self.app = app
+ self.trunk = trunk
+ self.page = None
+ self.values = dict()
+ self.errors = dict() # widget => list of str
+ self.redirect = None
+
+ self.debug = True
+
+ self.process_stack = list()
+ self.render_stack = list()
+
+ def print_process_calls(self, out=sys.stdout):
+ if self.process_stack:
+ self.process_stack[0].write(out)
+
+ def print_render_calls(self, out=sys.stdout):
+ if self.render_stack:
+ self.render_stack[0].write(out)
+
+ def branch(self):
+ return Session(self.app, self)
+
+ def get_page(self):
+ if self.page:
+ page = self.page
+ elif self.trunk:
+ page = self.trunk.get_page()
+ else:
+ page = None
+
+ return page
+
+ def set_page(self, page):
+ self.page = page
+
+ def get(self, key):
+ if key in self.values:
+ value = self.values[key]
+ elif self.trunk:
+ value = self.trunk.get(key)
+ else:
+ value = None
+
+ return value
+
+ def set(self, key, value):
+ # XXX interesting idea for debugging: catch where things get set
+ #if key.startswith("server.vhost.queue.edit.durable"):
+ # raise Exception()
+
+ self.values[key] = value
+
+ return value
+
+ def unset(self, key):
+ if key in self.values:
+ del self.values[key]
+
+ # XXX this is a little out of line with other session methods in
+ # that it uses widget as a key rather than having a method through
+ # widget to do that
+ def get_errors(self, widget):
+ return self.errors.get(widget)
+
+ def add_error(self, widget, error):
+ errors = self.errors.setdefault(widget, list())
+
+ errors.append(error)
+
+ def set_redirect(self, redirect):
+ self.redirect = redirect
+
+ def marshal(self):
+ return self.marshal_page() + "?" + self.marshal_url_vars()
+
+ def marshal_page(self):
+ return self.get_page().name
+
+ def marshal_url_vars(self, separator=";"):
+ params = set()
+ self.get_page().scope(self, params)
+ vars = list()
+
+ for param in params:
+ key = param.path()
+ value = self.get(key)
+ default = param.get_default(self)
+
+ if value not in (default, None):
+ skey = quote_plus(key)
+ svalue = quote_plus(param.marshal(value))
+
+ vars.append("%s=%s" % (skey, svalue))
+
+ return separator.join(vars)
+
+ def unmarshal(self, string):
+ if string.startswith("/"):
+ raise Exception("Illegal session string '" + string +
"'")
+
+ elems = string.split("?")
+
+ self.unmarshal_page(elems[0])
+
+ try:
+ self.unmarshal_url_vars(elems[1])
+ except IndexError:
+ pass
+
+ def unmarshal_page(self, string):
+ self.set_page(self.app.get_page(string))
+
+ def unmarshal_url_vars(self, string, separator=";"):
+ vars = string.split(separator)
+
+ for var in vars:
+ try:
+ skey, svalue = var.split("=")
+
+ key = unquote_plus(skey)
+ value = unquote_plus(svalue)
+
+ param = self.app.get_parameter(key)
+
+ if param:
+ param.add(self, param.unmarshal(value))
+ except ValueError:
+ pass
+
+class StringIOWriter(object):
+ def __init__(self):
+ self.writer = StringIO()
+
+ def write(self, string):
+ self.writer.write(string)
+
+ def to_string(self):
+ string = self.writer.getvalue()
+
+ self.writer.close()
+
+ return string
+
+class ListWriter(object):
+ def __init__(self):
+ self.strings = list()
+
+ def write(self, string):
+ self.strings.append(string)
+
+ def to_string(self):
+ return "".join(self.strings)
+
+class Writer(StringIOWriter):
+ pass
+
+class Template(object):
+ def __init__(self, widget, key):
+ self.widget = widget
+ self.key = key
+ self.fragments = None
+
+ def compile(self):
+ text = self.widget.get_string(self.key)
+
+ if not text:
+ raise Exception("Template '%s.%s' not found" \
+ % (self.widget.__class__.__name__, self.key))
+
+ return self.resolve(self.parse(text))
+
+ def parse(self, text):
+ strings = list()
+
+ start = 0
+ end = text.find("{")
+
+ while True:
+ if (end == -1):
+ strings.append(text[start:])
+ break
+
+ strings.append(text[start:end])
+
+ ccurly = text.find("}", end + 1)
+
+ if ccurly == -1:
+ start = end
+ end = -1
+ else:
+ ocurly = text.find("{", end + 1)
+
+ if ocurly == -1:
+ start = end
+ end = ccurly + 1
+ elif ocurly < ccurly:
+ start = end
+ end = ocurly
+ else:
+ strings.append("{" + text[end + 1:ccurly] + "}")
+
+ start = ccurly + 1
+ end = ocurly
+
+ return strings
+
+ def resolve(self, strings):
+ fragments = list()
+
+ for string in strings:
+ if string.startswith("{") and string.endswith("}"):
+ # Might be a placeholder; look for a matching render
+ # method
+ key = string[1:-1]
+ method = self.find_method("render_" + key)
+
+ if method:
+ fragments.append(method)
+ else:
+ child = self.widget.get_child(key)
+
+ if child:
+ fragments.append(child)
+ else:
+ fragments.append(string)
+ else:
+ fragments.append(string)
+
+ return fragments
+
+ # XXX Ought to make sure what we're returning is a method.
+ def find_method(self, name):
+ for cls in self.widget.__class__.__mro__:
+ method = getattr(cls, name, None)
+
+ if method:
+ return method
+
+ def render(self, session, object, writer):
+ if not self.fragments:
+ self.fragments = self.compile()
+
+ for elem in self.fragments:
+ if type(elem) is str:
+ writer.write(elem)
+ elif callable(elem):
+ writer.write(str(elem(self.widget, session, object) or ""))
+ else:
+ writer.write(str(elem.render(session, object) or ""))
+
+class WidgetCall(object):
+ def __init__(self, stack, widget, session, object):
+ self.stack = stack
+ self.widget = widget
+ self.session = session
+ self.session_values = copy(session.values)
+ self.object = object
+
+ self.caller = None
+ self.callees = list()
+
+ self.start = None
+ self.end = None
+
+ def open(self):
+ if self.stack:
+ self.caller = self.stack[-1]
+ self.caller.callees.append(self)
+
+ self.stack.append(self)
+
+ self.start = time()
+
+ def close(self):
+ self.end = time()
+
+ if len(self.stack) > 1:
+ self.stack.pop()
+
+ def write(self, writer):
+ writer.write(str(self.widget))
+ writer.write(" (call %i, caller %i)" % (id(self), id(self.caller)))
+ writer.write(os.linesep)
+
+ writer.write(" session: " + str(self.session))
+ writer.write(os.linesep)
+
+ for item in self.session_values.iteritems():
+ writer.write(" value: %s = %s" % item)
+ writer.write(os.linesep)
+
+ writer.write(" object: " + str(self.object))
+ writer.write(os.linesep)
+
+ writer.write(" times: %f, %f" % (self.start, self.end or -1))
+ writer.write(os.linesep)
+
+ for call in self.callees:
+ call.write(writer)
Added: mgmt/cumin/python/wooly/debug.py
===================================================================
--- mgmt/cumin/python/wooly/debug.py (rev 0)
+++ mgmt/cumin/python/wooly/debug.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,158 @@
+from wooly import *
+from wooly.widgets import *
+
+class WidgetInspector(Widget):
+ css = """
+ .WidgetInspector {
+ background-color: white;
+ padding: 1em;
+ border: 1px solid #ccc;
+ min-height: 60em;
+ }
+
+ .WidgetInspector .list {
+ float: left;
+ width: 20em;
+ background-color: #f7f7f7;
+ padding: 0.75em 1em;
+ border: 1px dashed #ccc;
+ margin: 0 1em 0 0;
+ }
+
+ """
+
+ html = """
+ <div class="WidgetInspector">
+ <div class="list">{widgets}</div>
+ {view}
+ </div>
+ """
+
+ def __init__(self, app, name):
+ super(WidgetInspector, self).__init__(app, name)
+
+ self.widgets = WidgetList(app, "widgets")
+ self.add_child(self.widgets)
+
+ self.view = WidgetView(app, "view")
+ self.add_child(self.view)
+
+class WidgetView(Widget):
+ css = """
+ """
+
+ html = """
+ <h2>{title}</h2>
+ """
+
+ def render_title(self, session, object):
+ return self.parent.widgets.get_selected_widget(session).name
+
+class WidgetList(Widget):
+ css = """
+ .WidgetList ul {
+ list-style: none;
+ margin: 0;
+ padding: 0 0 0 1.5em;
+ }
+
+ .WidgetList .selected {
+ font-weight: bold;
+ }
+ """
+
+ html = """
+ <div class="WidgetList"><ul>{widgets}</ul></div>
+ """
+
+ def __init__(self, app, name):
+ super(WidgetList, self).__init__(app, name)
+
+ self.widget = Parameter(app, "widget")
+ self.add_parameter(self.widget)
+
+ self.item = self.WidgetItem(app, "item")
+ self.add_child(self.item)
+
+ def do_process(self, session, object):
+ print "do_process"
+
+ if not self.get_selected_widget(session):
+ self.set_selected_widget(session, self.page())
+
+ def get_selected_widget(self, session):
+ return self.app.get_widget(self.widget.get(session))
+
+ def set_selected_widget(self, session, widget):
+ self.widget.set(session, widget.path())
+
+ def render_title(self, session, object):
+ return "Widgets"
+
+ def render_widgets(self, session, object):
+ return self.item.render(session, self.page())
+
+ class WidgetItem(Widget):
+ html = """
+ <li class="{selected}">{class} <a
href="{href}">{name}</a></li>
+ {children}
+ """
+
+ def render_selected(self, session, widget):
+ if widget == self.parent.get_selected_widget(session):
+ return "selected"
+
+ def render_href(self, session, widget):
+ branch = session.branch()
+ self.parent.set_selected_widget(branch, widget)
+ return branch.marshal()
+
+ def render_class(self, session, widget):
+ return widget.__class__.__name__
+
+ def render_name(self, session, widget):
+ return widget.name
+
+ def render_children(self, session, widget):
+ if len(widget.children):
+ writer = Writer()
+ writer.write("<ul>")
+
+ for child in widget.children:
+ writer.write(self.parent.item.render(session, child))
+
+ writer.write("</ul>")
+
+ return writer.to_string()
+
+ def xxx_render_widget(self, session, widget, depth, writer):
+ spacer = ". "
+ indent = spacer * depth
+
+ try:
+ cum_time = session.render_times[widget] * 1000
+ self_time = cum_time
+ for child in widget.children:
+ self_time -= session.render_times[child] * 1000
+
+ if widget.parent and cum_time > 0.2 * session.render_times[widget.parent]
* 1000:
+ hot = "hot"
+ else:
+ hot = ""
+
+ stime = " (<span class='wooly debug timing %s'>%f,
%f</span>)" % \
+ (hot, cum_time, self_time)
+ except KeyError:
+ stime = ""
+
+ writer.write("%s%s <b>%s</b>%s\n" % \
+ (indent, widget.__class__.__name__, widget.name, \
+ stime))
+
+ for param in widget.parameters:
+ writer.write("%s<span class='wooly debug param'>param %s
<strong>%s</strong> = %s</span>\n" % \
+ (indent + spacer, param.__class__.__name__, \
+ param.name, param.get(session)))
+
+ for child in widget.children:
+ self.render_widget(session, child, depth + 1, writer)
Added: mgmt/cumin/python/wooly/devel.py
===================================================================
--- mgmt/cumin/python/wooly/devel.py (rev 0)
+++ mgmt/cumin/python/wooly/devel.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,42 @@
+import sys, os
+
+from wooly import *
+
+class DevelPage(Page):
+ html = """
+ <html>
+ <head>
+ <title>{title}</title>
+ </head>
+ <body>{rtrace}</body>
+ </html>
+ """
+
+ def __init__(self, app, name):
+ super(DevelPage, self).__init__(app, name)
+
+ self.render_trace = self.RenderTrace(app, "rtrace")
+ self.add_child(self.render_trace)
+
+ class RenderTrace(Widget):
+ def do_render(self, session, object):
+ writer = Writer()
+
+ writer.write("<ul>")
+
+ if self.app.sessions:
+ call = self.app.sessions[-1].render_stack[0]
+ self.render_call(session, call, writer)
+
+ writer.write("</ul>")
+
+ return writer.to_string()
+
+ def render_call(self, session, call, writer):
+ writer.write("<li>%s</li>" % str(call.widget))
+ writer.write("<ul>")
+
+ for c in call.callees:
+ self.render_call(session, c, writer)
+
+ writer.write("</ul>")
Added: mgmt/cumin/python/wooly/forms.py
===================================================================
--- mgmt/cumin/python/wooly/forms.py (rev 0)
+++ mgmt/cumin/python/wooly/forms.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,177 @@
+from wooly import *
+from parameters import *
+from resources import *
+from widgets import ItemSet
+
+strings = StringCatalog(__file__)
+
+class Form(Widget):
+ def __init__(self, app, name):
+ super(Form, self).__init__(app, name)
+
+ self.form_params = set()
+
+ def add_form_parameter(self, param):
+ self.form_params.add(param)
+
+ def render_hidden_inputs(self, session, object):
+ writer = Writer()
+
+ params = set()
+ session.get_page().scope(session, params)
+ params.difference_update(self.form_params)
+
+ for param in params:
+ key = param.path()
+ value = session.get(key)
+ default = param.get_default(session)
+
+ if value not in (default, None):
+ writer.write("<input type='hidden' name='%s'
value='%s'/>" \
+ % (key, param.marshal(value)))
+
+ return writer.to_string()
+
+# XXX implement me
+#class FormInputItemSet(FormInput, ItemSet):
+# pass
+
+class FormInput(Widget):
+ def __init__(self, app, name, form):
+ super(FormInput, self).__init__(app, name)
+
+ self.form = form
+ self.param = None
+
+ self.tab_index = 100
+
+ def set_parameter(self, param):
+ if self.param:
+ raise Exception("Parameter already set")
+
+ self.param = param;
+ self.form.add_form_parameter(self.param)
+
+ def get_parameter(self):
+ return self.param
+
+ def get(self, session):
+ return self.param.get(session)
+
+ def set(self, session, value):
+ return self.param.set(session, value)
+
+ def get_default(self, session):
+ return self.param.get_default(session)
+
+ def set_default(self, default):
+ self.param.set_default(default)
+
+ def add_error(self, session, error):
+ session.add_error(self, error)
+
+ def get_errors(self, session):
+ return session.get_errors(self)
+
+ def set_tab_index(self, tab_index):
+ self.tab_index = tab_index
+
+ def render_name(self, session, object):
+ return self.param.path()
+
+ def render_value(self, session, object):
+ return self.param.marshal(self.param.get(session))
+
+ # XXX do this proper
+ def render_errors(self, session, object):
+ errors = self.get_errors(session)
+
+ if errors:
+ return "<ul class=\"errors\"><li>" + \
+ "</li><li>".join(errors) + \
+ "</li></ul>"
+
+ def render_tabindex(self, session, object):
+ return self.tab_index
+
+class TextInput(FormInput):
+ def __init__(self, app, name, form):
+ super(TextInput, self).__init__(app, name, form)
+
+ self.set_parameter(Parameter(app, "param"))
+ self.add_parameter(self.get_parameter())
+
+ self.size = 32
+
+ def set_size(self, size):
+ self.size = size
+
+ def render_size(self, session, object):
+ return self.size
+
+class CheckboxInput(FormInput):
+ def __init__(self, app, name, form):
+ super(CheckboxInput, self).__init__(app, name, form)
+
+ self.set_parameter(VoidBooleanParameter(app, "param"))
+ self.add_parameter(self.get_parameter())
+
+ def render_checked_attr(self, session, object):
+ return self.get(session) and "checked=\"checked\""
+
+class RadioInput(FormInput):
+ def __init__(self, app, name, form):
+ super(RadioInput, self).__init__(app, name, form)
+
+ self.value = None
+
+ def set_value(self, value):
+ self.value = value
+
+ def render_value(self, session, object):
+ return self.value
+
+ def render_checked_attr(self, session, object):
+ value = self.get(session)
+
+ return value and value == self.value and
"checked=\"checked\""
+
+class FormButton(FormInput):
+ def __init__(self, app, name, form):
+ FormInput.__init__(self, app, name, form)
+
+ self.set_parameter(BooleanParameter(app, "param"))
+ self.add_parameter(self.get_parameter())
+
+ def do_process(self, session, object):
+ if self.get(session):
+ self.set(session, False)
+
+ self.on_submit(session, object)
+
+ def on_submit(self, session, object):
+ pass
+
+ def render_value(self, session, object):
+ branch = session.branch()
+
+ self.set(branch, True)
+
+ return super(FormButton, self).render_value(branch, object)
+
+class CheckboxInputSet(FormInput, ItemSet):
+ def __init__(self, app, name, form):
+ super(CheckboxInputSet, self).__init__(app, name, form)
+
+ def render_item_value(self, session, object):
+ return None
+
+ def render_item_checked_attr(self, session, object):
+ return None
+
+class RadioInputSet(FormInput, ItemSet):
+ def render_item_value(self, session, object):
+ return None
+
+ def render_item_checked_attr(self, session, object):
+ return None
Added: mgmt/cumin/python/wooly/forms.strings
===================================================================
--- mgmt/cumin/python/wooly/forms.strings (rev 0)
+++ mgmt/cumin/python/wooly/forms.strings 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,26 @@
+[FormButton.html]
+<button type="submit" name="{name}" value="{value}"
tabindex="{tabindex}">{content}</button>
+
+[TextInput.html]
+{errors}
+<input type="text" name="{name}" value="{value}"
tabindex="{tabindex}" size="{size}"/>
+
+[CheckboxInput.html]
+<input type="checkbox" name="{name}" value="{value}"
tabindex="{tabindex}" {checked_attr}/>
+
+[RadioInput.html]
+<input type="radio" name="{name}" value="{value}"
tabindex="{tabindex}" {checked_attr}/>
+
+[CheckboxInputSet.html]
+{errors}{items}
+
+[CheckboxInputSet.item_html]
+<input type="checkbox" name="{name}"
value="{item_value}" tabindex="{tabindex}" {item_checked_attr}/>
+{item_content}
+
+[RadioInputSet.html]
+{errors}{items}
+
+[RadioInputSet.item_html]
+<input type="radio" name="{name}" value="{item_value}"
tabindex="{tabindex}" {item_checked_attr}/>
+{item_content}
Added: mgmt/cumin/python/wooly/model.py
===================================================================
--- mgmt/cumin/python/wooly/model.py (rev 0)
+++ mgmt/cumin/python/wooly/model.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,301 @@
+from new import instancemethod
+from threading import RLock
+
+class Model(object):
+ def __init__(self):
+ self.indexes = dict()
+ self.mclasses = dict()
+ self.__lock = RLock();
+
+ def get_index(self, cls):
+ return self.indexes.setdefault(cls, dict())
+
+ def lock(self):
+ self.__lock.acquire()
+
+ def unlock(self):
+ self.__lock.release()
+
+class ModelClass(object):
+ def __init__(self, model, name):
+ self.model = model
+ self.name = name
+ self.endpoints = set()
+
+class ModelAssociation(object):
+ def __init__(self, model, name):
+ self.model = model
+ self.name = name
+ self.endpoints = set()
+
+ def add_endpoint(self, mclass, name, multiplicity):
+ if not isinstance(mclass, ModelClass):
+ raise TypeError()
+
+ if multiplicity not in ("0..1", "0..n"):
+ raise Exception("Multiplicity not recognized")
+
+ if len(self.endpoints) > 1:
+ raise Exception("Too many endpoints")
+
+ endpoint = self.Endpoint()
+ endpoint.name = name
+ endpoint.multiplicity = multiplicity
+
+ self.endpoints.add(endpoint)
+ endpoint.association = self
+
+ mclass.endpoints.add(endpoint)
+ endpoint.mclass = mclass
+
+ return endpoint
+
+ class Endpoint(object):
+ def __init__(self):
+ self.association = None
+ self.mclass = None
+
+ self.name = None
+ self.multiplicity = None
+
+ def other(self):
+ for end in self.association.endpoints:
+ if end is not self:
+ return end
+
+ def is_scalar(self):
+ return self.multiplicity == "0..1"
+
+ def set_scalar(self, this, that):
+ this.__dict__[self.name] = that
+
+ def get_scalar(self, this):
+ return this.__dict__[self.name]
+
+ def items(self, this):
+ return this.__dict__[self.name + "_set"]
+
+ def add(self, this, that):
+ if self.is_scalar():
+ self.set_scalar(this, that)
+ else:
+ self.items(this).add(that)
+
+ def remove(self, this, that):
+ if self.is_scalar():
+ print "is_scalar"
+ self.set_scalar(this, None)
+ else:
+ print "items(%s).remove(%s)" % (this.id, that.id)
+ self.items(this).remove(that)
+
+ def object_items(self, this):
+ return self.items(this)
+
+ def add_object(self, this, that):
+ this.lock()
+ try:
+ self.add(this, that)
+
+ that.lock()
+ try:
+ self.other().add(that, this)
+ finally:
+ that.unlock()
+ finally:
+ this.unlock()
+
+ def remove_object(self, this, that):
+ print "remove_object"
+
+ this.lock()
+ try:
+ self.remove(this, that)
+
+ that.lock()
+ try:
+ self.other().remove(that, this)
+ finally:
+ that.unlock()
+ finally:
+ this.unlock()
+
+ def get_object(self, this):
+ return self.get_scalar(this)
+
+ def set_object(self, this, new_that):
+ this.lock()
+ try:
+ old_that = self.get_scalar(this)
+
+ if old_that != None:
+ old_that.lock()
+ try:
+ self.other().remove(old_that, this)
+ finally:
+ old_that.unlock()
+
+ self.set_scalar(this, new_that)
+
+ if new_that != None:
+ new_that.lock()
+ try:
+ self.other().add(new_that, this)
+ finally:
+ new_that.unlock()
+ finally:
+ this.unlock()
+
+class ModelObject(object):
+ sequence = 100
+
+ def __init__(self, model, mclass):
+ self.model = model
+ self.mclass = mclass
+ self.__lock = RLock()
+
+ for end in self.mclass.endpoints:
+ if end.is_scalar():
+ self.add_scalar_attributes(end)
+ else:
+ self.add_set_attributes(end)
+
+ self.lock()
+ try:
+ self.__class__.sequence += 1
+ self.id = self.__class__.sequence
+ model.get_index(self.mclass)[self.id] = self
+ finally:
+ self.unlock()
+
+ def lock(self):
+ self.__lock.acquire()
+
+ def unlock(self):
+ self.__lock.release()
+
+ def setmethod(self, name, func):
+ if not hasattr(self, name):
+ method = instancemethod(func, self, self.__class__)
+ setattr(self, name, method)
+
+ def add_set_attributes(this, end):
+ setattr(this, end.name + "_set", set())
+
+ def items_method(self): return end.object_items(this)
+ this.setmethod(end.name + "_items", items_method)
+
+ def do_add_method(self, that):
+ if that == None:
+ raise Exception()
+
+ end.add_object(this, that)
+
+ this.setmethod("do_add_" + end.name, do_add_method)
+
+ def add_method(self, that): do_add_method(self, that)
+ this.setmethod("add_" + end.name, add_method)
+
+ def do_remove_method(self, that):
+ if that == None:
+ raise Exception()
+
+ end.remove_object(this, that)
+
+ this.setmethod("do_remove_" + end.name, do_remove_method)
+
+ def remove_method(self, that): do_remove_method(self, that)
+ this.setmethod("remove_" + end.name, remove_method)
+
+ def add_scalar_attributes(this, end):
+ end.set_scalar(this, None)
+
+ def get_method(self): return end.get_object(this)
+ this.setmethod("get_" + end.name, get_method)
+
+ def set_method(self, that): end.set_object(this, that)
+ this.setmethod("set_" + end.name, set_method)
+
+ # Note that this doesn't go through the add_someobject methods
+ def remove(self):
+ self.lock()
+ try:
+ for end in self.mclass.endpoints:
+ this = self
+
+ if end.is_scalar():
+ end.set_object(this, None)
+ else:
+ for that in end.items(this).copy():
+ end.remove_object(this, that)
+
+ del self.model.get_index(self.mclass)[self.id]
+ finally:
+ self.unlock()
+
+ def __str__(self):
+ return self.__class__.__name__ + "(" + str(self.id) + ")"
+
+class TestModel(Model):
+ def __init__(self):
+ super(TestModel, self).__init__()
+
+ self.order = ModelClass(self, self.Order)
+ self.item = ModelClass(self, self.Item)
+
+ self.assoc = ModelAssociation(self, "order_item")
+ self.assoc.add_endpoint(self.order, "item", "0..n")
+ self.assoc.add_endpoint(self.item, "order", "0..1")
+
+ class Order(ModelObject):
+ def __init__(self, model):
+ super(TestModel.Order, self).__init__(model, model.order)
+
+ class Item(ModelObject):
+ def __init__(self, model):
+ super(TestModel.Item, self).__init__(model, model.item)
+
+ def test(self):
+ def results(heading):
+ print heading
+ for item in order.item_set:
+ print " item", item, "item.order", item.order
+
+ item0 = self.Item(self)
+ item1 = self.Item(self)
+ item2 = self.Item(self)
+
+ order = self.Order(self)
+
+ order.add_item(item0)
+ order.add_item(item1)
+ order.add_item(item2)
+
+ results("beginning state")
+
+ order.remove_item(item0)
+
+ results("remove item0 from order.items")
+
+ item1.set_order(order)
+
+ results("remove order from item1.order")
+
+ order.add_item(item1)
+
+ results("a")
+
+ item1.set_order(None)
+
+ results("b")
+
+ item1.set_order(order)
+
+ results("c")
+
+ item1.remove()
+
+ results("d")
+
+if __name__ == "__main__":
+ TestModel().test()
Added: mgmt/cumin/python/wooly/pages.py
===================================================================
--- mgmt/cumin/python/wooly/pages.py (rev 0)
+++ mgmt/cumin/python/wooly/pages.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,54 @@
+from datetime import datetime
+
+from wooly import Page, Parameter
+
+class CssPage(Page):
+ def __init__(self, app, name):
+ super(CssPage, self).__init__(app, name)
+
+ self.then = datetime.utcnow()
+
+ def get_last_modified(self, session):
+ return self.then
+
+ def get_content_type(self, session):
+ return "text/css"
+
+ def do_render(self, session, object):
+ return self.app.get_css()
+
+class ResourcePage(Page):
+ def __init__(self, app, name):
+ super(ResourcePage, self).__init__(app, name)
+
+ self.rname = Parameter(app, "name")
+ self.add_parameter(self.rname)
+
+ self.then = datetime.utcnow()
+
+ def get_last_modified(self, session):
+ return self.then
+
+ def get_content_type(self, session):
+ name = self.rname.get(session)
+
+ if name:
+ if name.endswith(".png"):
+ type = "image/png"
+ elif name.endswith(".jpeg"):
+ type = "image/jpeg"
+ elif name.endswith(".html"):
+ type = "text/html"
+ else:
+ type = "text/plain"
+
+ return type
+
+ def do_render(self, session, object):
+ name = self.rname.get(session)
+
+ if name:
+ resource = self.app.get_resource(name)
+
+ if resource:
+ return resource.read()
Added: mgmt/cumin/python/wooly/parameters.py
===================================================================
--- mgmt/cumin/python/wooly/parameters.py (rev 0)
+++ mgmt/cumin/python/wooly/parameters.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,53 @@
+from copy import copy
+
+from wooly import *
+
+# XXX the marshal side of this is not implemented
+class ListParameter(Parameter):
+ def __init__(self, app, name, param):
+ super(ListParameter, self).__init__(app, name)
+
+ self.param = param
+ self.default = list()
+
+ def get_default(self, session):
+ return copy(self.default)
+
+ def add(self, session, value):
+ lst = self.get(session)
+ lst.append(value)
+ return lst
+
+ def do_unmarshal(self, string):
+ return self.param.do_unmarshal(string)
+
+class IntegerParameter(Parameter):
+ def do_unmarshal(self, string):
+ return int(string)
+
+class BooleanParameter(Parameter):
+ def __init__(self, model, name):
+ Parameter.__init__(self, model, name)
+
+ self.set_default(False)
+
+ def do_unmarshal(self, string):
+ return string == "t"
+
+ def do_marshal(self, object):
+ return object and "t" or "f"
+
+class VoidBooleanParameter(Parameter):
+ def set_default(self, default):
+ raise Exception("Unsupported operation")
+
+ def get(self, session):
+ return self.path() in session.values
+
+ def set(self, session, value):
+ key = self.path()
+
+ if value:
+ session.set(key, None)
+ else:
+ session.unset(key)
Added: mgmt/cumin/python/wooly/resources.py
===================================================================
--- mgmt/cumin/python/wooly/resources.py (rev 0)
+++ mgmt/cumin/python/wooly/resources.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,71 @@
+import os
+import wooly
+
+class StringCatalog(object):
+ def __init__(self, file):
+ self.strings = None
+ self.path = os.path.splitext(file)[0] + ".strings"
+
+ def clear(self):
+ self.strings = None
+
+ def load(self):
+ try:
+ file = open(self.path)
+ self.strings = parse_catalog_file(file)
+ finally:
+ file.close()
+
+ def get(self, key):
+ if not self.strings:
+ self.load()
+
+ return self.strings.get(key)
+
+def parse_catalog_file(file):
+ strings = dict()
+ key = None
+ writer = wooly.Writer()
+
+ for line in file:
+ line = line.rstrip()
+
+ if line.startswith("[") and line.endswith("]"):
+ if key:
+ strings[key] = writer.to_string().strip()
+
+ writer = wooly.Writer()
+
+ key = line[1:-1]
+
+ continue
+
+ writer.write(line)
+ writer.write("\r\n")
+
+ strings[key] = writer.to_string().strip()
+
+ return strings
+
+class ResourceFinder(object):
+ def __init__(self):
+ self.dirs = list()
+
+ def add_dir(self, dir):
+ self.dirs.append(dir)
+
+ def find(self, name):
+ file = None
+
+ for dir in self.dirs:
+ try:
+ path = os.path.join(dir, name)
+
+ # XXX +1 exposing template resolution is useful
+ #print "Looking for resource '%s'" % path
+
+ file = open(path, "r")
+ except IOError:
+ pass
+
+ return file
Added: mgmt/cumin/python/wooly/server.py
===================================================================
--- mgmt/cumin/python/wooly/server.py (rev 0)
+++ mgmt/cumin/python/wooly/server.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,112 @@
+from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
+from traceback import print_exc
+from datetime import datetime
+from time import strptime
+
+from wooly import *
+from devel import DevelPage
+
+class WebServer(object):
+ def __init__(self, app, port=8080):
+ self.app = app
+ self.port = port
+
+ def run(self):
+ server = HTTPServer(("", self.port), self.RequestHandler)
+
+ # XXX hack, because HTTPServer and python conspire to make
+ # this hard
+ server.app = self.app
+
+ print "Cumin server started on port %s" % (self.port)
+ server.serve_forever()
+
+ class RequestHandler(BaseHTTPRequestHandler):
+ http_date = "%a, %d %b %Y %H:%M:%S %Z"
+
+ def do_GET(self):
+ session = Session(self.server.app)
+ session.unmarshal(self.path[1:])
+
+ self.service(session)
+
+ def do_POST(self):
+ session = Session(self.server.app)
+ session.unmarshal_page(self.path.split("?")[0])
+
+ content_type = str(self.headers.getheader("content-type"))
+
+ if content_type == "application/x-www-form-urlencoded":
+ length = int(self.headers.getheader("content-length"))
+ vars = self.rfile.read(length)
+ else:
+ raise Exception("Content type '%s' is not supported" \
+ % content_type)
+
+ if vars:
+ session.unmarshal_url_vars(vars, "&")
+
+ self.service(session)
+
+ def service(self, session):
+ page = session.get_page()
+
+ try:
+ page.process(session, None)
+ except:
+ self.error(session)
+ return
+
+ if session.redirect:
+ self.send_response(303)
+ self.send_header("Location", session.redirect)
+ self.end_headers()
+ return
+
+ ims = self.headers.getheader("if-modified-since")
+ modified = page.get_last_modified(session).replace \
+ (microsecond=0)
+
+ if ims:
+ since = datetime(*strptime(str(ims), self.http_date)[0:6])
+
+ if modified <= since:
+ self.send_response(304)
+ self.end_headers()
+ return
+
+ try:
+ response = page.render(session, None)
+ except:
+ self.error(session)
+ return
+
+ self.send_response(200)
+
+ type = page.get_content_type(session)
+
+ if type:
+ self.send_header("Content-Type", type)
+
+ if modified:
+ ts = modified.strftime("%a, %d %b %Y %H:%M:%S GMT")
+ self.send_header("Last-Modified", ts)
+
+ self.end_headers()
+
+ self.wfile.write(response)
+
+ page.save_session(session)
+
+ def error(self, session):
+ self.send_response(500)
+ self.send_header("Content-Type", "text/plain")
+ self.end_headers()
+
+ self.wfile.write("APPLICATION ERROR\n")
+ self.wfile.write("\n----- python trace -----\n")
+ print_exc(None, self.wfile)
+ self.wfile.write("\n----- process trace -----\n")
+ session.print_process_calls(self.wfile)
+ self.wfile.write("\n----- render trace -----\n")
+ session.print_render_calls(self.wfile)
Added: mgmt/cumin/python/wooly/widgets.py
===================================================================
--- mgmt/cumin/python/wooly/widgets.py (rev 0)
+++ mgmt/cumin/python/wooly/widgets.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,147 @@
+from wooly import *
+from parameters import *
+from resources import *
+
+strings = StringCatalog(__file__)
+
+class ModeSet(Widget):
+ def __init__(self, app, name):
+ super(ModeSet, self).__init__(app, name)
+
+ self.mode = Parameter(app, "mode")
+ self.add_parameter(self.mode)
+
+ def add_child(self, mode):
+ super(ModeSet, self).add_child(mode)
+
+ if not self.mode.default:
+ self.mode.set_default(mode.name)
+
+ def get_selected_mode(self, session):
+ return self.get_child(self.mode.get(session))
+
+ def set_selected_mode(self, session, mode):
+ self.mode.set(session, mode.name)
+
+ def show_mode(self, session, mode):
+ self.set_selected_mode(session, mode)
+
+ return mode
+
+ def scope(self, session, params):
+ params.update(self.parameters)
+
+ mode = self.get_selected_mode(session)
+
+ if mode:
+ mode.scope(session, params)
+
+ def do_process(self, session, object):
+ mode = self.get_selected_mode(session)
+
+ if mode:
+ mode.process(session, object)
+
+ def do_render(self, session, object):
+ mode = self.get_selected_mode(session)
+
+ if mode:
+ return mode.render(session, object)
+
+class TabSet(ModeSet):
+ def __init__(self, app, name):
+ super(TabSet, self).__init__(app, name)
+
+ def do_render(self, session, object):
+ writer = Writer()
+
+ self.template.render(session, object, writer)
+
+ return writer.to_string()
+
+ # XXX make this use an item template
+ def render_tabs(self, session, object):
+ writer = Writer()
+ str = """<li><a href="%s"
class="%s">%s</a></li>"""
+
+ for mode in self.children:
+ branch = session.branch()
+ self.set_selected_mode(branch, mode)
+ href = branch.marshal()
+
+ smode = self.get_selected_mode(session)
+ selected = smode == mode and "selected" or ""
+
+ content = mode.render_title(session, object)
+
+ writer.write(str % (href, selected, content))
+
+ return writer.to_string()
+
+ def render_mode(self, session, object):
+ mode = self.get_selected_mode(session)
+
+ if mode:
+ return mode.render(session, object)
+
+class Link(Widget):
+ def update_session(self, session, object):
+ pass
+
+ def render_href(self, session, object):
+ branch = session.branch()
+
+ self.update_session(branch, object)
+
+ return branch.marshal()
+
+class Toggle(Link):
+ def __init__(self, app, name):
+ super(Toggle, self).__init__(app, name)
+
+ self.toggled = BooleanParameter(app, "param")
+ self.add_parameter(self.toggled)
+
+ def get(self, session):
+ return self.toggled.get(session)
+
+ def set(self, session, toggled):
+ self.toggled.set(session, toggled)
+
+ def do_process(self, session, object):
+ if self.get(session):
+ self.on_click(session, object)
+
+ def on_click(self, session, object):
+ pass
+
+ def render_href(self, session, object):
+ branch = session.branch()
+ self.set(branch, not self.get(session))
+ return branch.marshal()
+
+ def render_state(self, session, object):
+ return self.toggled.get(session) and "on" or "off"
+
+class ItemSet(Widget):
+ def __init__(self, app, name):
+ super(ItemSet, self).__init__(app, name)
+
+ self.item_tmpl = Template(self, "item_html")
+
+ def get_items(self, session, object):
+ return None
+
+ def render_items(self, session, object):
+ items = self.get_items(session, object)
+
+ if items:
+ writer = Writer()
+
+ for item in items:
+ self.item_tmpl.render(session, item, writer)
+
+ return writer.to_string()
+
+ def render_item_content(self, session, item):
+ return None
Added: mgmt/cumin/python/wooly/widgets.strings
===================================================================
--- mgmt/cumin/python/wooly/widgets.strings (rev 0)
+++ mgmt/cumin/python/wooly/widgets.strings 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,60 @@
+[TabSet.css]
+ul.TabSet.tabs {
+ padding: 0;
+ margin: 1em 0 0 0;
+ list-style: none;
+}
+
+.TabSet.tabs li {
+ display: inline;
+}
+
+.TabSet.tabs li a {
+ padding: 0.25em 0.5em;
+ border-top: 1px solid #ccc;
+ border-right: 1px solid #ccc;
+ background-color: #f7f7f7;
+ color: #000;
+ line-height: 1.5em;
+}
+
+.TabSet.tabs li:first-child a {
+ border-left: 1px solid #ccc;
+}
+
+.TabSet.tabs li a.selected {
+ background-color: #fff;
+ position: relative;
+ z-index: 2;
+}
+
+.TabSet.mode {
+ background-color: white;
+ padding: 1em;
+ border: 1px solid #ccc;
+ margin: 0;
+ background-color: #fff;
+ position: relative;
+ z-index: 1;
+}
+
+[TabSet.html]
+<ul class="TabSet tabs">{tabs}</ul>
+<div class="TabSet mode">{mode}</div>
+
+[Link.html]
+<a href="{href}">{content}</a>
+
+[Toggle.css]
+.Toggle.on {
+ font-weight: bold;
+}
+
+[Toggle.html]
+<a href="{href}" class="Toggle {state}">{content}</a>
+
+[ItemSet.html]
+<ul class="ItemSet">{items}</ul>
+
+[ItemSet.item_html]
+<li>{item_content}</li>
Added: mgmt/cumin/resources/ajax-test.html
===================================================================
--- mgmt/cumin/resources/ajax-test.html (rev 0)
+++ mgmt/cumin/resources/ajax-test.html 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,32 @@
+<html>
+ <head>
+ <title>test</title>
+ <script>
+ var xhr = new XMLHttpRequest()
+
+ function getCount() {
+ xhr.open("get", "http://localhost:8080/count", true)
+ xhr.onreadystatechange = updateCount
+ xhr.send(null)
+ }
+
+ function updateCount() {
+ if (xhr.readyState == 4 && xhr.status == 200) {
+ countNode = xhr.responseXML.getElementsByTagName("count")[0]
+ count = countNode.firstChild.nodeValue
+
+ var body = document.getElementsByTagName("body")
+
+ var div = document.createElement("div")
+ div.appendChild(document.createTextNode(count))
+
+ body[0].appendChild(div)
+ }
+ }
+
+ setInterval(getCount, 5000)
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
Added: mgmt/cumin/resources/ajax.js
===================================================================
--- mgmt/cumin/resources/ajax.js (rev 0)
+++ mgmt/cumin/resources/ajax.js 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,67 @@
+// Rename this to wooly.js
+
+wooly = {};
+wooly.updaters = {};
+
+function WoolyUpdater(id, url, callback, interval) {
+ this.id = id;
+ this.url = url;
+ this.interval = interval;
+ this.callback = callback;
+ this.request = new XMLHttpRequest();
+
+ this.init = function() {
+ setInterval(this.fetch, this.interval);
+ }
+
+ this.fetch = function() {
+ this.request.open("get", this.url, true);
+ this.request.onreadystatechange = this.update
+ this.send(null)
+ }
+
+ this.update = function() {
+ if (request.readyState == 4 && request.status == 200) {
+ elem = document.getElementById(this.id)
+ this.callback(xml, elem)
+ }
+ }
+}
+
+function AjaxRequest() {
+ try {
+ this.request = window.XMLHttpRequest();
+ } catch (e) {
+ try {
+ this.request = new ActiveXObject("Msxml2.XMLHTTP");
+ } catch (ie) {
+ try {
+ this.request = new ActiveXObject("Microsoft.XMLHTTP");
+ } catch (iie) {
+ throw new Error("XMLHttpRequest not found");
+ }
+ }
+ }
+
+ // XXX configure with an xpath expression? pg 515 in the big js
+ // book
+
+ this.get = function(url, callback) {
+ this.request.open("GET", url, true);
+
+ // XXX set some headers
+ //this.request.setRequestHeader("If-Modified-Since",
+ // lastRequested.toString());
+
+ this.onreadystatechange = function() {
+ if (this.request.readyState == 4 && this.request.status == 200) {
+ callback(request.responseXML)
+ }
+ }
+
+ this.request.send(null)
+ }
+}
+
+req = new AjaxRequest();
+req.get("queue-xml?id={{id}}", updateStatus)
\ No newline at end of file
Added: mgmt/cumin/resources/exchange-20.png
===================================================================
(Binary files differ)
Property changes on: mgmt/cumin/resources/exchange-20.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: mgmt/cumin/resources/exchange-36.png
===================================================================
(Binary files differ)
Property changes on: mgmt/cumin/resources/exchange-36.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: mgmt/cumin/resources/exchange.svg
===================================================================
--- mgmt/cumin/resources/exchange.svg (rev 0)
+++ mgmt/cumin/resources/exchange.svg 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"
standalone="no"?>
+<!-- Created with Inkscape (
http://www.inkscape.org/) -->
+<svg
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
+
xmlns:cc="http://web.resource.org/cc/"
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+
xmlns:svg="http://www.w3.org/2000/svg"
+
xmlns="http://www.w3.org/2000/svg"
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd&q...
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45.1"
+ sodipodi:docbase="/home/justin/cumindev/cumin/resources"
+ sodipodi:docname="exchange.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="549.10742"
+ inkscape:cy="503.26882"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="1418"
+ inkscape:window-height="956"
+ inkscape:window-x="29"
+ inkscape:window-y="45" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g3181"
+
inkscape:export-filename="/home/justin/cumindev/cumin/resources/exchange-20.png"
+ inkscape:export-xdpi="3.506865"
+ inkscape:export-ydpi="3.506865">
+ <path
+ sodipodi:nodetypes="cccccccccccccc"
+ d="M 313.86973,832.3721 C 312.11118,709.27939 279.68924,569.91252
207.1601,445.66059 L 168.9849,468.05869 L 205.55073,331.59313 L 342.95181,368.40964 L
305.42972,390.07303 C 344.62364,462.24138 365.78921,497.41554 391.10471,587.93301 C
409.27317,551.38754 456.55215,499.0514 474.28581,483.53568 L 444.90847,454.15833 L
585.197,454.15833 L 585.197,590.33585 L 554.87823,560.01708 C 484.07919,636.62814
448.21282,711.11613 446.96398,832.3721 C 446.96398,832.3721 446.4501,832.3721
313.86973,832.3721 z "
+
style="fill:#ffaa66;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:12.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2164" />
+ <path
+ transform="translate(47.55914,-118.77957)"
+ d="M 296 598.36218 A 54 54 0 1 1 188,598.36218 A 54 54 0 1 1 296
598.36218 z"
+ sodipodi:ry="54"
+ sodipodi:rx="54"
+ sodipodi:cy="598.36218"
+ sodipodi:cx="242"
+ id="path2190"
+
style="fill:#ff5577;fill-opacity:1;stroke:#000000;stroke-width:12.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ transform="translate(229.66129,-27.66129)"
+ d="M 296 598.36218 A 54 54 0 1 1 188,598.36218 A 54 54 0 1 1 296
598.36218 z"
+ sodipodi:ry="54"
+ sodipodi:rx="54"
+ sodipodi:cy="598.36218"
+ sodipodi:cx="242"
+ id="path3165"
+
style="fill:#77aaff;fill-opacity:1;stroke:#000000;stroke-width:12.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+</svg>
Added: mgmt/cumin/resources/logo.png
===================================================================
(Binary files differ)
Property changes on: mgmt/cumin/resources/logo.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: mgmt/cumin/resources/logo.svg
===================================================================
--- mgmt/cumin/resources/logo.svg (rev 0)
+++ mgmt/cumin/resources/logo.svg 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"
standalone="no"?>
+<!-- Created with Inkscape (
http://www.inkscape.org/) -->
+<svg
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
+
xmlns:cc="http://web.resource.org/cc/"
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+
xmlns:svg="http://www.w3.org/2000/svg"
+
xmlns="http://www.w3.org/2000/svg"
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd&q...
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45"
+ sodipodi:modified="TRUE">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.35"
+ inkscape:cx="375"
+ inkscape:cy="520"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+
style="fill:#564979;fill-opacity:1;stroke:#bfdce8;stroke-width:0.07982907;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect2160"
+ width="39.203495"
+ height="22.692081"
+ x="176.57945"
+ y="149.64551" />
+ <g
+ id="g3141"
+
inkscape:export-filename="/home/jross/cumindev/cumin/resources/logo.png"
+ inkscape:export-xdpi="98.580963"
+ inkscape:export-ydpi="98.580963">
+ <text
+ transform="scale(1.0989239,0.9099811)"
+ sodipodi:linespacing="125%"
+ id="text3133"
+ y="178.24783"
+ x="164.23683"
+
style="font-size:10.9197731px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#ff9f00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream
Vera Sans"
+ xml:space="preserve"><tspan
+ y="178.24783"
+ x="164.23683"
+ id="tspan3135"
+ sodipodi:role="line">RHM</tspan></text>
+ <text
+ transform="scale(1.2362181,0.8089188)"
+ sodipodi:linespacing="125%"
+ id="text3137"
+ y="205.76395"
+ x="146.27437"
+
style="font-size:7.40763378px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream
Vera Sans"
+ xml:space="preserve"><tspan
+ y="205.76395"
+ x="146.27437"
+ id="tspan3139"
+ sodipodi:role="line">mgmt</tspan></text>
+ </g>
+ </g>
+</svg>
Added: mgmt/cumin/resources/object-20.png
===================================================================
(Binary files differ)
Property changes on: mgmt/cumin/resources/object-20.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: mgmt/cumin/resources/object-36.png
===================================================================
(Binary files differ)
Property changes on: mgmt/cumin/resources/object-36.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: mgmt/cumin/resources/object.svg
===================================================================
--- mgmt/cumin/resources/object.svg (rev 0)
+++ mgmt/cumin/resources/object.svg 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"
standalone="no"?>
+<!-- Created with Inkscape (
http://www.inkscape.org/) -->
+<svg
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
+
xmlns:cc="http://web.resource.org/cc/"
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+
xmlns:svg="http://www.w3.org/2000/svg"
+
xmlns="http://www.w3.org/2000/svg"
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd&q...
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45.1"
+ sodipodi:docbase="/home/justin/cumindev/cumin/resources"
+ sodipodi:docname="object.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.35"
+ inkscape:cx="375"
+ inkscape:cy="520"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="814"
+ inkscape:window-height="619"
+ inkscape:window-x="0"
+ inkscape:window-y="25" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g3139"
+ transform="translate(-194.28571,31.428571)"
+
inkscape:export-filename="/home/justin/cumindev/cumin/resources/object-20.png"
+ inkscape:export-xdpi="3.4734666"
+ inkscape:export-ydpi="3.4734666">
+ <rect
+ y="100.93361"
+ x="317.14285"
+ height="505.71429"
+ width="505.71429"
+ id="rect2166"
+
style="fill:#ffaa66;fill-opacity:1;stroke:#000000;stroke-width:12.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
/>
+ <rect
+ transform="matrix(0.7071068,-0.7071068,0.7071068,0.7071068,0,0)"
+ y="438.85617"
+ x="79.941772"
+ height="147.90282"
+ width="147.90282"
+ id="rect2160"
+
style="fill:#77aaff;fill-opacity:1;stroke:#000000;stroke-width:12.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
/>
+ <path
+ transform="translate(485.71428,-20)"
+ d="M 282.85715 492.36218 A 88.571434 88.571434 0 1 1 105.71429,492.36218 A
88.571434 88.571434 0 1 1 282.85715 492.36218 z"
+ sodipodi:ry="88.571434"
+ sodipodi:rx="88.571434"
+ sodipodi:cy="492.36218"
+ sodipodi:cx="194.28572"
+ id="path2162"
+
style="fill:#ff5577;fill-opacity:1;stroke:#000000;stroke-width:12.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+</svg>
Added: mgmt/cumin/resources/purple.png
===================================================================
(Binary files differ)
Property changes on: mgmt/cumin/resources/purple.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: mgmt/cumin/resources/queue-20.png
===================================================================
(Binary files differ)
Property changes on: mgmt/cumin/resources/queue-20.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: mgmt/cumin/resources/queue-36.png
===================================================================
(Binary files differ)
Property changes on: mgmt/cumin/resources/queue-36.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: mgmt/cumin/resources/queue.svg
===================================================================
--- mgmt/cumin/resources/queue.svg (rev 0)
+++ mgmt/cumin/resources/queue.svg 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"
standalone="no"?>
+<!-- Created with Inkscape (
http://www.inkscape.org/) -->
+<svg
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
+
xmlns:cc="http://web.resource.org/cc/"
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+
xmlns:svg="http://www.w3.org/2000/svg"
+
xmlns="http://www.w3.org/2000/svg"
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd&q...
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45.1"
+ sodipodi:docbase="/home/justin/cumindev/cumin/resources"
+ sodipodi:docname="queue.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.68801116"
+ inkscape:cx="441.43343"
+ inkscape:cy="526.18109"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="814"
+ inkscape:window-height="892"
+ inkscape:window-x="33"
+ inkscape:window-y="41" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g3146">
+ <path
+
transform="matrix(0.9063078,0.4226182,-0.4226182,0.9063078,86.342292,293.33708)"
+ d="M 552.93207 223.99181 A 195.27341 52.415497 0 1 1 162.38525,223.99181 A
195.27341 52.415497 0 1 1 552.93207 223.99181 z"
+ sodipodi:ry="52.415497"
+ sodipodi:rx="195.27341"
+ sodipodi:cy="223.99181"
+ sodipodi:cx="357.65866"
+ id="path3134"
+
style="fill:#ffaa66;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:12.50000018;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+
transform="matrix(0.9063078,0.4226182,-0.4226182,0.9063078,116.01145,231.71374)"
+ d="M 552.93207 223.99181 A 195.27341 52.415497 0 1 1 162.38525,223.99181 A
195.27341 52.415497 0 1 1 552.93207 223.99181 z"
+ sodipodi:ry="52.415497"
+ sodipodi:rx="195.27341"
+ sodipodi:cy="223.99181"
+ sodipodi:cx="357.65866"
+ id="path4105"
+
style="fill:#ffaa66;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:12.50000018;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+
transform="matrix(0.9063078,0.4226182,-0.4226182,0.9063078,145.68064,170.09034)"
+ d="M 552.93207 223.99181 A 195.27341 52.415497 0 1 1 162.38525,223.99181 A
195.27341 52.415497 0 1 1 552.93207 223.99181 z"
+ sodipodi:ry="52.415497"
+ sodipodi:rx="195.27341"
+ sodipodi:cy="223.99181"
+ sodipodi:cx="357.65866"
+ id="path4109"
+
style="fill:#ffaa66;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:12.50000018;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+
transform="matrix(0.9063078,0.4226182,-0.4226182,0.9063078,175.34983,108.467)"
+ d="M 552.93207 223.99181 A 195.27341 52.415497 0 1 1 162.38525,223.99181 A
195.27341 52.415497 0 1 1 552.93207 223.99181 z"
+ sodipodi:ry="52.415497"
+ sodipodi:rx="195.27341"
+ sodipodi:cy="223.99181"
+ sodipodi:cx="357.65866"
+ id="path4113"
+
style="fill:#ffaa66;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:12.50000018;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:nodetypes="cccccccc"
+ id="path5088"
+ d="M 454.10516,344.26002 L 399.27203,461.85003 L 359.20085,443.16454 L
406.83279,566.23776 L 529.50338,522.5779 L 489.4322,503.89241 L 544.76381,385.23338 L
454.10516,344.26002 z "
+
style="fill:#ff5577;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:12.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
/>
+ <path
+ sodipodi:nodetypes="cccccccc"
+ id="path4117"
+ d="M 310.19505,419.15001 L 365.02818,301.56001 L 324.95699,282.87451 L
449.85377,240.25274 L 495.25952,362.28789 L 455.18834,343.60238 L 399.85673,462.26141 L
310.19505,419.15001 z "
+
style="fill:#77aaff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:12.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
/>
+ </g>
+ </g>
+</svg>
Added: mgmt/cumin/resources/radio-button-checked.png
===================================================================
(Binary files differ)
Property changes on: mgmt/cumin/resources/radio-button-checked.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: mgmt/cumin/resources/radio-button.png
===================================================================
(Binary files differ)
Property changes on: mgmt/cumin/resources/radio-button.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: mgmt/cumin/resources/radio-buttons.svg
===================================================================
--- mgmt/cumin/resources/radio-buttons.svg (rev 0)
+++ mgmt/cumin/resources/radio-buttons.svg 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"
standalone="no"?>
+<!-- Created with Inkscape (
http://www.inkscape.org/) -->
+<svg
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
+
xmlns:cc="http://web.resource.org/cc/"
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+
xmlns:svg="http://www.w3.org/2000/svg"
+
xmlns="http://www.w3.org/2000/svg"
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd&q...
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45"
+ sodipodi:docbase="/home/jross/cumindev/cumin/resources"
+ sodipodi:docname="radio-buttons.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.4431138"
+ inkscape:cx="261"
+ inkscape:cy="591.5"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="814"
+ inkscape:window-height="620"
+ inkscape:window-x="423"
+ inkscape:window-y="159" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g3167"
+
inkscape:export-filename="/home/jross/cumindev/cumin/resources/radio-button-checked.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <path
+ transform="matrix(0.5990025,0,0,0.5990025,87.699389,185.92954)"
+ d="M 243.24474 452.02853 A 12.020815 12.020815 0 1 1 219.20311,452.02853 A
12.020815 12.020815 0 1 1 243.24474 452.02853 z"
+ sodipodi:ry="12.020815"
+ sodipodi:rx="12.020815"
+ sodipodi:cy="452.02853"
+ sodipodi:cx="231.22392"
+ id="path2160"
+
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1.66944211;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ sodipodi:type="arc" />
+ <path
+ transform="matrix(0.5990025,0,0,0.5990025,86.746382,184.65886)"
+ d="M 239.88597 454.23822 A 6.9826794 6.9826794 0 1 1 225.92061,454.23822 A
6.9826794 6.9826794 0 1 1 239.88597 454.23822 z"
+ sodipodi:ry="6.9826794"
+ sodipodi:rx="6.9826794"
+ sodipodi:cy="454.23822"
+ sodipodi:cx="232.90329"
+ id="path3133"
+
style="fill:#4e9fdd;fill-opacity:1;stroke:#bfdce8;stroke-opacity:1;stroke-width:1.66944211;stroke-miterlimit:4;stroke-dasharray:none"
+ sodipodi:type="arc" />
+ </g>
+ <path
+ sodipodi:type="arc"
+
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1.66944206;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3163"
+ sodipodi:cx="231.22392"
+ sodipodi:cy="452.02853"
+ sodipodi:rx="12.020815"
+ sodipodi:ry="12.020815"
+ d="M 243.24474 452.02853 A 12.020815 12.020815 0 1 1 219.20311,452.02853 A
12.020815 12.020815 0 1 1 243.24474 452.02853 z"
+ transform="matrix(0.5990025,0,0,0.5990025,121.41051,185.92954)"
+
inkscape:export-filename="/home/jross/cumindev/cumin/resources/radio-button.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ </g>
+</svg>
Added: mgmt/cumin/resources/wooly.js
===================================================================
--- mgmt/cumin/resources/wooly.js (rev 0)
+++ mgmt/cumin/resources/wooly.js 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,221 @@
+var wooly;
+
+(function() {
+ wooly = new Wooly();
+
+ function assert() {
+ for (var i = 0; i < arguments.length; i++) {
+ if (!arguments[i]) {
+ throw new Error("Assertion failure in " +
arguments.callee.caller.prototype);
+ }
+ }
+ }
+
+ function log() {
+ if (wooly.console) {
+ wooly.console.log.apply(wooly.console, arguments);
+ }
+ }
+
+ function dir() {
+ if (wooly.console) {
+ wooly.console.dir.apply(wooly.console, arguments);
+ }
+ }
+
+ function Wooly() {
+ this.request = new XMLHttpRequest();
+
+ this.assert = assert;
+ this.log = log;
+ this.dir = dir;
+
+ if (window.console) {
+ this.console = window.console;
+ }
+
+ this.setIntervalUpdate = function(id, url, callback, interval) {
+ var req = this.request;
+
+ function fetch() {
+ req.open("get", url, true);
+ req.onreadystatechange = update;
+ req.send(null);
+ }
+
+ var timerid = window.setInterval(fetch, interval);
+
+ function update() {
+ try {
+ if (req.readyState == 4 && req.status == 200) {
+ //dir(req);
+
+ var elem = wooly.doc().elem(id);
+
+ callback(wooly.doc(req.responseXML), elem);
+ }
+ } catch (e) {
+ log(e);
+ // XXX might want to retry for a bit before we do
+ // this
+ window.clearInterval(timerid);
+ throw e;
+ }
+ }
+ }
+
+ this._doc = new WoolyDocument(document);
+
+ this.doc = function(doc) {
+ if (doc) {
+ return new WoolyDocument(doc);
+ } else {
+ return this._doc;
+ }
+ }
+ }
+
+ function WoolyDocument(node) {
+ assert(node);
+
+ this.node = node;
+
+ this.elem = function(id) {
+ var node = this.node.getElementById(id);
+
+ if (node) {
+ return new WoolyElement(this, node);
+ }
+ }
+
+ this.elems = function(tag) {
+ var nodes = this.node.getElementsByTagName(tag);
+
+ return new WoolyIterator(this, WoolyElement,
+ nodes, 1, tag);
+ }
+ }
+
+ function WoolyIterator(doc, nodeClass, nodes, nodeType, nodeName) {
+ assert(doc);
+ assert(doc instanceof WoolyDocument);
+ assert(nodeClass);
+ assert(nodes);
+ assert(nodes instanceof NodeList);
+ assert(nodeType);
+ assert(typeof nodeType == "number");
+ if (nodeName) assert(typeof nodeName == "string");
+
+ this.doc = doc;
+ this.nodes = nodes;
+ this.nodeClass = nodeClass;
+ this.nodeType = nodeType;
+ this.nodeName = nodeName;
+
+ this.lastIndex = -1;
+
+ this.next = function() {
+ var node;
+
+ for (var i = this.lastIndex + 1; i < this.nodes.length; i++) {
+ node = this.nodes[i];
+
+ if (this.nodeType == null
+ || node.nodeType == this.nodeType) {
+ if (this.nodeName == null
+ || node.nodeName.toLowerCase() == this.nodeName) {
+ this.lastIndex = i;
+ return new this.nodeClass(this.doc, node);
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+
+ function WoolyElement(doc, node) {
+ assert(doc);
+ assert(doc instanceof WoolyDocument);
+ assert(node);
+ assert(node instanceof Node, node.nodeType == 1);
+
+ this.doc = doc;
+ this.node = node;
+
+ this.clear = function() {
+ var child = this.node.firstChild;
+ var next;
+
+ while (child) {
+ next = child.nextSibling;
+ this.node.removeChild(child);
+ child = next;
+ }
+
+ return this;
+ }
+
+ this.add = function(content) {
+ if (typeof content == "string") {
+ this.add(new WoolyText(this.doc, null).set(content));
+ } else if (content.hasOwnProperty("node")) {
+ this.node.appendChild(content.node);
+ } else {
+ throw new Error("Content is of unexpected type");
+ }
+
+ return this;
+ }
+
+ this.set = function(content) {
+ this.clear().add(content);
+
+ return this;
+ }
+
+ this.elems = function(name) {
+ return new WoolyIterator(this.doc, WoolyElement,
+ this.node.childNodes,
+ 1, name);
+ }
+
+ this.text = function() {
+ var children = this.node.childNodes;
+
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+
+ if (child.nodeType == 3) return new WoolyText(this.doc, child);
+ }
+
+ return null;
+ }
+ }
+
+ function WoolyText(doc, node) {
+ assert(doc);
+ assert(doc instanceof WoolyDocument);
+ if (node) assert(node instanceof Node, node.nodeType == 3);
+
+ this.doc = doc;
+
+ if (node == null) {
+ this.node = doc.node.createTextNode("");
+ } else {
+ this.node = node;
+ }
+
+ this.get = function() {
+ return this.node.data
+ }
+
+ this.set = function(data) {
+ assert(typeof data == "string");
+
+ this.node.data = data
+
+ return this;
+ }
+ }
+}())
Added: mgmt/cumin-test-0/bin
===================================================================
--- mgmt/cumin-test-0/bin (rev 0)
+++ mgmt/cumin-test-0/bin 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1 @@
+link ../cumin/bin
\ No newline at end of file
Property changes on: mgmt/cumin-test-0/bin
___________________________________________________________________
Name: svn:special
+ *
Added: mgmt/cumin-test-0/lib
===================================================================
--- mgmt/cumin-test-0/lib (rev 0)
+++ mgmt/cumin-test-0/lib 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1 @@
+link ../lib
\ No newline at end of file
Property changes on: mgmt/cumin-test-0/lib
___________________________________________________________________
Name: svn:special
+ *
Added: mgmt/cumin-test-0/python
===================================================================
--- mgmt/cumin-test-0/python (rev 0)
+++ mgmt/cumin-test-0/python 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1 @@
+link ../cumin/python
\ No newline at end of file
Property changes on: mgmt/cumin-test-0/python
___________________________________________________________________
Name: svn:special
+ *
Added: mgmt/cumin-test-0/resources
===================================================================
--- mgmt/cumin-test-0/resources (rev 0)
+++ mgmt/cumin-test-0/resources 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1 @@
+link ../cumin/resources
\ No newline at end of file
Property changes on: mgmt/cumin-test-0/resources
___________________________________________________________________
Name: svn:special
+ *
Added: mgmt/etc/cumindev.el
===================================================================
--- mgmt/etc/cumindev.el (rev 0)
+++ mgmt/etc/cumindev.el 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,10 @@
+(setq cumindev-home (getenv "CUMINDEV_HOME"))
+
+(if cumindev-home
+ (progn
+ ;(desktop-read cumindev-home)
+ (shell "dev")
+ (setq tags-file-name (concat cumindev-home "/etc/cumindev.tags"))
+ (setq grep-command (concat "find " cumindev-home " -name \\*.py
-print | xargs fgrep -n "))
+ (setq compile-command "cumin-test"))
+ (display-warning 'cumindev "Environment variable CUMINDEV_HOME not set"
:error))
Added: mgmt/etc/cumindev.profile
===================================================================
--- mgmt/etc/cumindev.profile (rev 0)
+++ mgmt/etc/cumindev.profile 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,11 @@
+if [ -z "$CUMINDEV_HOME" ]; then
+ export CUMINDEV_HOME="$PWD"
+fi
+
+export CUMIN_HOME="$CUMINDEV_HOME"/cumin-test-0
+
+export PYTHONPATH="$CUMIN_HOME"/lib:"$CUMIN_HOME"/python
+
+export CUMINDEV_ORIGINAL_PATH="$PATH"
+
+export
PATH="$CUMIN_HOME"/bin:"$CUMINDEV_HOME"/bin:"$CUMINDEV_ORIGINAL_PATH"
Added: mgmt/misc/templates.py
===================================================================
--- mgmt/misc/templates.py (rev 0)
+++ mgmt/misc/templates.py 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,113 @@
+text0 = """
+<form id="{id}" class="QueueForm mform" method="post"
action="?">
+ <div class="head">
+ <h1>{title}</h1>
+ </div>
+ <div class="body">
+ <span class="legend">Name</span>
+ <fieldset>
+ <div class="field">{queue_name}</div>
+ </fieldset>
+ <span class="legend">Latency Tuning</span>
+ <fieldset>
+ <div class="field">
+ {latency}
+ <em>Lower Latency:</em> Tune for shorter delays, with reduced volume
+ </div>
+ <div class="field">
+ {balanced}
+ <em>Balanced</em>
+ </div>
+ <div class="field">
+ {throughput}
+ <em>Higher Throughput:</em> Tune for increased volume, with longer
+ delays
+ </div>
+ </fieldset>
+ <span class="legend">Realms</span>
+ <fieldset>{realms}</fieldset>
+{hidden_inputs}
+ </div>
+ <div class="foot">
+ <div style="display: block; float:
left;"><button>Help</button></div>
+{cancel}
+{submit}
+ </div>
+</form>
+<script defer="defer">
+var id = "{id}";
+(function() {
+ // XXX elements[0] is a fieldset, at least in firefox
+ var elem = wooly.doc().elem(id).node.elements[1];
+ elem.focus();
+ elem.select();
+}())
+</script>
+"""
+
+def parse(text):
+ fragments = list()
+
+ start = 0
+ end = text.find("{")
+
+ while True:
+ if (end == -1):
+ fragments.append(text[start:])
+ break
+
+ fragments.append(text[start:end])
+
+ ccurly = text.find("}", end + 1)
+
+ if ccurly == -1:
+ start = end
+ end = -1
+ else:
+ ocurly = text.find("{", end + 1)
+
+ if ocurly == -1:
+ start = end
+ end = ccurly + 1
+ elif ocurly < ccurly:
+ start = end
+ end = ocurly
+ else:
+ fragments.append("{" + text[end + 1:ccurly] + "}")
+
+ start = ccurly + 1
+ end = ocurly
+
+ return fragments
+
+if __name__ == "__main__":
+ from time import clock
+ from cStringIO import StringIO
+
+ texts = ["x{y}z}a{b}c",
+ "x{y",
+ "x}y",
+ "{{{",
+ "}{}{",
+ "x{y{z}"]
+
+ for text in texts:
+ print text, parse(text)
+
+ frags = None
+ start = clock()
+
+ for i in range(10000):
+ frags = parse(text0)
+
+ print clock() - start
+
+ start = clock()
+
+ for i in range(10000):
+ buffer = StringIO()
+
+ for frag in frags:
+ buffer.write(frag)
+
+ print clock() - start
Added: mgmt/notes/Errors
===================================================================
--- mgmt/notes/Errors (rev 0)
+++ mgmt/notes/Errors 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,3 @@
+[justin@localhost cumindev]$ cumindev-etags
+/home/justin/cumindev/cumin/python/cumin/.#model.py: No such file or directory
+
Added: mgmt/notes/InterfaceQuestions
===================================================================
--- mgmt/notes/InterfaceQuestions (rev 0)
+++ mgmt/notes/InterfaceQuestions 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,5 @@
+ * Should the default exchange appear in the UI at all?
+
+ * Is there any place in the UI for transient queues and exchanges?
+ It would seem not, except perhaps in status information.
+
Added: mgmt/notes/ProtocolQuestions
===================================================================
--- mgmt/notes/ProtocolQuestions (rev 0)
+++ mgmt/notes/ProtocolQuestions 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,19 @@
+ * Why use null for the default exchange name? It would seem
+ consistent and more convenient in developing software to use a name
+ such as "amq.default"
+
+ * Can the routing key of the default binding to the default exchange
+ for new queues be changed? How different is the default exchange
+ from other types of exchanges? The docs suggest that the default
+ binding is for "point and shoot" use of the broker, so it would
+ make sense if the default exchange only supported simple
+ queue-named routing keys, no?
+
+ * I didn't run into anything that spelled out the multiplicity of
+ bindings vis a vis queues. I assumed that queues and exchanges
+ support many bindings.
+
+ * Can a queue, for instance, be in more than one realm?
+
+ * It strikes me that UML would answer many of my questions.
+
Added: mgmt/notes/Todo
===================================================================
--- mgmt/notes/Todo (rev 0)
+++ mgmt/notes/Todo 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,69 @@
+Big picture
+
+ * A more-or-less complete demonstration of an admin UI
+
+Higher
+
+Lower
+
+ * more form inputs, non scalar ones too
+
+ * Add an error banner to form
+
+ * use wsgiref instead of BaseHTTPServer
+
+ * Make sure HTTPServer handles concurrent requests; need to look at
+ documentation
+
+ - It does not, and it's not easily changed. Will need to switch to
+ wsgi stuff, I believe.
+
+ * When in queue mode, make the context nav go back to the right tab
+
+ * Change declared charset to iso-8859???, not utf-8, since it's
+ important to be honest
+
+ * Only do render timing conditionally
+
+ * Make debug and devel things contingent upon a start-time variable
+ "--debug"
+
+ * Make it a little simpler to express hrefs
+
+ * Add disabling to form inputs, and disable renaming of exchanges
+ that start with "amq." and the default exchange
+
+ * Make form help buttons pop up a (for now, empty) help page
+
+ * Icons: machine looking thing for vhost. For Cumin in general, I'm
+ not sure. The hammer and screwdriver?
+
+ * Make item counts in tab labels a little grayer, that is, less
+ intense than the name
+
+ * If debug is enabled, append a comment to the response containing
+ render and process traces
+
+ * Write a little test comparing wooly.Template to string.Template to
+ using the % operator
+
+ * Add ability to send a test message to a queue
+
+ * Add favicon and a mapping in the server to serve it
+
+ * Create a model.dtd
+
+ * Add scalar get/setters to ModelObject
+
+ * Separate wooly stuff into its own devel subdir
+
+ * When there is nothing in a set, render a [None]
+
+ * Add creation dates to some objects
+
+ * cumindev: Bind .strings to html-mode
+
+ * cumindev: add a cumin-test function and bind it to C-c C-c
+
+ * Consider adding a set_object to Frame, instead of having
+ set_somethingspecific on each frame.
Added: mgmt/notes/WoolyOverview
===================================================================
--- mgmt/notes/WoolyOverview (rev 0)
+++ mgmt/notes/WoolyOverview 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,40 @@
+Widget
+ - A display object
+ - Usually bound to a Template
+ - May have state represented in Parameters
+ - Lives in a tree of widgets
+ - Has a process method
+ - Manipulates state, both ui state and model state
+ - Called in the process phase, before rendering
+ - Has a render method
+ - Produces HTML
+ - Called after all processing is done
+
+Template
+ - A string with placeholders such as {foo}
+ - A placeholder {foo} resolves to: widget.render_foo(...)
+ - Or it resolves to: widget.get_child("foo").render(...)
+
+Application
+ - The static state of the app
+ - Holds Pages
+
+Page
+ - A top-level widget with some extra methods for producing HTTP
+ responses
+ - The root widget of all widget trees is a page
+
+Parameter
+ - Represents state for the life of the request/response
+ - Attached to a widget
+ - Stores its session-level state on a Session
+ - Marshals and unmarshals itself to url params
+
+Session
+ - The main source of state
+ - Can be "branched" for producing "future states", URLs
+
+ModeSet
+ - A widget that shows only one of its children
+ - Used for producing various UI behaviors, tabs for instance
+ - Generally useful for controlling visibility
Added: mgmt/notes/firebug-exception-bug.js
===================================================================
--- mgmt/notes/firebug-exception-bug.js (rev 0)
+++ mgmt/notes/firebug-exception-bug.js 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,23 @@
+var scratchy;
+
+(function() {
+ scratchy = new Scratchy();
+
+ function Scratchy() {
+ this.x = new Object();
+
+ this.itchy = function(callback, interval) {
+ var x = this.x;
+
+ setTimeout(show, 1000);
+
+ function show() {
+ try {
+ callback();
+ } catch (e) {
+ throw e;
+ }
+ }
+ }
+ }
+}());
Added: mgmt/notes/firebug-swallows-exceptions.html
===================================================================
--- mgmt/notes/firebug-swallows-exceptions.html (rev 0)
+++ mgmt/notes/firebug-swallows-exceptions.html 2007-10-08 15:51:14 UTC (rev 967)
@@ -0,0 +1,42 @@
+<html>
+<head>
+<title>Test</title>
+<script>
+var scratchy;
+
+(function() {
+ scratchy = new Scratchy();
+
+ function Scratchy() {
+ this.x = true;
+
+ this.itchy = function(callback) {
+ var y = this.x;
+
+ setTimeout(a, 1000);
+
+ function a() {
+ b();
+ }
+
+ function b() {
+ if (y) callback();
+ }
+ }
+ }
+}());
+</script>
+</head>
+<body>
+Test
+<script defer="defer">
+(function() {
+ var code = function() {
+ throw Error();
+ }
+
+ scratchy.itchy(code);
+}());
+</script>
+</body>
+</html>
\ No newline at end of file