summaryrefslogtreecommitdiffstats
path: root/st_web
diff options
context:
space:
mode:
authorNathan Straz <nstraz@redhat.com>2009-02-02 18:36:40 -0500
committerNathan Straz <nstraz@redhat.com>2009-02-02 18:36:40 -0500
commit7e1247b426ae86d7c660a9ca87f76d43773670f6 (patch)
treeb91f593102a185a998bec50255e52ef0022de948 /st_web
downloadsteeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.tar.gz
steeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.tar.xz
steeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.zip
Import a sanitized version of Steel Toe, the provisioning system.
Diffstat (limited to 'st_web')
-rw-r--r--st_web/README34
-rw-r--r--st_web/__init__.py0
-rw-r--r--st_web/context_processors.py4
-rw-r--r--st_web/st_config.py114
-rw-r--r--st_web/templates/st_web.css109
-rw-r--r--st_web/templates/st_web/confirm.html23
-rw-r--r--st_web/templates/st_web/index.html14
-rw-r--r--st_web/templates/st_web/install.html129
-rw-r--r--st_web/templates/st_web/perform.html15
-rw-r--r--st_web/templates/st_web/virtconfirm.html39
-rw-r--r--st_web/templates/st_web/virthosts.html170
-rw-r--r--st_web/templates/st_web/virtperform.html15
-rw-r--r--st_web/templates/st_web/virtvolumes.html65
-rw-r--r--st_web/templatetags/__init__.py0
-rw-r--r--st_web/templatetags/ifin.py72
-rw-r--r--st_web/urls.py11
-rw-r--r--st_web/views.py262
-rw-r--r--st_web/widgets.py71
18 files changed, 1147 insertions, 0 deletions
diff --git a/st_web/README b/st_web/README
new file mode 100644
index 0000000..940ee42
--- /dev/null
+++ b/st_web/README
@@ -0,0 +1,34 @@
+Steel Toe Web Interface
+
+Requirements:
+
+This is a Django application which was developed using Django 0.96.
+You will still need to generate a project which includes this
+application. Make the following changes to your project.
+
+In settings.py:
+
+INSTALLED_APPS = (
+ ...
+ 'st_web',
+)
+
+In urls.py:
+urlpatterns += patterns('',
+ (r'^steeltoe/', include('st_web.urls')),
+)
+
+
+It depends on the media context processor which was not part of
+Django 0.96, but is available in development versions of Django. A
+version of it is included in this application and should be added to the
+template context processors by including this snippet in your
+settings.py:
+
+from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
+TEMPLATE_CONTEXT_PROCESSORS += (
+ 'st_web.context_processors.media',
+)
+
+The stylesheet is included in the templates directory, you'll need to
+copy it to settings.MEDIA_ROOT/st_web/st_web.css.
diff --git a/st_web/__init__.py b/st_web/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/st_web/__init__.py
diff --git a/st_web/context_processors.py b/st_web/context_processors.py
new file mode 100644
index 0000000..482af15
--- /dev/null
+++ b/st_web/context_processors.py
@@ -0,0 +1,4 @@
+from django.conf import settings
+
+def media(request):
+ return {'MEDIA_URL': settings.MEDIA_URL}
diff --git a/st_web/st_config.py b/st_web/st_config.py
new file mode 100644
index 0000000..7869e10
--- /dev/null
+++ b/st_web/st_config.py
@@ -0,0 +1,114 @@
+
+from xml.sax import make_parser, handler
+from sets import Set
+from os.path import getmtime
+
+class SteelToeXML(handler.ContentHandler):
+
+ def __init__(self, filename="/etc/steeltoe/steeltoe.xml"):
+ self.filename = filename
+ self.reload()
+
+ def reload(self):
+ self.mtimes = {self.filename: getmtime(self.filename)}
+ self.config = {'bootloader': {}}
+ self.hosts = []
+ self.hostnames = Set()
+ self.groups = []
+ self.groupnames = Set()
+ self.trees = []
+ self.treenames = Set()
+ self.meta = {} # tree metadata index
+ self._context = []
+ self._curname = ""
+ parser = make_parser()
+ parser.setContentHandler(self)
+ parser.parse(self.filename)
+
+ def refresh(self):
+ reload = False
+ for (fn, mtime) in self.mtimes.items():
+ if mtime != getmtime(fn):
+ reload = True
+ if reload:
+ self.reload()
+
+ def get_group(self, name):
+ return [x for x in self.groups if x['name'] == name][0]
+
+ def get_host(self, name):
+ return [x for x in self.hosts if x['name'] == name][0]
+
+ def characters(self, content):
+ self._text = self._text + content
+
+ def startElement(self, name, attrs):
+ if name in ['config', 'tree', 'host', 'bootloader', 'kickstart', 'meta']:
+ self._context.append(name)
+ if name in ['group', 'tree', 'host']:
+ self._curname = attrs['name']
+ if name == 'repo':
+ if not self._curtree.has_key(name):
+ self._curtree[name] = [attrs['name']]
+ else:
+ self._curtree[name].append(attrs['name'])
+ if name == 'host':
+ self._curhost = {'name': self._curname}
+ self.hostnames.add(self._curname)
+ if name == 'tree':
+ self._curtree = {'name': self._curname, 'meta': {}}
+ self.treenames.add(self._curname)
+ if name == 'group':
+ self.groupnames.add(self._curname)
+ # Hack to handle simple XIncludes
+ if name == 'xi:include':
+ href = attrs['href']
+ self.mtimes[href] = getmtime(href)
+ subp = make_parser()
+ subp.setContentHandler(self)
+ subp.parse(href)
+ self._text = ""
+
+ def _parent(self):
+ if len(self._context):
+ return self._context[-1]
+ else:
+ None
+
+ def endElement(self, name):
+ self._text = self._text.strip()
+ if self._parent() == name:
+ self._context.pop()
+ if self._parent() == 'config':
+ self.config[name] = self._text
+ elif self._parent() == 'bootloader':
+ self.config['bootloader'][name] = self._text
+ elif name == 'group':
+ self.groups.append({ 'name': self._curname,
+ 'members': self._text.split()})
+ self._curname = None
+ elif self._parent() == 'kickstart' or name == 'kickstart':
+ # Ignore the kickstart sections for now
+ pass
+ elif self._parent() == 'host':
+ self._curhost[name] = self._text
+ elif name == 'host':
+ self.hosts.append(self._curhost)
+ self._curname = None
+ elif self._parent() == 'meta':
+ self._curtree['meta'][name] = self._text
+ if not self.meta.has_key(name):
+ self.meta[name] = {self._text: Set([self._curname])}
+ elif not self.meta[name].has_key(self._text):
+ self.meta[name][self._text] = Set([self._curname])
+ else:
+ self.meta[name][self._text].add(self._curname)
+ elif self._parent() == 'tree':
+ if name == 'path':
+ self._curtree[name] = self._text
+ elif name == 'tree':
+ self.trees.append(self._curtree)
+ self._curname = None
+
+
+steeltoe = SteelToeXML()
diff --git a/st_web/templates/st_web.css b/st_web/templates/st_web.css
new file mode 100644
index 0000000..b04c130
--- /dev/null
+++ b/st_web/templates/st_web.css
@@ -0,0 +1,109 @@
+h1 {
+ text-align: center;
+}
+
+h2 {
+ font-size: large;
+ font-weight: normal;
+}
+.yui-navset {
+ margin: 0 0 1em 5%;
+ width: 90%;
+}
+
+.yui-navset .yui-nav li {
+ padding: 1px 0 0;
+ background: #800000;
+}
+
+.yui-navset .yui-nav .selected {
+}
+
+.yui-navset .yui-nav a {
+ padding: 0 1em;
+ color: #000;
+ text-decoration: none;
+}
+
+.yui-navset .yui-nav .selected a,
+.yui-navset .yui-nav .selected a:focus,
+.yui-navset .yui-nav .selected a:hover {
+ background: #C00;
+ color: #FFF;
+}
+
+.yui-navset .yui-nav a:hover,
+.yui-navset .yui-nav a:focus {
+ background: #cc0000;
+ color: #FFF;
+ outline: 0;
+}
+
+.yui-content {
+ height: 20em;
+ overflow: auto;
+ border-left: 2px solid #C00;
+ border-right: 2px solid #C00;
+ border-top: 5px solid #C00;
+ border-bottom: 5px solid #C00;
+ padding: 0.25em 0;
+ background: white;
+}
+.yui-content p {
+ margin: 1em 1em;
+}
+
+.yui-content table {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.yui-content table td {
+ padding: 1px 1em;
+ border-left: 1px dotted;
+ border-right: 1px dotted;
+}
+.yui-content table tr.heading {
+ border-bottom: 2px solid #000;
+}
+
+.yui-content table tr.odd {
+ background: #FFF;
+}
+
+.yui-content table tr.even {
+ background: #FED;
+}
+
+.yui-content table td.input {
+ text-align: center;
+ width: 1em;
+}
+
+ul.errorlist {
+ margin: 0 0 1em 5%;
+ width: 75%;
+ background-color: #C00;
+ border: 2px solid #C00;
+}
+.errorlist li {
+ display: block;
+ background: #FDD;
+ padding: 0.25em 1em;
+}
+
+input {
+ background: #FED;
+ border: 1px solid #999;
+ color: black;
+}
+
+pre {
+ width: 75%;
+ margin: 0 0 1em 5%;
+ padding: 1em;
+ background: #FED;
+ border: 2px solid #C00;
+}
diff --git a/st_web/templates/st_web/confirm.html b/st_web/templates/st_web/confirm.html
new file mode 100644
index 0000000..a780a75
--- /dev/null
+++ b/st_web/templates/st_web/confirm.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<title>Steel Toe Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+</head>
+<body>
+<h1>Steel Toe Install Confirmation</h1>
+<h2>Please verify that you want to install:</h2>
+<ul><li>{{ tree }}</li></ul>
+<h2>on the following hosts:</h2>
+<ul>
+{% for host in hosts %}
+<li>{{ host }}</li>
+{% endfor %}
+</ul>
+<form action="." method="post">
+{{ form.tree.as_hidden }}
+{{ form.hosts.as_hidden }}
+<input type="submit" name="install" value="Confirm"/>
+<input type="submit" name="install" value="Cancel"/>
+</form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/index.html b/st_web/templates/st_web/index.html
new file mode 100644
index 0000000..cb2ef9f
--- /dev/null
+++ b/st_web/templates/st_web/index.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title>Steel Toe</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+</head>
+<body>
+<h1>Steel Toe</h1>
+<h2>Available Actions</h2>
+<ul>
+<li><a href="{% url st_web.views.install %}">Install</a></li>
+<li><a href="{% url st_web.views.virtinstall %}">Virtual Machine Install</a></li>
+</ul>
+</body>
+</html>
diff --git a/st_web/templates/st_web/install.html b/st_web/templates/st_web/install.html
new file mode 100644
index 0000000..e5870b0
--- /dev/null
+++ b/st_web/templates/st_web/install.html
@@ -0,0 +1,129 @@
+{% load ifin %}
+<html>
+<head>
+<title>Steel Toe Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}yui/tabview/assets/skins/sam/tabview.css" />
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/yahoo-dom-event/yahoo-dom-event.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/element/element-beta-min.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/tabview/tabview-min.js"></script>
+</head>
+<body>
+<h1>Steel Toe Install</h1>
+<form action="{% url st_web.views.install %}" method="post">
+
+{% if form.errors %}
+{{ form.non_field_errors }}
+{% endif %}
+
+<h2>Select Tree to install</h2>
+{{ form.tree.errors }}
+<div id="treeselect" class="yui-navset">
+ <ul class="yui-nav">
+ <li class="selected"><a href="#tall">All</a></li>
+ <li><a href="#trecent">Recently Used</a></li>
+ <li><a href="#tinstalled">Installed</a></li>
+ </ul>
+ <div class="yui-content">
+ <div id="tall">
+ {% regroup steeltoe.trees|dictsort:"name" by name as trees %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Tree Name</th><th>Kernel</th><th>Links</th></tr>
+ {% for tree in trees %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="tree" value="{{ tree.grouper }}" {% ifequal tree.grouper form.tree.data %}checked="checked"{% endifequal %}></td>
+ <td>{{ tree.grouper }}</td>
+ <td>{{ tree.list.0.meta.kernel }}</td>
+ <td>{% for limb in tree.list %}
+ <a href="http://{{ steeltoe.config.treeserver }}{{ limb.path }}">{{ limb.meta.arch }}</a>
+ {% endfor %}
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ </div>
+ <div id="trecent">
+ {% if request.session.trees %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Tree Name</th><th>Times installed</th></tr>
+ {% for tree in request.session.trees %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="tree" value="{{ tree.name }}" {% ifequal tree.name form.tree.data %}checked="checked"{% endifequal %}></td>
+ <td>{{ tree.name }}</td>
+ <td>{{ tree.count }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <p>A list of recently installed trees</p>
+ {% endif %}
+ </div>
+ <div id="tinstalled">
+ <p>A list of all trees installed and where</p>
+ </div>
+ </div>
+</div>
+
+<h2>Select Hosts on which to install</h2>
+{{ form.hosts.errors }}
+<div id="hostselect">
+ <ul class="yui-nav">
+ <li class="selected"><a href="#hall">All</a></li>
+ <li><a href="#hgroups">Groups</a></li>
+ <li><a href="#hrecent">Your Systems</a></li>
+ </ul>
+ <div class="yui-content">
+ <div id="hall">
+ <table>
+ <tr class="heading"><th>Select</th><th>Host name</th><th>Arch</th></tr>
+ {% for host in steeltoe.hosts|dictsort:"name" %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name form.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.arch }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ <div id="hgroups">
+ <table>
+ <tr class="heading"><th>Select</th><th>Group name</th><th>Group Members</th></tr>
+ {% for group in steeltoe.groups|dictsort:"name" %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ group.name }}" {% ifin group.name form.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ group.name }}</td>
+ <td>{% for m in group.members %} {{ m }} {% endfor %}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ <div id="hrecent">
+ {% if request.session.hosts %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Host Name</th><th>Times installed</th></tr>
+ {% for host in request.session.hosts %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name form.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.count }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <p>A list of hosts which you have installed on</p>
+ {% endif %}
+ </div>
+ </div>
+</div>
+
+<script>
+var tv = new YAHOO.widget.TabView('treeselect');
+var hv = new YAHOO.widget.TabView('hostselect');
+</script>
+
+<input type="reset" value="Clear Selections">
+<input type="submit" name="install" value="Install">
+</form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/perform.html b/st_web/templates/st_web/perform.html
new file mode 100644
index 0000000..2d1b195
--- /dev/null
+++ b/st_web/templates/st_web/perform.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>Steel Toe Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+</head>
+<body>
+<h1>Steel Toe Install Output</h1>
+<h2>Output from Steel Toe install command.</h2>
+<pre>
+{{ output }}
+</pre>
+
+<form action="." method="get"><input type="submit" value="Do Another Install"/></form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/virtconfirm.html b/st_web/templates/st_web/virtconfirm.html
new file mode 100644
index 0000000..c03dedc
--- /dev/null
+++ b/st_web/templates/st_web/virtconfirm.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+<title>Steel Toe Virtual Machine Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+</head>
+<body>
+<h1>Steel Toe Virtual Machine Install Confirmation</h1>
+
+<h2>Please verify the following operation:</h2>
+<p>You want to install <b>{{ tree }}</b>, using <b>{{ dom0 }}</b> as the installation host, on:</p>
+
+<ul>
+{% for host in hosts %}
+<li>{{ host.name }} with root device /dev/{{ host.vg }}/{{ host.lv }} ({{ host.size }}),</li>
+{% endfor %}
+</ul>
+{% if shared %}
+<p>and shared device{{ shared|length|pluralize }}</p>
+<ul>
+{% for v in shared %}
+<li>/dev/{{ v.vg }}/{{ v.lv }} ({{ v.size }})</li>
+{% endfor %}
+</ul>
+{% else %}
+<p>and no shared devices.</p>
+{% endif %}
+<form action="." method="post">
+{% for f in hostform %}
+{{ f.as_hidden }}
+{% endfor %}
+{% for f in volform %}
+{{ f.as_hidden }}
+{% endfor %}
+<input type="hidden" name="step" value="confirm"/>
+<input type="submit" name="install" value="Confirm"/>
+<input type="submit" name="install" value="Cancel"/>
+</form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/virthosts.html b/st_web/templates/st_web/virthosts.html
new file mode 100644
index 0000000..27f844c
--- /dev/null
+++ b/st_web/templates/st_web/virthosts.html
@@ -0,0 +1,170 @@
+{% load ifin %}
+<html>
+<head>
+<title>Steel Toe Virtual Machine Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}yui/tabview/assets/skins/sam/tabview.css" />
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/yahoo-dom-event/yahoo-dom-event.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/element/element-beta-min.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/tabview/tabview-min.js"></script>
+</head>
+<body>
+<h1>Steel Toe Virtual Machine Install</h1>
+<form action="{% url st_web.views.virtinstall %}" method="post">
+
+{% if hostform.errors %}
+{{ hostform.non_field_errors }}
+{% endif %}
+
+<h2>Select Tree to install</h2>
+{{ hostform.tree.errors }}
+<div id="treeselect" class="yui-navset">
+ <ul class="yui-nav">
+ <li class="selected"><a href="#tall">All</a></li>
+ <li><a href="#trecent">Recently Used</a></li>
+ <li><a href="#tinstalled">Installed</a></li>
+ </ul>
+ <div class="yui-content">
+ <div id="tall">
+ {% regroup steeltoe.trees|dictsort:"name" by name as trees %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Tree Name</th><th>Kernel</th><th>Links</th></tr>
+ {% for tree in trees %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="tree" value="{{ tree.grouper }}" {% ifequal tree.grouper hostform.tree.data %}checked="checked"{% endifequal %}></td>
+ <td>{{ tree.grouper }}</td>
+ <td>{{ tree.list.0.meta.kernel }}</td>
+ <td>{% for limb in tree.list %}
+ <a href="http://{{ steeltoe.config.treeserver }}{{ limb.path }}">{{ limb.meta.arch }}</a>
+ {% endfor %}
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ </div>
+ <div id="trecent">
+ {% if request.session.trees %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Tree Name</th><th>Times installed</th></tr>
+ {% for tree in request.session.trees %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="tree" value="{{ tree.name }}" {% ifequal tree.name hostform.tree.data %}checked="checked"{% endifequal %}></td>
+ <td>{{ tree.name }}</td>
+ <td>{{ tree.count }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <p>A list of recently installed trees</p>
+ {% endif %}
+ </div>
+ <div id="tinstalled">
+ <p>A list of all trees installed and where</p>
+ </div>
+ </div>
+</div>
+
+<h2>Select a Physical Host on which to install</h2>
+{{ hostform.dom0.errors }}
+<div id="dom0select">
+ <ul class="yui-nav">
+ <li class="selected"><a href="#hall">All</a></li>
+ <li><a href="#hrecent">Your Systems</a></li>
+ </ul>
+ <div class="yui-content">
+ <div id="hall">
+ <table>
+ <tr class="heading"><th>Select</th><th>Host name</th><th>Arch</th></tr>
+ {% for host in steeltoe.hosts|dictsort:"name" %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="dom0" value="{{ host.name }}" {% ifin host.name hostform.dom0.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.arch }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ <div id="hrecent">
+ {% if request.session.hosts %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Host Name</th><th>Times installed</th></tr>
+ {% for host in request.session.hosts %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="dom0" value="{{ host.name }}" {% ifin host.name hostform.dom0.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.count }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <p>A list of hosts which you have installed on</p>
+ {% endif %}
+ </div>
+ </div>
+</div>
+
+<h2>Select Virtual Hosts on which to install</h2>
+{{ hostform.hosts.errors }}
+<div id="hostselect">
+ <ul class="yui-nav">
+ <li class="selected"><a href="#hall">All</a></li>
+ <li><a href="#hgroups">Groups</a></li>
+ <li><a href="#hrecent">Your Systems</a></li>
+ </ul>
+ <div class="yui-content">
+ <div id="hall">
+ <table>
+ <tr class="heading"><th>Select</th><th>Host name</th><th>Arch</th></tr>
+ {% for host in steeltoe.hosts|dictsort:"name" %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name hostform.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.arch }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ <div id="hgroups">
+ <table>
+ <tr class="heading"><th>Select</th><th>Group name</th><th>Group Members</th></tr>
+ {% for group in steeltoe.groups|dictsort:"name" %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ group.name }}" {% ifin group.name hostform.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ group.name }}</td>
+ <td>{% for m in group.members %} {{ m }} {% endfor %}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ <div id="hrecent">
+ {% if request.session.hosts %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Host Name</th><th>Times installed</th></tr>
+ {% for host in request.session.hosts %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name hostform.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.count }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <p>A list of hosts which you have installed on</p>
+ {% endif %}
+ </div>
+ </div>
+</div>
+
+<script>
+var tv = new YAHOO.widget.TabView('treeselect');
+var dv = new YAHOO.widget.TabView('dom0select');
+var hv = new YAHOO.widget.TabView('hostselect');
+</script>
+
+<input type="hidden" name="step" value="hosts"/>
+<input type="reset" value="Clear Selections">
+<input type="submit" name="install" value="Next">
+</form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/virtperform.html b/st_web/templates/st_web/virtperform.html
new file mode 100644
index 0000000..5de5d60
--- /dev/null
+++ b/st_web/templates/st_web/virtperform.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>Steel Toe Virtual Machine Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+</head>
+<body>
+<h1>Steel Toe Virtual Machine Install Output</h1>
+<h2>Output from Steel Toe virtinstall command.</h2>
+<pre>
+{{ output }}
+</pre>
+
+<form action="." method="get"><input type="submit" value="Do Another Install"/></form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/virtvolumes.html b/st_web/templates/st_web/virtvolumes.html
new file mode 100644
index 0000000..3bf3571
--- /dev/null
+++ b/st_web/templates/st_web/virtvolumes.html
@@ -0,0 +1,65 @@
+{% load ifin %}
+<html>
+<head>
+<title>Steel Toe Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}yui/tabview/assets/skins/sam/tabview.css" />
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/yahoo-dom-event/yahoo-dom-event.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/element/element-beta-min.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/tabview/tabview-min.js"></script>
+</head>
+<body>
+<h1>Steel Toe Virtual Machine Install</h1>
+<form action="{% url st_web.views.virtinstall %}" method="post">
+
+{% if volform.fields %}
+
+<h2>Match volumes with virtual machines</h2>
+
+{% if volform.errors %}
+{{ volform.non_field_errors }}
+{% endif %}
+
+<div class="yui-navset">
+<div class="yui-content">
+<table>
+<tr class="heading">
+<th>Logical Volume</th><th>Volume Group</th><th>Size</th>
+{% for choice in volform.fields.values.0.choices %}<th>{{ choice.1 }}</th>{% endfor %}
+</tr>
+{% for bf in volform %}
+<tr class="{% cycle odd,even %}"><td>{{ bf.field.lv }}</td><td>{{ bf.field.vg }}</td><td>{{ bf.field.size }}</td>
+ {{ bf }}
+{% if bf.errors %}<td>{{ bf.errors }}</td>{% endif %}
+</tr>
+{% endfor %}
+</table>
+</div>
+</div>
+
+{% for f in hostform %}
+{{ f.as_hidden }}
+{% endfor %}
+
+<input type="hidden" name="step" value="vols"/>
+<input type="submit" name="install" value="Cancel">
+<input type="reset" value="Clear Selections">
+<input type="submit" name="install" value="Install">
+{% else %}
+<h2>No volumes found</h2>
+There are many reasons we couldn't find volumes to use.
+<ul>
+<li>The remote shell could not connect to the dom0.</li>
+<li>All logical volumes are currently in use.</li>
+</ul>
+{% for f in hostform %}
+{{ f.as_hidden }}
+{% endfor %}
+
+<input type="hidden" name="step" value="vols"/>
+<input type="submit" name="install" value="Cancel">
+
+{% endif %}
+</form>
+</body>
+</html>
diff --git a/st_web/templatetags/__init__.py b/st_web/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/st_web/templatetags/__init__.py
diff --git a/st_web/templatetags/ifin.py b/st_web/templatetags/ifin.py
new file mode 100644
index 0000000..d514985
--- /dev/null
+++ b/st_web/templatetags/ifin.py
@@ -0,0 +1,72 @@
+from django.template import resolve_variable
+from django.template import Node, NodeList
+from django.template import VariableDoesNotExist
+from django.template import Library
+
+register = Library()
+
+class IfInNode(Node):
+ def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
+ self.var1, self.var2 = var1, var2
+ self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+ self.negate = negate
+
+ def __repr__(self):
+ return "<IfInNode>"
+
+ def render(self, context):
+ try:
+ val1 = resolve_variable(self.var1, context)
+ except VariableDoesNotExist:
+ val1 = None
+ try:
+ val2 = resolve_variable(self.var2, context)
+ except VariableDoesNotExist:
+ val2 = []
+ try:
+ if (self.negate and not val1 in val2) or (not self.negate and val1 in val2):
+ return self.nodelist_true.render(context)
+ except TypeError:
+ return ""
+ return self.nodelist_false.render(context)
+
+def do_ifin(parser, token, negate):
+ bits = list(token.split_contents())
+ if len(bits) != 3:
+ raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
+ end_tag = 'end' + bits[0]
+ nodelist_true = parser.parse(('else', end_tag))
+ token = parser.next_token()
+ if token.contents == 'else':
+ nodelist_false = parser.parse((end_tag,))
+ parser.delete_first_token()
+ else:
+ nodelist_false = NodeList()
+ return IfInNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
+
+#@register.tag
+def ifin(parser, token):
+ """
+ Output the contents of the block if the first argument is in the second argument.
+
+ Examples::
+
+ {% ifin user.id comment.user_id %}
+ ...
+ {% endifin %}
+
+ {% ifnotin user.id comment.user_id %}
+ ...
+ {% else %}
+ ...
+ {% endifnotin %}
+ """
+ return do_ifin(parser, token, False)
+ifin = register.tag(ifin)
+
+#@register.tag
+def ifnotin(parser, token):
+ """Output the contents of the block if the first argument is not in the second argument. See ifin."""
+ return do_ifin(parser, token, True)
+
+ifnotin = register.tag(ifnotin)
diff --git a/st_web/urls.py b/st_web/urls.py
new file mode 100644
index 0000000..ee0152a
--- /dev/null
+++ b/st_web/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ # Example:
+ (r'^$', 'django.views.generic.simple.direct_to_template', {'template': 'st_web/index.html'}),
+ (r'^install/$', 'st_web.views.install'),
+ (r'^virtinstall/$', 'st_web.views.virtinstall'),
+
+ # Uncomment this for admin:
+# (r'^admin/', include('django.contrib.admin.urls')),
+)
diff --git a/st_web/views.py b/st_web/views.py
new file mode 100644
index 0000000..cfd3124
--- /dev/null
+++ b/st_web/views.py
@@ -0,0 +1,262 @@
+# Main views for the Steeltoe Web Interface.
+
+from sets import Set
+from commands import getoutput
+
+from django.http import *
+from django.template import RequestContext
+from django.shortcuts import render_to_response
+from django import newforms as forms
+from django.newforms.util import ErrorList
+from django.utils.datastructures import SortedDict
+
+from st_web.st_config import steeltoe
+from st_web.widgets import VolSelect
+
+def steeltoe_install_form():
+ "Generate a validatable form with choices from the steeltoe config"
+ def _get_trees():
+ return tuple([(x, x) for x in steeltoe.treenames])
+ def _get_hosts():
+ return tuple([(x, x) for x in steeltoe.hostnames | steeltoe.groupnames])
+
+ class SteeltoeInstallForm(forms.Form):
+ tree = forms.ChoiceField(choices = _get_trees(), widget=forms.RadioSelect)
+ hosts = forms.MultipleChoiceField(choices = _get_hosts(), widget=forms.CheckboxSelectMultiple)
+
+ def clean(self):
+ "Test that the arch of the hosts matches the archs of the tree"
+ errors = ErrorList()
+ t = self.clean_data.get('tree', None)
+ if t is None:
+ return self.clean_data
+ tree_archs = [x['meta']['arch'] for x in steeltoe.trees if x['name'] == t]
+ # Normalize the list into hostnames
+ hostlist = Set()
+ for host in self.clean_data.get('hosts', []):
+ if host in steeltoe.groupnames:
+ hostlist |= Set(steeltoe.get_group(host)['members'])
+ elif host in steeltoe.hostnames:
+ hostlist.add(host)
+ else:
+ errors.append("Host %s not valid" % host)
+ # Convert the list to host entries
+ hostlist = list(hostlist)
+ hostlist.sort()
+ self.clean_data['hosts'] = hostlist
+ hostlist = [steeltoe.get_host(x) for x in hostlist]
+ # Do the tests
+ for host in hostlist:
+ if not host['arch'] in tree_archs:
+ errors.append("Tree %s is not available for %s which host %s requires" % (t, host['arch'], host['name']))
+ if errors:
+ raise forms.ValidationError(errors)
+ return self.clean_data
+
+ return SteeltoeInstallForm
+
+def install(request):
+ if request.method == 'POST':
+ form = steeltoe_install_form()(request.POST)
+ if form.is_valid():
+ cmd = request.POST.get('install', 'Install')
+ if cmd == 'Install':
+ return render_to_response("st_web/confirm.html", dict(form.clean_data, form=form),
+ context_instance=RequestContext(request))
+ elif cmd == 'Confirm':
+ t = form.clean_data['tree']
+ h = ' '.join(form.clean_data['hosts'])
+ update_session(request.session, t, form.clean_data['hosts'])
+ output = getoutput("/usr/bin/steeltoe install -r %s %s" % (t, h))
+ return render_to_response("st_web/perform.html", {'output': output},
+ context_instance=RequestContext(request))
+ else:
+ steeltoe.refresh()
+ form = steeltoe_install_form()()
+ return render_to_response("st_web/install.html", { 'steeltoe': steeltoe , 'form': form },
+ context_instance=RequestContext(request))
+
+def steeltoe_virthost_form():
+ "Generate a validatable form with choices from the steeltoe config"
+ def _get_trees():
+ return tuple([(x, x) for x in steeltoe.treenames])
+ def _get_dom0s():
+ return tuple([(x, x) for x in steeltoe.hostnames])
+ def _get_hosts():
+ return tuple([(x, x) for x in steeltoe.hostnames | steeltoe.groupnames])
+
+ class SteeltoeVirtHostForm(forms.Form):
+ tree = forms.ChoiceField(choices = _get_trees(), widget=forms.RadioSelect)
+ dom0 = forms.ChoiceField(choices = _get_dom0s(), widget=forms.RadioSelect)
+ hosts = forms.MultipleChoiceField(choices = _get_hosts(), widget=forms.CheckboxSelectMultiple)
+
+ def clean(self):
+ "Test that the arch of the hosts matches the archs of the tree"
+ errors = ErrorList()
+ t = self.clean_data.get('tree', None)
+ if t is None:
+ return self.clean_data
+ tree_archs = [x['meta']['arch'] for x in steeltoe.trees if x['name'] == t]
+ d0 = self.clean_data.get('dom0', None)
+ if d0 is None:
+ return self.clean_data
+ # Normalize the list into hostnames
+ hostlist = Set()
+ for host in self.clean_data.get('hosts', []):
+ if host in steeltoe.groupnames:
+ hostlist |= Set(steeltoe.get_group(host)['members'])
+ elif host in steeltoe.hostnames:
+ hostlist.add(host)
+ else:
+ errors.append("Host %s not valid" % host)
+ # Convert the list to host entries
+ hostlist = list(hostlist)
+ hostlist.sort()
+ if d0 in hostlist:
+ errors.append("Can not install on %s from %s" % (d0, d0))
+ self.clean_data['hosts'] = hostlist
+ hostlist = [steeltoe.get_host(x) for x in hostlist]
+ # Do the tests
+ for host in hostlist:
+ if not host['arch'] in tree_archs:
+ errors.append("Tree %s is not available for %s which host %s requires" % (t, host['arch'], host['name']))
+ if errors:
+ raise forms.ValidationError(errors)
+ return self.clean_data
+
+ return SteeltoeVirtHostForm
+
+
+def steeltoe_virtvols_form(dom0, domUs):
+ "Generate a validatable form for the volumes which we're going to install the hosts to"
+ def _get_vols(host):
+ rsh = steeltoe.config.get("remoteshell", "/bin/false")
+ output = getoutput("%s -l root %s /usr/sbin/lvs --noheadings -o lv_name,vg_name,lv_size,lv_attr 2>/dev/null" % (rsh, host))
+ vols = []
+ for l in output.split('\n'):
+ if len(l.split()) != 4:
+ continue
+ d = dict(zip(('lv', 'vg', 'size', 'attr'), l.split()))
+ # skip over open volumes
+ if d['attr'][5] == 'o':
+ continue
+ vols.append(d)
+ return vols
+
+ def clean(form):
+ errors = ErrorList()
+ # Check that each host has one and only one root volume
+ hostroots = dict([(h, []) for h in form.hosts])
+ shared = []
+ for vol in form.fields:
+ host = form.clean_data[vol]
+ if host == 'none':
+ continue
+ elif host == 'shared':
+ shared.append(vol)
+ continue
+ elif not hostroots.has_key(host):
+ errors.append("Unexpected host " + host)
+ else:
+ hostroots[host].append(vol)
+ for host, roots in hostroots.items():
+ if len(roots) > 1:
+ errors.append("Too many roots assigned to %s" % host)
+ elif len(roots) == 0:
+ errors.append("Root not assigned to %s" % host)
+ form.clean_data['shared'] = [{'lv': form.fields[v].lv, 'vg': form.fields[v].vg, 'size': form.fields[v].size} for v in shared]
+ if errors:
+ raise forms.ValidationError(errors)
+ # Add an easily usable dict to look hosts' root vol
+ form.clean_data['hosts'] = [({'name': k, 'lv': form.fields[v[0]].lv, 'vg': form.fields[v[0]].vg, 'size': form.fields[v[0]].size}) for k, v in hostroots.items()]
+ return form.clean_data
+
+ vols = _get_vols(dom0)
+ choices = tuple([(h, 'Root for %s' % h) for h in domUs]) + (('shared', 'Shared Volume'), ('none', 'Not Used'))
+
+ base_fields = SortedDict()
+ for vol in vols:
+ f = forms.ChoiceField(choices, widget=VolSelect, initial='none')
+ for k, v in vol.items():
+ setattr(f, k, v)
+ base_fields["%(vg)s_%(lv)s" % vol] = f
+
+ return type('SteeltoeVirtVolForm', (forms.BaseForm,),
+ {'base_fields': base_fields,
+ 'hosts': domUs,
+ 'dom0': dom0,
+ 'vols': vols,
+ 'clean': clean,
+ })
+
+
+def virtinstall(request):
+ if request.method == 'POST':
+ # Any POST request should have a host form to validate.
+ hostform = steeltoe_virthost_form()(request.POST)
+ if hostform.is_valid():
+ step = request.POST.get('step', 'hosts')
+ cmd = request.POST.get('install', 'Next')
+ dom0 = hostform.clean_data['dom0']
+ hosts = hostform.clean_data['hosts']
+ if step == 'vols' and cmd == 'Cancel':
+ return render_to_response("st_web/virthosts.html",
+ { 'steeltoe': steeltoe , 'hostform': hostform },
+ context_instance=RequestContext(request))
+
+ if step in ('vols', 'confirm'):
+ volform = steeltoe_virtvols_form(dom0, hosts)(request.POST)
+ if volform.is_valid():
+ if step == 'vols' and cmd == 'Install':
+ data = dict(hostform.clean_data)
+ data.update(volform.clean_data)
+ data['hostform'] = hostform
+ data['volform'] = volform
+ return render_to_response("st_web/virtconfirm.html", data,
+ context_instance=RequestContext(request))
+ elif step == 'confirm' and cmd == 'Confirm':
+ t = hostform.clean_data['tree']
+ update_session(request.session, t, hostform.clean_data['hosts'])
+ output = ""
+ for h in volform.clean_data['hosts']:
+ cmdargs = ("/usr/bin/steeltoe", "virtinstall")
+ cmdargs += (t, h['name'], dom0)
+ cmdargs += "/dev/%(vg)s/%(lv)s" % h,
+ cmdargs += tuple(["/dev/%(vg)s/%(lv)s" % v for v in volform.clean_data['shared']])
+ output += getoutput(' '.join(cmdargs))
+ return render_to_response("st_web/virtperform.html", {'output': output},
+ context_instance=RequestContext(request))
+ # else: fall through to redisplay the volumes form
+ # else: fall through to redisplay the volumes form with errors
+ else:
+ volform = steeltoe_virtvols_form(dom0, hosts)()
+ # FIXME: What about back button for volumes -> hosts
+ return render_to_response("st_web/virtvolumes.html",
+ dict(hostform=hostform, volform=volform),
+ context_instance=RequestContext(request))
+ # else: host form is not valid, fall through to the first page
+ else:
+ steeltoe.refresh()
+ hostform = steeltoe_virthost_form()()
+ return render_to_response("st_web/virthosts.html", { 'steeltoe': steeltoe , 'hostform': hostform },
+ context_instance=RequestContext(request))
+
+
+def update_session(session, newtree, newhosts):
+ trees = session.get('trees', [])
+ if newtree in Set([x['name'] for x in trees]):
+ tree = [x for x in trees if x['name'] == newtree][0]
+ tree['count'] += 1
+ else:
+ trees.append({'name': newtree, 'count': 1})
+ trees.sort(lambda x, y: cmp(y['count'], x['count']) or cmp(x['name'], y['name']))
+ session['trees'] = [x for x in trees if x['name'] in steeltoe.treenames]
+ hosts = session.get('hosts', [])
+ for newhost in newhosts:
+ if newhost in Set([x['name'] for x in hosts]):
+ host = [x for x in hosts if x['name'] == newhost][0]
+ host['count'] += 1
+ else:
+ hosts.append({'name': newhost, 'count': 1})
+ hosts.sort(lambda x, y: cmp(y['count'], x['count']) or cmp(x['name'], y['name']))
+ session['hosts'] = [x for x in hosts if x['name'] in steeltoe.hostnames]
diff --git a/st_web/widgets.py b/st_web/widgets.py
new file mode 100644
index 0000000..c92424f
--- /dev/null
+++ b/st_web/widgets.py
@@ -0,0 +1,71 @@
+from django import newforms as forms
+from django.newforms.util import flatatt, StrAndUnicode, smart_unicode
+from itertools import chain
+
+__all__ = ('VolSelect')
+
+class RadioInput(StrAndUnicode):
+ "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
+ def __init__(self, name, value, attrs, choice, index):
+ self.name, self.value = name, value
+ self.attrs = attrs
+ self.choice_value = smart_unicode(choice[0])
+ self.choice_label = smart_unicode(choice[1])
+ self.index = index
+
+ def __unicode__(self):
+ return u'<label>%s %s</label>' % (self.tag(), self.choice_label)
+
+ def is_checked(self):
+ return self.value == self.choice_value
+
+ def tag(self):
+ if self.attrs.has_key('id'):
+ self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
+ final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
+ if self.is_checked():
+ final_attrs['checked'] = 'checked'
+ return u'<input%s />' % flatatt(final_attrs)
+
+class RadioFieldRenderer(StrAndUnicode):
+ "An object used by RadioSelect to enable customization of radio widgets."
+ def __init__(self, name, value, attrs, choices):
+ self.name, self.value, self.attrs = name, value, attrs
+ self.choices = choices
+
+ def __iter__(self):
+ for i, choice in enumerate(self.choices):
+ yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
+
+ def __getitem__(self, idx):
+ choice = self.choices[idx] # Let the IndexError propogate
+ return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
+
+ def __unicode__(self):
+ "Outputs a <ul> for this set of radio fields."
+ return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
+
+class VolInput(RadioInput):
+ "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
+ def __unicode__(self):
+ return u'<td class="input">%s</td>' % self.tag()
+
+class VolFieldRenderer(RadioFieldRenderer):
+ def __iter__(self):
+ for i, choice in enumerate(self.choices):
+ yield VolInput(self.name, self.value, self.attrs.copy(), choice, i)
+ def __getitem__(self, idx):
+ choice = self.choices[idx] # Let the IndexError propogate
+ return VolInput(self.name, self.value, self.attrs.copy(), choice, idx)
+ def __unicode__(self):
+ "Outputs the set of radio fields."
+ return u'\n'.join([u'%s' % w for w in self])
+
+
+class VolSelect(forms.RadioSelect):
+ def render(self, name, value, attrs=None, choices=()):
+ "Returns a VolFieldRenderer instance rather than a Unicode string."
+ if value is None: value = ''
+ str_value = smart_unicode(value)
+ attrs = attrs or {}
+ return VolFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices)))