/* -----------------------------------------------------------------------------
* 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("
\n");
/* TODO: We probably need to write a tag for relative URIs */
if(csslink)
graphDoc.write(" \n");
graphDoc.write("\n");
/* The graph area */
graphDoc.write("
\n");
/* Zoom overlay */
graphDoc.write("
\n");
/* The action buttons */
graphDoc.write("\n");
graphDoc.write("
");
graphDoc.write("
");
graphDoc.write("
");
graphDoc.write("
");
graphDoc.write("
");
/* Date selection drop down */
graphDoc.write("\n");
graphDoc.write("");
graphDoc.write("Last 30 Minutes ");
graphDoc.write("Last Hour ");
graphDoc.write("Last 3 Hours ");
graphDoc.write("Last 6 Hours ");
graphDoc.write("Last Day ");
graphDoc.write("Last 3 Days ");
graphDoc.write("Last Week ");
graphDoc.write("Last 2 Weeks ");
graphDoc.write("Last 2 Weeks ");
graphDoc.write("Last Month ");
graphDoc.write("Month Before Last ");
graphDoc.write("Last 3 Months ");
graphDoc.write("Last 6 Months ");
graphDoc.write("Last Year ");
graphDoc.write(" ");
/* And wrap it up */
graphDoc.write("");
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);
}