diff options
Diffstat (limited to 'html/rrdui.js')
-rw-r--r-- | html/rrdui.js | 586 |
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); +} |