From c57c79a36f1e4d2b66ef7f01bc0a82ef7d5996e8 Mon Sep 17 00:00:00 2001 From: Joey Hagedorn Date: Wed, 22 Nov 2006 21:18:05 +0000 Subject: finishing move git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@2521 ce84e21b-d406-0410-9b95-82705330c041 --- src/lib/Server/Reports/reports/__init__.py | 0 src/lib/Server/Reports/reports/models.py | 236 +++++++++++++++++++ src/lib/Server/Reports/reports/sql/client.sql | 1 + src/lib/Server/Reports/reports/templates/base.html | 45 ++++ .../reports/templates/clients/client-nodebox.html | 63 +++++ .../Reports/reports/templates/clients/detail.html | 14 ++ .../Reports/reports/templates/clients/index.html | 36 +++ .../reports/templates/config_items/index.html | 77 +++++++ .../Reports/reports/templates/displays/index.html | 18 ++ .../displays/summary-block-direct-links.html | 7 + .../reports/templates/displays/summary-block.html | 90 ++++++++ .../reports/templates/displays/summary.html | 29 +++ .../reports/templates/displays/sys_view.html | 20 ++ .../Reports/reports/templates/displays/timing.html | 52 +++++ .../Server/Reports/reports/templates/index.html | 15 ++ .../Reports/reports/templatetags/__init__.py | 0 .../reports/templatetags/django_templating_sigh.py | 40 ++++ src/lib/Server/Reports/reports/views.py | 255 +++++++++++++++++++++ 18 files changed, 998 insertions(+) create mode 100644 src/lib/Server/Reports/reports/__init__.py create mode 100644 src/lib/Server/Reports/reports/models.py create mode 100644 src/lib/Server/Reports/reports/sql/client.sql create mode 100644 src/lib/Server/Reports/reports/templates/base.html create mode 100644 src/lib/Server/Reports/reports/templates/clients/client-nodebox.html create mode 100644 src/lib/Server/Reports/reports/templates/clients/detail.html create mode 100644 src/lib/Server/Reports/reports/templates/clients/index.html create mode 100644 src/lib/Server/Reports/reports/templates/config_items/index.html create mode 100644 src/lib/Server/Reports/reports/templates/displays/index.html create mode 100644 src/lib/Server/Reports/reports/templates/displays/summary-block-direct-links.html create mode 100644 src/lib/Server/Reports/reports/templates/displays/summary-block.html create mode 100644 src/lib/Server/Reports/reports/templates/displays/summary.html create mode 100644 src/lib/Server/Reports/reports/templates/displays/sys_view.html create mode 100644 src/lib/Server/Reports/reports/templates/displays/timing.html create mode 100644 src/lib/Server/Reports/reports/templates/index.html create mode 100644 src/lib/Server/Reports/reports/templatetags/__init__.py create mode 100644 src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py create mode 100644 src/lib/Server/Reports/reports/views.py (limited to 'src/lib/Server/Reports/reports') diff --git a/src/lib/Server/Reports/reports/__init__.py b/src/lib/Server/Reports/reports/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/lib/Server/Reports/reports/models.py b/src/lib/Server/Reports/reports/models.py new file mode 100644 index 000000000..dff68d272 --- /dev/null +++ b/src/lib/Server/Reports/reports/models.py @@ -0,0 +1,236 @@ +'''Django models for BCFG reports''' +from django.db import models +from datetime import datetime, timedelta + +KIND_CHOICES = ( + #These are the kinds of config elements + ('ConfigFile', 'ConfigFile'), + ('Package', 'Package'), + ('Service', 'Service'), + ('SymLink', 'SymLink'), + ('Directory', 'Directory'), + ('Permissions','Permissions'), +) +PING_CHOICES = ( + #These are possible ping states + ('Up (Y)', 'Y'), + ('Down (N)', 'N') +) +class Client(models.Model): + '''object representing every client we have seen stats for''' + creation = models.DateTimeField() + name = models.CharField(maxlength=128, core=True) + current_interaction = models.ForeignKey('Interaction', + null=True, blank=True, + related_name="parent_client") + expiration = models.DateTimeField(blank=True, null=True) + + def __str__(self): + return self.name + + class Admin: + pass + +class Metadata(models.Model): + '''insert magical interface to client metadata here''' + client = models.ForeignKey(Client) + timestamp = models.DateTimeField() + def __str__(self): + return self.timestamp + +class Repository(models.Model): + '''insert magical interface to subversioned repository here''' + timestamp = models.DateTimeField() + def __str__(self): + return self.timestamp + +class Ping(models.Model): + '''represents a ping of a client (sparsely)''' + client = models.ForeignKey(Client, related_name="pings") + starttime = models.DateTimeField() + endtime = models.DateTimeField() + status = models.CharField(maxlength=4, choices=PING_CHOICES)#up/down + + class Meta: + get_latest_by = 'endtime' + +class InteractiveManager(models.Manager): + '''manages interactions objects''' + + '''returns most recent interaction as of specified timestamp in format: + '2006-01-01 00:00:00' or 'now' or None->'now' ''' + def interaction_per_client(self, maxdate = None): + from django.db import connection + cursor = connection.cursor() + + #in order to prevent traceback when zero records are returned. + #this could mask some database errors + try: + if (maxdate == 'now' or maxdate == None): + cursor.execute("select reports_interaction.id, x.client_id from (select client_id, MAX(timestamp) "+ + "as timer from reports_interaction GROUP BY client_id) x, reports_interaction where "+ + "reports_interaction.client_id = x.client_id AND reports_interaction.timestamp = x.timer") + else: + cursor.execute("select reports_interaction.id, x.client_id from (select client_id, timestamp, MAX(timestamp) "+ + "as timer from reports_interaction WHERE timestamp < %s GROUP BY client_id) x, reports_interaction where "+ + "reports_interaction.client_id = x.client_id AND reports_interaction.timestamp = x.timer", + [maxdate]) + in_idents = [item[0] for item in cursor.fetchall()] + except: + in_idents = [] + return self.filter(id__in = in_idents) + + +class Interaction(models.Model): + '''Models each reconfiguration operation interaction between client and server''' + 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()#repo revision at time of interaction + client_version = models.CharField(maxlength=32)#Client Version + goodcount = models.IntegerField()#of good config-items + totalcount = models.IntegerField()#of total config-items + + def __str__(self): + return "With " + self.client.name + " @ " + self.timestamp.isoformat() + + def percentgood(self): + if not self.totalcount == 0: + return (self.goodcount/float(self.totalcount))*100 + else: + return 0 + + def percentbad(self): + if not self.totalcount == 0: + return ((self.totalcount-self.goodcount)/(float(self.totalcount)))*100 + else: + return 0 + + def isclean(self): + if (self.bad_items.count() == 0 and self.goodcount == self.totalcount): + #if (self.state == "good"): + return True + else: + return False + + def isstale(self): + if (self == self.client.current_interaction):#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 + def save(self): + super(Interaction, self).save() #call the real save... + self.client.current_interaction = self.client.interactions.latest() + self.client.save()#save again post update + + objects = InteractiveManager() + + class Admin: + list_display = ('client', 'timestamp', 'state') + list_filter = ['client', 'timestamp'] + pass + class Meta: + get_latest_by = 'timestamp' + +class Reason(models.Model): + '''reason why modified or bad entry did not verify, or changed''' + owner = models.TextField(maxlength=128, blank=True) + current_owner = models.TextField(maxlength=128, blank=True) + group = models.TextField(maxlength=128, blank=True) + current_group = models.TextField(maxlength=128, blank=True) + perms = models.TextField(maxlength=4, blank=True)#txt fixes typing issue + current_perms = models.TextField(maxlength=4, blank=True) + status = models.TextField(maxlength=3, blank=True)#on/off/(None) + current_status = models.TextField(maxlength=1, blank=True)#on/off/(None) + to = models.TextField(maxlength=256, blank=True) + current_to = models.TextField(maxlength=256, blank=True) + version = models.TextField(maxlength=128, blank=True) + current_version = models.TextField(maxlength=128, blank=True) + current_exists = models.BooleanField()#False means its missing. Default True + current_diff = models.TextField(maxlength=1280, blank=True) + def _str_(self): + return "Reason" + +class Modified(models.Model): + '''Modified configuration element''' + interactions = models.ManyToManyField(Interaction, related_name="modified_items") + name = models.CharField(maxlength=128, core=True) + kind = models.CharField(maxlength=16, choices=KIND_CHOICES) + critical = models.BooleanField() + reason = models.ForeignKey(Reason) + def __str__(self): + return self.name + +class Extra(models.Model): + '''Extra configuration element''' + interactions = models.ManyToManyField(Interaction, related_name="extra_items") + name = models.CharField(maxlength=128, core=True) + kind = models.CharField(maxlength=16, choices=KIND_CHOICES) + critical = models.BooleanField() + reason = models.ForeignKey(Reason) + def __str__(self): + return self.name + +class Bad(models.Model): + '''Bad configuration element''' + interactions = models.ManyToManyField(Interaction, related_name="bad_items") + name = models.CharField(maxlength=128, core=True) + kind = models.CharField(maxlength=16, choices=KIND_CHOICES) + critical = models.BooleanField() + reason = models.ForeignKey(Reason) + def __str__(self): + return self.name + +class PerformanceManager(models.Manager): + '''Provides ability to effectively query for performance information + It is possible this should move to the view''' + #Date format for maxdate: '2006-01-01 00:00:00' + def performance_per_client(self, maxdate = None): + from django.db import connection + cursor = connection.cursor() + if (maxdate == 'now' or maxdate == None): + cursor.execute("SELECT reports_client.name, reports_performance.metric, reports_performance.value "+ + "FROM reports_performance, reports_performance_interaction, reports_client WHERE ( "+ + "reports_client.current_interaction_id = reports_performance_interaction.interaction_id AND "+ + "reports_performance.id = reports_performance_interaction.performance_id)") + else: + cursor.execute("select reports_client.name, reports_performance.metric, "+ + "reports_performance.value from (Select reports_interaction.client_id as client_id, "+ + "MAX(reports_interaction.timestamp) as timestamp from reports_interaction where "+ + "timestamp < %s GROUP BY reports_interaction.client_id) x, reports_client, "+ + "reports_interaction, reports_performance, reports_performance_interaction where "+ + "reports_client.id = x.client_id AND x.timestamp = reports_interaction.timestamp AND "+ + "x.client_id = reports_interaction.client_id AND reports_performance.id = "+ + "reports_performance_interaction.performance_id AND "+ + "reports_performance_interaction.interaction_id = reports_interaction.id", [maxdate]) + + results = {} + for row in cursor.fetchall(): + try: + results[row[0]].__setitem__(row[1], row[2]) + except KeyError: + results[row[0]] = {row[1]:row[2]} + + return results + +#performance metrics, models a performance-metric-item +class Performance(models.Model): + '''Object representing performance data for any interaction''' + interaction = models.ManyToManyField(Interaction, related_name="performance_items") + metric = models.CharField(maxlength=128, core=True) + value = models.FloatField(max_digits=32, decimal_places=16) + def __str__(self): + return self.metric + + objects = PerformanceManager() + diff --git a/src/lib/Server/Reports/reports/sql/client.sql b/src/lib/Server/Reports/reports/sql/client.sql new file mode 100644 index 000000000..6ef37a27f --- /dev/null +++ b/src/lib/Server/Reports/reports/sql/client.sql @@ -0,0 +1 @@ +CREATE VIEW reports_current_interactions AS SELECT x.client_id AS client_id, reports_interaction.id AS interaction_id FROM (select client_id, MAX(timestamp) as timer FROM reports_interaction GROUP BY client_id) x, reports_interaction WHERE reports_interaction.client_id = x.client_id AND reports_interaction.timestamp = x.timer \ No newline at end of file diff --git a/src/lib/Server/Reports/reports/templates/base.html b/src/lib/Server/Reports/reports/templates/base.html new file mode 100644 index 000000000..1bee97206 --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/base.html @@ -0,0 +1,45 @@ + + + + {% block title %}Bcfg2 Reporting System{% endblock %} + + + + {% block extra_header_info %}{% endblock %} + + + + + + +
+
+ {% block pagebanner %}{% endblock %} + {% block content %}{% endblock %} + +
+
+ + \ No newline at end of file diff --git a/src/lib/Server/Reports/reports/templates/clients/client-nodebox.html b/src/lib/Server/Reports/reports/templates/clients/client-nodebox.html new file mode 100644 index 000000000..dade598cf --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/clients/client-nodebox.html @@ -0,0 +1,63 @@ +{% load django_templating_sigh %} +{% if client %} + +
+ Time Ran: {{interaction.timestamp}} + + + + + + +

Node: + {{client.name}}

+ {% if interaction.repo_revision %}Revision: {{interaction.repo_revision}}{% endif %} +
+
 
+
 
+
+
+ {% if interaction.isclean %} +
+ Node is clean; Everything has been satisfactorily configured. +
+ {% endif %} + {% if interaction.isstale %} +
+ This node did not run within the last 24 hours-- it may be out of date. +
+ {% endif %} + {% if interaction.bad_items.all %} +
+ {{interaction.bad_items.count}} items did not verify and are considered Dirty.
+
    + {% for bad in interaction.bad_items.all|sortwell %} +
  • {{bad.kind}}: {{bad.name}}
  • + {% endfor %} +
+
+ {% endif %} + {% if interaction.modified_items.all %} +
+ {{interaction.modified_items.count}} items were modified in the last run.
+
    + {% for modified in interaction.modified_items.all|sortwell %} +
  • {{modified.kind}}: {{modified.name}}
  • + {% endfor %} +
+
+ {% endif %} + {% if interaction.extra_items.all %} +
+ {{interaction.extra_items.count}} extra configuration elements on the node.
+
    + {% for extra in interaction.extra_items.all|sortwell %} +
  • {{extra.kind}}: {{extra.name}}
  • + {% endfor %} +
+
+ {% endif %} +
+{% else %} +

No record could be found for this client.

+{% endif %} diff --git a/src/lib/Server/Reports/reports/templates/clients/detail.html b/src/lib/Server/Reports/reports/templates/clients/detail.html new file mode 100644 index 000000000..4ac2123c1 --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/clients/detail.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block title %}Info for: {{client.name}}{% endblock %} + +{% block content %} +Select time: + + +{% include "clients/client-nodebox.html" %} +{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/clients/index.html b/src/lib/Server/Reports/reports/templates/clients/index.html new file mode 100644 index 000000000..9870e2942 --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/clients/index.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block title %}Client Index Listing{% endblock %} + +{% block pagebanner %} +
+

