HTRACE-288. htraced: Add a user interface to view server version, metrics, and configuration (cmccabe)
diff --git a/htrace-htraced/go/src/org/apache/htrace/client/client.go b/htrace-htraced/go/src/org/apache/htrace/client/client.go
index dd8597d..fb46e62 100644
--- a/htrace-htraced/go/src/org/apache/htrace/client/client.go
+++ b/htrace-htraced/go/src/org/apache/htrace/client/client.go
@@ -52,13 +52,13 @@
 	hcr *hClient
 }
 
-// Get the htraced server information.
-func (hcl *Client) GetServerInfo() (*common.ServerInfo, error) {
+// Get the htraced server version information.
+func (hcl *Client) GetServerVersion() (*common.ServerVersion, error) {
 	buf, _, err := hcl.makeGetRequest("server/info")
 	if err != nil {
 		return nil, err
 	}
-	var info common.ServerInfo
+	var info common.ServerVersion
 	err = json.Unmarshal(buf, &info)
 	if err != nil {
 		return nil, errors.New(fmt.Sprintf("Error: error unmarshalling response "+
diff --git a/htrace-htraced/go/src/org/apache/htrace/common/rpc.go b/htrace-htraced/go/src/org/apache/htrace/common/rpc.go
index 5e57f08..f071e37 100644
--- a/htrace-htraced/go/src/org/apache/htrace/common/rpc.go
+++ b/htrace-htraced/go/src/org/apache/htrace/common/rpc.go
@@ -44,8 +44,8 @@
 	ClientDropped uint64 `json:",omitempty"`
 }
 
-// Info returned by /server/info
-type ServerInfo struct {
+// Info returned by /server/version
+type ServerVersion struct {
 	// The server release version.
 	ReleaseVersion string
 
diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go b/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go
index 14b7f73..98b1646 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go
@@ -70,7 +70,7 @@
 	addr := app.Flag("addr", "Server address.").String()
 	verbose = *app.Flag("verbose", "Verbose.").Default("false").Bool()
 	version := app.Command("version", "Print the version of this program.")
-	serverInfo := app.Command("serverInfo", "Print information retrieved from an htraced server.")
+	serverVersion := app.Command("serverVersion", "Print the version of the htraced server.")
 	serverStats := app.Command("serverStats", "Print statistics retrieved from the htraced server.")
 	serverStatsJson := serverStats.Flag("json", "Display statistics as raw JSON.").Default("false").Bool()
 	serverConf := app.Command("serverConf", "Print the server configuration retrieved from the htraced server.")
@@ -132,8 +132,8 @@
 	switch cmd {
 	case version.FullCommand():
 		os.Exit(printVersion())
-	case serverInfo.FullCommand():
-		os.Exit(printServerInfo(hcl))
+	case serverVersion.FullCommand():
+		os.Exit(printServerVersion(hcl))
 	case serverStats.FullCommand():
 		if *serverStatsJson {
 			os.Exit(printServerStatsJson(hcl))
@@ -187,13 +187,13 @@
 }
 
 // Print information retrieved from an htraced server via /server/info
-func printServerInfo(hcl *htrace.Client) int {
-	info, err := hcl.GetServerInfo()
+func printServerVersion(hcl *htrace.Client) int {
+	ver, err := hcl.GetServerVersion()
 	if err != nil {
 		fmt.Println(err.Error())
 		return EXIT_FAILURE
 	}
-	fmt.Printf("HTraced server version %s (%s)\n", info.ReleaseVersion, info.GitVersion)
+	fmt.Printf("HTraced server version %s (%s)\n", ver.ReleaseVersion, ver.GitVersion)
 	return EXIT_SUCCESS
 }
 
diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go b/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go
index ca0a425..0b38481 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go
@@ -30,8 +30,8 @@
 	"time"
 )
 
-func TestClientGetServerInfo(t *testing.T) {
-	htraceBld := &MiniHTracedBuilder{Name: "TestClientGetServerInfo",
+func TestClientGetServerVersion(t *testing.T) {
+	htraceBld := &MiniHTracedBuilder{Name: "TestClientGetServerVersion",
 		DataDirs: make([]string, 2)}
 	ht, err := htraceBld.Build()
 	if err != nil {
@@ -43,9 +43,9 @@
 	if err != nil {
 		t.Fatalf("failed to create client: %s", err.Error())
 	}
-	_, err = hcl.GetServerInfo()
+	_, err = hcl.GetServerVersion()
 	if err != nil {
-		t.Fatalf("failed to call GetServerInfo: %s", err.Error())
+		t.Fatalf("failed to call GetServerVersion: %s", err.Error())
 	}
 }
 
diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go b/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go
index 2a10805..a41e1c7 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go
@@ -50,22 +50,22 @@
 	w.Write([]byte(`{ "error" : "` + str + `"}`))
 }
 
-type serverInfoHandler struct {
+type serverVersionHandler struct {
 	lg *common.Logger
 }
 
-func (hand *serverInfoHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+func (hand *serverVersionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	setResponseHeaders(w.Header())
-	version := common.ServerInfo{ReleaseVersion: RELEASE_VERSION,
+	version := common.ServerVersion{ReleaseVersion: RELEASE_VERSION,
 		GitVersion: GIT_VERSION}
 	buf, err := json.Marshal(&version)
 	if err != nil {
 		writeError(hand.lg, w, http.StatusInternalServerError,
-			fmt.Sprintf("error marshalling ServerInfo: %s\n", err.Error()))
+			fmt.Sprintf("error marshalling ServerVersion: %s\n", err.Error()))
 		return
 	}
 	if hand.lg.DebugEnabled() {
-		hand.lg.Debugf("Returned serverInfo %s\n", string(buf))
+		hand.lg.Debugf("Returned ServerVersion %s\n", string(buf))
 	}
 	w.Write(buf)
 }
@@ -303,7 +303,8 @@
 
 	r := mux.NewRouter().StrictSlash(false)
 
-	r.Handle("/server/info", &serverInfoHandler{lg: rsv.lg}).Methods("GET")
+	r.Handle("/server/info", &serverVersionHandler{lg: rsv.lg}).Methods("GET")
+	r.Handle("/server/version", &serverVersionHandler{lg: rsv.lg}).Methods("GET")
 
 	serverStatsH := &serverStatsHandler{dataStoreHandler: dataStoreHandler{
 		store: store, lg: rsv.lg}}
diff --git a/htrace-htraced/src/test/java/org/apache/htrace/impl/HTracedProcess.java b/htrace-htraced/src/test/java/org/apache/htrace/impl/HTracedProcess.java
index 26c1a10..5c5e394 100644
--- a/htrace-htraced/src/test/java/org/apache/htrace/impl/HTracedProcess.java
+++ b/htrace-htraced/src/test/java/org/apache/htrace/impl/HTracedProcess.java
@@ -255,9 +255,9 @@
       "htraced");
   }
 
-  public String getServerInfoJson() throws Exception {
+  public String getServerVersionJson() throws Exception {
     ContentResponse response = httpClient.GET(
-        new URI(String.format("http://%s/server/info", httpAddr)));
+        new URI(String.format("http://%s/server/version", httpAddr)));
     Assert.assertEquals("application/json", response.getMediaType());
     Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
     return response.getContentAsString();
diff --git a/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedReceiver.java b/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedReceiver.java
index 99f00a1..0a89ff0 100644
--- a/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedReceiver.java
+++ b/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedReceiver.java
@@ -64,10 +64,10 @@
   };
 
   @Test(timeout = 60000)
-  public void testGetServerInfoJson() throws Exception {
+  public void testGetServerVersionJson() throws Exception {
     HTracedProcess ht = new HTracedProcess.Builder().build();
     try {
-      String response = ht.getServerInfoJson();
+      String response = ht.getServerVersionJson();
       assertTrue(response.contains("ReleaseVersion"));
     } finally {
       ht.destroy();
diff --git a/htrace-webapp/src/main/webapp/app/router.js b/htrace-webapp/src/main/webapp/app/router.js
index 607da44..dd86b55 100644
--- a/htrace-webapp/src/main/webapp/app/router.js
+++ b/htrace-webapp/src/main/webapp/app/router.js
@@ -23,6 +23,7 @@
   "routes": {
     "": "empty",
     "about": "about",
+    "serverInfo": "serverInfo",
     "search": "search",
     "*unknown": "unknown"
   },
@@ -34,18 +35,40 @@
 
   about: function() {
     console.log("Visiting #about.");
-    serverInfo = new htrace.ServerInfo();
     var router = this;
-    serverInfo.fetch({
-        "success": function(model, response, options) {
-          router.switchView(new htrace.AboutView({model: serverInfo, el: "#app"}));
-          router.activateNavBarEntry("about")
-        },
-        "error": function(model, response, options) {
-          window.alert("Failed to fetch htraced server info via GET " +
-                       "/server/info: " + JSON.stringify(response));
-        }
-      });
+    router.switchView(new htrace.AboutView({el: "#app"}));
+    router.activateNavBarEntry("about")
+  },
+
+  serverInfo: function() {
+    console.log("Visiting #serverInfo.");
+    var version = new htrace.ServerVersion();
+    var router = this;
+    version.fetch({
+      "error": function(model, response, options) {
+        window.alert("Failed to fetch htraced server version: " +
+                     JSON.stringify(response));
+      },
+      "success": function(model, response, options) {
+        stats = new htrace.ServerStats();
+        stats.fetch({
+          "error": function(model, response, options) {
+            window.alert("Failed to fetch htraced server stats: " +
+                         JSON.stringify(response));
+          },
+          "success": function(model, response, options) {
+            router.switchView(new htrace.ServerInfoView({
+              model: {
+                "version": version,
+                "stats": stats
+              },
+              el: "#app"
+            }))
+            router.activateNavBarEntry("serverInfo")
+          }
+        })
+      }
+    })
   },
 
   search: function() {
diff --git a/htrace-webapp/src/main/webapp/app/server_info.js b/htrace-webapp/src/main/webapp/app/server_configuration.js
similarity index 79%
copy from htrace-webapp/src/main/webapp/app/server_info.js
copy to htrace-webapp/src/main/webapp/app/server_configuration.js
index b03f706..e2684f5 100644
--- a/htrace-webapp/src/main/webapp/app/server_info.js
+++ b/htrace-webapp/src/main/webapp/app/server_configuration.js
@@ -17,15 +17,9 @@
  * under the License.
  */
 
-// htraced ServerInfo sent back from /serverInfo.
-// See rest.go.
-htrace.ServerInfo = Backbone.Model.extend({
-  defaults: {
-    "ReleaseVersion": "unknown",
-    "GitVersion": "unknown",
-  },
-
+// htraced server configuration information.  See rest.go.
+htrace.ServerConfiguration = Backbone.Model.extend({
   url: function() {
-    return "server/info";
+    return "server/conf";
   }
 });
diff --git a/htrace-webapp/src/main/webapp/app/server_info_view.js b/htrace-webapp/src/main/webapp/app/server_info_view.js
new file mode 100644
index 0000000..f8307ff
--- /dev/null
+++ b/htrace-webapp/src/main/webapp/app/server_info_view.js
@@ -0,0 +1,114 @@
+/*
+ * 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.ServerInfoView = Backbone.View.extend({
+  events: {
+    "click .serverConfigurationButton": "showServerConfigurationModal",
+  },
+
+  render: function() {
+    this.$el.html(_.template($("#server-info-view-template").html())
+      ({ model : this.model,
+         view : this}));
+    console.log("ServerInfoView#render");
+    return this;
+  },
+
+  close: function() {
+    console.log("ServerInfoView#close")
+    this.undelegateEvents();
+  },
+
+  getServerStatsTableHtml: function() {
+    var out = 
+      '<div class="panel-heading">' + 
+        '<div class="panel-title">Span Metrics</div>' +
+      '</div>' +
+      '<table class="table table-striped">' +
+        '<thead>' +
+          '<tr>' +
+            '<th>Remote</th>' +
+            '<th>Written</th>' +
+            '<th>ServerDropped</th>' +
+            '<th>ClientDropped</th>' +
+          '</tr>' +
+        '</thead>';
+    var remotes = [];
+    var stats = this.model.stats
+    var spanMetrics = stats.get("HostSpanMetrics")
+    for (var remote in spanMetrics) {
+      if (spanMetrics.hasOwnProperty(remote)) {
+        remotes.push(remote)
+      }
+    }
+    remotes = remotes.sort()
+    for (var i = 0; i < remotes.length; i++) {
+      var remote = remotes[0];
+      var smtx = spanMetrics[remote];
+      out = out + "<tr>" + 
+        "<td>" + remote + "</td>" +
+        "<td>" + smtx.Written + "</td>" +
+        "<td>" + smtx.ServerDropped + "</td>" +
+        "<td>" + smtx.ClientDropped + "</td>" +
+        "</tr>";
+    }
+    out = out + '</table>';
+    //console.log("out = " + out);
+    return out;
+  },
+
+  showServerConfigurationModal: function() {
+    var config = new htrace.ServerConfiguration();
+    var view = this;
+    config.fetch({
+      "error": function(model, response, options) {
+        window.alert("Failed to fetch htraced server configuration: " +
+                     JSON.stringify(response));
+      },
+      "success": function(model, response, options) {
+        var confKeys = [];
+        for (var confKey in model.attributes) {
+          confKeys.push(confKey)
+        }
+        console.log("confKeys = " + JSON.stringify(confKeys));
+        confKeys = confKeys.sort()
+        var out = '<table style="table-layout:fixed;width:100%;word-wrap:break-word">' +
+            '<thead>' +
+              '<tr>' +
+                '<th>Key</th>' +
+                '<th>Value</th>' +
+              '</tr>' +
+            '</thead>';
+        for (var i = 0; i < confKeys.length; i++) {
+          var colorString = ((i%2) == 1) ? "#f1f1f1" : "#ffffff";
+          out += _.template($("#table-row-template").html())(
+              {bgcolor: colorString, key: confKeys[i],
+                val: model.attributes[confKeys[i]]});
+          //out = out + '<tr><th>' + confKeys[i] + '</th><th>' +
+              //model.attributes[confKeys[i]] + '</th></tr>';
+        }
+        var out = out + '</table>';
+        htrace.showModal(_.template($("#modal-table-template").html())(
+            {title: "HTraced Server Configuration", body: out}));
+      }
+    })
+  }
+});
diff --git a/htrace-webapp/src/main/webapp/app/server_info.js b/htrace-webapp/src/main/webapp/app/server_stats.js
similarity index 80%
copy from htrace-webapp/src/main/webapp/app/server_info.js
copy to htrace-webapp/src/main/webapp/app/server_stats.js
index b03f706..e4289ef 100644
--- a/htrace-webapp/src/main/webapp/app/server_info.js
+++ b/htrace-webapp/src/main/webapp/app/server_stats.js
@@ -17,15 +17,13 @@
  * under the License.
  */
 
-// htraced ServerInfo sent back from /serverInfo.
-// See rest.go.
-htrace.ServerInfo = Backbone.Model.extend({
+// htraced server statistics.  See rest.go.
+htrace.ServerStats = Backbone.Model.extend({
   defaults: {
-    "ReleaseVersion": "unknown",
-    "GitVersion": "unknown",
+    "ReapedSpans": "(unknown)",
   },
 
   url: function() {
-    return "server/info";
+    return "server/stats";
   }
 });
diff --git a/htrace-webapp/src/main/webapp/app/server_info.js b/htrace-webapp/src/main/webapp/app/server_version.js
similarity index 86%
rename from htrace-webapp/src/main/webapp/app/server_info.js
rename to htrace-webapp/src/main/webapp/app/server_version.js
index b03f706..a049324 100644
--- a/htrace-webapp/src/main/webapp/app/server_info.js
+++ b/htrace-webapp/src/main/webapp/app/server_version.js
@@ -17,15 +17,14 @@
  * under the License.
  */
 
-// htraced ServerInfo sent back from /serverInfo.
-// See rest.go.
-htrace.ServerInfo = Backbone.Model.extend({
+// htraced server version information.  See rest.go.
+htrace.ServerVersion = Backbone.Model.extend({
   defaults: {
     "ReleaseVersion": "unknown",
     "GitVersion": "unknown",
   },
 
   url: function() {
-    return "server/info";
+    return "server/version";
   }
 });
diff --git a/htrace-webapp/src/main/webapp/index.html b/htrace-webapp/src/main/webapp/index.html
index ec28fe6..2cebefe 100644
--- a/htrace-webapp/src/main/webapp/index.html
+++ b/htrace-webapp/src/main/webapp/index.html
@@ -30,6 +30,7 @@
           <a class="navbar-brand" href="#">HTrace</a>
           <ul class="nav navbar-nav">
             <li id="about"><a href="#about">About</a></li>
+            <li id="serverInfo"><a href="#serverInfo">ServerInfo</a></li>
             <li id="search"><a href="#search">Search</a></li>
           </ul>
         </div>
@@ -40,16 +41,58 @@
     <footer></footer>
 
     <script id="about-view-template" type="text/template">
+      <div class="row" align="center">
+        <h1>Welcome to <a href="http://htrace.incubator.apache.org/">Apache HTrace</a></h1>
+      </div>
+      <div class="row" align="center">
+        <img src="image/owl.png" width="40%"><p/><p/>
+      </div>
+    </script>
+
+    <script id="server-info-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") %>
+          <h2>HTraced at <%= window.location.host %></h2>
+          <div class="panel panel-info">
+            <div class="panel-heading">
+              <div class="panel-title">Version</div>
+            </div>
+            <table class="table table-striped">
+            <tr>
+              <td>Server Release Version</td>
+              <td><%= model.version.get("ReleaseVersion") %></td>
+            </tr>
+            <tr>
+              <td>Server Git Hash</td>
+              <td><%= model.version.get("GitVersion") %></td>
+            </tr>
+            </table>
+          </div>
+          <div class="panel panel-info">
+            <div class="panel-heading">
+              <div class="panel-title">Global Metrics</div>
+            </div>
+            <table class="table table-striped">
+            <tr>
+              <td>Spans Reaped</td>
+              <td><%= model.stats.get("ReapedSpans") %></td>
+            </tr>
+            </tr>
+              <td>Datastore Start Time</td>
+              <td><%= htrace.dateToString(model.stats.get("LastStartMs")) %></td>
+            </tr>
+            </tr>
+              <td>Current Server Time</td>
+              <td><%= htrace.dateToString(model.stats.get("CurMs")) %></td>
+            </tr>
+            </table>
+          </div>
+          <div class="panel panel-info">
+            <%= view.getServerStatsTableHtml() %>
+          </div>
+          <button type="button" class="btn btn-info serverConfigurationButton">Server Configuration</button>
         </div>
         <div class="col-md-1">
         </div>
@@ -232,13 +275,16 @@
     <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/server_info_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/server_configuration.js" type="text/javascript"></script>
+    <script src="app/server_stats.js" type="text/javascript"></script>
+    <script src="app/server_version.js" type="text/javascript"></script>
 
     <script src="app/router.js" type="text/javascript"></script>
   </body>