Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -297,28 +297,6 @@ 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.

For argparse.py:

Expand Down
495 changes: 199 additions & 296 deletions service/src/jamon/org/apache/hive/tmpl/QueryProfileTmpl.jamon

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions service/src/java/org/apache/hive/service/servlet/WebUiLayout.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* 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.service.servlet;

/**
* Shared chrome for the modernized HiveServer2 Web UI (sidebar, status pills,
* theme boot). A single source of truth so the JSP pages (via ui-common.jspf)
* and the Jamon-rendered query page render an identical console layout.
*/
public final class WebUiLayout {
private WebUiLayout() {
}

/** Inline script that applies the persisted theme before first paint. */
public static String themeBoot() {
return "(function(){try{var t=localStorage.getItem('hive.ui.theme');"
+ "if(t==='light')document.documentElement.classList.remove('dark');"
+ "else document.documentElement.classList.add('dark');}catch(e){}})();";
}

/** Tailwind classes for a status pill colored by the given state. */
public static String pill(String s) {
String base = "inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-semibold ";
String u = s == null ? "" : s.toUpperCase();
if (u.contains("FINISH") || "CLOSED".equals(u) || "OK".equals(u)) {
return base + "bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400";
}
if (u.contains("ERROR") || u.contains("FAIL")) {
return base + "bg-red-100 text-red-700 dark:bg-red-500/15 dark:text-red-400";
}
if (u.contains("RUN")) {
return base + "bg-cyan-100 text-cyan-700 dark:bg-cyan-500/15 dark:text-cyan-300";
}
if (u.contains("CANCEL") || u.contains("TIMEOUT") || u.contains("TIMED")) {
return base + "bg-amber-100 text-amber-800 dark:bg-amber-500/15 dark:text-amber-400";
}
if (u.contains("PENDING") || u.contains("INITIAL") || u.contains("QUEUE")) {
return base + "bg-blue-100 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300";
}
return base + "bg-slate-100 text-slate-600 dark:bg-slate-700/40 dark:text-slate-300";
}

private static String groupLabel(String t) {
return "<div class=\"px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider "
+ "text-slate-400 dark:text-slate-500\">" + t + "</div>";
}

private static String navItem(String href, String label, String svg, boolean active) {
String base = "flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors ";
String cls = active
? base + "bg-brand/10 text-brand-700 dark:text-brand font-medium"
: base + "text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 "
+ "hover:text-slate-900 dark:hover:text-slate-100";
return "<a href=\"" + href + "\" class=\"" + cls + "\">"
+ "<svg class=\"w-[18px] h-[18px] shrink-0\" viewBox=\"0 0 24 24\" fill=\"none\" "
+ "stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" "
+ "stroke-linejoin=\"round\">" + svg + "</svg>"
+ "<span>" + label + "</span></a>";
}

/** The full left sidebar; {@code active} is the current page key. */
public static String sidebar(String active, String version, String uptime) {
if (active == null) {
active = "";
}
String iDash = "<rect x='3' y='3' width='7' height='7' rx='1'/>"
+ "<rect x='14' y='3' width='7' height='7' rx='1'/>"
+ "<rect x='3' y='14' width='7' height='7' rx='1'/>"
+ "<rect x='14' y='14' width='7' height='7' rx='1'/>";
String iCfg = "<path d='M4 7h10M18 7h2M4 12h2M10 12h10M4 17h7M15 17h5'/>"
+ "<circle cx='16' cy='7' r='2'/><circle cx='8' cy='12' r='2'/>"
+ "<circle cx='13' cy='17' r='2'/>";
String iMetrics = "<path d='M3 21h18'/>"
+ "<rect x='5' y='10' width='3' height='8' rx='1'/>"
+ "<rect x='11' y='5' width='3' height='13' rx='1'/>"
+ "<rect x='17' y='13' width='3' height='5' rx='1'/>";
String iStacks = "<path d='M12 3l9 5-9 5-9-5 9-5z'/><path d='M3 13l9 5 9-5'/>";
String iLogs = "<path d='M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z'/>"
+ "<path d='M14 3v5h5M9 13h6M9 17h6'/>";
String iLlap = "<rect x='3' y='4' width='18' height='6' rx='1'/>"
+ "<rect x='3' y='14' width='18' height='6' rx='1'/>"
+ "<path d='M7 7h.01M7 17h.01'/>";
String iLog = "<circle cx='12' cy='12' r='3'/>"
+ "<path d='M12 2v3M12 19v3M2 12h3M19 12h3M5 5l1.5 1.5M17.5 17.5L19 19"
+ "M19 5l-1.5 1.5M6.5 17.5L5 19'/>";
String foot = (uptime == null || uptime.isEmpty())
? "Hive " + version
: "Hive " + version + " &middot; up " + uptime;
return "<aside class=\"w-60 shrink-0 flex flex-col border-r border-slate-200 "
+ "dark:border-slate-800 bg-white dark:bg-slate-900\">"
+ "<div class=\"h-14 flex items-center gap-2.5 px-5 border-b border-slate-200 "
+ "dark:border-slate-800\">"
+ "<img src=\"/static/hive-logo.png\" alt=\"Hive\" class=\"shrink-0\" "
+ "style=\"height:24px;width:auto;display:block\">"
+ "<span class=\"font-semibold tracking-tight text-slate-900 "
+ "dark:text-slate-100 whitespace-nowrap\">Hive WebUI</span></div>"
+ "<nav class=\"flex-1 overflow-y-auto p-3 space-y-6\">"
+ "<div class=\"space-y-0.5\">" + groupLabel("Overview")
+ navItem("/hiveserver2.jsp", "Dashboard", iDash, "dashboard".equals(active))
+ navItem("/metrics.jsp", "Metrics", iMetrics, "metrics".equals(active))
+ navItem("/conf.jsp", "Configuration", iCfg, "config".equals(active)) + "</div>"
+ "<div class=\"space-y-0.5\">" + groupLabel("Diagnostics")
+ navItem("/stacks.jsp", "Stack traces", iStacks, "stacks".equals(active))
+ navItem("/logs.jsp", "Logs", iLogs, "logs".equals(active))
+ navItem("/logconf.jsp", "Logging", iLog, "logging".equals(active))
+ navItem("/llap.jsp", "LLAP daemons", iLlap, "llap".equals(active)) + "</div></nav>"
+ "<div class=\"p-4 border-t border-slate-200 dark:border-slate-800 text-xs text-slate-500\">"
+ "<div class=\"flex items-center gap-2\">"
+ "<span class=\"w-2 h-2 rounded-full bg-emerald-500 pulse-dot\"></span>Running</div>"
+ "<div class=\"mt-1\">" + foot + "</div></div></aside>";
}
}
56 changes: 56 additions & 0 deletions service/src/main/tailwind/input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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.
*/
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
html { scrollbar-color: rgb(100 116 139 / .4) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: rgb(100 116 139 / .35); border-radius: 9999px; }
}

