summaryrefslogtreecommitdiff
path: root/html/rrdui.js
diff options
context:
space:
mode:
Diffstat (limited to 'html/rrdui.js')
-rw-r--r--html/rrdui.js586
1 files changed, 586 insertions, 0 deletions
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);
+}