http://git-wip-us.apache.org/repos/asf/incubator-livy/blob/61b206e0/server/src/main/resources/com/cloudera/livy/server/ui/static/sessions-table.html ---------------------------------------------------------------------- diff --git a/server/src/main/resources/com/cloudera/livy/server/ui/static/sessions-table.html b/server/src/main/resources/com/cloudera/livy/server/ui/static/sessions-table.html new file mode 100644 index 0000000..3452b4c --- /dev/null +++ b/server/src/main/resources/com/cloudera/livy/server/ui/static/sessions-table.html @@ -0,0 +1,53 @@ +<!-- + Licensed to Cloudera, Inc. under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. Cloudera, Inc. 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. +--> + +<h4 id="interactive-sessions-header" class="sessions-template">Interactive Sessions</h4> + +<table class="table table-striped sessions-table sessions-template"> + <thead class="sessions-table-head"> + <tr> + <th>Session Id</th> + <th>Application Id</th> + <th> + <span data-toggle="tooltip" title="Remote user who submitted this session"> + Owner + </span> + </th> + <th> + <span data-toggle="tooltip" title="User to impersonate when running"> + Proxy User + </span> + </th> + <th> + <span data-toggle="tooltip" + title="Session kind (spark, pyspark, pyspark3, or sparkr)"> + Session Kind + </span> + </th> + <th> + <span data-toggle="tooltip" + title="Session State (not_started, starting, idle, busy, + shutting_down, error, dead, success)"> + State + </span> + </th> + </tr> + </thead> + <tbody class="sessions-table-body"> + </tbody> +</table> \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-livy/blob/61b206e0/server/src/main/scala/com/cloudera/livy/LivyConf.scala ---------------------------------------------------------------------- diff --git a/server/src/main/scala/com/cloudera/livy/LivyConf.scala b/server/src/main/scala/com/cloudera/livy/LivyConf.scala index 8fc4777..233aa63 100644 --- a/server/src/main/scala/com/cloudera/livy/LivyConf.scala +++ b/server/src/main/scala/com/cloudera/livy/LivyConf.scala @@ -64,6 +64,8 @@ object LivyConf { val SERVER_PORT = Entry("livy.server.port", 8998) val CSRF_PROTECTION = LivyConf.Entry("livy.server.csrf-protection.enabled", false) + val UI_ENABLED = Entry("livy.ui.enabled", true) + val IMPERSONATION_ENABLED = Entry("livy.impersonation.enabled", false) val SUPERUSERS = Entry("livy.superusers", null) http://git-wip-us.apache.org/repos/asf/incubator-livy/blob/61b206e0/server/src/main/scala/com/cloudera/livy/server/LivyServer.scala ---------------------------------------------------------------------- diff --git a/server/src/main/scala/com/cloudera/livy/server/LivyServer.scala b/server/src/main/scala/com/cloudera/livy/server/LivyServer.scala index 4bd5635..7bfe9ce 100644 --- a/server/src/main/scala/com/cloudera/livy/server/LivyServer.scala +++ b/server/src/main/scala/com/cloudera/livy/server/LivyServer.scala @@ -30,12 +30,14 @@ import org.apache.hadoop.security.authentication.server._ import org.eclipse.jetty.servlet.FilterHolder import org.scalatra.metrics.MetricsBootstrap import org.scalatra.metrics.MetricsSupportExtensions._ +import org.scalatra.ScalatraServlet import org.scalatra.servlet.{MultipartConfig, ServletApiImplicits} import com.cloudera.livy._ import com.cloudera.livy.server.batch.BatchSessionServlet import com.cloudera.livy.server.interactive.InteractiveSessionServlet import com.cloudera.livy.server.recovery.{SessionStore, StateStore} +import com.cloudera.livy.server.ui.UIServlet import com.cloudera.livy.sessions.{BatchSessionManager, InteractiveSessionManager} import com.cloudera.livy.sessions.SessionManager.SESSION_RECOVERY_MODE_OFF import com.cloudera.livy.utils.LivySparkUtils._ @@ -142,6 +144,20 @@ class LivyServer extends Logging { } } + // Servlet for hosting static files such as html, css, and js + // Necessary since Jetty cannot set it's resource base inside a jar + val staticResourceServlet = new ScalatraServlet { + get("/*") { + getClass.getResourceAsStream("ui/static/" + params("splat")) + } + } + + def uiRedirectServlet(path: String) = new ScalatraServlet { + get("/") { + redirect(path) + } + } + server.context.addEventListener( new ServletContextListener() with MetricsBootstrap with ServletApiImplicits { @@ -167,7 +183,16 @@ class LivyServer extends Logging { val batchServlet = new BatchSessionServlet(batchSessionManager, sessionStore, livyConf) mount(context, batchServlet, "/batches/*") - context.mountMetricsAdminServlet("/") + if (livyConf.getBoolean(UI_ENABLED)) { + val uiServlet = new UIServlet + mount(context, uiServlet, "/ui/*") + mount(context, staticResourceServlet, "/static/*") + mount(context, uiRedirectServlet("/ui/"), "/*") + } else { + mount(context, uiRedirectServlet("/metrics"), "/*") + } + + context.mountMetricsAdminServlet("/metrics") mount(context, livyVersionServlet, "/version/*") } catch { http://git-wip-us.apache.org/repos/asf/incubator-livy/blob/61b206e0/server/src/main/scala/com/cloudera/livy/server/ui/UIServlet.scala ---------------------------------------------------------------------- diff --git a/server/src/main/scala/com/cloudera/livy/server/ui/UIServlet.scala b/server/src/main/scala/com/cloudera/livy/server/ui/UIServlet.scala new file mode 100644 index 0000000..ca2d4a1 --- /dev/null +++ b/server/src/main/scala/com/cloudera/livy/server/ui/UIServlet.scala @@ -0,0 +1,76 @@ +/* + * Licensed to Cloudera, Inc. under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Cloudera, Inc. 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 com.cloudera.livy.server.ui + +import scala.xml.Node + +import org.scalatra.ScalatraServlet + +import com.cloudera.livy.LivyConf + +class UIServlet extends ScalatraServlet { + before() { contentType = "text/html" } + + def getHeader(title: String): Seq[Node] = + <head> + <link rel="stylesheet" href="/static/bootstrap.min.css" type="text/css"/> + <link rel="stylesheet" href="/static/livy-ui.css" type="text/css"/> + <script src="/static/jquery-3.2.1.min.js"></script> + <script src="/static/bootstrap.min.js"></script> + <script src="/static/all-sessions.js"></script> + <title>{title}</title> + </head> + + def navBar(pageName: String): Seq[Node] = + <nav class="navbar navbar-default"> + <div class="container-fluid"> + <div class="navbar-header"> + <a class="navbar-brand" href="#"> + <img alt="Livy" src="/static/livy-mini-logo.png"/> + </a> + </div> + <div class="collapse navbar-collapse"> + <ul class="nav navbar-nav"> + <li><a href="#">{pageName}</a></li> + </ul> + </div> + </div> + </nav> + + def createPage(pageName: String, pageContents: Seq[Node]): Seq[Node] = + <html> + {getHeader("Livy - " + pageName)} + <body> + <div class="container"> + {navBar(pageName)} + {pageContents} + </div> + </body> + </html> + + get("/") { + val content = + <div id="all-sessions"> + <div id="interactive-sessions"></div> + <div id="batches"></div> + </div> + + createPage("Sessions", content) + } +}