accumulo-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ctubb...@apache.org
Subject [34/59] [abbrv] ACCUMULO-656,ACCUMULO-658 Update package names
Date Sat, 07 Sep 2013 03:28:37 GMT
http://git-wip-us.apache.org/repos/asf/accumulo/blob/e0533561/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/BasicServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/BasicServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/BasicServlet.java
new file mode 100644
index 0000000..186bd43
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/BasicServlet.java
@@ -0,0 +1,269 @@
+/*
+ * 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.
+ */
+package org.apache.accumulo.monitor.servlets;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.Date;
+import java.util.TimerTask;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.Constants;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.monitor.LogService;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.server.client.HdfsZooInstance;
+import org.apache.accumulo.server.util.time.SimpleTimer;
+import org.apache.log4j.Logger;
+
+abstract public class BasicServlet extends HttpServlet {
+  
+  private static final long serialVersionUID = 1L;
+  protected static final Logger log = Logger.getLogger(BasicServlet.class);
+  static String cachedInstanceName = null;
+  private static String bannerText;
+  private static String bannerColor;
+  private static String bannerBackground;
+  
+  abstract protected String getTitle(HttpServletRequest req);
+  
+  @Override
+  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+    StringBuilder sb = new StringBuilder();
+    try {
+      Monitor.fetchData();
+      bannerText = sanitize(Monitor.getSystemConfiguration().get(Property.MONITOR_BANNER_TEXT));
+      bannerColor = Monitor.getSystemConfiguration().get(Property.MONITOR_BANNER_COLOR).replace("'", "'");
+      bannerBackground = Monitor.getSystemConfiguration().get(Property.MONITOR_BANNER_BACKGROUND).replace("'", "'");
+      pageStart(req, resp, sb);
+      pageBody(req, resp, sb);
+      pageEnd(req, resp, sb);
+    } catch (Throwable t) {
+      log.error("Error building page " + req.getRequestURI(), t);
+      sb.append("\n<pre>\n");
+      StringWriter sw = new StringWriter();
+      t.printStackTrace(new PrintWriter(sw));
+      sb.append(sanitize(sw.getBuffer().toString()));
+      sb.append("</pre>\n");
+    } finally {
+      resp.getWriter().print(sb);
+      resp.getWriter().flush();
+    }
+  }
+  
+  @Override
+  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+    doGet(req, resp);
+  }
+  
+  private static final String DEFAULT_CONTENT_TYPE = "text/html";
+  
+  public static final Cookie getCookie(HttpServletRequest req, String name) {
+    if (req.getCookies() != null)
+      for (Cookie c : req.getCookies())
+        if (c.getName().equals(name))
+          return c;
+    return null;
+  }
+  
+  protected void pageStart(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) throws Exception {
+    resp.setContentType(DEFAULT_CONTENT_TYPE);
+    int refresh = -1;
+    Cookie c = getCookie(req, "page.refresh.rate");
+    if (c != null && c.getValue() != null) {
+      try {
+        refresh = Integer.parseInt(c.getValue());
+      } catch (NumberFormatException e) {
+        // ignore improperly formatted user cookie
+      }
+    }
+    synchronized (BasicServlet.class) {
+      // Learn our instance name asynchronously so we don't hang up if zookeeper is down
+      if (cachedInstanceName == null) {
+        SimpleTimer.getInstance().schedule(new TimerTask() {
+          @Override
+          public void run() {
+            synchronized (BasicServlet.class) {
+              if (cachedInstanceName == null) {
+                cachedInstanceName = HdfsZooInstance.getInstance().getInstanceName();
+              }
+            }
+          }
+        }, 1000);
+      }
+    }
+    
+    // BEGIN PAGE
+    sb.append("<!--\n");
+    sb.append("  Licensed to the Apache Software Foundation (ASF) under one or more\n");
+    sb.append("  contributor license agreements.  See the NOTICE file distributed with\n");
+    sb.append("  this work for additional information regarding copyright ownership.\n");
+    sb.append("  The ASF licenses this file to You under the Apache License, Version 2.0\n");
+    sb.append("  (the \"License\"); you may not use this file except in compliance with\n");
+    sb.append("  the License.  You may obtain a copy of the License at\n");
+    sb.append("\n");
+    sb.append("    http://www.apache.org/licenses/LICENSE-2.0\n");
+    sb.append("\n");
+    sb.append("  Unless required by applicable law or agreed to in writing, software\n");
+    sb.append("  distributed under the License is distributed on an \"AS IS\" BASIS,\n");
+    sb.append("  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n");
+    sb.append("  See the License for the specific language governing permissions and\n");
+    sb.append("  limitations under the License.\n");
+    sb.append("-->\n");
+    sb.append("<html>\n");
+    
+    // BEGIN HEADER
+    sb.append("<head>\n");
+    sb.append("<title>").append(getTitle(req)).append(" - Accumulo ").append(Constants.VERSION).append("</title>\n");
+    if ((refresh > 0) && (req.getRequestURI().startsWith("/docs") == false) && (req.getRequestURI().startsWith("/vis") == false)
+        && (req.getRequestURI().startsWith("/shell") == false))
+      sb.append("<meta http-equiv='refresh' content='" + refresh + "' />\n");
+    sb.append("<meta http-equiv='Content-Type' content='").append(DEFAULT_CONTENT_TYPE).append("' />\n");
+    sb.append("<meta http-equiv='Content-Script-Type' content='text/javascript' />\n");
+    sb.append("<meta http-equiv='Content-Style-Type' content='text/css' />\n");
+    sb.append("<link rel='shortcut icon' type='image/jpg' href='/web/favicon.png' />\n");
+    sb.append("<link rel='stylesheet' type='text/css' href='/web/screen.css' media='screen' />\n");
+    sb.append("<script src='/web/functions.js' type='text/javascript'></script>\n");
+    
+    sb.append("<!--[if lte IE 8]><script language=\"javascript\" type=\"text/javascript\" src=\"/web/flot/excanvas.min.js\"></script><![endif]-->\n");
+    sb.append("<script language=\"javascript\" type=\"text/javascript\" src=\"/web/flot/jquery.js\"></script>\n");
+    sb.append("<script language=\"javascript\" type=\"text/javascript\" src=\"/web/flot/jquery.flot.js\"></script>\n");
+    
+    sb.append("</head>\n");
+    
+    // BEGIN BODY OPENING
+    sb.append("\n<body ").append(getBodyAttributes()).append(">\n");
+    sb.append("<div id='content-wrapper'>\n");
+    sb.append("<div id='content'>\n");
+    sb.append("<div id='header'>");
+    if (!bannerText.isEmpty()) {
+      sb.append("<div id='banner' style='color:").append(bannerColor).append(";background:").append(bannerBackground).append("'>").append(bannerText)
+          .append("</div>\n");
+    }
+    sb.append("<div id='headertitle'>");
+    sb.append("<h1>").append(getTitle(req)).append("</h1></div>\n");
+    sb.append("<div id='subheader'>Instance&nbsp;Name:&nbsp;").append(cachedInstanceName).append("&nbsp;&nbsp;&nbsp;Version:&nbsp;").append(Constants.VERSION)
+        .append("\n");
+    sb.append("<br><span class='smalltext'>Instance&nbsp;ID:&nbsp;").append(HdfsZooInstance.getInstance().getInstanceID()).append("</span>\n");
+    sb.append("<br><span class='smalltext'>").append(new Date().toString().replace(" ", "&nbsp;")).append("</span>");
+    sb.append("</div>\n"); // end <div id='subheader'>
+    sb.append("</div>\n"); // end <div id='header'>
+    
+    // BEGIN LEFT SIDE
+    sb.append("<div id='nav'>\n");
+    sb.append("<span id='nav-title'><a href='/'>Overview</a></span><br />\n");
+    sb.append("<hr />\n");
+    sb.append("<a href='/master'>Master&nbsp;Server</a><br />\n");
+    sb.append("<a href='/tservers'>Tablet&nbsp;Servers</a><br />\n");
+    sb.append("<a href='/vis'>Server Activity</a><br />\n");
+    sb.append("<a href='/gc'>Garbage&nbsp;Collector</a><br />\n");
+    sb.append("<a href='/tables'>Tables</a><br />\n");
+    sb.append("<a href='/trace/summary?minutes=10'>Recent&nbsp;Traces</a><br />\n");
+    sb.append("<a href='/docs'>Documentation</a><br />\n");
+    int numLogs = LogService.getInstance().getEvents().size();
+    if (numLogs > 0)
+      sb.append("<span class='error'><a href='/log'>Recent&nbsp;Logs&nbsp;<span class='smalltext'>(" + numLogs + ")</a></span></span><br />\n");
+    int numProblems = Monitor.getProblemSummary().entrySet().size();
+    if (numProblems > 0)
+      sb.append("<span class='error'><a href='/problems'>Table&nbsp;Problems&nbsp;<span class='smalltext'>(" + numProblems + ")</a></span></span><br />\n");
+    sb.append("<hr />\n");
+    sb.append("<a href='/xml'>XML</a><br />\n");
+    sb.append("<a href='/json'>JSON</a><hr />\n");
+    if (Monitor.isUsingSsl())
+      sb.append("<a href='/shell'>Shell</a><hr />\n");
+    sb.append("<div class='smalltext'>[<a href='").append("/op?action=refresh&value=").append(refresh < 1 ? "5" : "-1");
+    sb.append("&redir=").append(currentPage(req)).append("'>");
+    sb.append(refresh < 1 ? "en" : "dis").append("able&nbsp;auto-refresh</a>]</div>\n");
+    sb.append("</div>\n"); // end <div id='nav'>
+    
+    sb.append("<div id='main'");
+    if (bannerText.isEmpty())
+      sb.append(" style='bottom:0'");
+    sb.append(">\n");
+    sb.append("<!-- BEGIN MAIN BODY CONTENT -->\n\n");
+  }
+  
+  protected void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) throws Exception {
+    sb.append("This page intentionally left blank.");
+  }
+  
+  protected void pageEnd(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) throws Exception {
+    sb.append("\n<!-- END MAIN BODY CONTENT -->\n");
+    sb.append("</div>\n"); // end <div id='main'>
+    
+    // BEGIN FOOTER
+    sb.append("</div>\n"); // end <div id='content'>
+    sb.append("</div>\n"); // end <div id='content-wrapper'>
+    if (!bannerText.isEmpty()) {
+      sb.append("<div id='footer' style='color:").append(bannerColor).append(";background:").append(bannerBackground).append("'>").append(bannerText)
+          .append("</div>\n");
+    }
+    sb.append("</body>\n");
+    sb.append("</html>\n");
+  }
+  
+  /**
+   * Allow the concrete servlet implementation to provide attributes on the body HTML tag, such as 'onload', which can be used to call Javascript methods on
+   * page load. By default, nothing is specified.
+   */
+  protected String getBodyAttributes() {
+    return "";
+  }
+  
+  public static String encode(String s) {
+    try {
+      return URLEncoder.encode(s, Constants.UTF8.name());
+    } catch (UnsupportedEncodingException e) {
+      Logger.getLogger(BasicServlet.class).fatal(Constants.UTF8.name() + " is not a recognized encoding", e);
+      throw new RuntimeException(e);
+    }
+  }
+  
+  public static String decode(String s) {
+    try {
+      return URLDecoder.decode(s, Constants.UTF8.name());
+    } catch (UnsupportedEncodingException e) {
+      Logger.getLogger(BasicServlet.class).fatal(Constants.UTF8.name() + " is not a recognized encoding", e);
+      throw new RuntimeException(e);
+    }
+  }
+  
+  public static String sanitize(String xml) {
+    return xml.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
+  }
+  
+  public static String currentPage(HttpServletRequest req) {
+    String redir = req.getRequestURI();
+    if (req.getQueryString() != null)
+      redir += "?" + req.getQueryString();
+    return encode(redir);
+  }
+  
+  protected static void banner(StringBuilder sb, String klass, String text) {
+    sb.append("<br />\n<h2 class='").append(klass).append("'>").append(text).append("</h2>\n");
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e0533561/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/DefaultServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/DefaultServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/DefaultServlet.java
new file mode 100644
index 0000000..9280c1e
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/DefaultServlet.java
@@ -0,0 +1,374 @@
+/*
+ * 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.
+ */
+package org.apache.accumulo.monitor.servlets;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.file.FileUtil;
+import org.apache.accumulo.core.master.thrift.MasterMonitorInfo;
+import org.apache.accumulo.core.util.CachedConfiguration;
+import org.apache.accumulo.core.util.Duration;
+import org.apache.accumulo.core.util.NumUtil;
+import org.apache.accumulo.core.util.Pair;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.monitor.ZooKeeperStatus;
+import org.apache.accumulo.monitor.ZooKeeperStatus.ZooKeeperState;
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+import org.apache.accumulo.server.conf.ServerConfiguration;
+import org.apache.accumulo.server.trace.TraceFileSystem;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.ContentSummary;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+
+public class DefaultServlet extends BasicServlet {
+  
+  private static final long serialVersionUID = 1L;
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return req.getRequestURI().startsWith("/docs") ? "Documentation" : "Accumulo Overview";
+  }
+  
+  private void getResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    try {
+      String path = req.getRequestURI();
+      
+      if (path.endsWith(".jpg"))
+        resp.setContentType("image/jpeg");
+      
+      if (path.endsWith(".html"))
+        resp.setContentType("text/html");
+      
+      path = path.substring(1);
+      InputStream data = BasicServlet.class.getClassLoader().getResourceAsStream(path);
+      ServletOutputStream out = resp.getOutputStream();
+      try {
+        if (data != null) {
+          byte[] buffer = new byte[1024];
+          int n;
+          while ((n = data.read(buffer)) > 0)
+            out.write(buffer, 0, n);
+        } else {
+          out.write(("could not get resource " + path + "").getBytes());
+        }
+      } finally {
+        if (data != null)
+          data.close();
+      }
+    } catch (Throwable t) {
+      log.error(t, t);
+      throw new IOException(t);
+    }
+  }
+  
+  private void getDocResource(HttpServletRequest req, final HttpServletResponse resp) throws IOException {
+    final String path = req.getRequestURI();
+    if (path.endsWith(".html"))
+      resp.setContentType("text/html");
+    
+    // Allow user to only read any file in docs directory
+    final String aHome = System.getenv("ACCUMULO_HOME");
+    PermissionCollection pc = new Permissions();
+    pc.add(new FilePermission(aHome + "/docs/-", "read"));
+    
+    AccessControlContext acc = new AccessControlContext(new ProtectionDomain[] {new ProtectionDomain(null, pc)});
+    
+    IOException e = AccessController.doPrivileged(new PrivilegedAction<IOException>() {
+      
+      @Override
+      public IOException run() {
+        InputStream data = null;
+        try {
+          File file = new File(aHome + path);
+          data = new FileInputStream(file.getAbsolutePath());
+          byte[] buffer = new byte[1024];
+          int n;
+          ServletOutputStream out = resp.getOutputStream();
+          while ((n = data.read(buffer)) > 0)
+            out.write(buffer, 0, n);
+          return null;
+        } catch (IOException e) {
+          return e;
+        } finally {
+          if (data != null) {
+            try {
+              data.close();
+            } catch (IOException ex) {
+              log.error(ex, ex);
+            }
+          }
+        }
+      }
+    }, acc);
+    
+    if (e != null)
+      throw e;
+  }
+  
+  @Override
+  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+    if (req.getRequestURI().startsWith("/web"))
+      getResource(req, resp);
+    else if (req.getRequestURI().equals("/docs") || req.getRequestURI().equals("/docs/apidocs"))
+      super.doGet(req, resp);
+    else if (req.getRequestURI().startsWith("/docs"))
+      getDocResource(req, resp);
+    else if (req.getRequestURI().startsWith("/monitor"))
+      resp.sendRedirect("/master");
+    else if (req.getRequestURI().startsWith("/errors"))
+      resp.sendRedirect("/problems");
+    else
+      super.doGet(req, resp);
+  }
+  
+  public static final int GRAPH_WIDTH = 450;
+  public static final int GRAPH_HEIGHT = 150;
+  
+  private static void plotData(StringBuilder sb, String title, @SuppressWarnings("rawtypes") List data, boolean points) {
+    plotData(sb, title, points, new ArrayList<String>(), data);
+  }
+  
+  @SuppressWarnings("rawtypes")
+  private static void plotData(StringBuilder sb, String title, boolean points, List<String> labels, List... series) {
+    sb.append("<div class=\"plotHeading\">");
+    sb.append(title);
+    sb.append("</div>");
+    sb.append("</br>");
+    String id = "c" + title.hashCode();
+    sb.append("<div id=\"" + id + "\" style=\"width:" + GRAPH_WIDTH + "px;height:" + GRAPH_HEIGHT + "px;\"></div>\n");
+    
+    sb.append("<script type=\"text/javascript\">\n");
+    sb.append("$(function () {\n");
+    
+    for (int i = 0; i < series.length; i++) {
+      
+      @SuppressWarnings("unchecked")
+      List<Pair<Long,? extends Number>> data = series[i];
+      sb.append("    var d" + i + " = [");
+      
+      String sep = "";
+      for (Pair<Long,? extends Number> point : data) {
+        if (point.getSecond() == null)
+          continue;
+        
+        String y;
+        if (point.getSecond() instanceof Double)
+          y = String.format("%1.2f", point.getSecond());
+        else
+          y = point.getSecond().toString();
+        
+        sb.append(sep);
+        sep = ",";
+        sb.append("[" + utc2local(point.getFirst()) + "," + y + "]");
+      }
+      sb.append("    ];\n");
+    }
+    
+    String opts = "lines: { show: true }";
+    if (points)
+      opts = "points: { show: true, radius: 1 }";
+    
+    sb.append("    $.plot($(\"#" + id + "\"),");
+    String sep = "";
+    
+    String colors[] = new String[] {"red", "blue", "green", "black"};
+    
+    sb.append("[");
+    for (int i = 0; i < series.length; i++) {
+      sb.append(sep);
+      sep = ",";
+      sb.append("{ ");
+      if (labels.size() > 0) {
+        sb.append("label: \"" + labels.get(i) + "\", ");
+      }
+      sb.append("data: d" + i + ", " + opts + ", color:\"" + colors[i] + "\" }");
+    }
+    sb.append("], ");
+    sb.append("{yaxis:{}, xaxis:{mode:\"time\",minTickSize: [1, \"minute\"],timeformat: \"%H:%M<br />" + getShortTZName() + "\", ticks:3}});");
+    sb.append("   });\n");
+    sb.append("</script>\n");
+  }
+  
+  /**
+   * Shows the current time zone (based on the current time) short name
+   */
+  private static String getShortTZName() {
+    TimeZone tz = TimeZone.getDefault();
+    return tz.getDisplayName(tz.inDaylightTime(new Date()), TimeZone.SHORT);
+  }
+  
+  /**
+   * Converts a unix timestamp in UTC to one that is relative to the local timezone
+   */
+  private static Long utc2local(Long utcMillis) {
+    Calendar currentCalendar = Calendar.getInstance(); // default timezone
+    currentCalendar.setTimeInMillis(utcMillis + currentCalendar.getTimeZone().getOffset(utcMillis));
+    return currentCalendar.getTime().getTime();
+  }
+  
+  @Override
+  protected void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) throws IOException {
+    if (req.getRequestURI().equals("/docs") || req.getRequestURI().equals("/docs/apidocs")) {
+      sb.append("<object data='").append(req.getRequestURI()).append("/index.html' type='text/html' width='100%' height='100%'></object>");
+      return;
+    }
+    
+    sb.append("<table class='noborder'>\n");
+    sb.append("<tr>\n");
+    
+    sb.append("<td class='noborder'>\n");
+    doAccumuloTable(sb);
+    sb.append("</td>\n");
+    
+    sb.append("<td class='noborder'>\n");
+    doZooKeeperTable(sb);
+    sb.append("</td>\n");
+    
+    sb.append("</tr></table>\n");
+    sb.append("<br/>\n");
+    
+    sb.append("<p/><table class=\"noborder\">\n");
+    
+    sb.append("<tr><td>\n");
+    plotData(sb, "Ingest (Entries/s)", Monitor.getIngestRateOverTime(), false);
+    sb.append("</td><td>\n");
+    plotData(sb, "Scan (Entries/s)", false, Arrays.asList("Read", "Returned"), Monitor.getScanRateOverTime(), Monitor.getQueryRateOverTime());
+    sb.append("</td></tr>\n");
+    
+    sb.append("<tr><td>\n");
+    plotData(sb, "Ingest (MB/s)", Monitor.getIngestByteRateOverTime(), false);
+    sb.append("</td><td>\n");
+    plotData(sb, "Scan (MB/s)", Monitor.getQueryByteRateOverTime(), false);
+    sb.append("</td></tr>\n");
+    
+    sb.append("<tr><td>\n");
+    plotData(sb, "Load Average", Monitor.getLoadOverTime(), false);
+    sb.append("</td><td>\n");
+    plotData(sb, "Seeks", Monitor.getLookupsOverTime(), false);
+    sb.append("</td></tr>\n");
+    
+    sb.append("<tr><td>\n");
+    plotData(sb, "Minor Compactions", Monitor.getMinorCompactionsOverTime(), false);
+    sb.append("</td><td>\n");
+    plotData(sb, "Major Compactions", Monitor.getMajorCompactionsOverTime(), false);
+    sb.append("</td></tr>\n");
+    
+    sb.append("<tr><td>\n");
+    plotData(sb, "Index Cache Hit Rate", Monitor.getIndexCacheHitRateOverTime(), true);
+    sb.append("</td><td>\n");
+    plotData(sb, "Data Cache Hit Rate", Monitor.getDataCacheHitRateOverTime(), true);
+    sb.append("</td></tr>\n");
+    
+    sb.append("</table>\n");
+  }
+  
+  private void doAccumuloTable(StringBuilder sb) throws IOException {
+    // Accumulo
+    Configuration conf = CachedConfiguration.getInstance();
+    FileSystem fs = TraceFileSystem.wrap(FileUtil.getFileSystem(conf, ServerConfiguration.getSiteConfiguration()));
+    MasterMonitorInfo info = Monitor.getMmi();
+    sb.append("<table>\n");
+    sb.append("<tr><th colspan='2'><a href='/master'>Accumulo Master</a></th></tr>\n");
+    if (info == null) {
+      sb.append("<tr><td colspan='2'><span class='error'>Master is Down</span></td></tr>\n");
+    } else {
+      String consumed = "Unknown";
+      String diskUsed = "Unknown";
+      try {
+        Path path = new Path(Monitor.getSystemConfiguration().get(Property.INSTANCE_DFS_DIR));
+        log.debug("Reading the content summary for " + path);
+        try {
+          ContentSummary acu = fs.getContentSummary(path);
+          ContentSummary rootSummary = fs.getContentSummary(new Path("/"));
+          consumed = String.format("%.2f%%", acu.getSpaceConsumed() * 100. / rootSummary.getSpaceConsumed());
+          diskUsed = bytes(acu.getSpaceConsumed());
+        } catch (Exception ex) {
+          log.trace("Unable to get disk usage information from hdfs", ex);
+        }
+        
+        boolean highlight = false;
+        tableRow(sb, (highlight = !highlight), "Disk&nbsp;Used", diskUsed);
+        if (fs.getUsed() != 0)
+          tableRow(sb, (highlight = !highlight), "%&nbsp;of&nbsp;Used&nbsp;DFS", consumed);
+        tableRow(sb, (highlight = !highlight), "<a href='/tables'>Tables</a>", NumberType.commas(Monitor.getTotalTables()));
+        tableRow(sb, (highlight = !highlight), "<a href='/tservers'>Tablet&nbsp;Servers</a>", NumberType.commas(info.tServerInfo.size(), 1, Long.MAX_VALUE));
+        tableRow(sb, (highlight = !highlight), "<a href='/tservers'>Dead&nbsp;Tablet&nbsp;Servers</a>", NumberType.commas(info.deadTabletServers.size(), 0, 0));
+        tableRow(sb, (highlight = !highlight), "Tablets", NumberType.commas(Monitor.getTotalTabletCount(), 1, Long.MAX_VALUE));
+        tableRow(sb, (highlight = !highlight), "Entries", NumberType.commas(Monitor.getTotalEntries()));
+        tableRow(sb, (highlight = !highlight), "Lookups", NumberType.commas(Monitor.getTotalLookups()));
+        tableRow(sb, (highlight = !highlight), "Uptime", Duration.format(System.currentTimeMillis() - Monitor.getStartTime()));
+      } catch (Exception e) {
+        log.debug(e, e);
+      }
+    }
+    sb.append("</table>\n");
+  }
+  
+  private void doZooKeeperTable(StringBuilder sb) throws IOException {
+    // Zookeepers
+    sb.append("<table>\n");
+    sb.append("<tr><th colspan='3'>Zookeeper</th></tr>\n");
+    sb.append("<tr><th>Server</th><th>Mode</th><th>Clients</th></tr>\n");
+    
+    boolean highlight = false;
+    for (ZooKeeperState k : ZooKeeperStatus.getZooKeeperStatus()) {
+      if (k.clients >= 0) {
+        tableRow(sb, (highlight = !highlight), k.keeper, k.mode, k.clients);
+      } else {
+        tableRow(sb, false, k.keeper, "<span class='error'>Down</span>", "");
+      }
+    }
+    sb.append("</table>\n");
+  }
+  
+  private static String bytes(long big) {
+    return NumUtil.bigNumberForSize(big);
+  }
+  
+  public static void tableRow(StringBuilder sb, boolean highlight, Object... cells) {
+    sb.append(highlight ? "<tr class='highlight'>" : "<tr>");
+    for (int i = 0; i < cells.length; ++i) {
+      Object cell = cells[i];
+      String cellValue = cell == null ? "" : String.valueOf(cell).trim();
+      sb.append("<td class='").append(i < cells.length - 1 ? "left" : "right").append("'>").append(cellValue.isEmpty() ? "-" : cellValue).append("</td>");
+    }
+    sb.append("</tr>\n");
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e0533561/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/GcStatusServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/GcStatusServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/GcStatusServlet.java
new file mode 100644
index 0000000..0f0db9e
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/GcStatusServlet.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+package org.apache.accumulo.monitor.servlets;
+
+import java.text.SimpleDateFormat;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.gc.thrift.GCStatus;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.monitor.util.Table;
+import org.apache.accumulo.monitor.util.celltypes.DateTimeType;
+import org.apache.accumulo.monitor.util.celltypes.DurationType;
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+
+public class GcStatusServlet extends BasicServlet {
+  
+  private static final long serialVersionUID = 1L;
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return "Garbage Collector Status";
+  }
+  
+  @Override
+  protected void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+    GCStatus status = Monitor.getGcStatus();
+    
+    if (status != null) {
+      Table gcActivity = new Table("gcActivity", "Collection&nbsp;Activity");
+      gcActivity.addSortableColumn("Activity");
+      gcActivity.addSortableColumn("Finished", new DateTimeType(new SimpleDateFormat("MMM dd, yyyy kk:mm")), null);
+      gcActivity.addSortableColumn("Candidates", new NumberType<Long>(), null);
+      gcActivity.addSortableColumn("Deleted", new NumberType<Long>(), null);
+      gcActivity.addSortableColumn("In&nbsp;Use", new NumberType<Long>(), null);
+      gcActivity.addSortableColumn("Errors", new NumberType<Long>(0l, 1l), null);
+      gcActivity.addSortableColumn("Duration", new DurationType(), null);
+      
+      if (status.last.finished > 0)
+        gcActivity.addRow("File&nbsp;Collection,&nbsp;Last&nbsp;Cycle", status.last.finished, status.last.candidates, status.last.deleted, status.last.inUse,
+            status.last.errors, status.last.finished - status.last.started);
+      if (status.current.started > 0)
+        gcActivity.addRow("File&nbsp;Collection,&nbsp;Running", status.current.finished, status.current.candidates, status.current.deleted,
+            status.current.inUse, status.current.errors, System.currentTimeMillis() - status.current.started);
+      if (status.lastLog.finished > 0)
+        gcActivity.addRow("WAL&nbsp;Collection,&nbsp;Last&nbsp;Cycle", status.lastLog.finished, status.lastLog.candidates, status.lastLog.deleted,
+            status.lastLog.inUse, status.lastLog.errors, status.lastLog.finished - status.lastLog.started);
+      if (status.currentLog.started > 0)
+        gcActivity.addRow("WAL&nbsp;Collection,&nbsp;Running", status.currentLog.finished, status.currentLog.candidates, status.currentLog.deleted,
+            status.currentLog.inUse, status.currentLog.errors, System.currentTimeMillis() - status.currentLog.started);
+      gcActivity.generate(req, sb);
+    } else {
+      banner(sb, "error", "Collector is Unavailable");
+    }
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e0533561/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/JSONServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/JSONServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/JSONServlet.java
new file mode 100644
index 0000000..8980435
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/JSONServlet.java
@@ -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.
+ */
+package org.apache.accumulo.monitor.servlets;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.master.thrift.DeadServer;
+import org.apache.accumulo.core.master.thrift.TableInfo;
+import org.apache.accumulo.core.master.thrift.TabletServerStatus;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.monitor.util.celltypes.TServerLinkType;
+
+import com.google.gson.Gson;
+
+public class JSONServlet extends BasicServlet {
+  private static final long serialVersionUID = 1L;
+  
+  private Gson gson = new Gson();
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return "JSON Report";
+  }
+  
+  @Override
+  protected void pageStart(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+    resp.setContentType("application/json");
+  }
+  
+  private static Map<String,Object> addServer(String ip, String hostname, double osload, double ingest, double query, double ingestMB, double queryMB,
+      int scans, double scansessions, long holdtime) {
+    Map<String,Object> map = new HashMap<String,Object>();
+    map.put("ip", ip);
+    map.put("hostname", hostname);
+    map.put("osload", osload);
+    map.put("ingest", ingest);
+    map.put("query", query);
+    map.put("ingestMB", ingestMB);
+    map.put("queryMB", queryMB);
+    map.put("scans", scans);
+    map.put("scanssessions", scansessions);
+    map.put("holdtime", holdtime);
+    return map;
+  }
+  
+  @Override
+  protected void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+    if (Monitor.getMmi() == null || Monitor.getMmi().tableMap == null) {
+      return;
+    }
+    
+    Map<String,Object> results = new HashMap<String,Object>();
+    List<Map<String,Object>> servers = new ArrayList<Map<String,Object>>();
+    
+    for (TabletServerStatus status : Monitor.getMmi().tServerInfo) {
+      TableInfo summary = Monitor.summarizeTableStats(status);
+      servers.add(addServer(status.name, TServerLinkType.displayName(status.name), status.osLoad, summary.ingestRate, summary.queryRate,
+          summary.ingestByteRate / 1000000.0, summary.queryByteRate / 1000000.0, summary.scans.running + summary.scans.queued, Monitor.getLookupRate(),
+          status.holdTime));
+    }
+    
+    for (Entry<String,Byte> entry : Monitor.getMmi().badTServers.entrySet()) {
+      Map<String,Object> badServer = new HashMap<String,Object>();
+      badServer.put("ip", entry.getKey());
+      badServer.put("bad", true);
+      servers.add(badServer);
+    }
+    
+    for (DeadServer dead : Monitor.getMmi().deadTabletServers) {
+      Map<String,Object> deadServer = new HashMap<String,Object>();
+      deadServer.put("ip", dead.server);
+      deadServer.put("dead", true);
+      servers.add(deadServer);
+    }
+    
+    results.put("servers", servers);
+    sb.append(gson.toJson(results));
+  }
+  
+  @Override
+  protected void pageEnd(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {}
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e0533561/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/LogServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/LogServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/LogServlet.java
new file mode 100644
index 0000000..513c06a
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/LogServlet.java
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+package org.apache.accumulo.monitor.servlets;
+
+import java.text.SimpleDateFormat;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.monitor.DedupedLogEvent;
+import org.apache.accumulo.monitor.LogService;
+import org.apache.accumulo.monitor.util.Table;
+import org.apache.accumulo.monitor.util.celltypes.DateTimeType;
+import org.apache.accumulo.monitor.util.celltypes.StringType;
+import org.apache.log4j.Level;
+import org.apache.log4j.spi.LoggingEvent;
+
+public class LogServlet extends BasicServlet {
+  
+  private static final long serialVersionUID = 1L;
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return "Recent Logs";
+  }
+  
+  @Override
+  protected void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+    boolean clear = true;
+    SimpleDateFormat fmt = new SimpleDateFormat("dd HH:mm:ss,SSSS");
+    Table logTable = new Table("logTable", "Recent&nbsp;Logs");
+    logTable.addSortableColumn("Time", new DateTimeType(fmt), null);
+    logTable.addSortableColumn("Application");
+    logTable.addSortableColumn("Count");
+    logTable.addSortableColumn("Level", new LogLevelType(), null);
+    logTable.addSortableColumn("Message");
+    for (DedupedLogEvent dev : LogService.getInstance().getEvents()) {
+      clear = false;
+      LoggingEvent ev = dev.getEvent();
+      Object application = ev.getMDC("application");
+      if (application == null)
+        application = "";
+      String msg = ev.getMessage().toString();
+      StringBuilder text = new StringBuilder();
+      for (int i = 0; i < msg.length(); i++) {
+        char c = msg.charAt(i);
+        switch (Character.getType(c)) {
+          case Character.UNASSIGNED:
+          case Character.LINE_SEPARATOR:
+          case Character.NON_SPACING_MARK:
+          case Character.PRIVATE_USE:
+            c = '?';
+          default:
+            text.append(c);
+        }
+        
+      }
+      msg = text.toString();
+      if (ev.getThrowableStrRep() != null)
+        for (String line : ev.getThrowableStrRep())
+          msg += "\n\t" + line;
+      msg = sanitize(msg.trim());
+      msg = "<pre class='logevent'>" + msg + "</pre>";
+      logTable.addRow(ev.getTimeStamp(), application, dev.getCount(), ev.getLevel(), msg);
+    }
+    if (!clear)
+      logTable.setSubCaption("<a href='/op?action=clearLog&redir=" + currentPage(req) + "'>Clear&nbsp;All&nbsp;Events</a>");
+    logTable.generate(req, sb);
+    if (!clear)
+      sb.append("<div class='center'><a href='/op?action=clearLog&redir=").append(currentPage(req)).append("'>Clear&nbsp;All&nbsp;Events</a></div>\n");
+  }
+  
+  private static class LogLevelType extends StringType<Level> {
+    @Override
+    public String alignment() {
+      return "center";
+    }
+    
+    @Override
+    public String format(Object obj) {
+      if (obj == null)
+        return "-";
+      Level l = (Level) obj;
+      if (l.equals(Level.ERROR) || l.equals(Level.FATAL))
+        return "<div class='error'>" + l.toString() + "</div>";
+      else if (l.equals(Level.WARN))
+        return "<div class='warning'>" + l.toString() + "</div>";
+      else
+        return l.toString();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e0533561/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/MasterServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/MasterServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/MasterServlet.java
new file mode 100644
index 0000000..aad670d
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/MasterServlet.java
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+package org.apache.accumulo.monitor.servlets;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.client.impl.Tables;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.master.thrift.DeadServer;
+import org.apache.accumulo.core.master.thrift.MasterMonitorInfo;
+import org.apache.accumulo.core.master.thrift.MasterState;
+import org.apache.accumulo.core.master.thrift.RecoveryStatus;
+import org.apache.accumulo.core.master.thrift.TabletServerStatus;
+import org.apache.accumulo.core.util.AddressUtil;
+import org.apache.accumulo.core.util.StringUtil;
+import org.apache.accumulo.monitor.DedupedLogEvent;
+import org.apache.accumulo.monitor.LogService;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.monitor.util.Table;
+import org.apache.accumulo.monitor.util.TableRow;
+import org.apache.accumulo.monitor.util.celltypes.DurationType;
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+import org.apache.accumulo.monitor.util.celltypes.ProgressChartType;
+import org.apache.accumulo.monitor.util.celltypes.StringType;
+import org.apache.accumulo.server.client.HdfsZooInstance;
+import org.apache.log4j.Level;
+
+public class MasterServlet extends BasicServlet {
+  
+  private static final long serialVersionUID = 1L;
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    List<String> masters = Monitor.getInstance().getMasterLocations();
+    return "Master Server" + (masters.size() == 0 ? "" : ":" + AddressUtil.parseAddress(masters.get(0)).getHostName());
+  }
+  
+  @Override
+  protected void pageBody(HttpServletRequest req, HttpServletResponse response, StringBuilder sb) throws IOException {
+    Map<String,String> tidToNameMap = Tables.getIdToNameMap(HdfsZooInstance.getInstance());
+    
+    doLogEventBanner(sb);
+    TablesServlet.doProblemsBanner(sb);
+    doMasterStatus(req, sb);
+    doRecoveryList(req, sb);
+    TablesServlet.doTableList(req, sb, tidToNameMap);
+  }
+  
+  private void doLogEventBanner(StringBuilder sb) {
+    if (LogService.getInstance().getEvents().size() > 0) {
+      int error = 0, warning = 0, total = 0;
+      for (DedupedLogEvent dev : LogService.getInstance().getEvents()) {
+        switch (dev.getEvent().getLevel().toInt()) {
+          case Level.FATAL_INT:
+          case Level.ERROR_INT:
+            error++;
+            break;
+          case Level.WARN_INT:
+            warning++;
+            break;
+        }
+        total++;
+      }
+      banner(sb, error > 0 ? "error" : "warning", String.format("<a href='/log'>Log Events: %d Error%s, %d Warning%s, %d Total</a>", error, error == 1 ? ""
+          : "s", warning, warning == 1 ? "" : "s", total));
+    }
+  }
+  
+  private void doMasterStatus(HttpServletRequest req, StringBuilder sb) throws IOException {
+    
+    if (Monitor.getMmi() != null) {
+      String gcStatus = "Waiting";
+      if (Monitor.getGcStatus() != null) {
+        long start = 0;
+        String label = "";
+        if (Monitor.getGcStatus().current.started != 0 || Monitor.getGcStatus().currentLog.started != 0) {
+          start = Math.max(Monitor.getGcStatus().current.started, Monitor.getGcStatus().currentLog.started);
+          label = "Running";
+        } else if (Monitor.getGcStatus().lastLog.finished != 0) {
+          start = Monitor.getGcStatus().lastLog.finished;
+        }
+        if (start != 0) {
+          long diff = System.currentTimeMillis() - start;
+          gcStatus = label + " " + DateFormat.getInstance().format(new Date(start));
+          gcStatus = gcStatus.replace(" ", "&nbsp;");
+          long normalDelay = Monitor.getSystemConfiguration().getTimeInMillis(Property.GC_CYCLE_DELAY);
+          if (diff > normalDelay * 2)
+            gcStatus = "<span class='warning'>" + gcStatus + "</span>";
+        }
+      } else {
+        gcStatus = "<span class='error'>Down</span>";
+      }
+      if (Monitor.getMmi().state != MasterState.NORMAL) {
+        sb.append("<span class='warning'>Master State: " + Monitor.getMmi().state.name() + " Goal: " + Monitor.getMmi().goalState.name() + "</span>\n");
+      }
+      if (Monitor.getMmi().serversShuttingDown != null && Monitor.getMmi().serversShuttingDown.size() > 0 && Monitor.getMmi().state == MasterState.NORMAL) {
+        sb.append("<span class='warning'>Servers being stopped: " + StringUtil.join(Monitor.getMmi().serversShuttingDown, ", ") + "</span>\n");
+      }
+      
+      int guessHighLoad = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();
+      List<String> slaves = new ArrayList<String>();
+      for (TabletServerStatus up : Monitor.getMmi().tServerInfo) {
+        slaves.add(up.name);
+      }
+      for (DeadServer down : Monitor.getMmi().deadTabletServers) {
+        slaves.add(down.server);
+      }
+      List<String> masters = Monitor.getInstance().getMasterLocations();
+      
+      Table masterStatus = new Table("masterStatus", "Master&nbsp;Status");
+      masterStatus.addSortableColumn("Master", new StringType<String>(), "The hostname of the master server");
+      masterStatus.addSortableColumn("#&nbsp;Online<br />Tablet&nbsp;Servers", new PreciseNumberType((int) (slaves.size() * 0.8 + 1.0), slaves.size(),
+          (int) (slaves.size() * 0.6 + 1.0), slaves.size()), "Number of tablet servers currently available");
+      masterStatus.addSortableColumn("#&nbsp;Total<br />Tablet&nbsp;Servers", new PreciseNumberType(), "The total number of tablet servers configured");
+      masterStatus.addSortableColumn("Last&nbsp;GC", null, "The last time files were cleaned-up from HDFS.");
+      masterStatus.addSortableColumn("#&nbsp;Tablets", new NumberType<Integer>(0, Integer.MAX_VALUE, 2, Integer.MAX_VALUE), null);
+      masterStatus.addSortableColumn("#&nbsp;Unassigned<br />Tablets", new NumberType<Integer>(0, 0), null);
+      masterStatus.addSortableColumn("Entries", new NumberType<Long>(), "The total number of key/value pairs in Accumulo");
+      masterStatus.addSortableColumn("Ingest", new NumberType<Long>(), "The number of Key/Value pairs inserted, per second. "
+          + " Note that deleted records are \"inserted\" and will make the ingest " + "rate increase in the near-term.");
+      masterStatus.addSortableColumn("Entries<br />Read", new NumberType<Long>(),
+          "The total number of Key/Value pairs read on the server side.  Not all may be returned because of filtering.");
+      masterStatus.addSortableColumn("Entries<br />Returned", new NumberType<Long>(), "The total number of Key/Value pairs returned as a result of scans.");
+      masterStatus.addSortableColumn("Hold&nbsp;Time", new DurationType(0l, 0l), "The maximum amount of time that ingest has been held "
+          + "across all servers due to a lack of memory to store the records");
+      masterStatus.addSortableColumn("OS&nbsp;Load", new NumberType<Double>(0., guessHighLoad * 1., 0., guessHighLoad * 3.),
+          "The one-minute load average on the computer that runs the monitor web server.");
+      TableRow row = masterStatus.prepareRow();
+      row.add(masters.size() == 0 ? "<div class='error'>Down</div>" : AddressUtil.parseAddress(masters.get(0)).getHostName());
+      row.add(Monitor.getMmi().tServerInfo.size());
+      row.add(slaves.size());
+      row.add("<a href='/gc'>" + gcStatus + "</a>");
+      row.add(Monitor.getTotalTabletCount());
+      row.add(Monitor.getMmi().unassignedTablets);
+      row.add(Monitor.getTotalEntries());
+      row.add(Math.round(Monitor.getTotalIngestRate()));
+      row.add(Math.round(Monitor.getTotalScanRate()));
+      row.add(Math.round(Monitor.getTotalQueryRate()));
+      row.add(Monitor.getTotalHoldTime());
+      row.add(ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage());
+      masterStatus.addRow(row);
+      masterStatus.generate(req, sb);
+      
+    } else
+      banner(sb, "error", "Master Server Not Running");
+  }
+  
+  private void doRecoveryList(HttpServletRequest req, StringBuilder sb) {
+    MasterMonitorInfo mmi = Monitor.getMmi();
+    if (mmi != null) {
+      Table recoveryTable = new Table("logRecovery", "Log&nbsp;Recovery");
+      recoveryTable.setSubCaption("Some tablets were unloaded in an unsafe manner. Write-ahead logs are being recovered.");
+      recoveryTable.addSortableColumn("Server");
+      recoveryTable.addSortableColumn("Log");
+      recoveryTable.addSortableColumn("Time", new DurationType(), null);
+      recoveryTable.addSortableColumn("Copy/Sort", new ProgressChartType(), null);
+      int rows = 0;
+      for (TabletServerStatus server : mmi.tServerInfo) {
+        if (server.logSorts != null) {
+          for (RecoveryStatus recovery : server.logSorts) {
+            TableRow row = recoveryTable.prepareRow();
+            row.add(AddressUtil.parseAddress(server.name).getHostName());
+            row.add(recovery.name);
+            row.add((long) recovery.runtime);
+            row.add(recovery.progress);
+            recoveryTable.addRow(row);
+            rows++;
+          }
+        }
+      }
+      if (rows > 0)
+        recoveryTable.generate(req, sb);
+    }
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e0533561/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/OperationServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/OperationServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/OperationServlet.java
new file mode 100644
index 0000000..17c6a80
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/OperationServlet.java
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+package org.apache.accumulo.monitor.servlets;
+
+import java.io.IOException;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.Constants;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.zookeeper.ZooUtil;
+import org.apache.accumulo.monitor.LogService;
+import org.apache.accumulo.server.client.HdfsZooInstance;
+import org.apache.accumulo.server.master.state.DeadServerList;
+import org.apache.accumulo.server.problems.ProblemReports;
+import org.apache.accumulo.server.problems.ProblemType;
+import org.apache.log4j.Logger;
+
+public class OperationServlet extends BasicServlet {
+  
+  private static final long serialVersionUID = 1L;
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return "Operations";
+  }
+  
+  @Override
+  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    String redir = null;
+    try {
+      String operation = req.getParameter("action");
+      redir = req.getParameter("redir");
+      if (redir != null)
+        redir = decode(redir);
+      
+      if (operation != null) {
+        for (Class<?> subclass : OperationServlet.class.getClasses()) {
+          Object t;
+          try {
+            t = subclass.newInstance();
+          } catch (Exception e) {
+            continue;
+          }
+          if (t instanceof WebOperation) {
+            WebOperation op = (WebOperation) t;
+            if (op.getClass().getSimpleName().equalsIgnoreCase(operation + "Operation")) {
+              op.execute(req, resp, log);
+              break;
+            }
+          }
+        }
+      }
+    } catch (Throwable t) {
+      log.error(t, t);
+    } finally {
+      try {
+        if (redir != null)
+          resp.sendRedirect(redir);
+        else
+          resp.sendRedirect("/");
+        resp.flushBuffer();
+      } catch (Throwable t) {
+        log.error(t, t);
+      }
+    }
+  }
+  
+  private static interface WebOperation {
+    public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) throws Exception;
+  }
+  
+  public static class RefreshOperation implements WebOperation {
+    @Override
+    public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) {
+      String value = req.getParameter("value");
+      resp.addCookie(new Cookie("page.refresh.rate", value == null ? "5" : value));
+    }
+  }
+  
+  public static class ClearLogOperation implements WebOperation {
+    @Override
+    public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) {
+      LogService.getInstance().clear();
+    }
+  }
+  
+  public static class ClearTableProblemsOperation implements WebOperation {
+    @Override
+    public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) {
+      String table = req.getParameter("table");
+      try {
+        ProblemReports.getInstance().deleteProblemReports(table);
+      } catch (Exception e) {
+        log.error("Failed to delete problem reports for table " + table, e);
+      }
+    }
+  }
+  
+  public static class ClearProblemOperation implements WebOperation {
+    @Override
+    public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) {
+      String table = req.getParameter("table");
+      String resource = req.getParameter("resource");
+      if (resource != null)
+        resource = decode(resource);
+      String ptype = req.getParameter("ptype");
+      if (ptype != null)
+        ptype = decode(ptype);
+      try {
+        ProblemReports.getInstance().deleteProblemReport(table, ProblemType.valueOf(ptype), resource);
+      } catch (Exception e) {
+        log.error("Failed to delete problem reports for table " + table, e);
+      }
+    }
+  }
+  
+  public static class SortTableOperation implements WebOperation {
+    @Override
+    public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) throws IOException {
+      String page = req.getParameter("page");
+      String table = req.getParameter("table");
+      String asc = req.getParameter("asc");
+      String col = req.getParameter("col");
+      if (table == null || page == null || (asc == null && col == null))
+        return;
+      if (asc == null)
+        resp.addCookie(new Cookie("tableSort." + page + "." + table + "." + "sortCol", col));
+      else
+        resp.addCookie(new Cookie("tableSort." + page + "." + table + "." + "sortAsc", asc));
+    }
+  }
+  
+  public static class ToggleLegendOperation implements WebOperation {
+    @Override
+    public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) throws Exception {
+      String page = req.getParameter("page");
+      String table = req.getParameter("table");
+      String show = req.getParameter("show");
+      if (table == null || page == null || show == null)
+        return;
+      resp.addCookie(new Cookie("tableLegend." + page + "." + table + "." + "show", show));
+    }
+  }
+  
+  public static class ClearDeadServerOperation implements WebOperation {
+    @Override
+    public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) {
+      String server = decode(req.getParameter("server"));
+      Instance inst = HdfsZooInstance.getInstance();
+      // a dead server should have a uniq address: a logger or tserver
+      DeadServerList obit = new DeadServerList(ZooUtil.getRoot(inst) + Constants.ZDEADTSERVERS);
+      obit.delete(server);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e0533561/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/PreciseNumberType.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/PreciseNumberType.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/PreciseNumberType.java
new file mode 100644
index 0000000..66f97e1
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/PreciseNumberType.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+package org.apache.accumulo.monitor.servlets;
+
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+
+public class PreciseNumberType extends NumberType<Integer> {
+  
+  public PreciseNumberType(int warnMin, int warnMax, int errMin, int errMax) {
+    super(warnMin, warnMax, errMin, errMax);
+  }
+  
+  public PreciseNumberType() {}
+  
+  public static String bigNumber(long big, String[] SUFFIXES, long base) {
+    return String.format("%,d", big);
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e0533561/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ProblemServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ProblemServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ProblemServlet.java
new file mode 100644
index 0000000..d20328e
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ProblemServlet.java
@@ -0,0 +1,193 @@
+/*
+ * 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.
+ */
+package org.apache.accumulo.monitor.servlets;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.client.impl.Tables;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.monitor.util.Table;
+import org.apache.accumulo.monitor.util.TableRow;
+import org.apache.accumulo.monitor.util.celltypes.CellType;
+import org.apache.accumulo.monitor.util.celltypes.DateTimeType;
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+import org.apache.accumulo.monitor.util.celltypes.StringType;
+import org.apache.accumulo.server.client.HdfsZooInstance;
+import org.apache.accumulo.server.problems.ProblemReport;
+import org.apache.accumulo.server.problems.ProblemReports;
+import org.apache.accumulo.server.problems.ProblemType;
+
+public class ProblemServlet extends BasicServlet {
+  
+  private static final long serialVersionUID = 1L;
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return "Per-Table Problem Report";
+  }
+  
+  @Override
+  protected void pageBody(final HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+    Map<String,String> tidToNameMap = Tables.getIdToNameMap(HdfsZooInstance.getInstance());
+    doProblemSummary(req, sb, tidToNameMap);
+    doProblemDetails(req, sb, req.getParameter("table"), tidToNameMap);
+  }
+  
+  private static void doProblemSummary(final HttpServletRequest req, StringBuilder sb, final Map<String,String> tidToNameMap) {
+    if (Monitor.getProblemSummary().isEmpty() && Monitor.getProblemException() == null)
+      return;
+    
+    Table problemSummary = new Table("problemSummary", "Problem&nbsp;Summary", "error");
+    problemSummary.addSortableColumn("Table", new TableProblemLinkType(tidToNameMap), null);
+    for (ProblemType type : ProblemType.values())
+      problemSummary.addSortableColumn(type.name(), new NumberType<Integer>(), null);
+    problemSummary.addUnsortableColumn("Operations", new ClearTableProblemsLinkType(req, tidToNameMap), null);
+    
+    if (Monitor.getProblemException() != null) {
+      StringBuilder cell = new StringBuilder();
+      cell.append("<b>Failed to obtain problem reports</b> : " + Monitor.getProblemException().getMessage());
+      Throwable cause = Monitor.getProblemException().getCause();
+      while (cause != null) {
+        if (cause.getMessage() != null)
+          cell.append("<br />\n caused by : " + cause.getMessage());
+        cause = cause.getCause();
+      }
+      problemSummary.setSubCaption(cell.toString());
+    } else {
+      for (Entry<String,Map<ProblemType,Integer>> entry : Monitor.getProblemSummary().entrySet()) {
+        TableRow row = problemSummary.prepareRow();
+        row.add(entry.getKey());
+        for (ProblemType pt : ProblemType.values()) {
+          Integer pcount = entry.getValue().get(pt);
+          row.add(pcount == null ? 0 : pcount);
+        }
+        row.add(entry.getKey());
+        problemSummary.addRow(row);
+      }
+    }
+    problemSummary.generate(req, sb);
+  }
+  
+  private static void doProblemDetails(final HttpServletRequest req, StringBuilder sb, String tableId, Map<String,String> tidToNameMap) {
+    
+    if (Monitor.getProblemException() != null)
+      return;
+    
+    ArrayList<ProblemReport> problemReports = new ArrayList<ProblemReport>();
+    Iterator<ProblemReport> iter = tableId == null ? ProblemReports.getInstance().iterator() : ProblemReports.getInstance().iterator(tableId);
+    while (iter.hasNext())
+      problemReports.add(iter.next());
+    final SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss zzz");
+    Table problemTable = new Table("problemDetails", "Problem&nbsp;Details", "error");
+    problemTable.setSubCaption("Problems identified with tables.");
+    problemTable.addSortableColumn("Table", new TableProblemLinkType(tidToNameMap), null);
+    problemTable.addSortableColumn("Problem&nbsp;Type");
+    problemTable.addSortableColumn("Server");
+    problemTable.addSortableColumn("Time", new DateTimeType(sdf), null);
+    problemTable.addSortableColumn("Resource");
+    problemTable.addSortableColumn("Exception");
+    problemTable.addUnsortableColumn("Operations", new ClearProblemLinkType(req), null);
+    for (ProblemReport pr : problemReports) {
+      
+      TableRow row = problemTable.prepareRow();
+      row.add(pr.getTableName());
+      row.add(pr.getProblemType().name());
+      row.add(pr.getServer());
+      row.add(pr.getTime());
+      row.add(pr.getResource());
+      row.add(pr.getException());
+      row.add(pr);
+      problemTable.addRow(row);
+    }
+    problemTable.generate(req, sb);
+  }
+  
+  private static class TableProblemLinkType extends StringType<String> {
+    private Map<String,String> tidToNameMap;
+    
+    public TableProblemLinkType(Map<String,String> tidToNameMap) {
+      this.tidToNameMap = tidToNameMap;
+    }
+    
+    @Override
+    public String format(Object obj) {
+      if (obj == null)
+        return "-";
+      String table = String.valueOf(obj);
+      return String.format("<a href='/problems?table=%s'>%s</a>", table, Tables.getPrintableTableNameFromId(tidToNameMap, table));
+    }
+  }
+  
+  private static class ClearTableProblemsLinkType extends StringType<String> {
+    private HttpServletRequest req;
+    private Map<String,String> tidToNameMap;
+    
+    public ClearTableProblemsLinkType(HttpServletRequest req, Map<String,String> tidToNameMap) {
+      this.req = req;
+      this.tidToNameMap = tidToNameMap;
+    }
+    
+    @Override
+    public String alignment() {
+      return "right";
+    }
+    
+    @Override
+    public String format(Object obj) {
+      if (obj == null)
+        return "-";
+      String table = String.valueOf(obj);
+      return String.format("<a href='/op?table=%s&action=clearTableProblems&redir=%s'>clear ALL %s problems</a>", table, currentPage(req),
+          Tables.getPrintableTableNameFromId(tidToNameMap, table));
+    }
+  }
+  
+  private static class ClearProblemLinkType extends CellType<ProblemReport> {
+    private HttpServletRequest req;
+    
+    public ClearProblemLinkType(HttpServletRequest req) {
+      this.req = req;
+    }
+    
+    @Override
+    public String alignment() {
+      return "right";
+    }
+    
+    @Override
+    public String format(Object obj) {
+      if (obj == null)
+        return "-";
+      ProblemReport p = (ProblemReport) obj;
+      return String.format("<a href='/op?table=%s&action=clearProblem&redir=%s&resource=%s&ptype=%s'>clear this problem</a>", p.getTableName(),
+          currentPage(req), encode(p.getResource()), encode(p.getProblemType().name()));
+    }
+    
+    @Override
+    public int compare(ProblemReport o1, ProblemReport o2) {
+      return 0;
+    }
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e0533561/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java
new file mode 100644
index 0000000..881b294
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java
@@ -0,0 +1,331 @@
+/*
+ * 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.
+ */
+package org.apache.accumulo.monitor.servlets;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import jline.console.ConsoleReader;
+
+import org.apache.accumulo.core.util.shell.Shell;
+
+public class ShellServlet extends BasicServlet {
+  private static final long serialVersionUID = 1L;
+  private Map<String,ShellExecutionThread> userShells = new HashMap<String,ShellExecutionThread>();
+  private ExecutorService service = Executors.newCachedThreadPool();
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return "Shell";
+  }
+  
+  @Override
+  protected void pageBody(HttpServletRequest req, HttpServletResponse response, StringBuilder sb) throws IOException {
+    HttpSession session = req.getSession(true);
+    String user = (String) session.getAttribute("user");
+    if (user == null) {
+      // user attribute is null, check to see if username and password are passed as parameters
+      user = req.getParameter("user");
+      String pass = req.getParameter("pass");
+      String mock = req.getParameter("mock");
+      if (user == null || pass == null) {
+        // username or password are null, re-authenticate
+        sb.append(authenticationForm(req.getRequestURI()));
+        return;
+      }
+      try {
+        // get a new shell for this user
+        ShellExecutionThread shellThread = new ShellExecutionThread(user, pass, mock);
+        service.submit(shellThread);
+        userShells.put(session.getId(), shellThread);
+      } catch (IOException e) {
+        // error validating user, reauthenticate
+        sb.append("<div id='loginError'>Invalid user/password</div>" + authenticationForm(req.getRequestURI()));
+        return;
+      }
+      session.setAttribute("user", user);
+    }
+    if (!userShells.containsKey(session.getId())) {
+      // no existing shell for this user, re-authenticate
+      sb.append(authenticationForm(req.getRequestURI()));
+      return;
+    }
+    ShellExecutionThread shellThread = userShells.get(session.getId());
+    shellThread.getOutput();
+    shellThread.printInfo();
+    sb.append("<div id='shell'>\n");
+    sb.append("<pre id='shellResponse'>").append(shellThread.getOutput()).append("</pre>\n");
+    sb.append("<form><span id='shellPrompt'>").append(shellThread.getPrompt());
+    sb.append("</span><input type='text' name='cmd' id='cmd' onkeydown='return handleKeyDown(event.keyCode);'>\n");
+    sb.append("</form>\n</div>\n");
+    sb.append("<script type='text/javascript'>\n");
+    sb.append("var url = '").append(req.getRequestURL().toString()).append("';\n");
+    sb.append("var xmlhttp = new XMLHttpRequest();\n");
+    sb.append("var hsize = 1000;\n");
+    sb.append("var hindex = 0;\n");
+    sb.append("var history = new Array();\n");
+    sb.append("\n");
+    sb.append("function handleKeyDown(keyCode) {\n");
+    sb.append("  if (keyCode==13) {\n");
+    sb.append("    submitCmd(document.getElementById('cmd').value);\n");
+    sb.append("    hindex = history.length;\n");
+    sb.append("    return false;\n");
+    sb.append("  } else if (keyCode==38) {\n");
+    sb.append("    hindex = hindex==0 ? history.length : hindex - 1;\n");
+    sb.append("    if (hindex == history.length)\n");
+    sb.append("      document.getElementById('cmd').value = '';\n");
+    sb.append("    else\n");
+    sb.append("      document.getElementById('cmd').value = history[hindex];\n");
+    sb.append("    return false;\n");
+    sb.append("  } else if (keyCode==40) {\n");
+    sb.append("    hindex = hindex==history.length ? history.length : hindex + 1;\n");
+    sb.append("    if (hindex == history.length)\n");
+    sb.append("      document.getElementById('cmd').value = '';\n");
+    sb.append("    else\n");
+    sb.append("      document.getElementById('cmd').value = history[hindex];\n");
+    sb.append("    return false;\n");
+    sb.append("  }\n");
+    sb.append("  return true;\n");
+    sb.append("}\n");
+    sb.append("\n");
+    sb.append("function submitCmd(cmd) {\n");
+    sb.append("  if (cmd=='history') {\n");
+    sb.append("    document.getElementById('shellResponse').innerHTML += document.getElementById('shellPrompt').innerHTML+cmd+'\\n';\n");
+    sb.append("    document.getElementById('shellResponse').innerHTML += history.join('\\n');\n");
+    sb.append("    return\n");
+    sb.append("  }\n");
+    sb.append("  xmlhttp.open('POST',url+'?cmd='+cmd,false);\n");
+    sb.append("  xmlhttp.send();\n");
+    sb.append("  var text = xmlhttp.responseText;\n");
+    sb.append("  var index = text.lastIndexOf('\\n');\n");
+    sb.append("  if (index >= 0) {\n");
+    sb.append("    if (index > 0 && document.getElementById('cmd').type == 'text') {\n");
+    sb.append("      if (history.length == hsize)\n");
+    sb.append("        history.shift()\n");
+    sb.append("      history.push(cmd)\n");
+    sb.append("    }\n");
+    sb.append("    if (text.charAt(text.length-1)=='*') {\n");
+    sb.append("      document.getElementById('cmd').type = 'password';\n");
+    sb.append("      text = text.substring(0,xmlhttp.responseText.length-2);\n");
+    sb.append("    } else {\n");
+    sb.append("      document.getElementById('cmd').type = 'text';\n");
+    sb.append("    }\n");
+    sb.append("    document.getElementById('shellResponse').innerHTML += text.substring(0,index+1);\n");
+    sb.append("    document.getElementById('shellPrompt').innerHTML = text.substring(index+1);\n");
+    sb.append("    document.getElementById('cmd').value = '';\n");
+    sb.append("    document.getElementById('shell').scrollTop = document.getElementById('cmd').offsetTop;\n");
+    sb.append("  } else {\n");
+    sb.append("    window.location = url;\n");
+    sb.append("  }\n");
+    sb.append("}\n");
+    sb.append("</script>\n");
+    sb.append("<script type='text/javascript'>window.onload = function() { document.getElementById('cmd').select(); }</script>\n");
+  }
+  
+  @Override
+  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+    HttpSession session = req.getSession(true);
+    String user = (String) session.getAttribute("user");
+    if (user == null || !userShells.containsKey(session.getId())) {
+      // no existing shell for user, re-authenticate
+      doGet(req, resp);
+      return;
+    }
+    ShellExecutionThread shellThread = userShells.get(session.getId());
+    String cmd = req.getParameter("cmd");
+    if (cmd == null) {
+      // the command is null, just print prompt
+      resp.getWriter().append(shellThread.getPrompt());
+      resp.getWriter().flush();
+      return;
+    }
+    shellThread.addInputString(cmd);
+    shellThread.waitUntilReady();
+    if (shellThread.isDone()) {
+      // the command was exit, invalidate session
+      userShells.remove(session.getId());
+      session.invalidate();
+      return;
+    }
+    // get the shell's output
+    StringBuilder sb = new StringBuilder();
+    sb.append(shellThread.getOutput().replace("<", "&lt;").replace(">", "&gt;"));
+    if (sb.length() == 0 || !(sb.charAt(sb.length() - 1) == '\n'))
+      sb.append("\n");
+    // check if shell is waiting for input
+    if (!shellThread.isWaitingForInput())
+      sb.append(shellThread.getPrompt());
+    // check if shell is waiting for password input
+    if (shellThread.isMasking())
+      sb.append("*");
+    resp.getWriter().append(sb.toString());
+    resp.getWriter().flush();
+  }
+  
+  private String authenticationForm(String requestURI) {
+    return "<div id='login'><form method=POST action='" + requestURI + "'>"
+        + "<table><tr><td>Mock:&nbsp</td><td><input type='checkbox' name='mock' value='mock'></td></tr>"
+        + "<tr><td>Username:&nbsp;</td><td><input type='text' name='user'></td></tr>"
+        + "<tr><td>Password:&nbsp;</td><td><input type='password' name='pass'></td><td><input type='submit' value='Enter'></td></tr></table></form></div>";
+  }
+  
+  private static class StringBuilderOutputStream extends OutputStream {
+    StringBuilder sb = new StringBuilder();
+    
+    @Override
+    public void write(int b) throws IOException {
+      sb.append((char) (0xff & b));
+    }
+    
+    public String get() {
+      return sb.toString();
+    }
+    
+    public void clear() {
+      sb.setLength(0);
+    }
+  }
+  
+  private static class ShellExecutionThread extends InputStream implements Runnable {
+    private Shell shell;
+    StringBuilderOutputStream output;
+    private String cmd;
+    private int cmdIndex;
+    private boolean done;
+    private boolean readWait;
+    
+    private ShellExecutionThread(String username, String password, String mock) throws IOException {
+      this.done = false;
+      this.cmd = null;
+      this.cmdIndex = 0;
+      this.readWait = false;
+      this.output = new StringBuilderOutputStream();
+      ConsoleReader reader = new ConsoleReader(this, output);
+      this.shell = new Shell(reader, new PrintWriter(output));
+      shell.setLogErrorsToConsole();
+      if (mock != null) {
+        if (shell.config("--fake", "-u", username, "-p", password))
+          throw new IOException("mock shell config error");
+      } else if (shell.config("-u", username, "-p", password)) {
+        throw new IOException("shell config error");
+      }
+    }
+    
+    @Override
+    public synchronized int read() throws IOException {
+      if (cmd == null) {
+        readWait = true;
+        this.notifyAll();
+      }
+      while (cmd == null) {
+        try {
+          this.wait();
+        } catch (InterruptedException e) {}
+      }
+      readWait = false;
+      int c;
+      if (cmdIndex == cmd.length())
+        c = '\n';
+      else
+        c = cmd.charAt(cmdIndex);
+      cmdIndex++;
+      if (cmdIndex > cmd.length()) {
+        cmd = null;
+        cmdIndex = 0;
+        this.notifyAll();
+      }
+      return c;
+    }
+    
+    @Override
+    public synchronized void run() {
+      Thread.currentThread().setName("shell thread");
+      while (!shell.hasExited()) {
+        while (cmd == null) {
+          try {
+            this.wait();
+          } catch (InterruptedException e) {}
+        }
+        String tcmd = cmd;
+        cmd = null;
+        cmdIndex = 0;
+        try {
+          shell.execCommand(tcmd, false, true);
+        } catch (IOException e) {}
+        this.notifyAll();
+      }
+      done = true;
+      this.notifyAll();
+    }
+    
+    public synchronized void addInputString(String s) {
+      if (done)
+        throw new IllegalStateException("adding string to exited shell");
+      if (cmd == null) {
+        cmd = s;
+      } else {
+        throw new IllegalStateException("adding string to shell not waiting for input");
+      }
+      this.notifyAll();
+    }
+    
+    public synchronized void waitUntilReady() {
+      while (cmd != null) {
+        try {
+          this.wait();
+        } catch (InterruptedException e) {}
+      }
+    }
+    
+    public synchronized String getOutput() {
+      String s = output.get();
+      output.clear();
+      return s;
+    }
+    
+    public String getPrompt() {
+      return shell.getDefaultPrompt();
+    }
+    
+    public void printInfo() throws IOException {
+      shell.printInfo();
+    }
+    
+    public boolean isMasking() {
+      return shell.isMasking();
+    }
+    
+    public synchronized boolean isWaitingForInput() {
+      return readWait;
+    }
+    
+    public boolean isDone() {
+      return done;
+    }
+  }
+}


Mime
View raw message