@layer components {
/* Transparent Hive bee silhouette as a mask -> takes the element's color,
so it adapts to the active theme (amber via text-brand here). */
.hive-mark {
display: inline-block;
-webkit-mask: url('/static/hive-mark.png') center / contain no-repeat;
mask: url('/static/hive-mark.png') center / contain no-repeat;
background-color: currentColor;
}
}

@layer utilities {
@keyframes riseIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes breathe {
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgb(16 185 129 / .45); }
50% { opacity: .5; box-shadow: 0 0 0 5px rgb(16 185 129 / 0); }
}
@keyframes spinSlow { to { transform: rotate(360deg); } }
.pulse-dot { animation: breathe 2.8s ease-in-out infinite; }
.spin-slow { animation: spinSlow 1.6s linear infinite; }
.stagger > * { animation: riseIn .5s cubic-bezier(.2, .7, .2, 1) both; }
.stagger > *:nth-child(2) { animation-delay: .06s; }
.stagger > *:nth-child(3) { animation-delay: .12s; }
.stagger > *:nth-child(4) { animation-delay: .18s; }
.stagger > *:nth-child(5) { animation-delay: .24s; }
.stagger > *:nth-child(6) { animation-delay: .30s; }
@media (prefers-reduced-motion: reduce) {
.stagger > *, .pulse-dot, .spin-slow { animation: none !important; }
}
}
53 changes: 53 additions & 0 deletions service/src/main/tailwind/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.
*/
// Tailwind config for the Hive WebUI.
// Build (run from the repo root, with the standalone tailwindcss CLI). The CSS
// is committed and served locally by HiveServer2's Jetty (no CDN, airgap-safe);
// the final perl step strips Tailwind's attribution banner so the artifact has
// no external URL strings at all:
// tailwindcss -c service/src/main/tailwind/tailwind.config.js \
// -i service/src/main/tailwind/input.css \
// -o service/src/resources/hive-webapps/static/css/hive.tw.css --minify \
// && perl -i -pe 's{/\*!.*?\*/}{}g' service/src/resources/hive-webapps/static/css/hive.tw.css
module.exports = {
content: [
"service/src/resources/hive-webapps/**/*.{jsp,jspf,html,js}",
"service/src/jamon/**/*.jamon",
"service/src/java/org/apache/hive/service/servlet/WebUiLayout.java",
],
darkMode: "class",
theme: {
extend: {
colors: {
brand: {
DEFAULT: "#f2b134",
50: "#fef6e7",
100: "#fde9c2",
400: "#f2b134",
500: "#e09e22",
600: "#c4861a",
700: "#9c6a16",
},
},
fontFamily: {
sans: ["system-ui", "-apple-system", "Segoe UI", "Roboto", "Helvetica", "Arial", "sans-serif"],
mono: ["ui-monospace", "SFMono-Regular", "Menlo", "Consolas", "monospace"],
},
},
},
plugins: [],
};
120 changes: 120 additions & 0 deletions service/src/resources/hive-webapps/hiveserver2/conf.jsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<%--
/**
* 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.
*/
--%>
<%@ page contentType="text/html;charset=UTF-8"
import="org.apache.hadoop.conf.Configuration"
import="org.apache.hadoop.hive.conf.HiveConf.ConfVars"
import="org.apache.hive.common.util.HiveVersionInfo"
import="javax.servlet.ServletContext"
import="java.util.Objects"
import="jodd.net.HtmlEncoder"
%>
<%@ include file="ui-common.jspf" %>
<%!
boolean isSecret(String k) {
String l = k.toLowerCase();
return l.contains("password") || l.contains("secret") || l.contains("credential");
}
%>
<%
ServletContext ctx = getServletContext();
Configuration conf = (Configuration) ctx.getAttribute("hive.conf");
long startcode = conf != null ? conf.getLong("startcode", System.currentTimeMillis()) : System.currentTimeMillis();
long up = (System.currentTimeMillis() - startcode) / 1000;
String uptime = (up / 86400) + "d " + ((up % 86400) / 3600) + "h " + ((up % 3600) / 60) + "m";
ConfVars[] vars = ConfVars.values();
int total = vars.length, modified = 0;
for (ConfVars cv : vars) {
String cur = conf != null ? conf.get(cv.varname) : null;
if (cur != null && !Objects.equals(cur, cv.getDefaultValue())) modified++;
}
%>
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="utf-8">
<title>Configuration &middot; Hive WebUI</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/static/favicon.png">
<script><%= THEME_BOOT %></script>
<link href="/static/css/hive.tw.css?v=<%= startcode %>" rel="stylesheet">
<script src="/static/js/hive-ui.js?v=<%= startcode %>"></script>
</head>
<body class="font-sans bg-slate-50 dark:bg-slate-950 text-slate-700 dark:text-slate-300 antialiased">
<div class="flex min-h-screen">
<%= sidebar("config", HiveVersionInfo.getVersion(), uptime) %>
<div class="flex-1 min-w-0 flex flex-col">
<header class="sticky top-0 z-10 h-14 flex items-center gap-4 px-6 border-b border-slate-200 dark:border-slate-800 bg-white/80 dark:bg-slate-900/80 backdrop-blur">
<div>
<h1 class="text-base font-semibold leading-tight text-slate-900 dark:text-slate-100">Configuration</h1>
<p class="text-xs text-slate-400">Every HiveConf property &mdash; what it is, its value, and its default</p>
</div>
<div class="ml-auto flex items-center gap-2">
<a href="/conf" class="inline-flex items-center h-8 px-3 rounded-lg text-sm border border-slate-200 dark:border-slate-700 text-slate-500 hover:text-slate-900 dark:hover:text-slate-100" title="Raw Hadoop XML/JSON dump">Raw dump</a>
<button onclick="toggleTheme()" aria-label="Toggle theme" class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-slate-200 dark:border-slate-700 text-slate-500 hover:text-slate-900 dark:hover:text-slate-100"><span id="themeIcon">&#9728;</span></button>
</div>
</header>
<main class="p-6 w-full max-w-[1400px]">
<div class="flex flex-wrap items-center gap-4 mb-5 text-sm text-slate-500">
<span><b class="text-slate-900 dark:text-slate-200"><%= total %></b> properties</span>
<span><b class="text-amber-600 dark:text-amber-400"><%= modified %></b> modified</span>
<span><b class="text-slate-900 dark:text-slate-200" id="cfgShown"><%= total %></b> shown</span>
<div class="ml-auto flex items-center gap-3">
<label class="inline-flex items-center gap-2 cursor-pointer select-none"><input type="checkbox" id="cfgModified" class="accent-brand"> Modified only</label>
<input id="cfgSearch" placeholder="Search key or description&hellip;" class="h-9 w-80 px-3 rounded-lg text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 focus:outline-none focus:ring-2 focus:ring-brand/40">
</div>
</div>

