summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--graphics/action-date.xcfbin0 -> 1632 bytes
-rw-r--r--graphics/action-dir.xcfbin0 -> 1172 bytes
-rw-r--r--graphics/action-zoom.xcfbin0 -> 1476 bytes
-rw-r--r--html/action-date.gifbin0 -> 1024 bytes
-rw-r--r--html/action-next.gifbin0 -> 877 bytes
-rw-r--r--html/action-prev.gifbin0 -> 874 bytes
-rw-r--r--html/action-zoom.gifbin0 -> 965 bytes
-rw-r--r--html/index.html24
-rw-r--r--html/rrdui.js586
-rw-r--r--html/style.css157
-rwxr-xr-xtools/rrdui-cgi.py109
-rw-r--r--tools/rrdui-collect.py59
-rw-r--r--tools/rrdui-create.py28
-rw-r--r--tools/rrdui.py96
-rw-r--r--tools/rrdui.pycbin0 -> 2659 bytes
15 files changed, 1059 insertions, 0 deletions
diff --git a/graphics/action-date.xcf b/graphics/action-date.xcf
new file mode 100644
index 0000000..0cb14ac
--- /dev/null
+++ b/graphics/action-date.xcf
Binary files differ
diff --git a/graphics/action-dir.xcf b/graphics/action-dir.xcf
new file mode 100644
index 0000000..555a59c
--- /dev/null
+++ b/graphics/action-dir.xcf
Binary files differ
diff --git a/graphics/action-zoom.xcf b/graphics/action-zoom.xcf
new file mode 100644
index 0000000..e973da0
--- /dev/null
+++ b/graphics/action-zoom.xcf
Binary files differ
diff --git a/html/action-date.gif b/html/action-date.gif
new file mode 100644
index 0000000..a249e30
--- /dev/null
+++ b/html/action-date.gif
Binary files differ
diff --git a/html/action-next.gif b/html/action-next.gif
new file mode 100644
index 0000000..c01940a
--- /dev/null
+++ b/html/action-next.gif
Binary files differ
diff --git a/html/action-prev.gif b/html/action-prev.gif
new file mode 100644
index 0000000..f7eba32
--- /dev/null
+++ b/html/action-prev.gif
Binary files differ
diff --git a/html/action-zoom.gif b/html/action-zoom.gif
new file mode 100644
index 0000000..c1e9800
--- /dev/null
+++ b/html/action-zoom.gif
Binary files differ
diff --git a/html/index.html b/html/index.html
new file mode 100644
index 0000000..43c79ed
--- /dev/null
+++ b/html/index.html
@@ -0,0 +1,24 @@
+<html>
+ <head>
+ <script language="javascript">
+ // Settings
+ endpoint = "http://shondo/rrduiback/";
+ </script>
+ <title>Statistics Monitoring</title>
+ <link href="style.css" type="text/css" rel="stylesheet"/>
+ </head>
+ <body>
+ <table id="main-table" cellspacing="0" cellpadding="0">
+ <tr id="top-row"><td id="headers">
+
+ <!-- The header area: A template of our header -->
+ <span class="header" id="header-template">Text</span>
+
+ </td></tr>
+ <tr id="main-row"><td colspan="2" id="main-cell">
+ <iframe id="main-frame"></iframe>
+ </td></tr>
+ </table>
+ <script language="javascript" src="rrdui.js"/>
+ </body>
+</html>
diff --git a/html/rrdui.js b/html/rrdui.js
new file mode 100644
index 0000000..12bce92
--- /dev/null
+++ b/html/rrdui.js
@@ -0,0 +1,586 @@
+
+
+/* -----------------------------------------------------------------------------
+ * STARTUP
+ */
+
+/* TODO: Check setup variables */
+/* TODO: Loading indicator */
+
+var categoryCurrent = null;
+var categoryArea = document.getElementById("headers");
+var xmlData = null;
+var graphDoc = null;
+
+loadXml(endpoint, loadedGraphData);
+setupFrame();
+
+function loadedGraphData(doc)
+{
+ if(!doc)
+ {
+ displayError("Couldn't load graph data from server", categoryArea);
+ return;
+ }
+
+ xmlData = doc;
+
+ displayCategories();
+ displayCurrentPage();
+}
+
+
+/* -----------------------------------------------------------------------------
+ * GRAPHS
+ */
+
+function setupFrame()
+{
+ var frame = document.getElementById("main-frame");
+ graphDoc = frame.contentDocument;
+
+ /* Get the stylesheet info for frame */
+ var i, links = document.getElementsByTagName("link");
+ var csslink = null;
+ for(i = 0; i < links.length; i++)
+ {
+ if(links.item(i).getAttribute("rel") == "stylesheet")
+ csslink = links.item(i);
+ }
+
+ graphDoc.open();
+ graphDoc.write("<html><head>\n");
+ /* TODO: We probably need to write a <base> tag for relative URIs */
+ if(csslink)
+ graphDoc.write("<link href=\"" + csslink.getAttribute("href") +
+ "\" type=\"text/css\" rel=\"stylesheet\"/>\n");
+ graphDoc.write("</head><body class=\"in-frame\">\n");
+
+ /* The graph area */
+ graphDoc.write("<div id=\"graphs\" align=\"center\"></div>\n");
+
+ /* Zoom overlay */
+ graphDoc.write("<div id=\"zoom\"></div>\n");
+
+ /* The action buttons */
+ graphDoc.write("<div id=\"actions\">\n");
+ graphDoc.write("<img id=\"action-prev\" class=\"action\" src=\"action-prev.gif\"/><br/>");
+ graphDoc.write("<img id=\"action-next\" class=\"action\" src=\"action-next.gif\"/><br/>");
+ graphDoc.write("<img id=\"action-zoom\" class=\"action\" src=\"action-zoom.gif\"/><br/>");
+ graphDoc.write("<img id=\"action-goto\" class=\"action\" src=\"action-date.gif\"/><br/>");
+ graphDoc.write("</div>");
+
+ /* Date selection drop down */
+ graphDoc.write("<div id=\"goto\">\n");
+ graphDoc.write("<select id=\"goto-select\" size=\"6\">");
+ graphDoc.write("<option value=\"1800:0\">Last 30 Minutes</option>");
+ graphDoc.write("<option value=\"3600:0\">Last Hour</option>");
+ graphDoc.write("<option value=\"10800:0\">Last 3 Hours</option>");
+ graphDoc.write("<option value=\"21600:0\">Last 6 Hours</option>");
+ graphDoc.write("<option value=\"86400:0\">Last Day</option>");
+ graphDoc.write("<option value=\"259200:0\">Last 3 Days</option>");
+ graphDoc.write("<option value=\"604800:0\">Last Week</option>");
+ graphDoc.write("<option value=\"1209600:0\">Last 2 Weeks</option>");
+ graphDoc.write("<option value=\"1209600:0\">Last 2 Weeks</option>");
+ graphDoc.write("<option value=\"2678400:0\">Last Month</option>");
+ graphDoc.write("<option value=\"5356800:2678400\">Month Before Last</option>");
+ graphDoc.write("<option value=\"7948800:0\">Last 3 Months</option>");
+ graphDoc.write("<option value=\"15897600:0\">Last 6 Months</option>");
+ graphDoc.write("<option value=\"31536000:0\">Last Year</option>");
+ graphDoc.write("</select>");
+
+ /* And wrap it up */
+ graphDoc.write("</body></html>");
+ graphDoc.close();
+}
+
+function displayCurrentPage()
+{
+ var area = graphDoc.getElementById("graphs");
+
+ /* Hide all images not from this category */
+ var i, images = area.getElementsByTagName("img");
+ for(i = 0; i < images.length; i++)
+ {
+ if(images.item(i)._category == categoryCurrent)
+ images.item(i).style.display = "inline";
+ else
+ images.item(i).style.display = "none";
+ }
+
+ var data = findCategoryData(categoryCurrent);
+ if(!xmlData || !data)
+ {
+ displayError("Graph data isn't loaded from server", area);
+ return;
+ }
+
+ var tend = nowTime();
+ var tbeg = tend - 86400; /* One day by default */
+
+ var children = data.childNodes;
+ for(i = 0; i < children.length; i++)
+ {
+ var child = children.item(i);
+ if(child.nodeType != Node.ELEMENT_NODE ||
+ child.localName != "graph")
+ continue;
+
+ var name = child.getAttribute("name");
+ if(!name || !name.length)
+ continue;
+
+ /* Make sure we don't already have this graph */
+ var id = "graph-" + categoryCurrent + "-" + name;
+ var img = graphDoc.getElementById(id);
+ if(img != null)
+ continue;
+
+ img = document.createElement("img");
+
+ if(child.hasAttribute("title"))
+ {
+ img.setAttribute("title", child.getAttribute("title"));
+ img.setAttribute("alt", child.getAttribute("title"));
+ }
+ else
+ img.setAttribute("alt", "Graph");
+
+ img.setAttribute("class", "graph");
+ img.setAttribute("id", id);
+
+ area.appendChild(img);
+
+ img._category = categoryCurrent;
+ img._name = name;
+ img._tbeg = tbeg;
+ img._tend = tend;
+
+ reloadGraph(img);
+
+ img.onmousedown = function(evt)
+ { return zoomGraphStart(window.event ? window.event : evt); }
+ img.onmouseover = function(evt)
+ { return actionsDisplay(window.event ? window.event : evt); }
+ }
+}
+
+function reloadGraph(img)
+{
+ /* TODO: Move these into settings */
+ /* TODO: We should encode colons properly */
+ var GRAPH_COLOR_BACK = ":252424";
+ var GRAPH_COLOR_FONT = ":E7E1CC";
+ var GRAPH_WIDTH = 450;
+ var GRAPH_HEIGHT = 120
+
+ var uri = endpoint + "/graph?category=" + img._category + "&name=" + img._name;
+
+ /* Add date options */
+ uri += "&start=" + img._tbeg + "&end=" + img._tend;
+
+ /* And colors */
+ uri += "&color=BACK" + GRAPH_COLOR_BACK + "&color=FONT" + GRAPH_COLOR_FONT +
+ "&color=SHADEA" + GRAPH_COLOR_BACK + "&color=SHADEB" + GRAPH_COLOR_BACK;
+
+ /* Size */
+ uri += "&height=" + GRAPH_HEIGHT + "&width=" + GRAPH_WIDTH;
+
+ img.setAttribute("src", uri);
+}
+
+function zoomGraphStart(evt)
+{
+ if(evt.button != 0)
+ return;
+
+ if(evt.target.getAttribute("class") != "graph")
+ return;
+
+ var img = evt.target;
+ var zoom = graphDoc.getElementById("zoom");
+
+ zoom._img = img;
+ zoom._zooming = true;
+ zoom._from = evt.clientX + graphDoc.body.scrollLeft;
+
+ zoom._start = zoom._from - img.offsetLeft;
+ zoom._end = zoom._start;
+
+ zoom.style.width = 1;
+ zoom.style.left = zoom._from;
+ zoom.style.height = img.height;
+ zoom.style.top = img.offsetTop;
+ zoom.style.display = "block";
+
+ zoom.onmousemove = zoom._img.onmousemove = function(evt)
+ { return zoomGraphUpdate(window.event ? window.event : evt); }
+ zoom.onmouseup = zoom._img.onmouseup = function(evt)
+ { return zoomGraphDone(window.event ? window.event : evt); }
+ document.onmouseup = graphDoc.onmouseup = function(evt)
+ { return zoomGraphDone(window.event ? window.event : evt); }
+
+ evt.preventDefault();
+ evt.stopPropagation();
+ return false;
+}
+
+function zoomGraphUpdate(evt)
+{
+ var zoom = graphDoc.getElementById("zoom");
+ if(!zoom._zooming || !zoom._img)
+ return;
+
+ var to = evt.clientX + graphDoc.body.scrollLeft;
+ var width = to - zoom._from;
+ zoom._end = to - zoom._img.offsetLeft;
+
+ if(width == 0)
+ width = 1;
+
+ if(width < 0)
+ {
+ zoom.style.left = to;
+ zoom.style.width = -width;
+ }
+ else
+ {
+ zoom.style.left = zoom._from;
+ zoom.style.width = width;
+ }
+
+ zoom.style.height = zoom._img.height;
+ zoom.style.top = zoom._img.offsetTop;
+ zoom.style.display = "block";
+
+ evt.preventDefault();
+ evt.stopPropagation();
+ return false;
+}
+
+function zoomGraphDone(evt)
+{
+ var zoom = graphDoc.getElementById("zoom");
+ if(!zoom._zooming)
+ return;
+
+ if(evt.target == zoom || evt.target == zoom._img)
+ zoomGraphSelect(zoom);
+
+ zoomGraphCancel(evt);
+}
+
+function zoomGraphSelect(zoom)
+{
+ // Arbitrary values (TODO: Move these to CONFIG)
+ var GRAPH_MARGIN_LEFT = 67;
+ var GRAPH_MARGIN_RIGHT = 27;
+
+ var left = GRAPH_MARGIN_LEFT;
+ var right = zoom._img.width - GRAPH_MARGIN_RIGHT;
+
+ var start = zoom._start > zoom._end ? zoom._end : zoom._start;
+ var end = zoom._start > zoom._end ? zoom._start : zoom._end;
+
+ /* If too small selected, then punt */
+ if((end - start) <= 1)
+ return;
+
+ /* If entire thing selected, then punt */
+ if(start < left && end > right)
+ return;
+
+ /* If only margins selected, then punt */
+ if(!(start < right && end > left))
+ return;
+
+ /* Limit it to the right range */
+ var range = zoom._img.width - GRAPH_MARGIN_RIGHT - GRAPH_MARGIN_LEFT;
+ start = start < left ? 0 : start - left;
+ end = end > range ? range : end - left;
+
+ /* Compute the ratio pixels:time */
+ var ratio = (zoom._img._tend - zoom._img._tbeg) / range;
+
+ var obeg = zoom._img._tbeg;
+ var oend = zoom._img._tend;
+
+ zoom._img._tend = Math.floor((end * ratio) + zoom._img._tbeg);
+ zoom._img._tbeg = Math.floor((start * ratio) + zoom._img._tbeg);
+
+ reloadGraph(zoom._img);
+}
+
+function zoomGraphCancel(evt)
+{
+ var zoom = graphDoc.getElementById("zoom");
+
+ if(zoom._zooming) {
+ if(zoom._img)
+ zoom._img.onmousemove = zoom._img.onmouseup = zoom._img.onmouseleave = null;
+ zoom._img = null;
+ zoom.onmousemove = zoom.onmouseup = null;
+ zoom._zooming = false;
+ }
+
+ zoom.style.display = "none";
+}
+
+function zoomGraphOut(img)
+{
+ var factor = Math.floor((img._tend - img._tbeg) / 2.5);
+ var now = nowTime();
+
+ /* If near the end we have special behavior */
+ if(img._tend >= now - 300 || img._tend <= now)
+ {
+ img._tbeg -= factor;
+ img._tend = now;
+ }
+ else
+ {
+ factor = Math.floor(factor / 2);
+ img._tbeg -= factor
+ img._tend += factor;
+ }
+
+ reloadGraph(img);
+}
+
+function zoomGraphMove(img, dir)
+{
+ var factor = Math.floor((img._tend - img._tbeg) / 3);
+ if(!dir)
+ factor = -factor;
+ img._tbeg += factor;
+ img._tend += factor;
+ reloadGraph(img);
+}
+
+function actionsDisplay(evt)
+{
+ if(evt.target.getAttribute("class") != "graph")
+ return;
+
+ var img = evt.target;
+
+ var actions = graphDoc.getElementById("actions");
+ actions._img = img;
+ actions._display = true;
+
+ actions.style.top = img.offsetTop;
+ actions.style.left = img.offsetLeft + (img.width - 16);
+ actions.style.display = "block";
+
+ actions.onmouseout = img.onmouseout = function(evt)
+ { return actionsHide(window.event ? window.event : evt); }
+
+ /* TODO: We only actually have to do this once */
+ var i, acts = actions.getElementsByTagName("img");
+ for(i = 0; i < acts.length; i++)
+ {
+ acts.item(i).onclick = function(evt)
+ { return actionRun(window.event ? window.event : evt); }
+ }
+}
+
+function actionsHide(evt)
+{
+ var actions = graphDoc.getElementById("actions");
+ if(!actions._display)
+ return;
+
+ var x = evt.clientX - graphDoc.body.scrollLeft;
+ var y = evt.clientY - graphDoc.body.scrollTop;
+ var img = actions._img;
+
+ /* See if we're actually still within the image */
+ if(x >= img.offsetLeft && x <= img.offsetLeft + img.width &&
+ y >= img.offsetTop && y <= img.offsetTop + img.height)
+ return;
+
+ actions.style.display = "none";
+ img.onmouseout = actions.onmouseout = null;
+ actions._img = null;
+ actions._display = false;
+}
+
+function actionRun(evt)
+{
+ if(evt.target.getAttribute("class") != "action")
+ return;
+
+ var actions = graphDoc.getElementById("actions");
+ if(!actions._display)
+ return;
+
+ var act = evt.target;
+
+ switch(act.getAttribute("id"))
+ {
+ case "action-zoom":
+ zoomGraphOut(actions._img);
+ break;
+ case "action-next":
+ zoomGraphMove(actions._img, true);
+ break;
+ case "action-prev":
+ zoomGraphMove(actions._img, false);
+ break;
+ case "action-goto":
+ actionGoto(actions._img, act);
+ break;
+ }
+}
+
+function actionGoto(img, act)
+{
+ /* Display the selection thingy */
+ var got = graphDoc.getElementById("goto");
+
+ /* TODO: Make 120/170 a constant */
+ got.style.top = act.offsetTop;
+ got.style.left = (img.offsetLeft + img.width) - 170;
+ got.style.width = 120;
+ got.style.display = "block";
+
+ img.onmouseclick = actionGotoCancel;
+
+ var sel = graphDoc.getElementById("goto-select");
+ sel.onchange = actionGotoChange;
+ sel._img = img;
+}
+
+function actionGotoCancel()
+{
+ var got = graphDoc.getElementById("goto");
+ got.style.display = "none";
+}
+
+function actionGotoChange()
+{
+ var sel = graphDoc.getElementById("goto-select");
+ var img = sel._img;
+ sel.onchange = null;
+ sel._img = null;
+
+ times = sel.value.split(":");
+ img._tbeg = nowTime() - times[0];
+ img._tend = nowTime() - times[1];
+ reloadGraph(img);
+
+ actionGotoCancel();
+}
+
+/* -----------------------------------------------------------------------------
+ * GROUP CATEGORIES
+ */
+
+function findCategoryData(cat)
+{
+ var i;
+
+ if(!xmlData)
+ return null;
+
+ var cats = xmlData.getElementsByTagName("category");
+ for(i = 0; i < cats.length; i++)
+ {
+ if(cats.item(i).getAttribute("name") == cat)
+ return cats.item(i);
+ }
+
+ return null;
+}
+
+function changeCategory(cat)
+{
+ categoryCurrent = cat;
+ displayCurrentPage();
+}
+
+function displayCategories()
+{
+ var i;
+
+ /* Get the template and clean it up a bit */
+ var template = document.getElementById("header-template");
+ while(template.hasChildNodes())
+ template.removeChild(template.firstChild);
+
+ var groups = xmlData.getElementsByTagName("category");
+
+ for(i = 0; i < groups.length; i++)
+ {
+ var name = groups.item(i).getAttribute("name");
+
+ if(!categoryCurrent)
+ categoryCurrent = name;
+
+ var el = template.cloneNode(true);
+ el.removeAttribute("id");
+ el.appendChild(document.createTextNode(name));
+ el.setAttribute("group", name);
+ el.setAttribute("onclick", "changeCategory('" + name + "');");
+
+ categoryArea.appendChild(el);
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * HELPERS
+ */
+
+function loadXml(uri, callback)
+{
+ var xmlhttp;
+ var xmlHttpChange = function() {
+ if(xmlhttp.readyState == 4)
+ {
+ if(xmlhttp.status == 200)
+ callback(xmlhttp.responseXML);
+ else
+ callback(null);
+ }
+ }
+
+ /* Mozilla code */
+ if(window.XMLHttpRequest)
+ {
+ xmlhttp = new XMLHttpRequest();
+ xmlhttp.onreadystatechange = xmlHttpChange;
+ xmlhttp.open("GET", uri, true);
+ xmlhttp.send(null);
+ }
+
+ /* And now for the Nasty */
+ else if (window.ActiveXObject)
+ {
+ xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
+ /* TODO: need to do something when this fails (can it fail?) */
+ if(xmlhttp)
+ {
+ xmlhttp.onreadystatechange = xmlHttpChange;
+ xmlhttp.open("GET", uri, true);
+ xmlhttp.send()
+ }
+ }
+}
+
+function displayError(message, place)
+{
+ var el = document.createElement("span");
+ el.setAttribute("class", "error");
+ var text = document.createTextNode(message);
+ el.appendChild(text);
+
+ if(place)
+ place.appendChild(el);
+
+ return el;
+}
+
+function nowTime()
+{
+ return Math.floor((new Date()).getTime() / 1000);
+}
diff --git a/html/style.css b/html/style.css
new file mode 100644
index 0000000..6b45978
--- /dev/null
+++ b/html/style.css
@@ -0,0 +1,157 @@
+
+/* -----------------------------------------------------------------------------
+ * MAIN LAYOUT
+ */
+
+*
+{
+ color: #E7E1CC;
+ font-family: sans-serif;
+}
+
+body
+{
+ background-color: #1A1A1A
+}
+
+#main-table
+{
+ width: 100%;
+ height: 100%;
+ padding: 10px;
+}
+
+#top-row
+{
+ height: 30pt;
+ padding: 0px;
+}
+
+#main-row
+ { height: 100%; }
+
+#main-cell
+ { background-color: #252424; padding: 5pt; }
+
+#main-frame
+{
+ border: none;
+ height: 100%;
+ width: 100%;
+}
+
+/* -----------------------------------------------------------------------------
+ * GROUP HEADERS
+ */
+
+/* A group button at the top */
+.header
+{
+ background-color: #3D2C2C;
+ padding: 4pt 6pt;
+ font-size: 8pt;
+ font-weight: bold;
+ -moz-border-radius: 3pt;
+ cursor: pointer;
+ margin-right: 5pt;
+}
+
+/* Our sample header (which gets copied for each group) is hidden */
+#header-template
+ { display: none; }
+
+/* -----------------------------------------------------------------------------
+ * GRAPHS/FRAME
+ */
+
+body.in-frame
+{
+ background-color: #252424;
+}
+
+img.graph
+{
+ cursor: crosshair;
+ margin: 7px;
+}
+
+#zoom
+{
+ cursor: crosshair;
+ position: absolute;
+ background-color: red;
+ display: none;
+ top: 0px;
+ left: 0px;
+ width: 10px;
+ height: 10px;
+ filter: alpha(opacity=40);
+ -moz-opacity: 0.5;
+ -khtml-opacity: 0.5;
+ opacity: 0.5;
+}
+
+/* -----------------------------------------------------------------------------
+ * ACTION BAR
+ */
+
+#actions
+{
+ background-color: #252424;
+ position: absolute;
+ left: 50px;
+ top: 50px;
+ height: 160px;
+ display: none;
+}
+
+img.action
+{
+ margin: 3px;
+ cursor: pointer;
+ filter: alpha(opacity=75);
+ -moz-opacity: 0.75;
+ -khtml-opacity: 0.75;
+ opacity: 0.75;
+}
+
+img.action:hover
+{
+ filter: alpha(opacity=100);
+ -moz-opacity: 1;
+ -khtml-opacity: 1;
+ opacity: 1;
+}
+
+#goto
+{
+ position: absolute;
+ padding: 10px;
+ top: 0px;
+ left: 0px;
+ background-color: #252424;
+ display: none;
+}
+
+#goto-select
+{
+ border: 1px solid black;
+ font-size: 8pt;
+ font-weight: normal;
+ width: 120px;
+ background-color: #252424;
+}
+
+/* -----------------------------------------------------------------------------
+ * MISC
+ */
+
+.error
+{
+ background-color: #F3BDBD;
+ border: 1px solid red;
+ padding: 3pt;
+ font-size: 8pt;
+ color: black;
+}
+
diff --git a/tools/rrdui-cgi.py b/tools/rrdui-cgi.py
new file mode 100755
index 0000000..8b376f5
--- /dev/null
+++ b/tools/rrdui-cgi.py
@@ -0,0 +1,109 @@
+#!/usr/bin/python
+
+import os, sys, time
+import sets, cgi, shlex
+import rrdtool
+
+from rrdui import *
+
+def listGraphs():
+
+ graphs = loadGraphs()
+ categories = {}
+ for item in graphs:
+ if not categories.has_key(item.category):
+ categories[item.category] = []
+ categories[item.category].append(item)
+
+ groups = categories.keys()
+ groups.sort()
+
+ print "Content-Type: text/xml\n"
+ print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ print "<data>"
+ for group in groups:
+ print " <category name=\"%s\">" % group
+ categories[group].sort()
+ for item in categories[group]:
+ print " <graph name=\"%s\" width=\"%d\" height=\"%d\" title=\"%s\"/>" % \
+ (item.name, item.width, item.height, item.title)
+ print " </category>"
+ print "</data>"
+
+
+def displayGraph():
+
+ # print "Content-Type: text/plain"
+ print "Content-Type: image/png"
+ print ""
+
+ form = cgi.FieldStorage()
+ if not form.has_key("category") or not form.has_key("name"):
+ raise "Required arguments not specified"
+
+ name = form["name"].value
+ category = form["category"].value
+ item = GraphDef(category, name)
+
+ # Default to one day display
+ end = int(time.time())
+ start = end - 86400
+ if form.has_key("start"):
+ start = int(form["start"].value)
+ if form.has_key("end"):
+ end = int(form["end"].value)
+
+ # Default to Height and Width in graph
+ height = item.height
+ width = item.width
+ if form.has_key("width"):
+ width = int(form["width"].value)
+ if form.has_key("height"):
+ height = int(form["height"].value)
+
+
+ args = ["-", "--imgformat=PNG", "--rigid",
+ "--start=%d" % start,
+ "--end=%d" % end,
+ "--title=%s" % item.title,
+ "--height=%d" % height,
+ "--width=%d" % width ]
+
+ # TODO Check color syntax
+ if form.has_key("color"):
+ colors = form.getlist("color");
+ for color in colors:
+ args.append("--color")
+ args.append(color.replace(":", "#"))
+
+ commands = item.commands.replace("{START}",
+ time.strftime("%Y-%m-%d %H\\:%M", time.localtime(start)))
+ commands = commands.replace("{END}",
+ time.strftime("%Y-%m-%d %H\\:%M", time.localtime(end)))
+ commands = commands.replace("{RRD}", item.filedata)
+ args.extend(shlex.split(commands))
+
+ args.extend(shlex.split(item.options))
+
+ print >> sys.stderr, str(args)
+ rrdtool.graph(*args)
+
+
+if not os.environ.has_key("PATH_INFO"):
+ raise "PATH_INFO not set"
+
+path = os.environ["PATH_INFO"].strip("/")
+parts = path.split("/")
+
+method = parts[0]
+del parts[0]
+if not method:
+ method = "list"
+
+
+if method == "list":
+ listGraphs()
+elif method == "graph":
+ displayGraph()
+else:
+ raise "Invalid request: %s" % method
diff --git a/tools/rrdui-collect.py b/tools/rrdui-collect.py
new file mode 100644
index 0000000..892fdf8
--- /dev/null
+++ b/tools/rrdui-collect.py
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+
+import os, sys, time
+import rrdtool
+
+from rrdui import *
+
+def readItemValue(host, community, oid):
+
+ cmd = "snmpget -c %s -Ov -OQ -v 2c %s %s" % (community, host, oid)
+ p = os.popen(cmd, "r")
+ out = p.read()
+ (pid, status) = os.wait()
+ status = status >> 8
+
+ try:
+ p.close()
+ except IOError:
+ pass
+
+ if status != 0:
+ print "Couldn't query %s for %s" % (host, oid) # XXXXXX
+ out = "U"
+
+ return out.strip()
+
+
+def updateItems(graphs):
+ for item in graphs:
+ if not os.path.exists(item.filedata):
+ continue
+
+ (fields, interval) = item.getPollingInfo()
+
+ values = {}
+ for fieldname in fields.keys():
+ info = fields[fieldname].split(":")
+ if len(info) != 4:
+ raise "Bad src format" # XXXXXXXXX
+ if info[0] != "SNMP":
+ raise "Unknown src method: %s" % info[0] # XXXXXXXXX
+
+ value = readItemValue(host = info[1], community = info[2], oid = info[3])
+ values[fieldname] = value
+
+ print ":".join(values.keys()), ":".join(values.values())
+ args = [item.filedata, "--template", ":".join(values.keys()),
+ "N:%s" % ":".join(values.values())]
+ rrdtool.update(*args)
+
+
+graphs = loadGraphs()
+while True:
+ updateItems(graphs)
+ # TODO: Work out the interval stuff. for now all at 10 seconds
+ time.sleep(10)
+
+
+
diff --git a/tools/rrdui-create.py b/tools/rrdui-create.py
new file mode 100644
index 0000000..3b46021
--- /dev/null
+++ b/tools/rrdui-create.py
@@ -0,0 +1,28 @@
+#!/usr/bin/python
+
+import os, sys
+import rrdtool
+
+from rrdui import *
+
+def createItems(graphs):
+
+ for item in graphs:
+ if os.path.exists(item.filedata):
+ continue
+
+ args = [item.filedata, "-b-1y", "-s10"]
+
+ # The creation info
+ (fields, rras) = item.getCreateInfo()
+
+ # Flesh it out properly, and add it
+ args.extend(["RRA:%s" % r for r in rras])
+ args.extend(["DS:%s:%s" % (f, fields[f]) for f in fields.keys()])
+
+ # And create it
+ rrdtool.create(*args)
+
+# Basics
+graphs = loadGraphs()
+createItems(graphs)
diff --git a/tools/rrdui.py b/tools/rrdui.py
new file mode 100644
index 0000000..ee10eae
--- /dev/null
+++ b/tools/rrdui.py
@@ -0,0 +1,96 @@
+import os, sys, time
+import ConfigParser
+
+# TODO: Temporary
+CONFDIR = "/data/projects/rrdui/conf"
+WORKDIR = "/data/projects/rrdui/work"
+
+class GraphDef:
+ filename = None
+ filedata = None
+ title = ""
+ height = 0
+ width = 0
+ options = ""
+ commands = ""
+ category = ""
+ name = ""
+
+ __config = None
+
+ def __init__(self, category, name):
+ self.filename = "%s/%s/%s" % (CONFDIR, category, name)
+ self.filedata = "%s/%s-%s.rrd" % (WORKDIR, category, name)
+ self.category = category
+ self.name = name
+
+ cfg = self.__config = ConfigParser.RawConfigParser()
+ cfg.read(self.filename)
+
+ # Loading general stuff
+ if cfg.has_option("general", "title"):
+ self.title = cfg.get("general", "title")
+
+ # Loading graph stuff
+ if cfg.has_option("graph", "width"):
+ self.width = int(cfg.get("graph", "width"))
+ if cfg.has_option("graph", "height"):
+ self.height = int(cfg.get("graph", "height"))
+ if cfg.has_option("graph", "options"):
+ self.options = cfg.get("graph", "options")
+ if not cfg.has_option("graph", "commands"):
+ raise "Missing commands attribute in: %s" % self.filename
+ self.commands = cfg.get("graph", "commands")
+
+
+ def getCreateInfo(self):
+ cfg = self.__config
+ rra = None
+ fields = {}
+
+ # The RRA info
+ if cfg.has_option("create", "rra"):
+ rra = cfg.get("create", "rra").split()
+
+ # The various fields
+ for field in cfg.options("create"):
+ if not field.startswith("field."):
+ continue
+ fieldname = field[6:]
+ fields[fieldname] = cfg.get("create", field)
+
+ return (fields, rra)
+
+
+ def getPollingInfo(self):
+ cfg = self.__config
+ interval = 300
+ fields = {}
+
+ # The interval
+ if cfg.has_option("poll", "interval"):
+ interval = int(cfg.get("poll", "interval"))
+
+ # The various fields
+ for field in cfg.options("poll"):
+ if not field.startswith("field."):
+ continue
+ fieldname = field[6:]
+ fields[fieldname] = cfg.get("poll", field)
+
+ return (fields, interval)
+
+
+def loadGraphs():
+
+ # List files and add appropriate paths
+ graphs = []
+ for category in os.listdir(CONFDIR):
+ dir = "%s/%s" % (CONFDIR, category)
+ if not os.path.isdir(dir):
+ continue
+ for f in os.listdir(dir):
+ graphs.append(GraphDef(category, f))
+ return graphs
+
+
diff --git a/tools/rrdui.pyc b/tools/rrdui.pyc
new file mode 100644
index 0000000..4bd62ce
--- /dev/null
+++ b/tools/rrdui.pyc
Binary files differ