summaryrefslogtreecommitdiffstats
path: root/reports
diff options
context:
space:
mode:
authorJoey Hagedorn <hagedorn@mcs.anl.gov>2006-06-15 15:43:07 +0000
committerJoey Hagedorn <hagedorn@mcs.anl.gov>2006-06-15 15:43:07 +0000
commit14724835e271422f196d97b9a5a65346fbb02e94 (patch)
treedf94850b8c8e95df1a3e57e7586638e44df353e7 /reports
parent93fa7685dd976606273fc286a5f9988a1308cc85 (diff)
downloadbcfg2-14724835e271422f196d97b9a5a65346fbb02e94.tar.gz
bcfg2-14724835e271422f196d97b9a5a65346fbb02e94.tar.bz2
bcfg2-14724835e271422f196d97b9a5a65346fbb02e94.zip
Initial commit of Django based reporting subsystem. Only works with fake data so far...
git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@1883 ce84e21b-d406-0410-9b95-82705330c041
Diffstat (limited to 'reports')
-rw-r--r--reports/brpt/__init__.py0
-rw-r--r--reports/brpt/__init__.pycbin0 -> 141 bytes
-rwxr-xr-xreports/brpt/manage.py11
-rw-r--r--reports/brpt/reports/__init__.py0
-rw-r--r--reports/brpt/reports/__init__.pycbin0 -> 149 bytes
-rw-r--r--reports/brpt/reports/models.py128
-rw-r--r--reports/brpt/reports/models.pycbin0 -> 5369 bytes
-rw-r--r--reports/brpt/reports/templates/base.html46
-rw-r--r--reports/brpt/reports/templates/clients/client-nodebox.html62
-rw-r--r--reports/brpt/reports/templates/clients/detail.html14
-rw-r--r--reports/brpt/reports/templates/clients/index.html15
-rw-r--r--reports/brpt/reports/templates/displays/index.html18
-rw-r--r--reports/brpt/reports/templates/displays/summary-block-direct-links.html7
-rw-r--r--reports/brpt/reports/templates/displays/summary-block.html90
-rw-r--r--reports/brpt/reports/templates/displays/summary.html14
-rw-r--r--reports/brpt/reports/templates/displays/sys_view.html18
-rw-r--r--reports/brpt/reports/templates/displays/timing.html39
-rw-r--r--reports/brpt/reports/templates/index.html15
-rw-r--r--reports/brpt/reports/templatetags/__init__.py0
-rw-r--r--reports/brpt/reports/templatetags/__init__.pycbin0 -> 162 bytes
-rw-r--r--reports/brpt/reports/templatetags/django_templating_sigh.py24
-rw-r--r--reports/brpt/reports/templatetags/django_templating_sigh.pycbin0 -> 1586 bytes
-rw-r--r--reports/brpt/reports/views.py121
-rw-r--r--reports/brpt/reports/views.pycbin0 -> 4260 bytes
-rw-r--r--reports/brpt/settings.py75
-rw-r--r--reports/brpt/settings.pycbin0 -> 2107 bytes
-rw-r--r--reports/brpt/urls.py30
-rw-r--r--reports/brpt/urls.pycbin0 -> 1577 bytes
-rw-r--r--reports/site_media/base.css5
-rw-r--r--reports/site_media/boxypastel.css214
-rw-r--r--reports/site_media/global.css9
-rw-r--r--reports/site_media/layout.css30
-rw-r--r--reports/site_media/main.js27
-rw-r--r--reports/site_media/sorttable.js203
34 files changed, 1215 insertions, 0 deletions
diff --git a/reports/brpt/__init__.py b/reports/brpt/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/reports/brpt/__init__.py
diff --git a/reports/brpt/__init__.pyc b/reports/brpt/__init__.pyc
new file mode 100644
index 000000000..57e9107ae
--- /dev/null
+++ b/reports/brpt/__init__.pyc
Binary files differ
diff --git a/reports/brpt/manage.py b/reports/brpt/manage.py
new file mode 100755
index 000000000..5e78ea979
--- /dev/null
+++ b/reports/brpt/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/reports/brpt/reports/__init__.py b/reports/brpt/reports/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/reports/brpt/reports/__init__.py
diff --git a/reports/brpt/reports/__init__.pyc b/reports/brpt/reports/__init__.pyc
new file mode 100644
index 000000000..1d697e5f3
--- /dev/null
+++ b/reports/brpt/reports/__init__.pyc
Binary files differ
diff --git a/reports/brpt/reports/models.py b/reports/brpt/reports/models.py
new file mode 100644
index 000000000..ac3506214
--- /dev/null
+++ b/reports/brpt/reports/models.py
@@ -0,0 +1,128 @@
+from django.db import models
+#from timedelta import timedelta
+from datetime import datetime, timedelta
+# Create your models here.
+KIND_CHOICES = (
+ ('ConfigFile', 'ConfigFile'),
+ ('Package', 'Package'),
+ ('Service', 'Service'),
+ ('SymLink', 'SymLink'),
+ ('Directory', 'Directory'),
+ ('Permissions','Permissions'),
+)
+
+class Client(models.Model):
+ #This exists for clients that are no longer in the repository even! (timeless)
+ creation = models.DateTimeField()
+ name = models.CharField(maxlength=128)
+ def __str__(self):
+ return self.name
+
+ class Admin:
+ pass
+
+
+class Metadata(models.Model):
+ client = models.ForeignKey(Client)
+ timestamp = models.DateTimeField()
+ #INSERT magic interface to Metadata HERE
+ def __str__(self):
+ return self.timestamp
+
+class Repository(models.Model):
+ timestamp = models.DateTimeField()
+ #INSERT magic interface to any other config info here...
+ def __str__(self):
+ return self.timestamp
+
+
+#models each client-interaction
+class Interaction(models.Model):
+ client = models.ForeignKey(Client, related_name="interactions", core=True)
+ timestamp = models.DateTimeField()#Timestamp for this record
+ state = models.CharField(maxlength=32)#good/bad/modified/etc
+ repo_revision = models.IntegerField()#you got it. the repo in use at time of client interaction
+ client_version = models.CharField(maxlength=32)#really simple; version of client running
+ pingable = models.BooleanField()#This is (was-pingable)status as of last attempted interaction
+ goodcount = models.IntegerField()#of good config-items we store this number, because we don't count the
+ totalcount = models.IntegerField()#of total config-items specified--grab this from metadata instead?
+
+ def __str__(self):
+ return "With " + self.client.name + " @ " + self.timestamp.isoformat()
+
+ def percentgood(self):
+ return (self.goodcount/self.totalcount)*100
+
+ def percentbad(self):
+ return (self.totalcount-self.goodcount)/(self.totalcount)
+
+ def isclean(self):
+ if (self.bad_items.count() == 0 and self.extra_items.count() == 0 and self.goodcount == self.totalcount):
+ #if (self.state == "good"):
+ return True
+ else:
+ return False
+
+ def isstale(self):
+ if (self == self.client.interactions.order_by('-timestamp')[0]):#Is Mostrecent
+ if(datetime.now()-self.timestamp > timedelta(hours=25) ):
+ return True
+ else:
+ return False
+ else:
+ #Search for subsequent Interaction for this client
+ #Check if it happened more than 25 hrs ago.
+ if (self.client.interactions.filter(timestamp__gt=self.timestamp)
+ .order_by('timestamp')[0].timestamp - self.timestamp > timedelta(hours=25)):
+ return True
+ else:
+ return False
+
+
+ class Admin:
+ list_display = ('client', 'timestamp', 'state')
+ list_filter = ['client', 'timestamp']
+ pass
+
+
+
+
+class Modified(models.Model):
+ interaction = models.ForeignKey(Interaction, related_name="modified_items", edit_inline=models.STACKED)
+ name = models.CharField(maxlength=128, core=True)#name of modified thing.
+ kind = models.CharField(maxlength=16, choices=KIND_CHOICES)#Service/Package/ConfgFile...
+ how = models.CharField(maxlength=256)
+ def __str__(self):
+ return self.name
+
+
+
+class Extra(models.Model):
+ interaction = models.ForeignKey(Interaction, related_name="extra_items", edit_inline=models.STACKED)
+ name = models.CharField(maxlength=128, core=True)#name of Extra thing.
+ kind = models.CharField(maxlength=16, choices=KIND_CHOICES)#Service/Package/ConfgFile...
+ why = models.CharField(maxlength=256)#current state of some thing...
+ def __str__(self):
+ return self.name
+
+
+
+class Bad(models.Model):
+ interaction = models.ForeignKey(Interaction, related_name="bad_items", edit_inline=models.STACKED)
+ name = models.CharField(maxlength=128, core=True)#name of bad thing.
+ kind = models.CharField(maxlength=16, choices=KIND_CHOICES)#Service/Package/ConfgFile...
+ reason = models.CharField(maxlength=256)#that its bad...
+ def __str__(self):
+ return self.name
+
+
+#performance metrics, models a performance-metric-item
+class Performance(models.Model):
+ interaction = models.ForeignKey(Interaction, related_name="performance_items", edit_inline=models.STACKED)
+ metric = models.CharField(maxlength=128, core=True)
+ value = models.FloatField(max_digits=32, decimal_places=16)
+ def __str__(self):
+ return self.metric
+
+
+
diff --git a/reports/brpt/reports/models.pyc b/reports/brpt/reports/models.pyc
new file mode 100644
index 000000000..5d2c3cd75
--- /dev/null
+++ b/reports/brpt/reports/models.pyc
Binary files differ
diff --git a/reports/brpt/reports/templates/base.html b/reports/brpt/reports/templates/base.html
new file mode 100644
index 000000000..db242864e
--- /dev/null
+++ b/reports/brpt/reports/templates/base.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>{% block title %}Bcfg2 Reporting System{% endblock %}</title>
+ <link rel="stylesheet" type="text/css" href="/site_media/boxypastel.css" />
+ <link rel="stylesheet" type="text/css" href="/site_media/base.css" />
+ <script type="text/javascript" src="/site_media/main.js">
+ </script>
+ {% block extra_header_info %}{% endblock %}
+</head>
+
+<body>
+ <div id="header">
+ <div id="branding">
+ <h1>Bcfg2 Reporting System</h1>
+ </div>
+ <div id="user-tools">...Reporting into the future...</div>
+ </div>
+ <div id="sidebar">
+ {% block sidebar %}
+ <ul class="sidebar">
+ <li><a href="/" class="sidebar">Home</a></li>
+ <li><a href="/clients/" class="sidebar">Clients</a></li>
+ <li>
+ <a href="/displays/" class="sidebar">Displays</a>
+ <ul class="sidebar-level2">
+ <li><a href="/displays/sys-view/" class="sidebar">System</a></li>
+ <li><a href="/displays/summary/" class="sidebar">Summary</a></li>
+ <li><a href="/displays/timing/" class="sidebar">Timing</a></li>
+ </ul>
+ </li>
+ </ul>
+ {% endblock %}
+ </div>
+
+ <div id="content-main">
+ <div id="container">
+ {% block pagebanner %}{% endblock %}
+ {% block content %}{% endblock %}
+
+ <br /><br /><p><a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.0!</a></p>
+ </div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/reports/brpt/reports/templates/clients/client-nodebox.html b/reports/brpt/reports/templates/clients/client-nodebox.html
new file mode 100644
index 000000000..12b53d899
--- /dev/null
+++ b/reports/brpt/reports/templates/clients/client-nodebox.html
@@ -0,0 +1,62 @@
+{% if client %}
+ <a name="{{client.name}}"></a>
+ <div class="nodebox" name="{{client.name}}">
+ <span class="notebox">Time Ran: {{interaction.timestamp}}</span>
+ <span class="configbox">(-Insert Profile Name Here-)</span>
+
+ <table class="invisitable">
+ <tr><td width="43%"><h2>Node: <span class="nodename">
+ <a href="/clients/{{client.name}}/{{interaction.id}}">{{client.name}}</a></span></h2></td>
+ <td width="23%">
+ {% if interaction.repo_revision %}Revision: {{interaction.repo_revision}}{% endif %}
+ </td>
+ <td width="33%"><div class="statusborder">
+ <div class="greenbar" style="width: {{interaction.percentgood}}%;">&nbsp;</div>
+ <div class="redbar" style="width: {{interaction.percentbad}}%;">&nbsp;</div>
+ </div>
+ </td></tr>
+ </table>
+ {% if interaction.isclean %}
+ <div class="clean">
+ <span class="nodelisttitle">Node is clean; Everything has been satisfactorily configured.</span>
+ </div>
+ {% endif %}
+ {% if interaction.isstale %}
+ <div class="warning">
+ <span class="nodelisttitle">This node did not run within the last 24 hours-- it may be out of date.</span>
+ </div>
+ {% endif %}
+ {% if interaction.bad_items.all %}
+ <div class="bad">
+ <span class="nodelisttitle"><a href="javascript:toggleLayer('{{client.name}}-bad');" title="Click to expand" class="commentLink">{{interaction.bad_items.count}}</a> items did not verify and are considered Dirty.<br /></span>
+ <div class="items" id="{{client.name}}-bad"><ul class="plain">
+ {% for bad in interaction.bad_items.all %} {% comment %}HOWDOI? order_by('kind', 'name'){% endcomment %}
+ <li><strong>{{bad.kind}}: </strong><tt>{{bad.name}}</tt></li>
+ {% endfor %}
+ </ul></div>
+ </div>
+ {% endif %}
+ {% if interaction.modified_items.all %}
+ <div class="modified">
+ <span class="nodelisttitle"><a href="javascript:toggleLayer('{{client.name}}-modified');" title="Click to expand" class="commentLink">{{interaction.modified_items.count}}</a> items were modified in the last run.<br /></span>
+ <div class="items" id="{{client.name}}-modified"><ul class="plain">
+ {% for modified in interaction.modified_items.all %} {% comment %}HOWDOI? order_by('kind', 'name'){% endcomment %}
+ <li><strong>{{modified.kind}}: </strong><tt>{{modified.name}}</tt></li>
+ {% endfor %}
+ </ul></div>
+ </div>
+ {% endif %}
+ {% if interaction.extra_items.all %}
+ <div class="extra">
+ <span class="nodelisttitle"><a href="javascript:toggleLayer('{{client.name}}-extra');" title="Click to expand" class="commentLink">{{interaction.extra_items.count}}</a> extra configuration elements on the node.<br /></span>
+ <div class="items" id="{{client.name}}-extra"><ul class="plain">
+ {% for extra in interaction.extra_items.all %} {% comment %}HOWDOI? order_by('kind', 'name'){% endcomment %}
+ <li><strong>{{extra.kind}}: </strong><tt>{{extra.name}}</tt></li>
+ {% endfor %}
+ </ul></div>
+ </div>
+ {% endif %}
+ </div>
+{% else %}
+ <p>No record could be found for this client.</p>
+{% endif %}
diff --git a/reports/brpt/reports/templates/clients/detail.html b/reports/brpt/reports/templates/clients/detail.html
new file mode 100644
index 000000000..4ac2123c1
--- /dev/null
+++ b/reports/brpt/reports/templates/clients/detail.html
@@ -0,0 +1,14 @@
+{% extends "base.html" %}
+
+{% block title %}Info for: {{client.name}}{% endblock %}
+
+{% block content %}
+<b>Select time: </b>
+<select name=quick onChange="MM_jumpMenu('parent',this,0)">
+ {% for i in client.interactions.all %}
+ <option {% ifequal i.id interaction.id %}selected {% endifequal %} value="/clients/{{client.name}}/{{i.id}}/"> {{i.timestamp}}
+ {% endfor %}
+</select>
+
+{% include "clients/client-nodebox.html" %}
+{% endblock %}
diff --git a/reports/brpt/reports/templates/clients/index.html b/reports/brpt/reports/templates/clients/index.html
new file mode 100644
index 000000000..a474667b3
--- /dev/null
+++ b/reports/brpt/reports/templates/clients/index.html
@@ -0,0 +1,15 @@
+{% extends "base.html" %}
+
+{% block title %}Client Index Listing{% endblock %}
+
+{% block content %}
+{% if client_list %}
+ <ul>
+ {% for client in client_list %}
+ <li><a href="{{client.name}}/">{{ client.name }}</a></li>
+ {% endfor %}
+ </ul>
+{% else %}
+ <p>No client records are available.</p>
+{% endif %}
+{% endblock %}
diff --git a/reports/brpt/reports/templates/displays/index.html b/reports/brpt/reports/templates/displays/index.html
new file mode 100644
index 000000000..5d1d3bf76
--- /dev/null
+++ b/reports/brpt/reports/templates/displays/index.html
@@ -0,0 +1,18 @@
+{% extends "base.html" %}
+
+{% block title %}Display Index Listing{% endblock %}
+{% block pagebanner %}
+ <div class="header">
+ <h1>BCFG Display Index</h1>
+ {% comment %} <span class="notebox">Report Run @ {% now "F j, Y P"%}</span>{% endcomment %}
+ </div>
+ <br/>
+{% endblock %}
+
+{% block content %}
+<ul>
+<li><a href="/displays/sys-view/">System View</a></li>
+<li><a href="/displays/summary/">Summary Only</a></li>
+<li><a href="/displays/timing/">Timing</a></li>
+</ul>
+{% endblock %}
diff --git a/reports/brpt/reports/templates/displays/summary-block-direct-links.html b/reports/brpt/reports/templates/displays/summary-block-direct-links.html
new file mode 100644
index 000000000..a218e12b6
--- /dev/null
+++ b/reports/brpt/reports/templates/displays/summary-block-direct-links.html
@@ -0,0 +1,7 @@
+{% extends "displays/summary-block.html" %}
+{% block linkprefix1 %}/clients/{% endblock %}
+{% block linkprefix2 %}/clients/{% endblock %}
+{% block linkprefix3 %}/clients/{% endblock %}
+{% block linkprefix4 %}/clients/{% endblock %}
+{% block linkprefix5 %}/clients/{% endblock %}
+{% block linkprefix6 %}/clients/{% endblock %} \ No newline at end of file
diff --git a/reports/brpt/reports/templates/displays/summary-block.html b/reports/brpt/reports/templates/displays/summary-block.html
new file mode 100644
index 000000000..3632cc870
--- /dev/null
+++ b/reports/brpt/reports/templates/displays/summary-block.html
@@ -0,0 +1,90 @@
+{% load django_templating_sigh %}
+
+ <div class="nodebox">
+ <h2>Summary:</h2>
+ <p class="indented">{{client_list|length }} Nodes were included in your report.</p>
+ {% if clean_client_list %}
+ <div class="clean">
+ <span class="nodelisttitle"><a href="javascript:toggleLayer('goodsummary');" title="Click to Expand" class="commentLink">{{clean_client_list|length}}</a> nodes are clean.<br /></span>
+ <div class="items" id="goodsummary"><ul class="plain">
+ {% for client in clean_client_list %}
+ {% set_interaction "foo" %}
+ <li><b>Node: </b></tt>
+ <tt><a href="{% block linkprefix1 %}#{% endblock %}{{client.name}}">{{client.name}}</a></tt><span class="mini-date">{{interaction.timestamp}}</span></li>
+ {% endfor %}
+ </ul></div>
+ </div>
+ {% endif %}
+ {% if bad_client_list %}
+ <div class="bad">
+ <span class="nodelisttitle"><a href="javascript:toggleLayer('badsummary');" title="Click to Expand" class="commentLink">{{bad_client_list|length}}</a> nodes are bad.<br /></span>
+ <div class="items" id="badsummary"><ul class="plain">
+ {% for client in bad_client_list %}
+ {% set_interaction "foo" %}
+ <li><b>Node: </b></tt>
+ <tt><a href="{% block linkprefix2 %}#{% endblock %}{{client.name}}">{{client.name}}</a></tt><span class="mini-date">{{interaction.timestamp}}</span></li>
+ {% endfor %}
+ </ul></div>
+ </div>
+ {% endif %}
+ {% if modified_client_list %}
+ <div class="modified">
+ <span class="nodelisttitle"><a href="javascript:toggleLayer('modifiedsummary');" title="Click to Expand" class="commentLink">{{modified_client_list|length}}</a> nodes were modified in the previous run.<br /></span>
+ <div class="items" id="modifiedsummary"><ul class="plain">
+ {% for client in modified_client_list %}
+ {% set_interaction "foo" %}
+ <li><b>Node: </b></tt>
+ <tt><a href="{% block linkprefix3 %}#{% endblock %}{{client.name}}">{{client.name}}</a></tt><span class="mini-date">{{interaction.timestamp}}</span></li>
+ {% endfor %}
+ </ul></div>
+ </div>
+ {% endif %}
+ {% if extra_client_list %}
+ <div class="extra">
+ <span class="nodelisttitle"><a href="javascript:toggleLayer('extrasummary');" title="Click to Expand" class="commentLink">{{extra_client_list|length}}</a> nodes have extra configuration. (includes both good and bad nodes)<br /></span>
+ <div class="items" id="extrasummary"><ul class="plain">
+ {% for client in extra_client_list %}
+ {% set_interaction "foo" %}
+ <li><b>Node: </b></tt>
+ <tt><a href="{% block linkprefix4 %}#{% endblock %}{{client.name}}">{{client.name}}</a></tt><span class="mini-date">{{interaction.timestamp}}</span></li>
+ {% endfor %}
+ </ul></div>
+ </div>
+ {% endif %}
+ {% if stale_up_client_list %}
+ <div class="warning">
+ <span class="nodelisttitle"><a href="javascript:toggleLayer('vstalesummary');" title="Click to Expand" class="commentLink">{{stale_up_client_list|length}}</a> nodes did not run within the last 24 hours but were pingable.<br /></span>
+ <div class="items" id="vstalesummary"><ul class="plain">
+ {% for client in stale_up_client_list %}
+ {% set_interaction "foo" %}
+ <li><b>Node: </b></tt>
+ <tt><a href="{% block linkprefix5 %}#{% endblock %}{{client.name}}">{{client.name}}</a></tt><span class="mini-date">{{interaction.timestamp}}</span></li>
+ {% endfor %}
+ </ul></div>
+ </div>
+ {% endif %}
+ {% if stale_all_client_list %}
+ <div class="all-warning">
+ <span class="nodelisttitle"><a href="javascript:toggleLayer('stalesummary');" title="Click to Expand" class="commentLink">{{stale_all_client_list|length}}</a> nodes did not run within the last 24 hours. (includes nodes up and down)<br /></span>
+ <div class="items" id="stalesummary"><ul class="plain">
+ {% for client in stale_all_client_list %}
+ {% set_interaction "foo" %}
+ <li><b>Node: </b></tt>
+ <tt><a href="{% block linkprefix6 %}#{% endblock %}{{client.name}}">{{client.name}}</a></tt><span class="mini-date">{{interaction.timestamp}}</span></li>
+ {% endfor %}
+ </ul></div>
+ </div>
+ {% endif %}
+ {% if down_client_list %}
+ <div class="down">
+ <span class="nodelisttitle"><a href="javascript:toggleLayer('unpingablesummary');" title="Click to Expand" class="commentLink">{{down_client_list|length}}</a> nodes were down.<br /></span>
+ <div class="items" id="unpingablesummary"><ul class="plain">
+ {% for client in down_client_list %}
+ {% set_interaction "foo" %}
+ <li><b>Node: </b></tt>
+ <tt><a href="#{{client.name}}">{{client.name}}</a></tt><span class="mini-date">{{interaction.timestamp}}</span></li>
+ {% endfor %}
+ </ul></div>
+ </div>
+ {% endif %}
+ </div> \ No newline at end of file
diff --git a/reports/brpt/reports/templates/displays/summary.html b/reports/brpt/reports/templates/displays/summary.html
new file mode 100644
index 000000000..f3c3c1339
--- /dev/null
+++ b/reports/brpt/reports/templates/displays/summary.html
@@ -0,0 +1,14 @@
+{% extends "base.html" %}
+
+{% block title %}Display Index Listing{% endblock %}
+{% block pagebanner %}
+ <div class="header">
+ <h1>BCFG Clients Summary</h1>
+ <span class="notebox">Report Run @ {% now "F j, Y P"%}</span>
+ </div>
+ <br/>
+{% endblock %}
+
+{% block content %}
+ {% include "displays/summary-block-direct-links.html" %}
+{% endblock %}
diff --git a/reports/brpt/reports/templates/displays/sys_view.html b/reports/brpt/reports/templates/displays/sys_view.html
new file mode 100644
index 000000000..bf80f545d
--- /dev/null
+++ b/reports/brpt/reports/templates/displays/sys_view.html
@@ -0,0 +1,18 @@
+{% extends "base.html" %}
+{% load django_templating_sigh %}
+
+{% block title %}System-View Display{% endblock %}
+{% block pagebanner %}
+ <div class="header">
+ <h1>BCFG Performance Timings</h1>
+ <span class="notebox">Report Run @ {% now "F j, Y P"%}</span>
+ </div>
+ <br/>
+{% endblock %}
+{% block content %}
+ {% include "displays/summary-block.html" %}
+ {% for client in client_list %}
+ {% set_interaction "foo" %}
+ {% include "clients/client-nodebox.html" %}
+ {% endfor %}
+{% endblock %}
diff --git a/reports/brpt/reports/templates/displays/timing.html b/reports/brpt/reports/templates/displays/timing.html
new file mode 100644
index 000000000..c3573851a
--- /dev/null
+++ b/reports/brpt/reports/templates/displays/timing.html
@@ -0,0 +1,39 @@
+{% extends "base.html" %}
+
+{% block extra_header_info %}
+<script type="text/javascript" src="/site_media/sorttable.js">
+</script>{% endblock%}
+{% comment %} THIS ABOVE PART MAY BE SITE DEPENDENT-- CHANGE {% endcomment %}
+{% block title %}Display Index Listing{% endblock %}
+
+{% block content %}
+ <div class="header">
+ <h1>BCFG Performance Timings</h1>
+ <span class="notebox">Report Run @ {% now "F j, Y P"%}</span>
+ </div>
+ <br/>
+ <center>
+ <table id="t1" class="sortable">
+ <tr>
+ <th class="sortable">Hostname</th>
+ <th class="sortable">Parse</th>
+ <th class="sortable">Probe</th>
+ <th class="sortable">Inventory</th>
+ <th class="sortable">Install</th>
+ <th class="sortable">Config</th>
+ <th class="sortable">Total</th>
+ </tr>
+ {% for dict_unit in stats_list %}
+ <tr>
+ <td class="sortable"><a href="/clients/{{dict_unit.name}}/">{{dict_unit.name}}</a></td>
+ <td class="sortable">{{dict_unit.parse}}</td>
+ <td class="sortable">{{dict_unit.probe}}</td>
+ <td class="sortable">{{dict_unit.inventory}}</td>
+ <td class="sortable">{{dict_unit.install}}</td>
+ <td class="sortable">{{dict_unit.config}}</td>
+ <td class="sortable">{{dict_unit.total}}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </center>
+{% endblock %} \ No newline at end of file
diff --git a/reports/brpt/reports/templates/index.html b/reports/brpt/reports/templates/index.html
new file mode 100644
index 000000000..002a3f770
--- /dev/null
+++ b/reports/brpt/reports/templates/index.html
@@ -0,0 +1,15 @@
+{% extends "base.html" %}
+
+{% block pagebanner %}
+ <div class="header">
+ <h1>BCFG Reports</h1>
+ {% comment %} <span class="notebox">Report Run @ {% now "F j, Y P"%}</span>{% endcomment %}
+ </div>
+ <br/>
+{% endblock %}
+{% block content %}
+<h1>Welcome to the Bcfg2 Reporting System</h1>
+<p>
+Please use the links at the left to navigate.
+</p>
+{% endblock %}
diff --git a/reports/brpt/reports/templatetags/__init__.py b/reports/brpt/reports/templatetags/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/reports/brpt/reports/templatetags/__init__.py
diff --git a/reports/brpt/reports/templatetags/__init__.pyc b/reports/brpt/reports/templatetags/__init__.pyc
new file mode 100644
index 000000000..c1161f868
--- /dev/null
+++ b/reports/brpt/reports/templatetags/__init__.pyc
Binary files differ
diff --git a/reports/brpt/reports/templatetags/django_templating_sigh.py b/reports/brpt/reports/templatetags/django_templating_sigh.py
new file mode 100644
index 000000000..146b2e0f9
--- /dev/null
+++ b/reports/brpt/reports/templatetags/django_templating_sigh.py
@@ -0,0 +1,24 @@
+from django import template
+from brpt.reports.models import Client, Interaction, Bad, Modified, Extra
+
+register = template.Library()
+
+def set_interaction(parser, token):
+ try:
+ # Splitting by None == splitting by spaces.
+ tag_name, format_string = token.contents.split(None, 1)
+ except ValueError:
+ raise template.TemplateSyntaxError, "%r tag requires an argument" % token.contents[0]
+ if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
+ raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
+ return SetInteraction(format_string[1:-1])
+
+
+class SetInteraction(template.Node):
+ def __init__(self, times):
+ self.times = times#do soemthing to select different interaction with host
+ def render(self, context):
+ context['interaction'] = context['client'].interactions.order_by('-timestamp')[0]
+ return ''
+
+register.tag('set_interaction', set_interaction)
diff --git a/reports/brpt/reports/templatetags/django_templating_sigh.pyc b/reports/brpt/reports/templatetags/django_templating_sigh.pyc
new file mode 100644
index 000000000..f985dbdd8
--- /dev/null
+++ b/reports/brpt/reports/templatetags/django_templating_sigh.pyc
Binary files differ
diff --git a/reports/brpt/reports/views.py b/reports/brpt/reports/views.py
new file mode 100644
index 000000000..2edf43e1c
--- /dev/null
+++ b/reports/brpt/reports/views.py
@@ -0,0 +1,121 @@
+# Create your views here.
+#from django.shortcuts import get_object_or_404, render_to_response
+from django.template import Context, loader
+from django.http import HttpResponseRedirect, HttpResponse
+from django.shortcuts import render_to_response, get_object_or_404
+from brpt.reports.models import Client, Interaction, Bad, Modified, Extra
+from datetime import datetime
+
+
+def index(request):
+ return render_to_response('index.html')
+
+def client_index(request):
+ client_list = Client.objects.all().order_by('name')
+ return render_to_response('clients/index.html',{'client_list': client_list})
+
+def client_detail(request, hostname = -1, pk = -1):
+ #SETUP error pages for when you specify a client or interaction that doesn't exist
+ client = get_object_or_404(Client, name=hostname)
+ if(pk == -1):
+ interaction = client.interactions.order_by('-timestamp')[0]
+ else:
+ interaction = client.interactions.get(pk=pk)
+
+ return render_to_response('clients/detail.html',{'client': client, 'interaction': interaction})
+
+def display_sys_view(request):
+ client_lists = prepare_client_lists(request)
+ return render_to_response('displays/sys_view.html', client_lists)
+
+def display_summary(request):
+ client_lists = prepare_client_lists(request)
+ return render_to_response('displays/summary.html', client_lists)
+
+def display_timing(request):
+ #We're going to send a list of dictionaries. Each dictionary will be a row in the table
+ #+------+-------+----------------+-----------+---------+----------------+-------+
+ #| name | parse | probe download | inventory | install | cfg dl & parse | total |
+ #+------+-------+----------------+-----------+---------+----------------+-------+
+ client_list = Client.objects.all().order_by('-name')
+ stats_list = []
+ #if we have stats for a client, go ahead and add it to the list(wrap in TRY)
+ for client in client_list:#Go explicitly to an interaction ID! (new item in dictionary)
+ performance_items = client.interactions.order_by('-timestamp')[0].performance_items#allow this to be selectable(hist)
+ dict_unit = {}
+ try:
+ dict_unit["name"] = client.name #node name
+ except:
+ dict_unit["name"] = "n/a"
+ try:
+ dict_unit["parse"] = performance_items.get(metric="config_parse").value - performance_items.get(metric="config_download").value #parse
+ except:
+ dict_unit["parse"] = "n/a"
+ try:
+ dict_unit["probe"] = performance_items.get(metric="probe_upload").value - performance_items.get(metric="start").value #probe
+ except:
+ dict_unit["probe"] = "n/a"
+ try:
+ dict_unit["inventory"] = performance_items.get(metric="inventory").value - performance_items.get(metric="initialization").value #inventory
+ except:
+ dict_unit["inventory"] = "n/a"
+ try:
+ dict_unit["install"] = performance_items.get(metric="install").value - performance_items.get(metric="inventory").value #install
+ except:
+ dict_unit["install"] = "n/a"
+ try:
+ dict_unit["config"] = performance_items.get(metric="config_parse").value - performance_items.get(metric="probe_upload").value#config download & parse
+ except:
+ dict_unit["config"] = "n/a"
+ try:
+ dict_unit["total"] = performance_items.get(metric="finished").value - performance_items.get(metric="start").value #total
+ except:
+ dict_unit["total"] = "n/a"
+
+ #make sure all is formatted as such: #.##
+ stats_list.append(dict_unit)
+
+
+ return render_to_response('displays/timing.html',{'client_list': client_list, 'stats_list': stats_list})
+
+def display_index(request):
+ return render_to_response('displays/index.html')
+
+def prepare_client_lists(request):
+ client_list = Client.objects.all().order_by('name')#change this to order by interaction's state
+ clean_client_list = []
+ bad_client_list = []
+ extra_client_list = []
+ modified_client_list = []
+ stale_up_client_list = []
+ stale_all_client_list = []
+ down_client_list = []
+ for client in client_list:#but we need clientlist for more than just this loop
+ i = client.interactions.order_by('-timestamp')[0]
+# if i.state == 'good':
+ if i.isclean():
+ clean_client_list.append(client)
+ else:
+ bad_client_list.append(client)
+ if i.isstale():
+ if i.pingable:
+ stale_up_client_list.append(client)
+ stale_all_client_list.append(client)
+ else:
+ stale_all_client_list.append(client)
+ if not i.pingable:
+ down_client_list.append(client)
+ if len(i.modified_items.all()) > 0:
+ modified_client_list.append(client)
+ if len(i.extra_items.all()) > 0:
+ extra_client_list.append(client)
+
+ #if the list is empty set it to None?
+ return {'client_list': client_list,
+ 'clean_client_list': clean_client_list,
+ 'bad_client_list': bad_client_list,
+ 'extra_client_list': extra_client_list,
+ 'modified_client_list': modified_client_list,
+ 'stale_up_client_list': stale_up_client_list,
+ 'stale_all_client_list': stale_all_client_list,
+ 'down_client_list': down_client_list}
diff --git a/reports/brpt/reports/views.pyc b/reports/brpt/reports/views.pyc
new file mode 100644
index 000000000..0b163a1be
--- /dev/null
+++ b/reports/brpt/reports/views.pyc
Binary files differ
diff --git a/reports/brpt/settings.py b/reports/brpt/settings.py
new file mode 100644
index 000000000..bcdcdbd0b
--- /dev/null
+++ b/reports/brpt/settings.py
@@ -0,0 +1,75 @@
+# Django settings for brpt project.
+
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ ('Joey Hagedorn', 'hagedorn@mcs.anl.gov'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'sqlite3' # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
+DATABASE_NAME = '/Users/joey/anl-mcs/dev/bcfg2/reports/brpt-db' # Or path to database file if using sqlite3.
+DATABASE_USER = '' # Not used with sqlite3.
+DATABASE_PASSWORD = '' # Not used with sqlite3.
+DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
+
+# Local time zone for this installation. All choices can be found here:
+# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
+# http://blogs.law.harvard.edu/tech/stories/storyReader$15
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT.
+# Example: "http://media.lawrence.com"
+MEDIA_URL = ''
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'eb5+y%oy-qx*2+62vv=gtnnxg1yig_odu0se5$h0hh#pc*lmo7'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.middleware.doc.XViewMiddleware',
+)
+
+ROOT_URLCONF = 'brpt.urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates".
+ # Always use forward slashes, even on Windows.
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.admin',
+ 'brpt.reports'
+)
diff --git a/reports/brpt/settings.pyc b/reports/brpt/settings.pyc
new file mode 100644
index 000000000..393fe8e27
--- /dev/null
+++ b/reports/brpt/settings.pyc
Binary files differ
diff --git a/reports/brpt/urls.py b/reports/brpt/urls.py
new file mode 100644
index 000000000..3f284cc6c
--- /dev/null
+++ b/reports/brpt/urls.py
@@ -0,0 +1,30 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ # Example:
+ # (r'^brpt/', include('brpt.apps.foo.urls.foo')),
+ (r'^/*$','brpt.reports.views.index'),
+ (r'^clients/(?P<hostname>\S+)/(?P<pk>\d+)/$', 'brpt.reports.views.client_detail'),
+ (r'^clients/(?P<hostname>\S+)/$', 'brpt.reports.views.client_detail'),
+ (r'^clients/(?P<hostname>\S+)$', 'brpt.reports.views.client_detail'),
+ #hack because hostnames have periods and we still want to append slash
+ (r'^clients/$','brpt.reports.views.client_index'),
+
+ (r'^displays/sys-view/$','brpt.reports.views.display_sys_view'),
+ (r'^displays/summary/$','brpt.reports.views.display_summary'),
+ (r'^displays/timing/$','brpt.reports.views.display_timing'),
+ (r'^displays/$','brpt.reports.views.display_index'),
+
+ # Uncomment this for admin:
+ (r'^admin/', include('django.contrib.admin.urls')),
+
+
+
+
+ #Remove this when not doing DEVELOPMENT
+ #and i quote:
+ #Using this method is inefficient and insecure. Do not use this in a production setting. Use this only for development.
+ (r'^site_media/(.*)$', 'django.views.static.serve', {'document_root': '/Users/joey/anl-mcs/dev/bcfg2/reports/site_media/'}),
+
+
+)
diff --git a/reports/brpt/urls.pyc b/reports/brpt/urls.pyc
new file mode 100644
index 000000000..0bf6eff8b
--- /dev/null
+++ b/reports/brpt/urls.pyc
Binary files differ
diff --git a/reports/site_media/base.css b/reports/site_media/base.css
new file mode 100644
index 000000000..50ac79832
--- /dev/null
+++ b/reports/site_media/base.css
@@ -0,0 +1,5 @@
+
+/* Import other styles */
+@import url('global.css');
+@import url('layout.css');
+@import url('boxypastel.css); \ No newline at end of file
diff --git a/reports/site_media/boxypastel.css b/reports/site_media/boxypastel.css
new file mode 100644
index 000000000..896c8c428
--- /dev/null
+++ b/reports/site_media/boxypastel.css
@@ -0,0 +1,214 @@
+/* body */
+/*body {*/
+/* background-color: #fff;*/
+/* color: #000;*/
+/* font: 12px 'Lucida Grande', Arial, Helvetica, sans-serif;*/
+/* margin-left:25px;*/
+/* margin-right:100px;*/
+/* }*/
+
+
+
+/* links */
+a:link {
+ color: #00f;
+ text-decoration: none;
+ }
+a:visited {
+ color: #00a;
+ text-decoration: none;
+ }
+a:hover {
+ color: #00a;
+ text-decoration: underline;
+ }
+a:active {
+ color: #00a;
+ text-decoration: underline;
+ }
+/* divs*/
+div.bad {
+ border: 1px solid #660000;
+ background: #FF6A6A;
+ margin: 10px 0;
+ padding: 8px;
+ text-align: left;
+ margin-left:50px;
+ margin-right:50px;
+ }
+div.modified {
+ border: 1px solid #CC9900;
+ background: #FFEC8B;
+ margin: 10px 0;
+ padding: 8px;
+ text-align: left;
+ margin-left:50px;
+ margin-right:50px;
+ }
+div.clean {
+ border: 1px solid #006600;
+ background: #9AFF9A;
+ margin: 10px 0;
+ padding: 8px;
+ text-align: left;
+ margin-left:50px;
+ margin-right:50px;
+ }
+div.extra {
+ border: 1px solid #006600;
+ background: #6699CC;
+ margin: 10px 0;
+ padding: 8px;
+ text-align: left;
+ margin-left:50px;
+ margin-right:50px;
+ }
+div.warning {
+ border: 1px solid #CC3300;
+ background: #FF9933;
+ margin: 10px 0;
+ padding: 8px;
+ text-align: left;
+ margin-left:50px;
+ margin-right:50px;
+ }
+div.all-warning {
+ border: 1px solid #DD5544;
+ background: #FFD9A2;
+ margin: 10px 0;
+ padding: 8px;
+ text-align: left;
+ margin-left:50px;
+ margin-right:50px;
+ }
+div.down {
+ border: 1px solid #999;
+ background-color: #DDD;
+ margin: 10px 0;
+ padding: 8px;
+ text-align: left;
+ margin-left:50px;
+ margin-right:50px;
+ }
+div.items {
+ display: none;
+ }
+div.nodebox {
+ border: 1px solid #c7cfd5;
+ background: #f1f5f9;
+ margin: 20px 0;
+ padding: 8px 8px 16px 8px;
+ text-align: left;
+ position:relative;
+ }
+div.header {
+ background-color: #DDD;
+ padding: 8px;
+ text-indent:50px;
+ position:relative;
+ }
+/*Divs For Statusbar*/
+div.redbar {
+ border: 0px solid #660000;
+ background: #FF6666;
+ margin: 0px;
+ float: left;
+ }
+
+div.greenbar {
+ border: 0px solid #006600;
+ background: #66FF66;
+ margin: 0px;
+ float: left;
+ }
+div.statusborder {
+ border: 1px solid #000000;
+ background: #FF6666;
+ margin: 0px;
+ float: right;
+ width: 100%;
+ }
+ /*invisitable*/
+table.invisitable {
+ width: 100%;
+ border: 0px;
+ cell-padding: 0px;
+ padding: 0px;
+ border-width: 0px;
+ }
+/*Spans*/
+span.nodename {
+ font-style: italic;
+ }
+span.nodelisttitle {
+ font-size: 14px;
+ }
+span.mini-date {
+ font-size: 10px;
+ position: absolute;
+ right: 65px;
+ }
+
+h2 {
+ font-size: 16px;
+ color: #000;
+ }
+
+ul.plain {
+ list-style-type:none;
+ text-align: left;
+ }
+
+.notebox {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ padding: 1px;
+ text-indent:0px;
+ border: 1px solid #FFF;
+ background: #999;
+ color: #FFF;
+ }
+
+.configbox {
+ position: absolute;
+ bottom: 0px;
+ right: 0px;
+ padding: 1px;
+ text-indent:0px;
+ border: 1px solid #999;
+ background: #FFF;
+ color: #999;
+ }
+
+p.indented{
+ text-indent: 50px
+ }
+
+/* Sortable tables */
+table.sortable a.sortheader {
+ background-color:#dfd;
+ font-weight: bold;
+ text-decoration: none;
+ display: block;
+
+}
+table.sortable {
+ padding: 2px 4px 2px 4px;
+ border: 1px solid #000000;
+ border-spacing: 0px
+}
+td.sortable{
+ padding: 2px 8px 2px 8px;
+}
+
+th.sortable{
+ background-color:#F3DD91;
+ border: 1px solid #FFFFFF;
+}
+tr.tablelist {
+ background-color:#EDF3FE;
+}
+tr.tablelist-alt{
+ background-color:#FFFFFF;
+}
diff --git a/reports/site_media/global.css b/reports/site_media/global.css
new file mode 100644
index 000000000..1dc57f3ca
--- /dev/null
+++ b/reports/site_media/global.css
@@ -0,0 +1,9 @@
+
+body {
+ margin:0;
+ padding:0;
+ font-size:12px;
+ font-family:"Lucida Grande","Bitstream Vera Sans",Verdana,Arial,sans-serif;
+ color:#000;
+ background:#fff;
+ }
diff --git a/reports/site_media/layout.css b/reports/site_media/layout.css
new file mode 100644
index 000000000..f6f08098c
--- /dev/null
+++ b/reports/site_media/layout.css
@@ -0,0 +1,30 @@
+/* Page Structure */
+#container { position:absolute; top: 3em; margin-left:1em; margin-right:2em; padding:0; margin-top:1.5em; min-width: 650px; }
+#header { width:100%; }
+#content-main { float:left; }
+
+/* HEADER */
+#header { background:#000; color:#ffc; position:absolute;}
+#header a:link, #header a:visited { color:white; }
+#header a:hover { text-decoration:underline; }
+#branding h1 { padding:0 10px; font-size:18px; margin:8px 0; font-weight:normal; color:#f4f379; }
+#branding h2 { padding:0 10px; font-size:14px; margin:-8px 0 8px 0; font-weight:normal; color:#ffc; }
+#user-tools { position:absolute; top:0; right:0; padding:1.2em 10px; font-size:11px; text-align:right; }
+
+/*SIDEBAR*/
+#sidebar {float: left; position: relative; width: auto; height: 100%; margin-top: 3em; padding-right: 1.5em; padding-left: 1.5em; padding-top: 1em; padding-bottom:3em; background: #000; color:ffc; }
+a.sidebar:link {color: #fff;}
+a.sidebar:active {color: #fff;}
+a.sidebar:visited {color: #fff;}
+a.sidebar:hover {color: #fff;}
+ul.sidebar {
+ color: #ffc;
+ text-decoration: none;
+ list-style-type: none;
+ text-indent: -1em;
+}
+ul.sidebar-level2 {
+ text-indent: -2em;
+ list-style-type: none;
+ font-size: 11px;
+} \ No newline at end of file
diff --git a/reports/site_media/main.js b/reports/site_media/main.js
new file mode 100644
index 000000000..556130466
--- /dev/null
+++ b/reports/site_media/main.js
@@ -0,0 +1,27 @@
+function toggleLayer(whichLayer)
+ {
+ if (document.getElementById)
+ {
+ // this is the way the standards work
+ var style2 = document.getElementById(whichLayer).style;
+ style2.display = style2.display? "":"block";
+ }
+ else if (document.all)
+ {
+ // this is the way old msie versions work
+ var style2 = document.all[whichLayer].style;
+ style2.display = style2.display? "":"block";
+ }
+ else if (document.layers)
+ {
+ // this is the way nn4 works
+ var style2 = document.layers[whichLayer].style;
+ style2.display = style2.display? "":"block";
+ }
+ }
+
+function MM_jumpMenu(targ,selObj,restore)
+{ //v3.0
+ eval(targ+".location='"+selObj.options[selObj.selectedIndex].value+"'");
+ if (restore) selObj.selectedIndex=0;
+} \ No newline at end of file
diff --git a/reports/site_media/sorttable.js b/reports/site_media/sorttable.js
new file mode 100644
index 000000000..83c87708e
--- /dev/null
+++ b/reports/site_media/sorttable.js
@@ -0,0 +1,203 @@
+
+//
+// Sourced originally from:
+//
+// http://www.kryogenix.org/code/browser/sorttable/
+
+
+
+addEvent(window, "load", sortables_init);
+
+var SORT_COLUMN_INDEX;
+
+function sortables_init() {
+ // Find all tables with class sortable and make them sortable
+ if (!document.getElementsByTagName) return;
+ tbls = document.getElementsByTagName("table");
+ for (ti=0;ti<tbls.length;ti++) {
+ thisTbl = tbls[ti];
+ if (((' '+thisTbl.className+' ').indexOf("sortable") != -1) && (thisTbl.id)) {
+ //initTable(thisTbl.id);
+ ts_makeSortable(thisTbl);
+ }
+ }
+}
+
+function ts_makeSortable(table) {
+ if (table.rows && table.rows.length > 0) {
+ var firstRow = table.rows[0];
+ }
+ if (!firstRow) return;
+
+ // Assign classes to the rows when the table's first loaded
+ for (i=1;i<table.rows.length;i++) {
+ if (i%2 == 0) table.rows[i].className='tablelist';
+ else table.rows[i].className='tablelist tablelist-alt';
+ }
+
+ // We have a first row: assume it's the header, and make its contents clickable links
+ for (var i=0;i<firstRow.cells.length;i++) {
+ var cell = firstRow.cells[i];
+ var txt = ts_getInnerText(cell);
+ cell.innerHTML = '<a href="#" onclick="ts_resortTable(this, '+i+');return false;">'+txt+'<span class="sortarrow"></span></a>';
+ }
+}
+
+function ts_getInnerText(el) {
+ if (typeof el == "string") return el;
+ if (typeof el == "undefined") { return el };
+ if (el.innerText) return el.innerText; //Not needed but it is faster
+ var str = "";
+
+ var cs = el.childNodes;
+ var l = cs.length;
+ for (var i = 0; i < l; i++) {
+ switch (cs[i].nodeType) {
+ case 1: //ELEMENT_NODE
+ str += ts_getInnerText(cs[i]);
+ break;
+ case 3: //TEXT_NODE
+ str += cs[i].nodeValue;
+ break;
+ }
+ }
+ return str;
+}
+
+function ts_resortTable(lnk, clid) {
+ // get the span
+ var span;
+ for (var ci=0;ci<lnk.childNodes.length;ci++) {
+ if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
+ }
+ var spantext = ts_getInnerText(span);
+ var td = lnk.parentNode;
+ var column = clid || td.cellIndex;
+ var table = getParent(td,'TABLE');
+
+ // Work out a type for the column
+ if (table.rows.length <= 1) return;
+ var itm = ts_getInnerText(table.rows[1].cells[column]);
+ sortfn = ts_sort_caseinsensitive;
+ if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) sortfn = ts_sort_date;
+ if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) sortfn = ts_sort_date;
+
+ if (itm.match(/^[\d\.]+$/)) sortfn = ts_sort_numeric;
+ SORT_COLUMN_INDEX = column;
+ var firstRow = new Array();
+ var newRows = new Array();
+ for (i=0;i<table.rows[0].length;i++) { firstRow[i] = table.rows[0][i]; }
+ for (j=1;j<table.rows.length;j++) { newRows[j-1] = table.rows[j]; }
+
+ newRows.sort(sortfn);
+
+ if (span.getAttribute("sortdir") == 'down') {
+ ARROW = '';
+ newRows.reverse();
+ span.setAttribute('sortdir','up');
+ } else {
+ ARROW = '';
+ span.setAttribute('sortdir','down');
+ }
+
+ // Assign updated classes to the rows when the sort's finished
+ for (i=0;i<newRows.length;i++) {
+ if (i%2 == 1) newRows[i].className='tablelist';
+ else newRows[i].className='tablelist tablelist-alt';
+ }
+
+ // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
+ // don't do sortbottom rows
+ for (i=0;i<newRows.length;i++) { if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) table.tBodies[0].appendChild(newRows[i]);}
+ // do sortbottom rows only
+ for (i=0;i<newRows.length;i++) { if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1)) table.tBodies[0].appendChild(newRows[i]);}
+
+ // Delete any other arrows there may be showing
+ var allspans = document.getElementsByTagName("span");
+ for (var ci=0;ci<allspans.length;ci++) {
+ if (allspans[ci].className == 'sortarrow') {
+ if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
+ allspans[ci].innerHTML = '';
+ }
+ }
+ }
+
+ span.innerHTML = ARROW;
+}
+
+function getParent(el, pTagName) {
+ if (el == null) return null;
+ else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase()) // Gecko bug, supposed to be uppercase
+ return el;
+ else
+ return getParent(el.parentNode, pTagName);
+}
+function ts_sort_date(a,b) {
+ // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
+ aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
+ bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
+ if (aa.length == 10) {
+ dt1 = aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);
+ } else {
+ yr = aa.substr(6,2);
+ if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
+ dt1 = yr+aa.substr(3,2)+aa.substr(0,2);
+ }
+ if (bb.length == 10) {
+ dt2 = bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);
+ } else {
+ yr = bb.substr(6,2);
+ if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
+ dt2 = yr+bb.substr(3,2)+bb.substr(0,2);
+ }
+ if (dt1==dt2) return 0;
+ if (dt1<dt2) return -1;
+ return 1;
+}
+
+function ts_sort_currency(a,b) {
+ aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
+ bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
+ return parseFloat(aa) - parseFloat(bb);
+}
+
+function ts_sort_numeric(a,b) {
+ aa = parseFloat(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
+ if (isNaN(aa)) aa = 0;
+ bb = parseFloat(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));
+ if (isNaN(bb)) bb = 0;
+ return aa-bb;
+}
+
+function ts_sort_caseinsensitive(a,b) {
+ aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).toLowerCase();
+ bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).toLowerCase();
+ if (aa==bb) return 0;
+ if (aa<bb) return -1;
+ return 1;
+}
+
+function ts_sort_default(a,b) {
+ aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
+ bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
+ if (aa==bb) return 0;
+ if (aa<bb) return -1;
+ return 1;
+}
+
+
+function addEvent(elm, evType, fn, useCapture)
+// addEvent and removeEvent
+// cross-browser event handling for IE5+, NS6 and Mozilla
+// By Scott Andrew
+{
+ if (elm.addEventListener){
+ elm.addEventListener(evType, fn, useCapture);
+ return true;
+ } else if (elm.attachEvent){
+ var r = elm.attachEvent("on"+evType, fn);
+ return r;
+ } else {
+ alert("Handler could not be removed");
+ }
+}