Clients List

+
+
+{% endblock %} + +{% block content %} +{% if client_list_a %} + +{% else %} +

No client records are available.

+{% endif %} +{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/config_items/index.html b/src/lib/Server/Reports/reports/templates/config_items/index.html new file mode 100644 index 000000000..952172715 --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/config_items/index.html @@ -0,0 +1,77 @@ +{% extends "base.html" %} + +{% block extra_header_info %} + + +{% endblock%} +{% block title %}Configuration Element Details{% endblock %} + +{% block pagebanner %} +
+

Configuration Element Details

+
+
+{% endblock %} + +{% block content %} +{% ifequal mod_or_bad "bad" %} +
+

Bad {{item.kind}}: {{item.name}}

+
+{% else %} +
+

Modified {{item.kind}}: {{item.name}}

+
+{% endifequal %} +
+
+ + +
    + {% if client_list_b %} + {% for client in client_list_b %} +
  • + {% endfor %} + {% endif %} +
+
+ +{% if item.reason.current_owner %} + +{% endif %}{% if item.reason.current_group %} + +{% endif %}{% if item.reason.current_perms %} + +{% endif %}{% if item.reason.current_status %} + +{% endif %}{% if item.reason.current_to %} + +{% endif %}{% if item.reason.current_version %} + +{% endif %}{% if not item.reason.current_exists %} + +{% endif %}{% if item.reason.current_diff %} + +{% endif %} +
ReasonCurrent StatusSpecified in BCFG
Owner: {{item.reason.owner}}{{item.reason.current_owner}}
Group: {{item.reason.group}}{{item.reason.current_group}}
Permissions: {{item.reason.perms}}{{item.reason.current_perms}}
Status: {{item.reason.status}}{{item.reason.current_status}}
Link Destination: {{item.reason.to}}{{item.reason.current_to}}
Version: {{item.reason.version}}{{item.reason.current_version}}
Existance: This item does not currently exist on the host but is specified to exist in the configuration.
Unified Diff:
{{item.reason.current_diff}}
+
+
+ +Enter date or use calendar popup: +
+@ + +Calendar + + | +
+



+{% if associated_client_list %} +

The following clients had this problem as of {{timestamp_date}}@{{timestamp_time}}:

+ {% for client in associated_client_list %} + {{client.name}}
+ {% endfor %} +
+
+{% else %} +

No Clients had this problem at {{timestamp}}

+{% endif %} + + + + + + + +{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/displays/index.html b/src/lib/Server/Reports/reports/templates/displays/index.html new file mode 100644 index 000000000..5d1d3bf76 --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/displays/index.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %}Display Index Listing{% endblock %} +{% block pagebanner %} +
+

BCFG Display Index

+ {% comment %} Report Run @ {% now "F j, Y P"%}{% endcomment %} +
+
+{% endblock %} + +{% block content %} + +{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/displays/summary-block-direct-links.html b/src/lib/Server/Reports/reports/templates/displays/summary-block-direct-links.html new file mode 100644 index 000000000..a218e12b6 --- /dev/null +++ b/src/lib/Server/Reports/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/src/lib/Server/Reports/reports/templates/displays/summary-block.html b/src/lib/Server/Reports/reports/templates/displays/summary-block.html new file mode 100644 index 000000000..a42176183 --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/displays/summary-block.html @@ -0,0 +1,90 @@ +{% load django_templating_sigh %} + +
+

Summary:

+

{{client_list|length }} Nodes were included in your report.

+ {% if clean_client_list %} +
+ {{clean_client_list|length}} nodes are clean.
+
    + {% for client in clean_client_list|sortname %} + {% set_interaction "foo" %} +
  • Node: + {{client.name}}{{interaction.timestamp}}
  • + {% endfor %} +
+
+ {% endif %} + {% if bad_client_list %} +
+ {{bad_client_list|length}} nodes are bad.
+
    + {% for client in bad_client_list|sortname %} + {% set_interaction "foo" %} +
  • Node: + {{client.name}}{{interaction.timestamp}}
  • + {% endfor %} +
+
+ {% endif %} + {% if modified_client_list %} +
+ {{modified_client_list|length}} nodes were modified in the previous run.
+
    + {% for client in modified_client_list|sortname %} + {% set_interaction "foo" %} +
  • Node: + {{client.name}}{{interaction.timestamp}}
  • + {% endfor %} +
+
+ {% endif %} + {% if extra_client_list %} +
+ {{extra_client_list|length}} nodes have extra configuration. (includes both good and bad nodes)
+
    + {% for client in extra_client_list|sortname %} + {% set_interaction "foo" %} +
  • Node: + {{client.name}}{{interaction.timestamp}}
  • + {% endfor %} +
+
+ {% endif %} + {% if stale_up_client_list %} +
+ {{stale_up_client_list|length}} nodes did not run within the last 24 hours but were pingable.
+
    + {% for client in stale_up_client_list|sortname %} + {% set_interaction "foo" %} +
  • Node: + {{client.name}}{{interaction.timestamp}}
  • + {% endfor %} +
+
+ {% endif %} + {% if stale_all_client_list %} +
+ {{stale_all_client_list|length}} nodes did not run within the last 24 hours. (includes nodes up and down)
+
    + {% for client in stale_all_client_list|sortname %} + {% set_interaction "foo" %} +
  • Node: + {{client.name}}{{interaction.timestamp}}
  • + {% endfor %} +
+
+ {% endif %} + {% if down_client_list %} +
+ {{down_client_list|length}} nodes were down.
+
    + {% for client in down_client_list|sortname %} + {% set_interaction "foo" %} +
  • Node: + {{client.name}}{{interaction.timestamp}}
  • + {% endfor %} +
+
+ {% endif %} +
\ No newline at end of file diff --git a/src/lib/Server/Reports/reports/templates/displays/summary.html b/src/lib/Server/Reports/reports/templates/displays/summary.html new file mode 100644 index 000000000..cf253c25c --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/displays/summary.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} +{% block extra_header_info %} + + +{% endblock%} +{% block title %}Display Index Listing{% endblock %} +{% block pagebanner %} +
+

BCFG Clients Summary

+ Report Run @ {% now "F j, Y P"%} +
+
+{% endblock %} + +{% block content %} +
+ +Enter date or use calendar popup: +
+@ + +Calendar + + | +
+



+ {% include "displays/summary-block-direct-links.html" %} +{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/displays/sys_view.html b/src/lib/Server/Reports/reports/templates/displays/sys_view.html new file mode 100644 index 000000000..1298059bf --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/displays/sys_view.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} +{% load django_templating_sigh %} + +{% block title %}System-View Display{% endblock %} +{% block pagebanner %} +
+

Grand System View

+ Report Run @ {% now "F j, Y P"%} +
+
+{% endblock %} +{% block content %} +

This view is deprecated and will be removed soon.


Please use the "Summary" view and drill down instead.
+ + {% include "displays/summary-block.html" %} + {% for client in client_list %} + {% set_interaction "foo" %} + {% include "clients/client-nodebox.html" %} + {% endfor %} +{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/displays/timing.html b/src/lib/Server/Reports/reports/templates/displays/timing.html new file mode 100644 index 000000000..e9020b8ef --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/displays/timing.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} + +{% block extra_header_info %} + + + +{% endblock%} +{% block title %}Display Index Listing{% endblock %} + +{% block content %} +
+

BCFG Performance Timings

+ Report Run @ {% now "F j, Y P"%} +
+
+
+ +Enter date or use calendar popup: +
+@ + +Calendar + + | +
+



+
+ + + + + + + + + + + {% for dict_unit in stats_list %} + + + + + + + + + + {% endfor %} +
HostnameParseProbeInventoryInstallConfigTotal
{{dict_unit.name}}{{dict_unit.parse}}{{dict_unit.probe}}{{dict_unit.inventory}}{{dict_unit.install}}{{dict_unit.config}}{{dict_unit.total}}
+
+{% endblock %} \ No newline at end of file diff --git a/src/lib/Server/Reports/reports/templates/index.html b/src/lib/Server/Reports/reports/templates/index.html new file mode 100644 index 000000000..002a3f770 --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/index.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block pagebanner %} +
+