<section class="rounded-xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 overflow-hidden">
<div class="overflow-x-auto"><table class="w-full text-sm" id="cfgTable">
<thead><tr class="text-left text-[11px] uppercase tracking-wide text-slate-400 border-b border-slate-200 dark:border-slate-800">
<th class="px-5 py-2.5 font-medium w-[30%]">Property</th>
<th class="px-5 py-2.5 font-medium w-[16%]">Value</th>
<th class="px-5 py-2.5 font-medium w-[14%]">Default</th>
<th class="px-5 py-2.5 font-medium">Description</th>
</tr></thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-800">
<%
for (ConfVars cv : vars) {
String key = cv.varname;
String def = cv.getDefaultValue();
String cur = conf != null ? conf.get(key) : null;
if (cur == null) cur = def;
boolean mod = cur != null && !Objects.equals(cur, def);
boolean secret = isSecret(key);
String curDisp = secret ? "********" : (cur == null ? "" : cur);
String defDisp = secret ? "********" : (def == null ? "" : def);
String desc = cv.getDescription();
%>
<tr data-modified="<%= mod ? "1" : "0" %>" class="hover:bg-slate-50 dark:hover:bg-slate-800/40 align-top">
<td class="px-5 py-2.5 font-mono text-xs text-brand-700 dark:text-brand break-all"><%= HtmlEncoder.text(key) %><% if (mod) { %> <span class="inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold bg-amber-100 text-amber-800 dark:bg-amber-500/15 dark:text-amber-400">MODIFIED</span><% } %></td>
<td class="px-5 py-2.5 font-mono text-xs break-all"><%= HtmlEncoder.text(curDisp) %></td>
<td class="px-5 py-2.5 font-mono text-xs text-slate-400 break-all"><%= HtmlEncoder.text(defDisp) %></td>
<td class="px-5 py-2.5 text-slate-500 dark:text-slate-400"><%= desc == null ? "" : HtmlEncoder.text(desc) %></td>
</tr>
<% } %>
</tbody>
</table></div>
</section>
<p class="mt-4 text-xs text-slate-400">Secrets (password / secret / credential keys) are masked.</p>
</main>
</div>
</div>
</body>
</html>
Loading