azkaban-aplcache

creating API and UI to compose and publish messages (#1335) Whatever

8/16/2017 9:26:56 PM

Details

diff --git a/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java b/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java
index 8aa7acc..45d9925 100644
--- a/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java
+++ b/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java
@@ -54,6 +54,7 @@ import azkaban.webapp.servlet.ExecutorServlet;
 import azkaban.webapp.servlet.HistoryServlet;
 import azkaban.webapp.servlet.IndexRedirectServlet;
 import azkaban.webapp.servlet.JMXHttpServlet;
+import azkaban.webapp.servlet.NoteServlet;
 import azkaban.webapp.servlet.ProjectManagerServlet;
 import azkaban.webapp.servlet.ProjectServlet;
 import azkaban.webapp.servlet.ScheduleServlet;
@@ -126,7 +127,7 @@ public class AzkabanWebServer extends AzkabanServer {
 
   @Deprecated
   private static AzkabanWebServer app;
-  
+
   private final VelocityEngine velocityEngine;
   private final StatusService statusService;
   private final Server server;
@@ -476,6 +477,7 @@ public class AzkabanWebServer extends AzkabanServer {
     root.addServlet(new ServletHolder(new TriggerManagerServlet()), "/triggers");
     root.addServlet(new ServletHolder(new StatsServlet()), "/stats");
     root.addServlet(new ServletHolder(new StatusServlet(this.statusService)), "/status");
+    root.addServlet(new ServletHolder(new NoteServlet()), "/notes");
 
     final ServletHolder restliHolder = new ServletHolder(new RestliServlet());
     restliHolder.setInitParameter("resourcePackages", "azkaban.restli");
diff --git a/azkaban-web-server/src/main/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java b/azkaban-web-server/src/main/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
index 807ee0e..673abee 100644
--- a/azkaban-web-server/src/main/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
+++ b/azkaban-web-server/src/main/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
@@ -285,6 +285,9 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
     page.add("azkaban_name", this.name);
     page.add("azkaban_label", this.label);
     page.add("azkaban_color", this.color);
+    page.add("note_type", NoteServlet.type);
+    page.add("note_message", NoteServlet.message);
+    page.add("note_url", NoteServlet.url);
     page.add("utils", utils);
     page.add("timezone", TimeZone.getDefault().getID());
     page.add("currentTime", (new DateTime()).getMillis());
@@ -331,6 +334,9 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
     page.add("azkaban_name", this.name);
     page.add("azkaban_label", this.label);
     page.add("azkaban_color", this.color);
+    page.add("note_type", NoteServlet.type);
+    page.add("note_message", NoteServlet.message);
+    page.add("note_url", NoteServlet.url);
     page.add("timezone", TimeZone.getDefault().getID());
     page.add("currentTime", (new DateTime()).getMillis());
     page.add("context", req.getContextPath());
diff --git a/azkaban-web-server/src/main/java/azkaban/webapp/servlet/NoteServlet.java b/azkaban-web-server/src/main/java/azkaban/webapp/servlet/NoteServlet.java
new file mode 100644
index 0000000..10fdf13
--- /dev/null
+++ b/azkaban-web-server/src/main/java/azkaban/webapp/servlet/NoteServlet.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017 LinkedIn Corp.
+ *
+ * Licensed 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 azkaban.webapp.servlet;
+
+import azkaban.server.HttpRequestUtils;
+import azkaban.server.session.Session;
+import azkaban.user.Permission.Type;
+import azkaban.user.User;
+import azkaban.webapp.AzkabanWebServer;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.log4j.Logger;
+
+public class NoteServlet extends LoginAbstractAzkabanServlet {
+
+  private static final long serialVersionUID = 1L;
+  private static final Logger logger = Logger .getLogger(NoteServlet.class);
+
+  public static String type = null;
+  public static String message = null;
+  public static String url= null;
+  private AzkabanWebServer server;
+
+  @Override
+  public void init(final ServletConfig config) throws ServletException {
+    super.init(config);
+    this.server = (AzkabanWebServer) getApplication();
+  }
+
+  @Override
+  protected void handleGet(final HttpServletRequest req, final HttpServletResponse resp,
+      final Session session) throws ServletException, IOException {
+    if(isAdmin(session.getUser())) {
+      handleNotePageLoad(req, resp, session);
+    } else  {
+      warningNonAdminUsers(resp, "The requested user doesn't have admin permission");
+    }
+  }
+
+  private void warningNonAdminUsers(final HttpServletResponse resp, final String message) throws IOException {
+    final HashMap<String, Object> ret = new HashMap<>();
+    ret.put("error", message);
+    this.writeJSON(resp, ret);
+  }
+
+  private void handleNotePageLoad(final HttpServletRequest req,
+                                     final HttpServletResponse resp, final Session session) throws ServletException,
+      IOException {
+
+    final Page page = newPage(req, resp, session,
+            "azkaban/webapp/servlet/velocity/notepage.vm");
+
+    page.add("note_type", type);
+    page.add("note_message", message);
+    page.add("note_url", url);
+    page.render();
+  }
+
+  @Override
+  protected void handlePost(final HttpServletRequest req, final HttpServletResponse resp,
+                            final Session session) throws ServletException, IOException {
+    if (isAdmin(session.getUser()) && hasParam(req, "ajax")) {
+      handleAJAXAction(req, resp, session);
+    } else {
+      warningNonAdminUsers(resp, "The requested user doesn't have admin permission, "
+          + "Or the HTTP request doesn't include ajax.");
+    }
+  }
+
+  private void handleAJAXAction(final HttpServletRequest req,
+      final HttpServletResponse resp, final Session session) throws ServletException,
+      IOException {
+    final HashMap<String, Object> ret = new HashMap<>();
+    final String ajaxName = getParam(req, "ajax");
+    try {
+      if (ajaxName.equals("addNote")) {
+        ajaxAddNotes(req, ret);
+      } else if (ajaxName.equals("removeNote")){
+        ajaxRemoveNotes(ret);
+      } else {
+        ret.put("error", "Can not find the ajax operation");
+      }
+    } catch (final Exception e) {
+      ret.put("error", e.getMessage());
+    }
+    this.writeJSON(resp, ret);
+  }
+
+  private void ajaxAddNotes(final HttpServletRequest req,
+                            final Map<String, Object> ret) throws ServletException {
+    type = getParam(req, "type");
+    message= getParam(req, "message");
+    url = getParam(req, "url");
+    logger.info("receive note message. Type: " + type + " message: " + message + " url: " + url);
+    ret.put("status", "success");
+  }
+
+  private void ajaxRemoveNotes(final Map<String, Object> ret) throws ServletException {
+    type = null;
+    message= null;
+    url = null;
+    logger.info("removing note from memory.");
+    ret.put("status", "success");
+  }
+
+  private boolean isAdmin(final User user) {
+    return HttpRequestUtils.hasPermission(this.server.getUserManager(), user, Type.ADMIN);
+  }
+}
diff --git a/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/nav.vm b/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/nav.vm
index 042c1b8..5881745 100644
--- a/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/nav.vm
+++ b/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/nav.vm
@@ -14,11 +14,61 @@
  * the License.
 *#
 
+<style type="text/css">
+  #banner {
+    width: 100%;
+    background: orange;
+    height: 70px;
+    position: float;
+    top: 0;
+    display: none;
+    left: 0;
+    text-align: center;
+    line-height: 70px;
+    font-weight: bold;
+  }
+
+  #banner span:hover {
+    color: red;
+    cursor: pointer;
+  }
+</style>
 <script type="text/javascript">
-      function navMenuClick(url) {
-        window.location.href = url;
-      }
+  function navMenuClick(url) {
+    window.location.href = url;
+  }
+
+  $(function(){
+
+    if( $("#banner").data("id").indexOf('$') == -1  && $('#banner').is(':empty') == false) {
+      $('#banner').attr('Target', '_blank');
+      $("#banner").on("click", function(){
+        window.open($("#banner").data("url"));
+      });
+
+      $("#banner").slideDown(function()  {
+        if($("#banner").data("id") == "Warning") {
+          $('#bannerText').append('<img ' + 'src=' + '"images/warning.png"' + ' width="50" height="50" ' + ' style=' + '"display:inline;" >' + ' </img>');
+          $('#bannerText').append('<span style="font-size: 150%;" >' + $("#banner").data("message") + '</span>');
+        } else if ($("#banner").data("id") == "Action Required") {
+          $('#bannerText').append('<img ' + 'src=' + '"images/warning.png"' + ' width="50" height="50" ' + ' style=' + '"display:inline;" >' + ' </img>');
+          $('#bannerText').append('<span style="font-size: 150%;" >' + $("#banner").data("id") + ":    " + '</span>');
+          $('#bannerText').append('<span style="font-size: 150%;" >' + $("#banner").data("message") + '</span>');
+        }
+        // The banner will side up after 30 seconds
+        setTimeout(function() {
+          $("#banner").slideUp();
+        }, 30000);
+      });
+    }
+  });
 </script>
+
+<div id='banner' data-id="$note_type" data-message="$note_message" data-url="$note_url" >
+    <div id="bannerText">
+    </div>
+</div>
+
 <div class="navbar navbar-inverse navbar-static-top">
     <div class="container-full">
         <div class="navbar-header">
diff --git a/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/notepage.vm b/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/notepage.vm
new file mode 100644
index 0000000..29142e5
--- /dev/null
+++ b/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/notepage.vm
@@ -0,0 +1,103 @@
+#*
+ * Copyright 2017 LinkedIn, Inc
+ *
+ * Licensed 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.
+*#
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+
+  #parse("azkaban/webapp/servlet/velocity/style.vm")
+  #parse("azkaban/webapp/servlet/velocity/javascript.vm")
+
+  <link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
+  <link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui.css" />
+
+  <script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-1.10.1.custom.js"></script>
+  <script type="text/javascript" src="${context}/js/azkaban/view/note.js"></script>
+  <script type="text/javascript">
+    var contextURL = "${context}";
+    var currentTime = ${currentTime};
+    var timezone = "${timezone}";
+    var errorMessage = null;
+    var successMessage = null;
+  </script>
+</head>
+<body>
+
+<style type="text/css">
+  .show-and-hide-true {
+    display:none;
+  }
+</style>
+
+  #set ($current_page="notes")
+  #parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+  #if ($errorMsg)
+    #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
+  #else
+
+  <div class="az-page-header">
+    <div class="container-full">
+      <h1>Notification System</h1>
+    </div>
+  </div>
+
+  <div class="container">
+    <form class="form-horizontal" role="form">
+
+      <div class="form-group">
+        <label class="control-label col-sm-3">Type: </label>
+        <div class="col-sm-6">
+          <div class="row show-and-hide-content">
+            <div class="col-sm-4">
+              <label class="radio-inline">
+                <input type="radio" name='note-type' value="Warning">Warning
+              </label>
+            </div>
+            <div class="col-sm-4">
+              <label class="radio-inline">
+                <input type="radio" name='note-type' value="Action Required">Action Required
+              </label>
+            </div>
+          </div>
+        </div>
+      </div> <!-- /.form-group -->
+
+      <div class="form-group">
+        <label for="firstName" class="col-sm-3 control-label">Message: </label>
+        <div class="col-sm-6">
+          <input type="text" id="message" placeholder="Message" class="form-control" autofocus>
+        </div>
+      </div>
+      <div class="form-group">
+        <label for="email" class="col-sm-3 control-label">Redirect: </label>
+        <div class="col-sm-9">
+          <input type="text" id="url" placeholder="URL" class="form-control">
+        </div>
+      </div>
+      <div class="form-group">
+        <div class="col-sm-3 col-sm-offset-3">
+          <button type="submit" id="submit-button" class="btn btn-primary btn-block">Submit Note</button>
+        </div>
+        <div class="col-sm-2 col-sm-offset-4">
+          <button type="submit" id="clear-button" class="btn btn-warning btn-block">Clear Note</button>
+        </div>
+      </div>
+    </form> <!-- /form -->
+  </div> <!-- ./container -->
+  #end
+</body>
+</html>
diff --git a/azkaban-web-server/src/web/images/warning.png b/azkaban-web-server/src/web/images/warning.png
new file mode 100644
index 0000000..3f4d539
Binary files /dev/null and b/azkaban-web-server/src/web/images/warning.png differ
diff --git a/azkaban-web-server/src/web/js/azkaban/view/note.js b/azkaban-web-server/src/web/js/azkaban/view/note.js
new file mode 100644
index 0000000..018af48
--- /dev/null
+++ b/azkaban-web-server/src/web/js/azkaban/view/note.js
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 LinkedIn Corp.
+ *
+ * Licensed 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.
+ */
+$(function() {
+
+  $("#submit-button").click(function(){
+    console.log("======create note=====")
+    var radioValue = $("input[name='note-type']:checked").val();
+    var message = $('#message').val();
+    var url = $('#url').val();
+
+    var triggerURL = "/notes";
+    var redirectURL = "/notes";
+    var requestData = {"ajax": "addNote", "type": radioValue, "message": message, "url": url};
+    var successHandler = function(data) {
+      if (data.error) {
+        $('#errorMsg').text(data.error);
+      }
+      else {
+        window.location = redirectURL;
+      }
+    };
+    $.post(triggerURL, requestData, successHandler, "json");
+  });
+
+  $("#clear-button").click(function(){
+
+    console.log("======form clear=====")
+    var requestData = {"ajax": "removeNote"};
+    var triggerURL = "/notes";
+    var redirectURL = "/notes";
+    var successHandler = function(data) {
+      if (data.error) {
+        $('#errorMsg').text(data.error);
+      }
+      else {
+        window.location = redirectURL;
+      }
+    };
+    $.post(triggerURL, requestData, successHandler, "json");
+  });
+});