BCFG Reports

+ {% comment %} Report Run @ {% now "F j, Y P"%}{% endcomment %} +
+
+{% endblock %} +{% block content %} +

Welcome to the Bcfg2 Reporting System

+

+Please use the links at the left to navigate. +

+{% endblock %} diff --git a/src/lib/Server/Reports/reports/templatetags/__init__.py b/src/lib/Server/Reports/reports/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py b/src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py new file mode 100644 index 000000000..85f4d61a2 --- /dev/null +++ b/src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py @@ -0,0 +1,40 @@ +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]) + +def sortwell(value): + "sorts a list(or evaluates queryset to list) of bad, extra, or modified items in the best way for presentation" + configItems = list(value) + configItems.sort(lambda x,y: cmp(x.name, y.name)) + configItems.sort(lambda x,y: cmp(x.kind, y.kind)) + return configItems +def sortname(value): + "sorts a list( or evaluates queryset) by name" + configItems = list(value) + configItems.sort(lambda x,y: cmp(x.name, y.name)) + return configItems + +class SetInteraction(template.Node): + def __init__(self, times): + self.times = times#do soemthing to select different interaction with host? + def render(self, context): + try: + context['interaction'] = context['client_interaction_dict'][context['client'].id] + except:#I don't fully know what the implications of this are. + pass + return '' + +register.tag('set_interaction', set_interaction) +register.filter('sortwell', sortwell) +register.filter('sortname', sortname) diff --git a/src/lib/Server/Reports/reports/views.py b/src/lib/Server/Reports/reports/views.py new file mode 100644 index 000000000..44c858a82 --- /dev/null +++ b/src/lib/Server/Reports/reports/views.py @@ -0,0 +1,255 @@ +# 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, Performance, Reason +from datetime import datetime, timedelta +from time import strptime +from django.db import connection +from django.db.backends import util +from django.contrib.auth.decorators import login_required + +def index(request): + return render_to_response('index.html') + +def config_item_modified(request, eyedee =None, timestamp = 'now'): + #if eyedee = None, dump with a 404 + timestamp = timestamp.replace("@"," ") + mod_or_bad = "modified" + + item = Modified.objects.get(id=eyedee) + #if everything is blank except current_exists, do something special + cursor = connection.cursor() + if timestamp == 'now': + cursor.execute("select client_id from reports_interaction, reports_modified_interactions, reports_client "+ + "WHERE reports_client.current_interaction_id = reports_modified_interactions.interaction_id "+ + "AND reports_modified_interactions.interaction_id = reports_interaction.id "+ + "AND reports_modified_interactions.modified_id = %s", [eyedee]) + associated_client_list = Client.objects.filter(id__in=[x[0] for x in cursor.fetchall()]) + else: + interact_queryset = Interaction.objects.interaction_per_client(timestamp) + interactionlist = [] + [interactionlist.append(x.id) for x in interact_queryset] + if not interactionlist == []: + cursor.execute("select client_id from reports_interaction, reports_modified_interactions, reports_client "+ + "WHERE reports_modified_interactions.interaction_id IN %s "+ + "AND reports_modified_interactions.interaction_id = reports_interaction.id "+ + "AND reports_modified_interactions.modified_id = %s", [interactionlist, eyedee]) + associated_client_list = Client.objects.filter(id__in=[x[0] for x in cursor.fetchall()]) + else: + associated_client_list = [] + + if timestamp == 'now': + timestamp = datetime.now().isoformat('@') + + return render_to_response('config_items/index.html', {'item':item, + 'mod_or_bad':mod_or_bad, + 'associated_client_list':associated_client_list, + 'timestamp' : timestamp, + 'timestamp_date' : timestamp[:10], + 'timestamp_time' : timestamp[11:19]}) + + +def config_item_bad(request, eyedee = None, timestamp = 'now'): + timestamp = timestamp.replace("@"," ") + mod_or_bad = "bad" + item = Bad.objects.get(id=eyedee) + cursor = connection.cursor() + if timestamp == 'now': + cursor.execute("select client_id from reports_interaction, reports_bad_interactions, reports_client "+ + "WHERE reports_client.current_interaction_id = reports_bad_interactions.interaction_id "+ + "AND reports_bad_interactions.interaction_id = reports_interaction.id "+ + "AND reports_bad_interactions.bad_id = %s", [eyedee]) + associated_client_list = Client.objects.filter(id__in=[x[0] for x in cursor.fetchall()]) + else: + interact_queryset = Interaction.objects.interaction_per_client(timestamp) + interactionlist = [] + [interactionlist.append(x.id) for x in interact_queryset] + if not interactionlist == []: + cursor.execute("SELECT DISTINCT client_id from reports_interaction, reports_bad_interactions, reports_client "+ + "WHERE reports_bad_interactions.interaction_id IN %s "+ + "AND reports_bad_interactions.interaction_id = reports_interaction.id "+ + "AND reports_bad_interactions.bad_id = %s", [interactionlist, eyedee]) + associated_client_list = Client.objects.filter(id__in=[x[0] for x in cursor.fetchall()]) + else: + associated_client_list = None + + if timestamp == 'now': + timestamp = datetime.now().isoformat('@') + + return render_to_response('config_items/index.html', {'item':item, + 'mod_or_bad':mod_or_bad, + 'associated_client_list':associated_client_list, + 'timestamp' : timestamp, + 'timestamp_date' : timestamp[:10], + 'timestamp_time' : timestamp[11:19]}) + + + +def client_index(request): + client_list = Client.objects.all().order_by('name') + client_list_a = client_list[:len(client_list)/2] + client_list_b = client_list[len(client_list)/2:] + return render_to_response('clients/index.html', {'client_list_a': client_list_a, + 'client_list_b': client_list_b}) + +def client_detail(request, hostname = None, pk = None): + #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 == None): + interaction = client.current_interaction + else: + interaction = client.interactions.get(pk=pk)#can this be a get object or 404? + return render_to_response('clients/detail.html', {'client': client, 'interaction': interaction}) + +def display_sys_view(request, timestamp = 'now'): + client_lists = prepare_client_lists(request, timestamp) + return render_to_response('displays/sys_view.html', client_lists) + +def display_summary(request, timestamp = 'now'): + + client_lists = prepare_client_lists(request, timestamp) + #this returns timestamp and the timestamp parts too + return render_to_response('displays/summary.html', client_lists) + +def display_timing(request, timestamp = 'now'): + #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 not timestamp == 'now': + results = Performance.objects.performance_per_client(timestamp.replace("@"," ")) + else: + results = Performance.objects.performance_per_client() + timestamp = datetime.now().isoformat('@') + + for client in client_list:#Go explicitly to an interaction ID! (new item in dictionary) + try: + d = results[client.name] + except KeyError: + d = {} + + dict_unit = {} + try: + dict_unit["name"] = client.name #node name + except: + dict_unit["name"] = "n/a" + try: + dict_unit["parse"] = round(d["config_parse"] - d["config_download"], 4) #parse + except: + dict_unit["parse"] = "n/a" + try: + dict_unit["probe"] = round(d["probe_upload"] - d["start"], 4) #probe + except: + dict_unit["probe"] = "n/a" + try: + dict_unit["inventory"] = round(d["inventory"] - d["initialization"], 4) #inventory + except: + dict_unit["inventory"] = "n/a" + try: + dict_unit["install"] = round(d["install"] - d["inventory"], 4) #install + except: + dict_unit["install"] = "n/a" + try: + dict_unit["config"] = round(d["config_parse"] - d["probe_upload"], 4)#config download & parse + except: + dict_unit["config"] = "n/a" + try: + dict_unit["total"] = round(d["finished"] - d["start"], 4) #total + except: + dict_unit["total"] = "n/a" + + stats_list.append(dict_unit) + + return render_to_response('displays/timing.html', {'client_list': client_list, + 'stats_list': stats_list, + 'timestamp' : timestamp, + 'timestamp_date' : timestamp[:10], + 'timestamp_time' : timestamp[11:19]}) + +def display_index(request): + return render_to_response('displays/index.html') + +def prepare_client_lists(request, timestamp = 'now'): + timestamp = timestamp.replace("@"," ") + #client_list = Client.objects.all().order_by('name')#change this to order by interaction's state + client_interaction_dict = {} + clean_client_list = [] + bad_client_list = [] + extra_client_list = [] + modified_client_list = [] + stale_up_client_list = [] + #stale_all_client_list = [] + down_client_list = [] + + cursor = connection.cursor() + + interact_queryset = Interaction.objects.interaction_per_client(timestamp) + # or you can specify a time like this: '2007-01-01 00:00:00' + [client_interaction_dict.__setitem__(x.client_id,x) for x in interact_queryset] + client_list = Client.objects.filter(id__in=client_interaction_dict.keys()).order_by('name') + + [clean_client_list.append(x) for x in Client.objects.filter(id__in=[y.client_id for y in interact_queryset.filter(state='clean')])] + [bad_client_list.append(x) for x in Client.objects.filter(id__in=[y.client_id for y in interact_queryset.filter(state='dirty')])] + + client_ping_dict = {} + [client_ping_dict.__setitem__(x,'Y') for x in client_interaction_dict.keys()]#unless we know otherwise... + + try: + cursor.execute("select reports_ping.status, x.client_id from (select client_id, MAX(endtime) "+ + "as timer from reports_ping GROUP BY client_id) x, reports_ping where "+ + "reports_ping.client_id = x.client_id AND reports_ping.endtime = x.timer") + [client_ping_dict.__setitem__(x[1],x[0]) for x in cursor.fetchall()] + except: + pass #This is to fix problems when you have only zero records returned + + client_down_ids = [y for y in client_ping_dict.keys() if client_ping_dict[y]=='N'] + if not client_down_ids == []: + [down_client_list.append(x) for x in Client.objects.filter(id__in=client_down_ids)] + + if (timestamp == 'now' or timestamp == None): + cursor.execute("select client_id, MAX(timestamp) as timestamp from reports_interaction GROUP BY client_id") + results = cursor.fetchall() + for x in results: + if type(x[1]) == type(""): + x[1] = util.typecast_timestamp(x[1]) + stale_all_client_list = Client.objects.filter(id__in=[x[0] for x in results if datetime.now() - x[1] > timedelta(days=1)]) + else: + cursor.execute("select client_id, timestamp, MAX(timestamp) as timestamp from reports_interaction "+ + "WHERE timestamp < %s GROUP BY client_id", [timestamp]) + t = strptime(timestamp,"%Y-%m-%d %H:%M:%S") + datetimestamp = datetime(t[0], t[1], t[2], t[3], t[4], t[5]) + results = cursor.fetchall() + for x in results: + if type(x[1]) == type(""): + x[1] = util.typecast_timestamp(x[1]) + stale_all_client_list = Client.objects.filter(id__in=[x[0] for x in results if datetimestamp - x[1] > timedelta(days=1)]) + + [stale_up_client_list.append(x) for x in stale_all_client_list if not client_ping_dict[x.id]=='N'] + + + cursor.execute("SELECT reports_client.id FROM reports_client, reports_interaction, reports_modified_interactions WHERE reports_client.id=reports_interaction.client_id AND reports_interaction.id = reports_modified_interactions.interaction_id GROUP BY reports_client.id") + modified_client_list = Client.objects.filter(id__in=[x[0] for x in cursor.fetchall()]) + + cursor.execute("SELECT reports_client.id FROM reports_client, reports_interaction, reports_extra_interactions WHERE reports_client.id=reports_interaction.client_id AND reports_interaction.id = reports_extra_interactions.interaction_id GROUP BY reports_client.id") + extra_client_list = Client.objects.filter(id__in=[x[0] for x in cursor.fetchall()]) + + if timestamp == 'now': + timestamp = datetime.now().isoformat('@') + + return {'client_list': client_list, + 'client_interaction_dict':client_interaction_dict, + '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, + 'timestamp' : timestamp, + 'timestamp_date' : timestamp[:10], + 'timestamp_time' : timestamp[11:19]} -- cgit v1.2.3-1-g7c22