http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/app/search_view.js ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/app/search_view.js b/htrace-webapp/src/main/webapp/app/search_view.js new file mode 100644 index 0000000..aeb4273 --- /dev/null +++ b/htrace-webapp/src/main/webapp/app/search_view.js @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; +htrace.SearchView = Backbone.View.extend({ + initialize : function() { + this.predicateViews = []; + this.highestPredicateIndex = 0; + this.searchInProgress = false; + this.searchResults = new htrace.SearchResults(); + this.resultsView = new htrace.SearchResultsView({ + searchResults: this.searchResults, + el: "#resultsView" + }); + }, + + events: { + "click #searchButton": "searchHandler", + "click #clearButton": "clearHandler", + "click .add-field": "dropdownHandler", + "blur #begin": "blurBeginHandler", + "blur #end": "blurEndHandler", + "click #zoomButton": "zoomHandler" + }, + + searchHandler: function(e){ + e.preventDefault(); + + this.doSearch(e.ctrlKey); + }, + + clearHandler: function(e){ + e.preventDefault(); + + this.resultsView.clearHandler(true); + }, + + doSearch: function(showDebug){ + if (this.searchInProgress) { + console.log("Can't start a new search while another one is in " + + "progress."); + return false; + } + + // Check if there are no search criteria. + if (this.predicateViews.length == 0) { + htrace.showModalWarning("No Search Criteria Specified", + "You have not specified any search criteria. " + + "Use the 'Add Predicate' button to specify what to search for."); + return false; + } + + // Build the predicate array. + predicates = [] + var predicateViewsLen = this.predicateViews.length; + for (var i = 0; i < predicateViewsLen; i++) { + var predicateView = this.predicateViews[i]; + try { + predicates.push(predicateView.getPredicate()); + } catch(err) { + htrace.showModalWarning("Search Field Validation Error", + "Invalid search string for the '" + predicateView.ptype.name + + "' field.<p/>" + err); + return false; + } + } + var queryJson = { + pred: predicates, + lim: 20 + }; + // If there are existing search results, we want results which "come after" + // those. So pass the last span we saw as a continuation token. + if (this.searchResults.size() > 0) { + queryJson.prev = + this.searchResults.at(this.searchResults.size() - 1).unparse(); + } + var searchView = this; + var queryResults = new htrace.QueryResults({queryJson: queryJson}); + console.log("Starting span query " + queryResults.url()); + this.searchInProgress = true; + queryResults.fetch({ + success: function(model, response, options){ + var firstResults = (searchView.searchResults.size() === 0); + console.log("Success on span query " + queryResults.url() + ": got " + + queryResults.size() + " result(s). firstResults=" + firstResults); + searchView.searchResults.add(queryResults.models); + if (firstResults) { + // After the initial search, zoom to fit everything. + // On subsequent searches, we leave the viewport alone. + searchView.resultsView.zoomHandler(); + } + searchView.searchInProgress = false; + if (showDebug) { + htrace.showModalWarning("Search Debug", + "This is the search debug box, accessible by holding down the " + + "control key while clicking the search button.<p/>" + + "<h3>Query JSON</h3><pre>" + queryResults.prettyQueryString() + + "</pre><p/><h3>Response JSON</h3><pre>" + + JSON.stringify(queryResults, null, 2) + "</pre><p/>"); + } else if (queryResults.size() == 0) { + if (firstResults) { + htrace.showModalWarning("No Results Found", + "No results were found for your query.<p/>"); + } else { + htrace.showModalWarning("No Additional Results Found", + "No additional results were found for your query.<p/>"); + } + } + searchView.resultsView.render(); + }, + error: function(model, response, options){ + searchView.searchResults.reset(); + var err = "Error " + JSON.stringify(response, null, 2) + + " on span query " + queryResults.url(); + console.log(err); + alert(err); + searchView.searchInProgress = false; + } + }); + return false; + }, + + dropdownHandler: function(e){ + e.preventDefault(); + var text = $(e.target).text(); + var ptype = htrace.parsePType(text); + if (!ptype) { + alert("Unable to parse predicate type '" + text + "'"); + return false; + } + var index = this.highestPredicateIndex; + this.highestPredicateIndex++; + var el = "pred" + index; + $("#predicates").append('<div id="' + el + '"/></div>'); + predicateView = new htrace.PredicateView({ + el: "#" + el, + index: index, + ptype: ptype, + searchView: this + }); + this.predicateViews.push(predicateView); + predicateView.render(); + return true; + }, + + blurBeginHandler: function(e) { + return this.resultsView.handleBeginOrEndChange(e, "begin"); + }, + + blurEndHandler: function(e) { + return this.resultsView.handleBeginOrEndChange(e, "end"); + }, + + zoomHandler: function(e) { + e.preventDefault(); + this.resultsView.zoomHandler(); + }, + + removePredicateView: function(predicateView) { + this.predicateViews = _.without(this.predicateViews, predicateView); + }, + + render: function() { + this.$el.html(_.template($("#search-view-template").html()) + ({ model : this.model })) + this.resultsView.render(); + console.log("SearchView#render"); + return this; + }, + + close: function() { + console.log("SearchView#close") + while (this.predicateViews.length > 0) { + this.predicateViews[0].remove(); + } + this.resultsView.remove(); + this.resultsView = null; + this.undelegateEvents(); + } +});
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/app/server_info.js ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/app/server_info.js b/htrace-webapp/src/main/webapp/app/server_info.js new file mode 100644 index 0000000..b03f706 --- /dev/null +++ b/htrace-webapp/src/main/webapp/app/server_info.js @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// htraced ServerInfo sent back from /serverInfo. +// See rest.go. +htrace.ServerInfo = Backbone.Model.extend({ + defaults: { + "ReleaseVersion": "unknown", + "GitVersion": "unknown", + }, + + url: function() { + return "server/info"; + } +}); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/app/span.js ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/app/span.js b/htrace-webapp/src/main/webapp/app/span.js new file mode 100644 index 0000000..cd87543 --- /dev/null +++ b/htrace-webapp/src/main/webapp/app/span.js @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +// The invalid span ID, which is all zeroes. +htrace.INVALID_SPAN_ID = "00000000000000000000000000000000"; + +// Convert an array of htrace.Span models into a comma-separated string. +htrace.spanModelsToString = function(spans) { + var ret = ""; + var prefix = ""; + for (var i = 0; i < spans.length; i++) { + ret += prefix + JSON.stringify(spans[i].unparse()); + prefix = ", "; + } + return ret; +}; + +// Convert an array of return results from ajax calls into an array of +// htrace.Span models. +htrace.parseMultiSpanAjaxQueryResults = function(ajaxCalls) { + var parsedSpans = []; + for (var i = 0; i < ajaxCalls.length; i++) { + var text = ajaxCalls[i][0]; + var result = ajaxCalls[i][1]; + if (ajaxCalls[i]["status"] != "200") { + throw "ajax error: " + ajaxCalls[i].statusText; + } + var parsedSpan = new htrace.Span({}); + try { + parsedSpan.parse(ajaxCalls[i].responseJSON, {}); + } catch (e) { + throw "span parse error: " + e; + } + parsedSpans.push(parsedSpan); + } + return parsedSpans; +}; + +htrace.sortSpansByBeginTime = function(spans) { + return spans.sort(function(a, b) { + if (a.get("begin") < b.get("begin")) { + return -1; + } else if (a.get("begin") > b.get("begin")) { + return 1; + } else { + return 0; + } + }); +}; + +htrace.getReifiedParents = function(span) { + return span.get("reifiedParents") || []; +}; + +htrace.getReifiedChildren = function(span) { + return span.get("reifiedChildren") || []; +}; + +htrace.Span = Backbone.Model.extend({ + // Parse a span sent from htraced. + // We use more verbose names for some attributes. + // Missing attributes are treated as zero or empty. Numerical attributes are + // forced to be numbers. + parse: function(response, options) { + var span = {}; + this.set("spanId", response.a ? response.a : htrace.INVALID_SPAN_ID); + this.set("tracerId", response.r ? response.r : ""); + this.set("parents", response.p ? response.p : []); + this.set("description", response.d ? response.d : ""); + this.set("begin", response.b ? parseInt(response.b, 10) : 0); + this.set("end", response.e ? parseInt(response.e, 10) : 0); + if (response.t) { + var t = response.t.sort(function(a, b) { + if (a.t < b.t) { + return -1; + } else if (a.t > b.t) { + return 1; + } else { + return 0; + } + }); + this.set("timeAnnotations", t); + } else { + this.set("timeAnnotations", []); + } + this.set("infoAnnotations", response.n ? response.n : {}); + this.set("selected", false); + + // reifiedChildren starts off as null and will be filled in as needed. + this.set("reifiedChildren", null); + + // If there are parents, reifiedParents starts off as null. Otherwise, we + // know it is the empty array. + this.set("reifiedParents", (this.get("parents").length == 0) ? [] : null); + + return span; + }, + + // Transform a span model back into a JSON string suitable for sending over + // the wire. + unparse: function() { + var obj = { }; + if (!(this.get("spanId") === htrace.INVALID_SPAN_ID)) { + obj.a = this.get("spanId"); + } + if (!(this.get("tracerId") === "")) { + obj.r = this.get("tracerId"); + } + if (this.get("parents").length > 0) { + obj.p = this.get("parents"); + } + if (this.get("description").length > 0) { + obj.d = this.get("description"); + } + if (this.get("begin") > 0) { + obj.b = this.get("begin"); + } + if (this.get("end") > 0) { + obj.e = this.get("end"); + } + if (this.get("timeAnnotations").length > 0) { + obj.t = this.get("timeAnnotations"); + } + if (_.size(this.get("infoAnnotations")) > 0) { + obj.n = this.get("infoAnnotations"); + } + return obj; + }, + + // + // Although the parent IDs are always present in the 'parents' field of the + // span, sometimes we need the actual parent span models. In that case we + // must "reify" them (make them real). + // + // This functionReturns a jquery promise which reifies all the parents of this + // span and stores them into reifiedParents. The promise returns the empty + // string on success, or an error string on failure. + // + reifyParents: function() { + var span = this; + var numParents = span.get("parents").length; + var ajaxCalls = []; + // Set up AJAX queries to reify the parents. + for (var i = 0; i < numParents; i++) { + ajaxCalls.push($.ajax({ + url: "span/" + span.get("parents")[i], + data: {}, + contentType: "application/json; charset=utf-8", + dataType: "json" + })); + } + var rootDeferred = jQuery.Deferred(); + $.when.apply($, ajaxCalls).then(function() { + var reifiedParents = []; + try { + reifiedParents = htrace.parseMultiSpanAjaxQueryResults(ajaxCalls); + } catch (e) { + rootDeferred.resolve("Error reifying parents for " + + span.get("spanId") + ": " + e); + return; + } + reifiedParents = htrace.sortSpansByBeginTime(reifiedParents); + // The current span is a child of the reified parents. There may be other + // children of those parents, but we are ignoring that here. By making + // this non-null, the "expand children" button will not appear for these + // paren spans. + for (var j = 0; j < reifiedParents.length; j++) { + reifiedParents[j].set("reifiedChildren", [span]); + } + console.log("Setting reified parents for " + span.get("spanId") + + " to " + htrace.spanModelsToString (reifiedParents)); + span.set("reifiedParents", reifiedParents); + rootDeferred.resolve(""); + }); + return rootDeferred.promise(); + }, + + // + // The span itself does not contain its children. However, the server has an + // index which can be used to easily find the children of a particular span. + // + // This function returns a jquery promise which reifies all the children of + // this span and stores them into reifiedChildren. The promise returns the + // empty string on success, or an error string on failure. + // + reifyChildren: function() { + var rootDeferred = jQuery.Deferred(); + var span = this; + $.ajax({ + url: "span/" + span.get("spanId") + "/children?lim=50", + data: {}, + contentType: "application/json; charset=utf-8", + dataType: "json" + }).done(function(childIds) { + var ajaxCalls = []; + for (var i = 0; i < childIds.length; i++) { + ajaxCalls.push($.ajax({ + url: "span/" + childIds[i], + data: {}, + contentType: "application/json; charset=utf-8", + dataType: "json" + })); + }; + $.when.apply($, ajaxCalls).then(function() { + var reifiedChildren; + try { + reifiedChildren = htrace.parseMultiSpanAjaxQueryResults(ajaxCalls); + } catch (e) { + reifiedChildren = rootDeferred.resolve("Error reifying children " + + "for " + span.get("spanId") + ": " + e); + return; + } + reifiedChildren = htrace.sortSpansByBeginTime(reifiedChildren); + // The current span is a parent of the new child. + // There may be other parents, but we are ignoring that here. + // By making this non-null, the "expand parents" button will not + // appear for these child spans. + for (var j = 0; j < reifiedChildren.length; j++) { + reifiedChildren[j].set("reifiedParents", [span]); + } + console.log("Setting reified children for " + span.get("spanId") + + " to " + htrace.spanModelsToString (reifiedChildren)); + span.set("reifiedChildren", reifiedChildren); + rootDeferred.resolve(""); + }); + }).fail(function(statusData) { + // Check if the /children query failed. + rootDeferred.resolve("Error querying children of " + + span.get("spanId") + ": got " + statusData); + return; + }); + return rootDeferred.promise(); + }, + + // Get the earliest begin time of this span or any of its reified parents or + // children. + getEarliestBegin: function() { + var earliestBegin = this.get("begin"); + htrace.treeTraverseDepthFirstPre(this, htrace.getReifiedParents, 0, + function(span, depth) { + earliestBegin = Math.min(earliestBegin, span.get("begin")); + }); + htrace.treeTraverseDepthFirstPre(this, htrace.getReifiedChildren, 0, + function(span, depth) { + earliestBegin = Math.min(earliestBegin, span.get("begin")); + }); + return earliestBegin; + }, + + // Get the earliest begin time of this span or any of its reified parents or + // children. + getLatestEnd: function() { + var latestEnd = this.get("end"); + htrace.treeTraverseDepthFirstPre(this, htrace.getReifiedParents, 0, + function(span, depth) { + latestEnd = Math.max(latestEnd, span.get("end")); + }); + htrace.treeTraverseDepthFirstPre(this, htrace.getReifiedChildren, 0, + function(span, depth) { + latestEnd = Math.max(latestEnd, span.get("end")); + }); + return latestEnd; + }, +}); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/app/span_details_view.js ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/app/span_details_view.js b/htrace-webapp/src/main/webapp/app/span_details_view.js new file mode 100644 index 0000000..9a37055 --- /dev/null +++ b/htrace-webapp/src/main/webapp/app/span_details_view.js @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +htrace.SpanDetailsView = Backbone.View.extend({ + initialize: function(options) { + this.el = options.el; + this.model = options.model; + } + + render: function() { + this.$el.html(_.template($("#about-view-template").html()) + ({ model : this.model })); + console.log("AboutView#render"); + return this; + }, + + close: function() { + console.log("AboutView#close") + this.undelegateEvents(); + } +}); http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/app/span_group_widget.js ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/app/span_group_widget.js b/htrace-webapp/src/main/webapp/app/span_group_widget.js new file mode 100644 index 0000000..ad0b482 --- /dev/null +++ b/htrace-webapp/src/main/webapp/app/span_group_widget.js @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +// Widget containing a group of trace spans displayed on the canvas. +htrace.SpanGroupWidget = function(params) { + this.draw = function() { + this.ctx.save(); + this.ctx.fillStyle="#ffffff"; + this.ctx.fillRect(this.x0, this.y0, this.xF - this.x0, this.yF - this.y0); + this.ctx.strokeStyle="#aaaaaa"; + this.ctx.beginPath(); + this.ctx.moveTo(this.x0, this.y0); + this.ctx.lineTo(this.xF, this.y0); + this.ctx.stroke(); + this.ctx.beginPath(); + this.ctx.moveTo(this.x0, this.yF); + this.ctx.lineTo(this.xF, this.yF); + this.ctx.stroke(); + this.ctx.restore(); + return true; + }; + + this.createSpanWidget = function(node, indentLevel, + allowUpButton, allowDownButton) { + new htrace.SpanWidget({ + manager: this.manager, + ctx: this.ctx, + span: node, + x0: this.x0, + xB: this.xB, + xD: this.xD, + xF: this.xF, + xT: this.childIndent * indentLevel, + y0: this.spanY, + yF: this.spanY + this.spanWidgetHeight, + allowUpButton: allowUpButton, + allowDownButton: allowDownButton, + begin: this.begin, + end: this.end + }); + this.spanY += this.spanWidgetHeight; + } + + this.handle = function(e) { + switch (e.type) { + case "draw": + this.draw(); + return true; + } + } + + for (var k in params) { + this[k]=params[k]; + } + this.manager.register("draw", this); + this.spanY = this.y0 + 4; + + // Figure out how much to indent each child's description text. + this.childIndent = Math.max(10, (this.xF - this.xD) / 50); + + // Get the maximum depth of the parents tree to find out how far to indent. + var parentTreeHeight = + htrace.treeHeight(this.span, htrace.getReifiedParents); + + // Traverse the parents tree upwards. + var thisWidget = this; + htrace.treeTraverseDepthFirstPost(this.span, htrace.getReifiedParents, 0, + function(node, depth) { + if (depth > 0) { + thisWidget.createSpanWidget(node, + parentTreeHeight - depth, true, false); + } + }); + thisWidget.createSpanWidget(this.span, parentTreeHeight, true, true); + // Traverse the children tree downwards. + htrace.treeTraverseDepthFirstPre(this.span, htrace.getReifiedChildren, 0, + function(node, depth) { + if (depth > 0) { + thisWidget.createSpanWidget(node, + parentTreeHeight + depth, false, true); + } + }); + this.yF = this.spanY + 4; + return this; +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/app/span_widget.js ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/app/span_widget.js b/htrace-webapp/src/main/webapp/app/span_widget.js new file mode 100644 index 0000000..50bea91 --- /dev/null +++ b/htrace-webapp/src/main/webapp/app/span_widget.js @@ -0,0 +1,309 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +htrace.showSpanDetails = function(span) { + var info = { + spanID: span.get("spanID"), + begin: htrace.dateToString(span.get("begin"), 10), + end: htrace.dateToString(span.get("end"), 10), + duration: ((span.get("end") - span.get("begin")) + " ms") + }; + var explicitOrder = { + spanId: 1, + begin: 2, + end: 3, + duration: 4 + }; + keys = ["duration"]; + for(k in span.attributes) { + if (k == "reifiedChildren") { + continue; + } + if (k == "reifiedParents") { + continue; + } + if (k == "selected") { + continue; + } + if (k == "timeAnnotations") { + // For timeline annotations, make the times into top-level keys. + var timeAnnotations = span.get("timeAnnotations"); + for (var i = 0; i < timeAnnotations.length; i++) { + var key = htrace.dateToString(timeAnnotations[i].t); + keys.push(key); + info[key] = timeAnnotations[i].m; + explicitOrder[key] = 200; + } + continue; + } + if (k == "infoAnnotations") { + // For info annotations, move the keys to the top level. + // Surround them in brackets to make it clear that they are + // user-defined. + var infoAnnotations = span.get("infoAnnotations"); + _.each(infoAnnotations, function(value, key) { + key = "[" + key + "]"; + keys.push(key); + info[key] = value; + explicitOrder[key] = 200; + }); + continue; + } + keys.push(k); + if (info[k] == null) { + info[k] = span.get(k); + } + } + // We sort the keys so that the stuff we want at the top appears at the top, + // and everything else is in alphabetical order. + keys = keys.sort(function(a, b) { + var oa = explicitOrder[a] || 100; + var ob = explicitOrder[b] || 100; + if (oa < ob) { + return -1; + } else if (oa > ob) { + return 1; + } else if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } + }); + var len = keys.length; + var h = '<table style="table-layout:fixed;width:100%;word-wrap:break-word">'; + for (i = 0; i < len; i++) { + // Make every other row grey to improve visibility. + var colorString = ((i%2) == 1) ? "#f1f1f1" : "#ffffff"; + h += _.template($("#table-row-template").html())( + {bgcolor: colorString, key: keys[i], val: info[keys[i]]}); + } + h += '</table>'; + htrace.showModal(_.template($("#modal-table-template").html())( + {title: "Span Details", body: h})); +}; + +// Widget containing the trace span displayed on the canvas. +htrace.SpanWidget = function(params) { + this.draw = function() { + this.drawBackground(); + this.drawTracerId(); + this.drawDescription(); + }; + + // Draw the background of this span widget. + this.drawBackground = function() { + this.ctx.save(); + if (this.span.get("selected")) { + this.ctx.fillStyle="#ffccff"; + } else { + this.ctx.fillStyle="#ffffff"; + } + this.ctx.fillRect(this.x0, this.y0, this.xSize, this.ySize); + this.ctx.restore(); + } + + // Draw process ID text. + this.drawTracerId = function() { + this.ctx.save(); + this.ctx.fillStyle="#000000"; + this.ctx.font = (this.ySize - 2) + "px sans-serif"; + this.ctx.beginPath(); + this.ctx.rect(this.x0, this.y0, this.xB - this.x0, this.ySize); + this.ctx.clip(); + this.ctx.fillText(this.span.get('tracerId'), this.x0, this.yF - 4); + this.ctx.restore(); + }; + + // Draw the span description + this.drawDescription = function() { + // Draw the light blue bar representing time. + this.ctx.save(); + this.ctx.beginPath(); + this.ctx.rect(this.xD, this.y0, this.xF - this.xD, this.ySize); + this.ctx.clip(); + this.ctx.strokeStyle="#000000"; + this.ctx.fillStyle="#a7b7ff"; + var beginX = this.timeToPosition(this.span.get('begin')); + var endX = this.timeToPosition(this.span.get('end')); + + // If the span is completely off the screen, draw a diamond at either the + // beginning or the end of the bar to indicate whether it's too early or too + // late to be seen. + if (endX < this.x0) { + beginX = this.xD; + endX = this.xD; + } + if (beginX > this.xF) { + beginX = this.xF; + endX = this.xF; + } + + var gapY = 2; + var epsilon = Math.max(2, Math.floor(this.xSize / 1000)); + if (endX - beginX < epsilon) { + // The time interval is too narrow to see. Draw a diamond on the point instead. + this.ctx.beginPath(); + this.ctx.moveTo(beginX, this.y0 + gapY); + this.ctx.lineTo(beginX + (Math.floor(this.ySize / 2) - gapY), + this.y0 + Math.floor(this.ySize / 2)); + this.ctx.lineTo(beginX, this.yF - gapY); + this.ctx.lineTo(beginX - (Math.floor(this.ySize / 2) - gapY), + this.y0 + Math.floor(this.ySize / 2)); + this.ctx.closePath(); + this.ctx.fill(); + } else { + // Draw a bar from the start time to the end time. +// console.log("beginX=" + beginX + ", endX=" + endX + +// ", begin=" + this.span.get('begin') + ", end=" + this.span.get('end')); + this.ctx.fillRect(beginX, this.y0 + gapY, endX - beginX, + this.ySize - (gapY * 2)); + + // Draw a dots showing time points where annotations are. + var annotations = this.span.get('timeAnnotations'); + var annotationY = this.y0 + gapY; + var annotationW = 4; + var annotationH = (this.ySize - (gapY * 2)) / 2; + this.ctx.fillStyle="#419641"; + for (var i = 0; i < annotations.length; i++) { + this.ctx.fillRect(this.timeToPosition(annotations[i].t), annotationY, + annotationW, annotationH); + } + } + + // Draw description text + this.ctx.fillStyle="#000000"; + this.ctx.font = (this.ySize - gapY) + "px sans-serif"; + this.ctx.fillText(this.span.get('description'), + this.xD + this.xT, + this.yF - gapY - 2); + + this.ctx.restore(); + }; + + // Convert a time in milliseconds since the epoch to an x position. + this.timeToPosition = function(time) { + return this.xD + + (((time - this.begin) * (this.xF - this.xD)) / + (this.end - this.begin)); + }; + + this.handle = function(e) { + switch (e.type) { + case "mouseDown": + if (!htrace.inBoundingBox(e.x, e.y, + this.x0, this.xF, this.y0, this.yF)) { + return true; + } + if (e.raw.ctrlKey) { + // If the control key is pressed, we toggle the current selection. + // The user can create multiple selections this way. + if (this.span.get("selected")) { + this.span.set("selected", false); + } else { + this.span.set("selected", true); + } + } else { + var that = this; + this.manager.searchResultsView.applyToAllSpans(function(span) { + // Note: we don't want to set the selection state unless we need + // to. Setting the state (even to the same thing it already is) + // triggers a full re-render, if the span is one in the results + // collection. A full re-render slows us down and disrupts events + // like double-clicking. + if (that.span === span) { + if (!span.get("selected")) { + span.set("selected", true); + } + } else if (span.get("selected")) { + span.set("selected", false); + } + }); + } + return true; + case "draw": + this.draw(); + return true; + case "dblclick": + if (htrace.inBoundingBox(e.x, e.y, + this.x0, this.xF, this.y0, this.yF)) { + htrace.showSpanDetails(this.span); + } + return true; + } + }; + + for (var k in params) { + this[k]=params[k]; + } + this.xSize = this.xF - this.x0; + this.ySize = this.yF - this.y0; + this.xDB = this.xD - this.xB; + this.manager.register("draw", this); + + var widget = this; + if ((this.span.get("reifiedParents") == null) && (this.allowUpButton)) { + new htrace.TriangleButton({ + ctx: this.ctx, + manager: this.manager, + direction: "up", + x0: this.xB + 2, + xF: this.xB + (this.xDB / 2) - 2, + y0: this.y0 + 2, + yF: this.yF - 2, + callback: function() { + $.when(widget.span.reifyParents()).done(function (result) { + console.log("reifyParents: result was '" + result + "'"); + if (result != "") { + alert(result); + } else { + widget.manager.searchResultsView.render(); + } + }); + }, + }); + } + if ((this.span.get("reifiedChildren") == null) && (this.allowDownButton)) { + new htrace.TriangleButton({ + ctx: this.ctx, + manager: this.manager, + direction: "down", + x0: this.xB + (this.xDB / 2) + 2, + xF: this.xD - 2, + y0: this.y0 + 2, + yF: this.yF - 2, + callback: function() { + $.when(widget.span.reifyChildren()).done(function (result) { + console.log("reifyChildren: result was '" + result + "'"); + if (result != "") { + alert(result); + } else { + widget.manager.searchResultsView.render(); + } + }); + }, + }); + } + this.manager.register("mouseDown", this); + this.manager.register("dblclick", this); + return this; +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/app/string.js ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/app/string.js b/htrace-webapp/src/main/webapp/app/string.js new file mode 100644 index 0000000..c9c514b --- /dev/null +++ b/htrace-webapp/src/main/webapp/app/string.js @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +// Parse an ISO8601 date string into a moment.js object. +htrace.parseDate = function(val) { + if (val.match(/^[0-9]([0-9]*)$/)) { + // Treat an all-numeric field as UTC milliseconds since the epoch. + return moment.utc(parseInt(val, 10)); + } + // Look for approved date formats. + var toTry = [ + "YYYY-MM-DDTHH:mm:ss,SSS", + "YYYY-MM-DDTHH:mm:ss", + "YYYY-MM-DDTHH:mm", + "YYYY-MM-DD" + ]; + for (var i = 0; i < toTry.length; i++) { + var m = moment.utc(val, toTry[i], true); + if (m.isValid()) { + return m; + } + } + throw "Please enter the date either as YYYY-MM-DDTHH:mm:ss,SSS " + + "in UTC, or as the number of milliseconds since the epoch."; +}; + +// Convert a moment.js moment into an ISO8601-style date string. +htrace.dateToString = function(val) { + return moment.utc(val).format("YYYY-MM-DDTHH:mm:ss,SSS"); +}; + +// Normalize a span ID into the format the server expects to see-- +// i.e. something like 00000000000000000000000000000000. +htrace.normalizeSpanId = function(str) { + if (str.length != 36) { + throw "The length of '" + str + "' was " + str.length + + ", but span IDs must be 36 characters long."; + } + if (str.search(/[^0-9a-fA-F]/) != -1) { + throw "Span IDs must contain only hexadecimal digits, but '" + str + + "' contained invalid characters."; + } + return str; +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/app/time_cursor.js ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/app/time_cursor.js b/htrace-webapp/src/main/webapp/app/time_cursor.js new file mode 100644 index 0000000..1caaa9a --- /dev/null +++ b/htrace-webapp/src/main/webapp/app/time_cursor.js @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +// Draws a vertical bar selecting a time. +htrace.TimeCursor = function(params) { + this.positionToTime = function(x) { + if ((x < this.x0) || (x > this.xF)) { + return -1; + } + return this.begin + + (((x - this.x0) * (this.end - this.begin)) / (this.xF - this.x0)); + }; + + this.timeToPosition = function(time) { + return this.x0 + (((time - this.begin) * + (this.xF - this.x0)) / (this.end - this.begin)); + }; + + this.draw = function() { + if (this.selectedTime != -1) { + this.ctx.save(); + this.ctx.beginPath(); + this.ctx.rect(this.x0, this.y0, + this.xF - this.x0, this.yF - this.y0); + this.ctx.clip(); + this.ctx.strokeStyle="#ff0000"; + var x = this.timeToPosition(this.selectedTime); + this.ctx.beginPath(); + this.ctx.moveTo(x, this.y0); + this.ctx.lineTo(x, this.yF); + this.ctx.stroke(); + this.ctx.restore(); + } + }; + + this.handle = function(e) { + switch (e.type) { + case "mouseMove": + if (htrace.inBoundingBox(e.x, e.y, + this.x0, this.xF, this.y0, this.yF)) { + this.selectedTime = this.positionToTime(e.x); + if (this.selectedTime < 0) { + $(this.el).val(""); + } else { + $(this.el).val(htrace.dateToString(this.selectedTime)); + } + return true; + } + return true; + case "draw": + this.draw(); + return true; + } + }; + + this.selectedTime = -1; + for (var k in params) { + this[k]=params[k]; + } + this.manager.register("mouseMove", this); + this.manager.register("draw", this); + return this; +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/app/tree.js ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/app/tree.js b/htrace-webapp/src/main/webapp/app/tree.js new file mode 100644 index 0000000..046085c --- /dev/null +++ b/htrace-webapp/src/main/webapp/app/tree.js @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +// +// Get the height of a tree-- that is, the number of edges on the longest +// downward path between the root and a leaf +// +htrace.treeHeight = function(node, getDescendants) { + var height = 0; + var descendants = getDescendants(node); + for (var i = 0; i < descendants.length; i++) { + height = Math.max(height, + 1 + htrace.treeHeight(descendants[i], getDescendants)); + } + return height; +}; + +// +// Perform a depth-first, post-order traversal on the tree, invoking the +// callback on every node with the node and depth as the arguments. +// +// Example: +// 5 +// / \ +// 3 4 +// / \ +// 1 2 +// +htrace.treeTraverseDepthFirstPost = function(node, getDescendants, depth, cb) { + var descendants = getDescendants(node); + for (var i = 0; i < descendants.length; i++) { + htrace.treeTraverseDepthFirstPost(descendants[i], + getDescendants, depth + 1, cb); + } + cb(node, depth); +}; + +// +// Perform a depth-first, pre-order traversal on the tree, invoking the +// callback on every node with the node and depth as the arguments. +// +// Example: +// 1 +// / \ +// 2 5 +// / \ +// 3 4 +// +htrace.treeTraverseDepthFirstPre = function(node, getDescendants, depth, cb) { + cb(node, depth); + var descendants = getDescendants(node); + for (var i = 0; i < descendants.length; i++) { + htrace.treeTraverseDepthFirstPre(descendants[i], + getDescendants, depth + 1, cb); + } +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/app/triangle_button.js ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/app/triangle_button.js b/htrace-webapp/src/main/webapp/app/triangle_button.js new file mode 100644 index 0000000..f252476 --- /dev/null +++ b/htrace-webapp/src/main/webapp/app/triangle_button.js @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +// Triangle button widget. +htrace.TriangleButton = function(params) { + this.fgColor = "#6600ff"; + this.bgColor = "#ffffff"; + this.selected = false; + this.direction = "down"; + + this.draw = function() { + this.ctx.save(); + var fg = this.selected ? this.bgColor : this.fgColor; + var bg = this.selected ? this.fgColor : this.bgColor; + this.ctx.beginPath(); + this.ctx.rect(this.x0, this.y0, + this.xF - this.x0, this.yF - this.y0); + this.ctx.clip(); + this.ctx.fillStyle = bg; + this.ctx.strokeStyle = fg; + this.ctx.fillRect(this.x0, this.y0, + this.xF - this.x0, this.yF - this.y0); + this.ctx.lineWidth = 3; + this.ctx.strokeRect(this.x0, this.y0, + this.xF - this.x0, this.yF - this.y0); + var xPad = (this.xF - this.x0) / 5; + var yPad = (this.yF - this.y0) / 5; + this.ctx.fillStyle = fg; + this.ctx.strokeStyle = fg; + this.ctx.beginPath(); + this.ctx.strokeStyle = fg; + if (this.direction === "up") { + this.ctx.moveTo(Math.floor(this.x0 + ((this.xF - this.x0) / 2)), + this.y0 + yPad); + this.ctx.lineTo(this.xF - xPad, this.yF - yPad); + this.ctx.lineTo(this.x0 + xPad, this.yF - yPad); + } else if (this.direction === "down") { + this.ctx.moveTo(this.x0 + xPad, this.y0 + yPad); + this.ctx.lineTo(this.xF - xPad, this.y0 + yPad); + this.ctx.lineTo(Math.floor(this.x0 + ((this.xF - this.x0) / 2)), + this.yF - yPad); + } else { + console.log("TriangleButton: unknown direction " + this.direction); + } + this.ctx.closePath(); + this.ctx.fill(); + this.ctx.restore(); + }; + + this.handle = function(e) { + switch (e.type) { + case "mouseDown": + if (!htrace.inBoundingBox(e.x, e.y, + this.x0, this.xF, this.y0, this.yF)) { + return true; + } + this.manager.register("mouseUp", this); + this.manager.register("mouseMove", this); + this.manager.register("mouseOut", this); + this.selected = true; + return false; + case "mouseUp": + if (this.selected) { + this.callback(); + this.selected = false; + } + this.manager.unregister("mouseUp", this); + this.manager.unregister("mouseMove", this); + this.manager.unregister("mouseOut", this); + return true; + case "mouseMove": + this.selected = htrace.inBoundingBox(e.x, e.y, + this.x0, this.xF, this.y0, this.yF); + return true; + case "mouseOut": + this.selected = false; + return true; + case "draw": + this.draw(); + return true; + } + }; + + for (var k in params) { + this[k]=params[k]; + } + this.manager.register("mouseDown", this); + this.manager.register("draw", this); + return this; +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/app/widget_manager.js ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/app/widget_manager.js b/htrace-webapp/src/main/webapp/app/widget_manager.js new file mode 100644 index 0000000..e519485 --- /dev/null +++ b/htrace-webapp/src/main/webapp/app/widget_manager.js @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var htrace = htrace || {}; + +// Check if a point is inside a bounding box. +htrace.inBoundingBox = function(x, y, x0, xF, y0, yF) { + return ((x >= x0) && (x <= xF) && (y >= y0) && (y <= yF)); + } + +// Manages a set of widgets on the canvas. +// Buttons and sliders are both widgets. +htrace.WidgetManager = function(params) { + this.listeners = { + "mouseDown": [], + "mouseUp": [], + "mouseMove": [], + "mouseOut": [], + "dblclick": [], + "draw": [], + }; + + this.register = function(type, widget) { + this.listeners[type].push(widget); + } + + this.registerHighPriority = function(type, widget) { + this.listeners[type].unshift(widget); + } + + this.unregister = function(type, widget) { + this.listeners[type] = _.without(this.listeners[type], widget); + } + + this.handle = function(e) { + // Make a copy of the listeners, in case the handling functions change the + // array. + var listeners = this.listeners[e.type].slice(); + var len = listeners.length; + for (var i = 0; i < len; i++) { + if (!listeners[i].handle(e)) { + break; + } + } + }; + + for (var k in params) { + this[k]=params[k]; + } + return this; +}; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/custom.css ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/custom.css b/htrace-webapp/src/main/webapp/custom.css new file mode 100644 index 0000000..17945cb --- /dev/null +++ b/htrace-webapp/src/main/webapp/custom.css @@ -0,0 +1,101 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.navbar-default { + background-color: #001da9; + border-color: #b2b5db; +} +.navbar-default .navbar-brand { + color: #ecf0f1; +} +.navbar-default .navbar-brand:hover, .navbar-default .navbar-brand:focus { + color: #ffffff; +} +.navbar-default .navbar-text { + color: #ecf0f1; +} +.navbar-default .navbar-nav > li > a { + color: #ecf0f1; +} +.navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus { + color: #ffffff; +} +.navbar-default .navbar-nav > li > .dropdown-menu { + background-color: #001da9; +} +.navbar-default .navbar-nav > li > .dropdown-menu > li > a { + color: #ecf0f1; +} +.navbar-default .navbar-nav > li > .dropdown-menu > li > a:hover, +.navbar-default .navbar-nav > li > .dropdown-menu > li > a:focus { + color: #ffffff; + background-color: #b2b5db; +} +.navbar-default .navbar-nav > li > .dropdown-menu > li > .divider { + background-color: #001da9; +} +.navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #b2b5db; +} +.navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus { + color: #ffffff; + background-color: #b2b5db; +} +.navbar-default .navbar-toggle { + border-color: #b2b5db; +} +.navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { + background-color: #b2b5db; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #ecf0f1; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #ecf0f1; +} +.navbar-default .navbar-link { + color: #ecf0f1; +} +.navbar-default .navbar-link:hover { + color: #ffffff; +} +.htrace-canvas-container { + overflow: hidden; + position: relative; +} +.htrace-canvas { + position: absolute; + top: 0px; + left: 0px; +} + +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #ecf0f1; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #b2b5db; + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/image/owl.png ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/image/owl.png b/htrace-webapp/src/main/webapp/image/owl.png new file mode 100644 index 0000000..be6fabd Binary files /dev/null and b/htrace-webapp/src/main/webapp/image/owl.png differ http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/6b3d2d82/htrace-webapp/src/main/webapp/index.html ---------------------------------------------------------------------- diff --git a/htrace-webapp/src/main/webapp/index.html b/htrace-webapp/src/main/webapp/index.html new file mode 100644 index 0000000..ec28fe6 --- /dev/null +++ b/htrace-webapp/src/main/webapp/index.html @@ -0,0 +1,246 @@ +<!doctype html> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<html lang="en-US"> + <head> + <title>HTrace</title> + <meta charset="utf-8" name="viewport" + content="width=device-width, initial-scale=1.0"> + <link href="lib/bootstrap-3.3.1/css/bootstrap.css" rel="stylesheet" type="text/css"> + <link href="custom.css" rel="stylesheet" type="text/css"> + </head> + <body> + <header id="header" role="banner"> + <nav class="navbar navbar-default navbar-static-top" role="navigation"> + <div class="collapse navbar-collapse"> + <a class="navbar-brand" href="#">HTrace</a> + <ul class="nav navbar-nav"> + <li id="about"><a href="#about">About</a></li> + <li id="search"><a href="#search">Search</a></li> + </ul> + </div> + </nav> + </header> + <div id="app" class="container-fluid" role="application"></div> + <div id="modal" class="modal fade"></div> + <footer></footer> + + <script id="about-view-template" type="text/template"> + <div class="row"> + <div class="col-md-1"> + </div> + <div class="col-md-10"> + <h1>Welcome to HTrace</h1> + <img src="image/owl.png" width="15%"> + <h2>Server Version</h2> + <%= model.get("ReleaseVersion") %> + <h2>Server Git Hash</h2> + <%= model.get("GitVersion") %> + </div> + <div class="col-md-1"> + </div> + </div> + </script> + + <script id="search-view-template" type="text/template"> + <div class="row" id="searchView"> + <div class="col-md-3" role="form"> + <div class="panel panel-default"> + <div class="panel-heading"> + <h1 class="panel-title">Timeline</h1> + </div style="border: 1px solid #000000;"> + <div class="panel-body"> + <div class="form-horizontal"> + <div class="form-group"> + <label class="col-sm-2 control-label">Begin</label> + <div class="col-sm-10"> + <input type="text" class="form-control" id="begin" value="1970-01-01T00:00:00,000"/> + </div> + </div> + <div class="form-group"> + <label class="col-sm-2 control-label">End</label> + <div class="col-sm-10"> + <input type="text" class="form-control" id="end" value="1970-01-01T00:00:00,100"/> + </div> + </div> + <div class="form-group"> + <label class="col-sm-2 control-label">Cur</label> + <div class="col-sm-10"> + <input type="text" class="form-control" id="selectedTime" value=""/> + </div> + </div> + <div class="form-horizontal"> + <button type="button" class="btn btn btn-warning" + id="zoomButton">Zoom</button> + </div> + </div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading"> + <h1 class="panel-title">Search</h1> + </div style="border: 1px solid #000000;"> + <div class="panel-body"> + <form> + <div id="predicates"> + </div> + <div class="form-group"> + <div class="btn-group"> + <button type="button" data-toggle="dropdown" + aria-expanded="false" + class="btn btn-default dropdown-toggle"> + Add Predicate<span class="caret"></span> + </button> + <ul class="dropdown-menu" role="menu"> + <li><a href="javascript:void(0)" + class="add-field">Began after</a></li> + <li><a href="javascript:void(0)" + class="add-field">Began at or before</a></li> + <li><a href="javascript:void(0)" + class="add-field">Ended after</a></li> + <li><a href="javascript:void(0)" + class="add-field">Ended at or before</a></li> + <li><a href="javascript:void(0)" + class="add-field">Description contains</a></li> + <li><a href="javascript:void(0)" + class="add-field">Description is exactly</a></li> + <li><a href="javascript:void(0)" + class="add-field">Duration is longer than</a></li> + <li><a href="javascript:void(0)" + class="add-field">Duration is at most</a></li> + <li><a href="javascript:void(0)" + class="add-field">Span ID is</a></li> + <li><a href="javascript:void(0)" + class="add-field">TracerId contains</a></li> + <li><a href="javascript:void(0)" + class="add-field">TracerId is exactly</a></li> + </ul> + </div> + <button type="submit" class="btn btn-primary" id="searchButton"> + Search</button> + </div> + <div class="form-group"> + <button type="button" class="btn btn btn-danger" id="clearButton"> + Clear</button> + </div> + </form> + </div> + </div> + </div> + <div class="col-md-9" id="resultsView"> + </div> + </div> + </script> + + <script id="predicate-template" type="text/template"> + <form class="form-horizontal"> + <div class="form-group"> + <%= desc %> + <button type="button" class="btn pull-right btn-link btn-sm closeButton" + >X</button><br/> + <input type="text" class="form-control"/> + </div> + </form> + </script> + + <script id="search-results-view-template" type="text/template"> + <!-- tabindex=1 is needed or else the canvas can never gain mouse focus on Chrome. --> + <canvas id="resultsCanvas" class="htrace-canvas" tabindex="1"> + <h2>Sorry, your browser does not support the HTML5 canvas element. Please + upgrade to a newer browser.</h2> + </canvas> + </script> + + <script id="modal-warning-template" type="text/template"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" + aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + <h4 class="modal-title"><%= title %></h4> + </div> + <div class="modal-body"> + <%= body %><p/> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" data-dismiss="modal">Close</button> + </div> + </div> + </div> + </script> + + <script id="modal-table-template" type="text/template"> + <div class="modal-dialog modal-lg"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" + aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + <h4 class="modal-title"><%= title %></h4> + </div> + <div class="modal-body"> + <%= body %><p/> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" data-dismiss="modal">Close</button> + </div> + </div> + </div> + </script> + + <script id="table-row-template" type="text/template"> + <tr bgcolor="<%= bgcolor %>"> + <td style="width:30%;word-wrap:break-word"><%- key %></td> + <td style="width:70%;word-wrap:break-word"><%- val %></td> + </tr> + </script> + + <script src="lib/jquery-2.1.4.js" type="text/javascript"></script> + <script src="lib/bootstrap-3.3.1/js/bootstrap.min.js" type="text/javascript"></script> + <script src="lib/underscore-1.7.0.js" type="text/javascript"></script> + <script src="lib/backbone-1.1.2.js" type="text/javascript"></script> + <script src="lib/moment-2.10.3.js" type="text/javascript"></script> + + <script src="app/string.js" type="text/javascript"></script> + <script src="app/tree.js" type="text/javascript"></script> + <script src="app/time_cursor.js" type="text/javascript"></script> + + <script src="app/widget_manager.js" type="text/javascript"></script> + <script src="app/triangle_button.js" type="text/javascript"></script> + <script src="app/partition_widget.js" type="text/javascript"></script> + + <script src="app/span.js" type="text/javascript"></script> + + <script src="app/span_group_widget.js" type="text/javascript"></script> + <script src="app/span_widget.js" type="text/javascript"></script> + <script src="app/search_results.js" type="text/javascript"></script> + <script src="app/about_view.js" type="text/javascript"></script> + <script src="app/modal.js" type="text/javascript"></script> + <script src="app/predicate.js" type="text/javascript"></script> + <script src="app/predicate_view.js" type="text/javascript"></script> + <script src="app/query_results.js" type="text/javascript"></script> + <script src="app/search_results_view.js" type="text/javascript"></script> + <script src="app/search_view.js" type="text/javascript"></script> + <script src="app/server_info.js" type="text/javascript"></script> + + <script src="app/router.js" type="text/javascript"></script> + </body> +</html> +
