summaryrefslogtreecommitdiff
path: root/html/ajax
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2006-08-30 21:18:01 +0000
committerStef Walter <stef@memberwebs.com>2006-08-30 21:18:01 +0000
commit96e3074f9de2b0c7ba8b0b11edfa42ffd2803037 (patch)
treec7b5addc5b6f0b14fe028b54fae6384e1cd1f2a6 /html/ajax
parente228b6ac441b5a9ceff398d874ba7b4a118c819b (diff)
Reorganize things, and build and install them properly
Diffstat (limited to 'html/ajax')
-rw-r--r--html/ajax/action-date.gifbin0 -> 1024 bytes
-rw-r--r--html/ajax/action-next.gifbin0 -> 877 bytes
-rw-r--r--html/ajax/action-prev.gifbin0 -> 874 bytes
-rw-r--r--html/ajax/action-zoom.gifbin0 -> 965 bytes
-rw-r--r--html/ajax/frame.html43
-rw-r--r--html/ajax/index.html50
-rw-r--r--html/ajax/rrdui.js685
-rw-r--r--html/ajax/style.css164
8 files changed, 942 insertions, 0 deletions
diff --git a/html/ajax/action-date.gif b/html/ajax/action-date.gif
new file mode 100644
index 0000000..a249e30
--- /dev/null
+++ b/html/ajax/action-date.gif
Binary files differ
diff --git a/html/ajax/action-next.gif b/html/ajax/action-next.gif
new file mode 100644
index 0000000..c01940a
--- /dev/null
+++ b/html/ajax/action-next.gif
Binary files differ
diff --git a/html/ajax/action-prev.gif b/html/ajax/action-prev.gif
new file mode 100644
index 0000000..f7eba32
--- /dev/null
+++ b/html/ajax/action-prev.gif
Binary files differ
diff --git a/html/ajax/action-zoom.gif b/html/ajax/action-zoom.gif
new file mode 100644
index 0000000..c1e9800
--- /dev/null
+++ b/html/ajax/action-zoom.gif
Binary files differ
diff --git a/html/ajax/frame.html b/html/ajax/frame.html
new file mode 100644
index 0000000..3c9aae8
--- /dev/null
+++ b/html/ajax/frame.html
@@ -0,0 +1,43 @@
+<html>
+ <head>
+ <link href="style.css" type="text/css" rel="stylesheet"/>
+ </head>
+ <body class="in-frame" onload="window.parent._frameLoaded = true;">
+
+ <div id="graphs" align="center"></div>
+ <div id="zoom"></div>
+
+ <!-- The action buttons -->
+ <div id="actions">
+ <img id="action-prev" class="action" src="action-prev.gif"
+ alt="Move Backwards" title="Move Backwards"/><br/>
+ <img id="action-next" class="action" src="action-next.gif"
+ alt="Move Forwards" title="Move Forwards"/><br/>
+ <img id="action-zoom" class="action" src="action-zoom.gif"
+ alt="Zoom Out" title="Zoom Out"/><br/>
+ <img id="action-goto" class="action" src="action-date.gif"
+ alt="Select a Time Span" title="Select a Time Span"/><br/>
+ </div>
+
+ <!-- Date selection drop down -->
+ <div id="goto">
+ <select id="goto-select" size="8">
+ <option value="1800:0">Last 30 Minutes</option>
+ <option value="3600:0">Last Hour</option>
+ <option value="10800:0">Last 3 Hours</option>
+ <option value="21600:0">Last 6 Hours</option>
+ <option value="86400:0">Last Day</option>
+ <option value="172800:86400">Yesterday</option>
+ <option value="259200:0">Last 3 Days</option>
+ <option value="604800:0">Last Week</option>
+ <option value="1209600:0">Last 2 Weeks</option>
+ <option value="1209600:0">Last 2 Weeks</option>
+ <option value="2678400:0">Last Month</option>
+ <option value="5356800:2678400">Month Before Last</option>
+ <option value="7948800:0">Last 3 Months</option>
+ <option value="15897600:0">Last 6 Months</option>
+ <option value="31536000:0">Last Year</option>
+ </select>
+ </div>
+ </body>
+</html>
diff --git a/html/ajax/index.html b/html/ajax/index.html
new file mode 100644
index 0000000..3ed3264
--- /dev/null
+++ b/html/ajax/index.html
@@ -0,0 +1,50 @@
+<html>
+ <head>
+
+ <!-- CUSTOMIZE: Configure these settings -->
+ <script language="javascript">
+
+ // Specify settings here
+ var ENDPOINT = "/rrdui-back/";
+
+ // The background and foregrond colors of the graph
+ var GRAPH_COLOR_BACK = ":252424";
+ var GRAPH_COLOR_FONT = ":E7E1CC";
+
+ // The graph size
+ var GRAPH_WIDTH = 450;
+ var GRAPH_HEIGHT = 120
+
+ // Amount of space on the right and left of a graph
+ var GRAPH_MARGIN_LEFT = 67;
+ var GRAPH_MARGIN_RIGHT = 27;
+
+ </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>
+
+ <table border="0" width="100%">
+ <tr><td id="headers" width="60%">
+ <span class="header" id="header-template">Text</span>
+ </td>
+ <td align="right" id="logo">
+
+ <!-- CUSTOMIZE: Add your logo or title here -->
+ Network Statistics
+
+ </td></tr>
+ </table>
+
+ </td></tr>
+ <tr id="main-row"><td colspan="2" id="main-cell">
+ <iframe id="main-frame" src="frame.html"
+ border="0" frameborder="0"></iframe>
+ </td></tr>
+ </table>
+ <script language="javascript" src="rrdui.js"></script>
+ </body>
+</html>
diff --git a/html/ajax/rrdui.js b/html/ajax/rrdui.js
new file mode 100644
index 0000000..5ed315a
--- /dev/null
+++ b/html/ajax/rrdui.js
@@ -0,0 +1,685 @@
+
+
+/* -----------------------------------------------------------------------------
+ * STARTUP
+ */
+
+// Fix up the ENDPOINT config setting
+if(ENDPOINT.charAt(0) != '/')
+ ENDPOINT = '/' + ENDPOINT;
+
+/* TODO: Check setup variables */
+/* TODO: Loading indicator */
+
+var categoryCurrent = null;
+var categoryArea = document.getElementById("headers");
+var xmlData = null;
+var gdoc = null;
+var gwindow = null;
+
+function init()
+{
+ /* Set by frame.html */
+ if(!window._frameLoaded)
+ {
+ window.setTimeout("init();", 100);
+ return;
+ }
+
+ setupFrame();
+ loadXml(makeEndpointURI(), loadedGraphData);
+}
+
+init();
+
+function loadedGraphData(doc)
+{
+ if(!doc)
+ {
+ displayError("Couldn't load graph data from server", categoryArea);
+ return;
+ }
+
+ xmlData = doc;
+
+ displayCategories();
+ displayCurrentPage();
+}
+
+
+/* -----------------------------------------------------------------------------
+ * GRAPHS
+ */
+
+function getFrameDocument()
+{
+ var frame = document.getElementById("main-frame");
+ /* COMPAT: IE and Mozilla access the content of frames differently */
+ return frame.contentDocument ? frame.contentDocument : frame.contentWindow.document;
+}
+
+function setupFrame()
+{
+ gdoc = getFrameDocument();
+ var frame = document.getElementById("main-frame");
+ /* COMPAT: IE and Mozilla access the content of frames differently */
+ gwindow = frame.contentDocument ? frame.contentDocument.defaultView : frame.contentWindow;
+}
+
+function displayCurrentPage()
+{
+ var area = gdoc.getElementById("graphs");
+ var img;
+
+ /* Hide all images not from this category */
+ var i, images = area.getElementsByTagName("img");
+ for(i = 0; i < images.length; i++)
+ {
+ img = images.item(i);
+ var vis = (img._category == categoryCurrent);
+
+ img.style.display = vis ? "inline" : "none";
+
+ if(vis != img._visible)
+ {
+ /* The graph needs reloading if now turning visible */
+ img._visible = vis;
+ if(vis)
+ reloadGraph(img, false);
+ else
+ img._loading = false;
+ }
+ }
+
+ var graphs = findCategoryGraphs(categoryCurrent);
+ if(!graphs)
+ {
+ displayError("Graph data isn't loaded from server", area);
+ return;
+ }
+
+ var tend = nowTime();
+ var tbeg = tend - 86400; /* One day by default */
+
+ for(i = 0; i < graphs.length; i++)
+ {
+ var child = graphs[i];
+
+ var name = child.getAttribute("name");
+ if(!name || !name.length)
+ continue;
+
+ /* Make sure we don't already have this graph */
+ var id = "graph-" + categoryCurrent + "-" + name;
+ img = gdoc.getElementById(id);
+ if(img != null)
+ continue;
+
+ var interval = child.getAttribute("interval");
+ if(!interval || isNaN(interval) || interval <= 0)
+ interval = 0;
+ interval = Number(interval);
+
+ img = gdoc.createElement("img");
+
+ var title = child.getAttribute("title");
+ if(title && title.length)
+ {
+ img.setAttribute("title", child.getAttribute("title"));
+ img.setAttribute("alt", child.getAttribute("title"));
+ }
+ else
+ img.setAttribute("alt", "Graph");
+
+ img.className = "graph";
+ img.id = id;
+ img.style.cursor = "crosshair";
+
+ area.appendChild(img);
+
+ img._category = categoryCurrent;
+ img._name = name;
+ img._tbeg = tbeg;
+ img._tend = tend;
+ img._tinterval = interval;
+ img._visible = true;
+ img._loading = false;
+
+ reloadGraph(img, false);
+
+ img.onmousedown = function(evt)
+ { actionGotoCancel(); return zoomGraphStart(evt || gwindow.event); }
+ img.onmouseover = function(evt)
+ { return actionsDisplay(gwindow.event ? gwindow.event : evt); }
+
+ /* Setup for an auto scroll */
+ if(interval)
+ {
+ /* Bump up auto scroll interval to 5 seconds */
+ var interval = img._tinterval < 5 ? 5 : img._tinterval;
+ window.setInterval(autoScroll, interval * 1000, img);
+ }
+ }
+}
+
+function reloadGraph(img, force)
+{
+ img._last = nowTime();
+
+ if(!img._visible)
+ return;
+
+ /* TODO: We should encode colons properly */
+
+ var uri = makeEndpointURI() + "/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;
+
+ if(force || img.getAttribute("src") != uri)
+ {
+ img._loading = true;
+ updateCursors(img);
+
+ /* HACK: The onload event for IMG is called with a strange event target */
+ img.onload = new Function("reloadedGraph('" + img.id + "');");
+ img.setAttribute("src", uri);
+ }
+}
+
+function reloadedGraph(id)
+{
+ var img = getFrameDocument().getElementById(id);
+ img._loading = false;
+ updateCursors(img);
+}
+
+function updateCursors(img)
+{
+ if(img._actions)
+ {
+ var i, acts = img._actions.getElementsByTagName("img");
+ for(i = 0; i < acts.length; i++)
+ acts.item(i).style.cursor = img._loading ? "wait" : "pointer";
+ }
+
+ img.style.cursor = img._loading ? "wait" : "crosshair";
+}
+
+function autoScroll(img)
+{
+ /* Don't auto scroll if we have loading issues */
+ if(img._loading)
+ return;
+
+ /* If we're displaying 'now' somewhere in the graph... */
+ var now = nowTime();
+ if((img._tend + (img._tinterval * 2)) >= now)
+ {
+ var diff = now - img._last;
+
+ if(diff > 0)
+ {
+ /* ... then scroll by X seconds to scroll the graph */
+ img._tbeg += diff;
+ img._tend += diff;
+ reloadGraph(img, false);
+ }
+ }
+}
+
+
+
+/* -----------------------------------------------------------------------------
+ * ZOOM
+ */
+
+function zoomGraphStart(evt)
+{
+ /* COMPAT: In IE the left button is 1, Mozilla is 0 */
+ var lbutton = gwindow.event ? 1 : 0;
+
+ if(evt.button != lbutton)
+ return;
+
+ /* COMPAT: Target is handled differently in Internet nastiness */
+ var img = evt.target || evt.srcElement;
+
+ if(img.className != "graph")
+ return;
+
+ var zoom = gdoc.getElementById("zoom");
+
+ zoom._img = img;
+ zoom._zooming = true;
+ zoom._from = evt.clientX + gdoc.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(gwindow.event ? gwindow.event : evt); }
+ zoom.onmouseup = zoom._img.onmouseup = function(evt)
+ { return zoomGraphDone(gwindow.event ? gwindow.event : evt); }
+ document.onmouseup = gdoc.onmouseup = function(evt)
+ { return zoomGraphDone(gwindow.event ? gwindow.event : evt); }
+
+ cancelEvent(evt);
+ return false;
+}
+
+function zoomGraphUpdate(evt)
+{
+ var zoom = gdoc.getElementById("zoom");
+ if(!zoom._zooming || !zoom._img)
+ return;
+
+ var to = evt.clientX + gdoc.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";
+
+ cancelEvent(evt);
+ return false;
+}
+
+function zoomGraphDone(evt)
+{
+ var zoom = gdoc.getElementById("zoom");
+ if(!zoom._zooming)
+ return;
+
+ /* COMPAT: Target is handled differently in Internet nastiness */
+ var targ = evt.target || evt.srcElement;
+ if(targ == zoom || targ == zoom._img)
+ zoomGraphSelect(zoom);
+
+ zoomGraphCancel(evt);
+}
+
+function zoomGraphSelect(zoom)
+{
+ 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 - left > 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, true);
+}
+
+function zoomGraphCancel(evt)
+{
+ var zoom = gdoc.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, true);
+}
+
+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, true);
+}
+
+function actionsDisplay(evt)
+{
+ /* COMPAT: Target is handled differently in Internet nastiness */
+ var img = evt.target || evt.srcElement;
+
+ if(img.className != "graph")
+ return;
+
+ var actions = gdoc.getElementById("actions");
+
+ /* Disconnect from previous image */
+ if(actions._img)
+ actions._img._actions = null;
+
+ /* Attach to this image */
+ actions._img = img;
+ img._actions = actions;
+ updateCursors(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(gwindow.event ? gwindow.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(gwindow.event ? gwindow.event : evt); }
+ }
+}
+
+function actionsHide(evt)
+{
+ var actions = gdoc.getElementById("actions");
+ if(!actions._display)
+ return;
+
+ var x = evt.clientX + gdoc.body.scrollLeft;
+ var y = evt.clientY + gdoc.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;
+ img._actions = null;
+ actions._img = null;
+ actions._display = false;
+}
+
+function actionRun(evt)
+{
+ /* COMPAT: Target is handled differently in Internet nastiness */
+ var act = evt.target || evt.srcElement;
+
+ if(act.className != "action")
+ return;
+
+ var actions = gdoc.getElementById("actions");
+ if(!actions._display)
+ return;
+
+ 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 = gdoc.getElementById("goto");
+
+ /* TODO: Make numbers constants */
+ got.style.top = img.offsetTop + 40;
+ got.style.left = (img.offsetLeft + img.width) - 160;
+ got.style.width = 120;
+ got.style.display = "block";
+
+ var sel = gdoc.getElementById("goto-select");
+ sel.onchange = actionGotoChange;
+ sel._img = img;
+ sel.value = null;
+ sel.selectedIndex = -1;
+}
+
+function actionGotoCancel()
+{
+ var got = gdoc.getElementById("goto");
+ got.style.display = "none";
+}
+
+function actionGotoChange()
+{
+ var sel = gdoc.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, true);
+
+ actionGotoCancel();
+}
+
+/* -----------------------------------------------------------------------------
+ * GROUP CATEGORIES
+ */
+
+function findCategoryGraphs(cat)
+{
+ var i;
+
+ if(!xmlData)
+ return null;
+
+ var ret = new Array();
+ var graphs = xmlData.getElementsByTagName("graph");
+ for(i = 0; i < graphs.length; i++)
+ {
+ if(graphs.item(i).getAttribute("category") == cat)
+ ret.push(graphs.item(i));
+ }
+ return ret;
+}
+
+function changeCategory(evt)
+{
+ var header = evt.target || evt.srcElement;
+ categoryCurrent = header.getAttribute("category");
+ displayCurrentPage();
+}
+
+function displayCategories()
+{
+ var cats = new Object();
+ var cat, i, name;
+
+ /* Get the template and clean it up a bit */
+ var template = document.getElementById("header-template");
+
+ var graphs = xmlData.getElementsByTagName("graph");
+ for(i = 0; i < graphs.length; i++)
+ {
+ cat = graphs.item(i).getAttribute("category");
+ if(!cat)
+ {
+ cat = "Other";
+ graphs.item(i).setAttribute("category", "Other");
+ }
+ cats[cat] = true;
+ }
+
+ for(name in cats)
+ {
+ if(!categoryCurrent)
+ categoryCurrent = name;
+
+ var el = template.cloneNode(false);
+ el.removeAttribute("id");
+ el.appendChild(document.createTextNode(name));
+ el.setAttribute("category", name);
+
+ el.onclick = function(evt)
+ { return changeCategory(evt || window.event); }
+
+ categoryArea.insertBefore(el, template);
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * 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.className = "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);
+}
+
+function debug(msg)
+{
+ setTimeout(function() { throw new Error("[debug] " + msg); }, 0);
+}
+
+function makeEndpointURI()
+{
+ /* TODO: This should use the scheme of the current URI */
+ return "http://" + document.location.host + ENDPOINT;
+}
+
+function cancelEvent(evt)
+{
+ /* COMPAT: Totally different for IE and mozilla */
+ if(evt.preventDefault)
+ evt.preventDefault();
+ if(evt.stopPropagation)
+ evt.stopPropagation();
+ if("cancelBubble" in evt)
+ evt.cancelBubble = true;
+ if("returnValue" in evt)
+ evt.returnValue = false;
+}
diff --git a/html/ajax/style.css b/html/ajax/style.css
new file mode 100644
index 0000000..b72e4ca
--- /dev/null
+++ b/html/ajax/style.css
@@ -0,0 +1,164 @@
+
+/* -----------------------------------------------------------------------------
+ * 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%;
+}
+
+#logo
+{
+ font-size: 16pt;
+ font-weight: bold;
+}
+
+/* -----------------------------------------------------------------------------
+ * 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
+ { visibility: hidden; }
+
+/* -----------------------------------------------------------------------------
+ * GRAPHS/FRAME
+ */
+
+body.in-frame
+{
+ background-color: #252424;
+}
+
+img.graph
+{
+ 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: 120px;
+ display: none;
+}
+
+.action
+{
+ margin: 3px;
+ cursor: pointer;
+
+ /* COMPAT: IE doesn't support :hover on anything but links
+ so transparency has no use whatsover */
+
+ -moz-opacity: 0.75;
+ -khtml-opacity: 0.75;
+ opacity: 0.75;
+}
+
+img.action:hover
+{
+ -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;
+}
+