diff options
author | Stef Walter <stef@memberwebs.com> | 2006-01-20 23:10:33 +0000 |
---|---|---|
committer | Stef Walter <stef@memberwebs.com> | 2006-01-20 23:10:33 +0000 |
commit | 9a813d3324c2dae32fcfd690591a9e31cdad649d (patch) | |
tree | 2b96e4e2008143f261721187293c2b517285400a /html |
Initial import
Diffstat (limited to 'html')
-rw-r--r-- | html/action-date.gif | bin | 0 -> 1024 bytes | |||
-rw-r--r-- | html/action-next.gif | bin | 0 -> 877 bytes | |||
-rw-r--r-- | html/action-prev.gif | bin | 0 -> 874 bytes | |||
-rw-r--r-- | html/action-zoom.gif | bin | 0 -> 965 bytes | |||
-rw-r--r-- | html/index.html | 24 | ||||
-rw-r--r-- | html/rrdui.js | 586 | ||||
-rw-r--r-- | html/style.css | 157 |
7 files changed, 767 insertions, 0 deletions
diff --git a/html/action-date.gif b/html/action-date.gif Binary files differnew file mode 100644 index 0000000..a249e30 --- /dev/null +++ b/html/action-date.gif diff --git a/html/action-next.gif b/html/action-next.gif Binary files differnew file mode 100644 index 0000000..c01940a --- /dev/null +++ b/html/action-next.gif diff --git a/html/action-prev.gif b/html/action-prev.gif Binary files differnew file mode 100644 index 0000000..f7eba32 --- /dev/null +++ b/html/action-prev.gif diff --git a/html/action-zoom.gif b/html/action-zoom.gif Binary files differnew file mode 100644 index 0000000..c1e9800 --- /dev/null +++ b/html/action-zoom.gif 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; +} + |