summaryrefslogtreecommitdiff
path: root/lorrycontroller/htmlstatus.py
diff options
context:
space:
mode:
Diffstat (limited to 'lorrycontroller/htmlstatus.py')
-rw-r--r--lorrycontroller/htmlstatus.py258
1 files changed, 258 insertions, 0 deletions
diff --git a/lorrycontroller/htmlstatus.py b/lorrycontroller/htmlstatus.py
new file mode 100644
index 0000000..aae75c7
--- /dev/null
+++ b/lorrycontroller/htmlstatus.py
@@ -0,0 +1,258 @@
+# Copyright (C) 2012 Codethink Limited
+#
+
+import os
+import time
+from cgi import escape
+
+state_names = [
+ "Initialisation",
+ "Load Troves",
+ "Remove old repos",
+ "Create new repos",
+ "Process Lorries",
+ "Finished"
+ ]
+
+
+class HTMLStatusManager(object):
+ '''Manage the HTML status page for lorry-controller.'''
+
+
+ def __init__(self, app):
+ self.app = app
+ self.state = 0
+ self.series = None
+ self.filename = self.app.settings['html-file']
+ self.mgr = None
+ self.processing = None
+ self.processing_time = None
+ self.failing = None
+ self.all_lorries_ever = set()
+
+ def set_failing(self, failmsg):
+ self.failing = failmsg
+ self.write_out_status()
+
+ def set_mgr(self, mgr):
+ self.mgr = mgr
+
+ def set_processing(self, proc):
+ self.processing = proc
+ self.processing_time = time.time()
+ self.write_out_status()
+
+ def bump_state(self):
+ self.state = self.state + 1
+ self.processing = None
+ self.write_out_status()
+
+ def write_out_status(self):
+ if self.filename is None: return
+ try:
+ with open(self.filename + ".new", "w") as ofh:
+ ofh.write("<!DOCTYPE html>\n")
+ ofh.write(self.gen_html())
+ ofh.write("\n")
+ target = self.filename
+ if self.series is not None:
+ target += ".%d" % self.series
+ self.series += 1
+ os.rename(self.filename + ".new", target)
+ except:
+ os.unlink(self.filename + ".new")
+ raise
+
+ def gen_html(self):
+ head = self.gen_head()
+ body = self.gen_body()
+ return self.tag("html", content=head+"\n"+body, gap=True)
+
+ def gen_head(self):
+ title = self.tag("title", content="Lorry Controller")
+ css = self.tag("link", href="trove.css", rel="stylesheet",
+ type="text/css")
+ script = self.tag("script", tyle="text/JavaScript", content='''
+<!--
+function reloadAfter(timeout) {
+ setTimeout("location.reload(true);", timeout);
+}
+// -->
+''')
+ return self.tag("head", content=title+css+script)
+
+ def gen_body(self):
+ # 1. Rough header, branded as trove
+ curtime = time.asctime(time.gmtime())
+ link = "/"
+ if self.series is not None:
+ link = self.filename + ".%d" % (self.series + 1)
+ header = '''
+<table id='header'><tr><td class='logo' rowspan='2'>
+<a href='%s'><img src='trove.png' alt='trove logo'/></a></td>
+<td class='main'>Status of Lorry Controller</td></tr>
+<tr><td class='sub'>Updated at %s</td></tr></table>
+''' % (link, curtime)
+ # 2. List of steps and where we are currently
+ steps = self.gen_steps()
+
+ # 4. Main content
+ content = self.gen_content()
+
+ # 5. footer
+ footer = self.gen_footer()
+
+ # magic args
+ kwargs = {
+ "onload": "JavaScript:reloadAfter(5000);",
+ }
+ if self.state >= (len(state_names)-1):
+ kwargs["onload"] = "JavaScript:reloadAfter(10000);",
+ return self.tag("body", content=self.tag(
+ "div", content=(header+steps+content+footer),
+ Class="lorrycontroller"), **kwargs)
+
+ def gen_content(self):
+ if self.failing is not None:
+ return self.tag("div", Class="failure", content=self.failing)
+ # 1. Troves known
+ troves = self.gen_troves()
+ # 2. Lorries known
+ lorries = self.gen_lorries()
+
+ return self.tag("div", Class="content", content=
+ self.tag("div", id="troves", content=troves) +
+ self.tag("div", id="lorries", content=lorries))
+
+ def gen_troves(self):
+ troves = []
+ now = time.time()
+ for trove in self.app.conf.troves:
+ troveinfo = {}
+ if self.mgr is not None:
+ troveinfo = self.mgr.trove_state.get(trove['uuid'], {})
+ uuid = self.tag("td", content=escape(trove['uuid']))
+ state = "Up to date"
+ if self.processing == trove['uuid']:
+ state = "Processing since " + \
+ time.asctime(time.gmtime(self.processing_time))
+ elif troveinfo.get('next-vls', now - 1) < now:
+ state = "Due to be checked"
+ state = self.tag("td", content=escape(state))
+ nextdue = self.tag("td", content=escape(time.asctime(
+ time.gmtime(troveinfo.get('next-vls', now - 1)))))
+ lorrycount = len([l for l in self.app.conf.lorries.itervalues()
+ if l['controller-uuid'] == trove['uuid']])
+ lorrycount = self.tag("td", content=str(lorrycount))
+
+ troves.append(self.tag("tr", content=
+ uuid+state+nextdue+lorrycount))
+ if len(troves) == 0:
+ content = "No troves detected"
+ else:
+ header = self.tag("tr", content=
+ self.tag("th", content="Trove UUID") +
+ self.tag("th", content="Status") +
+ self.tag("th", content="Next due") +
+ self.tag("th", content="Lorries created"))
+ content = self.tag("table", content=
+ header + "".join(troves))
+
+ return content
+
+ def gen_lorries(self):
+ lorries = []
+ now = time.time()
+ all_lorry_names = set(self.app.conf.lorries.keys())
+ if self.mgr is not None:
+ all_lorry_names.update(set(self.mgr.lorry_state.keys()))
+ self.all_lorries_ever.update(all_lorry_names)
+ all_lorry_names = list(self.all_lorries_ever)
+ all_lorry_names.sort()
+ for lorry_name in all_lorry_names:
+ lorry = self.app.conf.lorries.get(lorry_name, None)
+ dead_lorry = False
+ dead_and_gone = False
+ if lorry is None:
+ lorrystate = self.mgr.lorry_state.get(lorry_name, None)
+ if lorrystate is None:
+ dead_and_gone = True
+ lorry = {}
+ else:
+ lorry = lorrystate['lorry']
+ dead_lorry = True
+ lorryinfo = {}
+ if self.mgr is not None:
+ lorryinfo = self.mgr.lorry_state.get(lorry_name, {})
+ uuid = self.tag("td", content=
+ escape(lorry.get('controller-uuid', 'Dead')))
+ state = "Waiting until " + \
+ time.asctime(time.gmtime(lorryinfo.get('next-due', now - 1)))
+ if self.processing == lorry_name:
+ state = "Processing since " + \
+ time.asctime(time.gmtime(self.processing_time))
+ elif lorryinfo.get('next-due', now - 1) < now:
+ state = "Due to be checked"
+ if self.mgr is not None:
+ if self.mgr.lorry_state.get(lorry_name, None) is None:
+ state = "Needs creating"
+ if dead_lorry:
+ state = "Dead - To be removed"
+ if dead_and_gone:
+ state = "Dead"
+ if self.processing == lorry_name:
+ state = "Removing since " + \
+ time.asctime(time.gmtime(self.processing_time))
+ state = self.tag("td", content=escape(state))
+ lastresult = self.tag("td", content=self.tag(
+ "pre", content=escape(lorryinfo.get('result', '-'))))
+ nextdue = self.tag("td", content=escape(time.asctime(
+ time.gmtime(lorryinfo.get('next-due', now - 1)))))
+ lorryname = self.tag("td", content=escape(lorry_name))
+ lorries.append(self.tag("tr", content=
+ lorryname+uuid+state+lastresult+nextdue))
+ if len(lorries) == 0:
+ content = "No lorries detected yet"
+ else:
+ header = self.tag("tr", content=
+ self.tag("th", content="Lorry Name") +
+ self.tag("th", content="Comes From") +
+ self.tag("th", content="Status") +
+ self.tag("th", content="Last result") +
+ self.tag("th", content="Next due"))
+ content = self.tag("table", content=
+ header + "".join(lorries))
+
+ return content
+
+
+ def gen_footer(self):
+ curtime = time.asctime(time.gmtime())
+ return self.tag("div", Class="footer", content=
+ "Generated by Lorry Controller at " + curtime)
+
+ def gen_steps(self):
+ steps = []
+ for idx in xrange(len(state_names)):
+ if idx < self.state:
+ Class = "donestep"
+ elif idx == self.state:
+ Class = "activestep"
+ else:
+ Class = "pendingstep"
+ steps.append(self.tag("span", Class=Class,
+ content=state_names[idx]))
+ return self.tag("table", Class="steps", content=
+ self.tag("tr", content=
+ self.tag("td", content=
+ "".join(steps))))
+
+ def tag(self, tagname, content=None, gap=False, **kwargs):
+ tagval = " ".join([tagname] +
+ ["%s=%r" % (k.lower(), v) for k, v in kwargs.iteritems()])
+ gap = "\n" if gap else ""
+ if content is None:
+ return "<%s />" % tagval
+ else:
+ return "<%s>%s%s%s</%s>" % (tagval, gap, content, gap, tagname)
+