diff --git LICENSE LICENSE index f26f4e1..96ea54b 100644 --- LICENSE +++ LICENSE @@ -558,3 +558,26 @@ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +For json.human.js/json.human.css: + +Copyright (c) 2016 Mariano Guerra + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git common/src/java/org/apache/hive/http/HttpServer.java common/src/java/org/apache/hive/http/HttpServer.java index 32956b1..9ceaf3f 100644 --- common/src/java/org/apache/hive/http/HttpServer.java +++ common/src/java/org/apache/hive/http/HttpServer.java @@ -22,6 +22,8 @@ import java.io.IOException; import java.net.URL; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Set; @@ -31,6 +33,7 @@ import javax.servlet.http.HttpServletResponse; import com.google.common.base.Preconditions; +import org.apache.commons.math3.util.Pair; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.CommonConfigurationKeys; import org.apache.hadoop.hive.conf.HiveConf; @@ -108,13 +111,15 @@ private HttpServer(final Builder b) throws IOException { private int port; private int maxThreads; private HiveConf conf; - private Map contextAttrs = new HashMap(); + private final Map contextAttrs = new HashMap(); private String keyStorePassword; private String keyStorePath; private String spnegoPrincipal; private String spnegoKeytab; private boolean useSPNEGO; private boolean useSSL; + private final List>> servlets = + new LinkedList>>(); public Builder(String name) { Preconditions.checkArgument(name != null && !name.isEmpty(), "Name must be specified"); @@ -188,6 +193,11 @@ public Builder setContextAttribute(String name, Object value) { contextAttrs.put(name, value); return this; } + + public Builder addServlet(String endpoint, Class servlet) { + servlets.add(new Pair>(endpoint, servlet)); + return this; + } } public void start() throws Exception { @@ -383,6 +393,10 @@ void initializeWebServer(Builder b) { addServlet("conf", "/conf", ConfServlet.class); addServlet("stacks", "/stacks", StackServlet.class); + for (Pair> p : b.servlets) { + addServlet(p.getFirst(), "/" + p.getFirst(), p.getSecond()); + } + ServletContextHandler staticCtx = new ServletContextHandler(contexts, "/static"); staticCtx.setResourceBase(appDir + "/static"); diff --git llap-client/src/java/org/apache/hadoop/hive/llap/registry/impl/LlapRegistryService.java llap-client/src/java/org/apache/hadoop/hive/llap/registry/impl/LlapRegistryService.java index 2b4516b..49b2653 100644 --- llap-client/src/java/org/apache/hadoop/hive/llap/registry/impl/LlapRegistryService.java +++ llap-client/src/java/org/apache/hadoop/hive/llap/registry/impl/LlapRegistryService.java @@ -17,8 +17,6 @@ import java.util.HashMap; import java.util.Map; -import com.google.common.base.Preconditions; - import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.conf.HiveConf.ConfVars; @@ -29,6 +27,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Preconditions; + public class LlapRegistryService extends AbstractService { private static final Logger LOG = LoggerFactory.getLogger(LlapRegistryService.class); @@ -61,6 +61,7 @@ public static synchronized LlapRegistryService getClient(Configuration conf) { String name = hosts.substring(1); if (yarnRegistries.containsKey(name)) { registry = yarnRegistries.get(name); + registry.start(); } else { registry = new LlapRegistryService(false); registry.init(conf); diff --git llap-server/src/java/org/apache/hadoop/hive/llap/cli/LlapStatusServiceDriver.java llap-server/src/java/org/apache/hadoop/hive/llap/cli/LlapStatusServiceDriver.java index d1193ad..45ba5d0 100644 --- llap-server/src/java/org/apache/hadoop/hive/llap/cli/LlapStatusServiceDriver.java +++ llap-server/src/java/org/apache/hadoop/hive/llap/cli/LlapStatusServiceDriver.java @@ -20,6 +20,7 @@ import java.io.IOException; +import java.io.PrintWriter; import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedList; @@ -67,7 +68,7 @@ public LlapStatusServiceDriver() { } - private int run(String[] args) { + public int run(String[] args) { SliderClient sliderClient = null; try { @@ -148,14 +149,13 @@ private int run(String[] args) { } } - private void outputJson() throws LlapStatusCliException { + public void outputJson(PrintWriter writer) throws LlapStatusCliException { ObjectMapper mapper = new ObjectMapper(); mapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false); mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL); mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_EMPTY); try { - System.out - .println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(appStatusBuilder)); + writer.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(appStatusBuilder)); } catch (IOException e) { throw new LlapStatusCliException(ExitCode.LLAP_JSON_GENERATION_ERROR, "Failed to create JSON", e); @@ -455,7 +455,7 @@ private ExitCode populateAppStatusFromLlapRegistry(LlapStatusOptions options, Ap private Long appStartTime; private Long appFinishTime; - private List llapInstances = new LinkedList<>(); + private final List llapInstances = new LinkedList<>(); private transient Map containerToInstanceMap = new HashMap<>(); @@ -771,7 +771,7 @@ public ExitCode getExitCode() { RUNNING_ALL, COMPLETE, UNKNOWN } - enum ExitCode { + public enum ExitCode { SUCCESS(0), INCORRECT_USAGE(10), YARN_ERROR(20), @@ -806,7 +806,7 @@ public static void main(String[] args) { LlapStatusServiceDriver statusServiceDriver = new LlapStatusServiceDriver(); ret = statusServiceDriver.run(args); if (ret == ExitCode.SUCCESS.getInt()) { - statusServiceDriver.outputJson(); + statusServiceDriver.outputJson(new PrintWriter(System.out)); } } catch (Throwable t) { diff --git ql/src/java/org/apache/hadoop/hive/ql/optimizer/physical/LlapDecider.java ql/src/java/org/apache/hadoop/hive/ql/optimizer/physical/LlapDecider.java index d3ec7ff..c0b7a32 100644 --- ql/src/java/org/apache/hadoop/hive/ql/optimizer/physical/LlapDecider.java +++ ql/src/java/org/apache/hadoop/hive/ql/optimizer/physical/LlapDecider.java @@ -73,17 +73,17 @@ import org.slf4j.LoggerFactory; /** - * LlapDecider takes care of tagging certain vertices in the execution - * graph as "llap", which in turn causes them to be submitted to an - * llap daemon instead of a regular yarn container. + * LlapDecider takes care of tagging certain vertices in the execution graph as + * "llap", which in turn causes them to be submitted to an llap daemon instead + * of a regular yarn container. * - * The actual algoritm used is driven by LLAP_EXECUTION_MODE. "all", - * "none" and "map" mechanically tag those elements. "auto" tries to - * be smarter by looking for suitable vertices. + * The actual algorithm used is driven by LLAP_EXECUTION_MODE. "all", "none" and + * "map" mechanically tag those elements. "auto" tries to be smarter by looking + * for suitable vertices. * - * Regardless of the algorithm used, it's always ensured that there's - * not user code that will be sent to the daemon (ie.: script - * operators, temporary functions, etc) + * Regardless of the algorithm used, it's always ensured that there's not user + * code that will be sent to the daemon (ie.: script operators, temporary + * functions, etc) */ public class LlapDecider implements PhysicalPlanResolver { diff --git service/pom.xml service/pom.xml index 41a4ef1..ae82cd3 100644 --- service/pom.xml +++ service/pom.xml @@ -49,6 +49,12 @@ hive-service-rpc ${project.version} + + org.apache.hive + hive-llap-server + ${project.version} + + commons-codec diff --git service/src/java/org/apache/hive/http/LlapServlet.java service/src/java/org/apache/hive/http/LlapServlet.java new file mode 100644 index 0000000..2095f07 --- /dev/null +++ service/src/java/org/apache/hive/http/LlapServlet.java @@ -0,0 +1,83 @@ +/** + * 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.hive.http; + +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hive.llap.cli.LlapStatusServiceDriver; + +@SuppressWarnings("serial") +public class LlapServlet extends HttpServlet { + + private static final Log LOG = LogFactory.getLog(JMXJsonServlet.class); + static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + + /** + * Initialize this servlet. + */ + @Override + public void init() throws ServletException { + } + + /** + * Process a GET request for the specified resource. + * + * @param request + * The servlet request we are processing + * @param response + * The servlet response we are creating + */ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) { + try { + if (!HttpServer.isInstrumentationAccessAllowed(getServletContext(), request, response)) { + return; + } + PrintWriter writer = null; + try { + response.setContentType("application/json; charset=utf8"); + response.setHeader(ACCESS_CONTROL_ALLOW_METHODS, "GET"); + response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); + response.setHeader("Cache-Control", "no-transform,public,max-age=60,s-maxage=60"); + + writer = response.getWriter(); + LlapStatusServiceDriver driver = new LlapStatusServiceDriver(); + int ret = driver.run(new String[] { "-n", "llap0" }); + if (ret == LlapStatusServiceDriver.ExitCode.SUCCESS.getInt()) { + driver.outputJson(writer); + } + + } finally { + if (writer != null) { + writer.close(); + } + } + } catch (Exception e) { + LOG.error("Caught exception while processing llap status request", e); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } +} \ No newline at end of file diff --git service/src/java/org/apache/hive/service/server/HiveServer2.java service/src/java/org/apache/hive/service/server/HiveServer2.java index 882f4ae..d61edf5 100644 --- service/src/java/org/apache/hive/service/server/HiveServer2.java +++ service/src/java/org/apache/hive/service/server/HiveServer2.java @@ -59,6 +59,7 @@ import org.apache.hive.common.util.HiveStringUtils; import org.apache.hive.common.util.HiveVersionInfo; import org.apache.hive.http.HttpServer; +import org.apache.hive.http.LlapServlet; import org.apache.hive.service.CompositeService; import org.apache.hive.service.ServiceException; import org.apache.hive.service.cli.CLIService; @@ -181,6 +182,7 @@ public void run() { builder.setSPNEGOKeytab(spnegoKeytab); builder.setUseSPNEGO(true); } + builder.addServlet("llap", LlapServlet.class); webServer = builder.build(); webServer.addServlet("query_page", "/query_page", QueryProfileServlet.class); } diff --git service/src/resources/hive-webapps/hiveserver2/hiveserver2.jsp service/src/resources/hive-webapps/hiveserver2/hiveserver2.jsp index 293a8ef..3c187b6 100644 --- service/src/resources/hive-webapps/hiveserver2/hiveserver2.jsp +++ service/src/resources/hive-webapps/hiveserver2/hiveserver2.jsp @@ -75,6 +75,7 @@ SessionManager sessionManager =
  • Metrics Dump
  • Hive Configuration
  • Stack Trace
  • +
  • Llap Daemons
  • diff --git service/src/resources/hive-webapps/hiveserver2/llap.html service/src/resources/hive-webapps/hiveserver2/llap.html new file mode 100644 index 0000000..e1424b8 --- /dev/null +++ service/src/resources/hive-webapps/hiveserver2/llap.html @@ -0,0 +1,47 @@ + + + + + + HiveServer2 + + + + + + + + + + + + + + + +
    + + diff --git service/src/resources/hive-webapps/static/css/json.human.css service/src/resources/hive-webapps/static/css/json.human.css new file mode 100644 index 0000000..2ee552f --- /dev/null +++ service/src/resources/hive-webapps/static/css/json.human.css @@ -0,0 +1,110 @@ +.jh-root, .jh-type-object, .jh-type-array, .jh-key, .jh-value, .jh-root tr{ + -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ + -moz-box-sizing: border-box; /* Firefox, other Gecko */ + box-sizing: border-box; /* Opera/IE 8+ */ + font-weight: bold; +} + +.jh-key, .jh-value{ + margin: 0; + padding: 0.2em; + font-weight: bold; +} + +.jh-value{ + border-left: 1px solid #ddd; +} + +.jh-type-number{ + text-align: center; + color: #5286BC; +} + +.jh-type-bool-true{ + text-align: center; + color: #5A811C; +} + +.jh-type-bool-false{ + text-align: center; + color: #D45317; +} + +.jh-type-bool-image { + width: 20px; + height: 20px; + margin-right: 5px; + vertical-align: bottom; +} + +.jh-type-string{ + font-style: italic; + color: #6E6E6E; +} + +.jh-array-key{ + font-style: italic; + font-size: small; + text-align: center; +} + +.jh-object-key, .jh-array-key{ + color: #444; + vertical-align: top; +} + +.jh-type-object > tbody > tr:nth-child(odd), .jh-type-array > tbody > tr:nth-child(odd){ + background-color: #f5f5f5; +} + +.jh-type-object > tbody > tr:nth-child(even), .jh-type-array > tbody > tr:nth-child(even){ + background-color: #fff; +} + +.jh-type-object, .jh-type-array{ + width: 100%; + border-collapse: collapse; +} + +.jh-root{ + border: 1px solid #ccc; + margin: 0.2em; +} + +th.jh-key{ + text-align: left; +} + +.jh-type-object > tbody > tr, .jh-type-array > tbody > tr{ + border: 1px solid #ddd; + border-bottom: none; +} + +.jh-type-object > tbody > tr:last-child, .jh-type-array > tbody > tr:last-child{ + border-bottom: 1px solid #ddd; +} + +.jh-type-object > tbody > tr:hover, .jh-type-array > tbody > tr:hover{ + border: 1px solid #F99927; +} + +.jh-empty{ + font-style: italic; + color: #999; + font-size: small; +} + +.jh-a { + text-decoration: none; +} + +.jh-a:hover{ + text-decoration: underline; +} + +.jh-a span.jh-type-string { + text-decoration: none; + color : #268ddd; + font-style: normal; +} + diff --git service/src/resources/hive-webapps/static/js/json.human.js service/src/resources/hive-webapps/static/js/json.human.js new file mode 100644 index 0000000..2241623 --- /dev/null +++ service/src/resources/hive-webapps/static/js/json.human.js @@ -0,0 +1,452 @@ +/* json.human.js - http://marianoguerra.github.io/json.human.js */ +/* Licensed under MIT - see above site for details */ + +/*globals define, module, require, document*/ +(function (root, factory) { + "use strict"; + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof module !== 'undefined' && module.exports) { + module.exports = factory(); + } else { + root.JsonHuman = factory(); + } +}(this, function () { + "use strict"; + + var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + function makePrefixer(prefix) { + return function (name) { + return prefix + "-" + name; + }; + } + + function isArray(obj) { + return toString.call(obj) === '[object Array]'; + } + + function sn(tagName, className, data) { + var result = document.createElement(tagName); + + result.className = className; + result.appendChild(document.createTextNode("" + data)); + + return result; + } + + function scn(tagName, className, child) { + var result = document.createElement(tagName), + i, len; + + result.className = className; + + if (isArray(child)) { + for (i = 0, len = child.length; i < len; i += 1) { + result.appendChild(child[i]); + } + } else { + result.appendChild(child); + } + + return result; + } + + function linkNode(child, href, target){ + var a = scn("a", HYPERLINK_CLASS_NAME, child); + a.setAttribute('href', href); + a.setAttribute('target', target); + return a; + } + + var toString = Object.prototype.toString, + prefixer = makePrefixer("jh"), + p = prefixer, + ARRAY = 2, + BOOL = 4, + INT = 8, + FLOAT = 16, + STRING = 32, + OBJECT = 64, + SPECIAL_OBJECT = 128, + FUNCTION = 256, + UNK = 1, + + STRING_CLASS_NAME = p("type-string"), + STRING_EMPTY_CLASS_NAME = p("type-string") + " " + p("empty"), + + BOOL_TRUE_CLASS_NAME = p("type-bool-true"), + BOOL_FALSE_CLASS_NAME = p("type-bool-false"), + BOOL_IMAGE = p("type-bool-image"), + INT_CLASS_NAME = p("type-int") + " " + p("type-number"), + FLOAT_CLASS_NAME = p("type-float") + " " + p("type-number"), + + OBJECT_CLASS_NAME = p("type-object"), + OBJ_KEY_CLASS_NAME = p("key") + " " + p("object-key"), + OBJ_VAL_CLASS_NAME = p("value") + " " + p("object-value"), + OBJ_EMPTY_CLASS_NAME = p("type-object") + " " + p("empty"), + + FUNCTION_CLASS_NAME = p("type-function"), + + ARRAY_KEY_CLASS_NAME = p("key") + " " + p("array-key"), + ARRAY_VAL_CLASS_NAME = p("value") + " " + p("array-value"), + ARRAY_CLASS_NAME = p("type-array"), + ARRAY_EMPTY_CLASS_NAME = p("type-array") + " " + p("empty"), + + HYPERLINK_CLASS_NAME = p('a'), + + UNKNOWN_CLASS_NAME = p("type-unk"); + + function getType(obj) { + var type = typeof obj; + + switch (type) { + case "boolean": + return BOOL; + case "string": + return STRING; + case "number": + return (obj % 1 === 0) ? INT : FLOAT; + case "function": + return FUNCTION; + default: + if (isArray(obj)) { + return ARRAY; + } else if (obj === Object(obj)) { + if (obj.constructor === Object) { + return OBJECT; + } + return OBJECT | SPECIAL_OBJECT + } else { + return UNK; + } + } + } + + function _format(data, options, parentKey) { + + var result, container, key, keyNode, valNode, len, children, tr, value, + isEmpty = true, + isSpecial = false, + accum = [], + type = getType(data); + + // Initialized & used only in case of objects & arrays + var hyperlinksEnabled, aTarget, hyperlinkKeys ; + + if (type === BOOL) { + var boolOpt = options.bool; + container = document.createElement('div'); + + if (boolOpt.showImage) { + var img = document.createElement('img'); + img.setAttribute('class', BOOL_IMAGE); + + img.setAttribute('src', + '' + (data ? boolOpt.img.true : boolOpt.img.false)); + + container.appendChild(img); + } + + if (boolOpt.showText) { + container.appendChild(data ? + sn("span", BOOL_TRUE_CLASS_NAME, boolOpt.text.true) : + sn("span", BOOL_FALSE_CLASS_NAME, boolOpt.text.false)); + } + + result = container; + + } else if (type === STRING) { + if (data === "") { + result = sn("span", STRING_EMPTY_CLASS_NAME, "(Empty Text)"); + } else { + aTarget = options.hyperlinks.target; + hyperlinkKeys = options.hyperlinks.keys; + hyperlinksEnabled = + options.hyperlinks.enable && + hyperlinkKeys && + hyperlinkKeys.length > 0; + if (hyperlinksEnabled && indexOf.call(hyperlinkKeys, parentKey) >= 0) { + result = scn("span", STRING_CLASS_NAME, linkNode(sn("span",STRING_CLASS_NAME, data), data, aTarget)); + } else { + result = sn("span", STRING_CLASS_NAME, data); + } + } + } else if (type === INT) { + result = sn("span", INT_CLASS_NAME, data); + } else if (type === FLOAT) { + result = sn("span", FLOAT_CLASS_NAME, data); + } else if (type & OBJECT) { + if (type & SPECIAL_OBJECT) { + isSpecial = true; + } + children = []; + + aTarget = options.hyperlinks.target; + hyperlinkKeys = options.hyperlinks.keys; + + // Is Hyperlink Key + hyperlinksEnabled = + options.hyperlinks.enable && + hyperlinkKeys && + hyperlinkKeys.length > 0; + + for (key in data) { + isEmpty = false; + + value = data[key]; + + valNode = _format(value, options, key); + + var converted = key.replace( /([A-Z])/g, " $1" ); + converted = converted.charAt(0).toUpperCase() + converted.slice(1); + + keyNode = sn("th", OBJ_KEY_CLASS_NAME, converted); + + if( hyperlinksEnabled && + typeof(value) === 'string' && + indexOf.call(hyperlinkKeys, key) >= 0){ + + valNode = scn("td", OBJ_VAL_CLASS_NAME, linkNode(valNode, value, aTarget)); + } else { + valNode = scn("td", OBJ_VAL_CLASS_NAME, valNode); + } + + tr = document.createElement("tr"); + tr.appendChild(keyNode); + tr.appendChild(valNode); + + children.push(tr); + } + + if (isSpecial) { + result = sn('span', STRING_CLASS_NAME, data.toString()) + } else if (isEmpty) { + result = sn("span", OBJ_EMPTY_CLASS_NAME, "(Empty Object)"); + } else { + result = scn("table", OBJECT_CLASS_NAME, scn("tbody", '', children)); + } + } else if (type === FUNCTION) { + result = sn("span", FUNCTION_CLASS_NAME, data); + } else if (type === ARRAY) { + if (data.length > 0) { + children = []; + var showArrayIndices = options.showArrayIndex; + var showArrayAsFlatTable = options.showArrayAsFlatTable; + var flatTableKeys = options.flatTableKeys = options.flatTableKeys; + + aTarget = options.hyperlinks.target; + hyperlinkKeys = options.hyperlinks.keys; + + // Hyperlink of arrays? + hyperlinksEnabled = parentKey && options.hyperlinks.enable && + hyperlinkKeys && + hyperlinkKeys.length > 0 && + indexOf.call(hyperlinkKeys, parentKey) >= 0; + + if (showArrayAsFlatTable && data.length > 0) { + var first = data[0]; + var objKeys = Object.keys(first); + var headerNodes = []; + + if (flatTableKeys.length == 0) { + for (var k in objKeys) { + flatTableKeys.push(objKeys[k]); + } + } + + var objKeyMod = []; + for (var k in objKeys) { + if ($.inArray(objKeys[k], flatTableKeys) >= 0) { + objKeyMod.push(objKeys[k]); + var converted = objKeys[k].replace(/([A-Z])/g, " $1" ); + converted = converted.charAt(0).toUpperCase() + converted.slice(1); + headerNodes.push(sn("th", ARRAY_KEY_CLASS_NAME, converted)); + } + } + objKeys = objKeyMod; + + var headerRow = scn("tr", UNKNOWN_CLASS_NAME, headerNodes); + children.push(headerRow); + for (key = 0, len = data.length; key < len; key += 1) { + value = data[key]; + var row = document.createElement("tr"); + for (var k in objKeys) { + var v = value[objKeys[k]]; + if (hyperlinksEnabled && typeof(v) === "string") { + valNode = _format(v, options, objKeys[k]); + valNode = scn("td", ARRAY_VAL_CLASS_NAME, + linkNode(valNode, v, aTarget)); + } else { + valNode = scn("td", ARRAY_VAL_CLASS_NAME, + _format(v, options, objKeys[k])); + } + row.appendChild(valNode); + } + children.push(row); + } + } + else { + for (key = 0, len = data.length; key < len; key += 1) { + + keyNode = sn("th", ARRAY_KEY_CLASS_NAME, key); + value = data[key]; + + if (hyperlinksEnabled && typeof(value) === "string") { + valNode = _format(value, options, key); + valNode = scn("td", ARRAY_VAL_CLASS_NAME, + linkNode(valNode, value, aTarget)); + } else { + valNode = scn("td", ARRAY_VAL_CLASS_NAME, + _format(value, options, key)); + } + + tr = document.createElement("tr"); + + if (showArrayIndices) { + tr.appendChild(keyNode); + } + tr.appendChild(valNode); + + children.push(tr); + } + } + result = scn("table", ARRAY_CLASS_NAME, scn("tbody", '', children)); + } else { + result = sn("span", ARRAY_EMPTY_CLASS_NAME, "(Empty List)"); + } + } else { + result = sn("span", UNKNOWN_CLASS_NAME, data); + } + + return result; + } + + function format(data, options) { + options = validateOptions(options || {}); + + var result; + + result = _format(data, options); + result.className = result.className + " " + prefixer("root"); + + return result; + } + + function validateOptions(options){ + options = validateArrayIndexOption(options); + options = validateArrayAsFlatTableOption(options); + options = validateHyperlinkOptions(options); + options = validateBoolOptions(options); + + // Add any more option validators here + + return options; + } + + function validateArrayIndexOption(options) { + if(options.showArrayIndex === undefined){ + options.showArrayIndex = true; + } else { + // Force to boolean just in case + options.showArrayIndex = options.showArrayIndex ? true: false; + } + + return options; + } + + function validateArrayAsFlatTableOption(options) { + if(options.showArrayAsFlatTable === undefined){ + options.showArrayAsFlatTable = false; + } else { + // Force to boolean just in case + options.showArrayAsFlatTable = options.showArrayAsFlatTable ? true: false; + } + + if (options.showArrayAsFlatTable) { + options.flatTableKeys = isArray(options.flatTableKeys) ? options.flatTableKeys : []; + } + + return options; + } + + function validateHyperlinkOptions(options){ + var hyperlinks = { + enable : false, + keys : null, + target : '' + }; + + if(options.hyperlinks && options.hyperlinks.enable) { + hyperlinks.enable = true; + + hyperlinks.keys = isArray(options.hyperlinks.keys) ? options.hyperlinks.keys : []; + + if(options.hyperlinks.target) { + hyperlinks.target = '' + options.hyperlinks.target; + } else { + hyperlinks.target = '_blank'; + } + } + + options.hyperlinks = hyperlinks; + + return options; + } + + function validateBoolOptions(options){ + if(!options.bool){ + options.bool = { + text: { + true : "true", + false : "false" + }, + img : { + true: "", + false: "" + }, + showImage : false, + showText : true + }; + } else { + var boolOptions = options.bool; + + // Show text if no option + if(!boolOptions.showText && !boolOptions.showImage){ + boolOptions.showImage = false; + boolOptions.showText = true; + } + + if(boolOptions.showText){ + if(!boolOptions.text){ + boolOptions.text = { + true : "true", + false : "false" + }; + } else { + var t = boolOptions.text.true, f = boolOptions.text.false; + + if(getType(t) != STRING || t === ''){ + boolOptions.text.true = 'true'; + } + + if(getType(f) != STRING || f === ''){ + boolOptions.text.false = 'false'; + } + } + } + + if(boolOptions.showImage){ + if(!boolOptions.img.true && !boolOptions.img.false){ + boolOptions.showImage = false; + } + } + } + + return options; + } + + return { + format: format + }; +})); diff --git service/src/resources/hive-webapps/static/js/llap.js service/src/resources/hive-webapps/static/js/llap.js new file mode 100644 index 0000000..e84fd03 --- /dev/null +++ service/src/resources/hive-webapps/static/js/llap.js @@ -0,0 +1,37 @@ +window.options = { + showArrayAsFlatTable: true, + flatTableKeys: ['containerId','webUrl'], + + showArrayIndex: false, + hyperlinks : { + enable : true, + keys: ['amWebUrl','statusUrl','webUrl'], + target : '_blank' + }, + + bool : { + showText : true, + text : { + true : "true", + false : "false" + }, + showImage : true, + img : { + true : 'css/true.png', + false : 'css/false.png' + } + } +}; + +$(document).ready(function () { + var showData = $('#show-data'); + + $.getJSON('llap', function (data) { + console.log(data); + + showData.empty(); + var node = JsonHuman.format(data, window.options); + showData[0].appendChild(node); + }); + showData.text('Loading the JSON file.'); +});