浏览代码

HUE-1126 [pig] Display logs of other running scripts and/or past run scripts

Restructured dashboard to show logs of older scripts as well
Multiple entry point for running scripts logging
Fixed a bug on the script list and on logs refresh ajax call
Enrico Berti 12 年之前
父节点
当前提交
e3f7cc3
共有 5 个文件被更改,包括 147 次插入68 次删除
  1. 2 0
      apps/pig/src/pig/api.py
  2. 87 58
      apps/pig/src/pig/templates/app.mako
  3. 1 4
      apps/pig/src/pig/views.py
  4. 12 0
      apps/pig/static/css/pig.css
  5. 45 6
      apps/pig/static/js/pig.ko.js

+ 2 - 0
apps/pig/src/pig/api.py

@@ -165,10 +165,12 @@ class OozieApi:
         'appName': hue_pig and hue_pig.dict['name'] or _('Unsaved script'),
         'appName': hue_pig and hue_pig.dict['name'] or _('Unsaved script'),
         'scriptId': hue_pig and hue_pig.id or -1,
         'scriptId': hue_pig and hue_pig.id or -1,
         'progress': job.get_progress(),
         'progress': job.get_progress(),
+        'progressPercent': '%d%%' % job.get_progress(),
         'user': job.user,
         'user': job.user,
         'absoluteUrl': job.get_absolute_url(),
         'absoluteUrl': job.get_absolute_url(),
         'canEdit': has_job_edition_permission(job, self.user),
         'canEdit': has_job_edition_permission(job, self.user),
         'killUrl': reverse('oozie:manage_oozie_jobs', kwargs={'job_id':job.id, 'action':'kill'}),
         'killUrl': reverse('oozie:manage_oozie_jobs', kwargs={'job_id':job.id, 'action':'kill'}),
+        'watchUrl': reverse('pig:watch', kwargs={'job_id': job.id}) + '?format=python',
         'created': hasattr(job, 'createdTime') and job.createdTime and job.createdTime and ((job.type == 'Bundle' and job.createdTime) or format_time(job.createdTime)),
         'created': hasattr(job, 'createdTime') and job.createdTime and job.createdTime and ((job.type == 'Bundle' and job.createdTime) or format_time(job.createdTime)),
         'startTime': hasattr(job, 'startTime') and format_time(job.startTime) or None,
         'startTime': hasattr(job, 'startTime') and format_time(job.startTime) or None,
         'run': hasattr(job, 'run') and job.run or 0,
         'run': hasattr(job, 'run') and job.run or 0,

+ 87 - 58
apps/pig/src/pig/templates/app.mako

@@ -28,7 +28,7 @@ ${ commonheader(_('Pig'), "pig", user, "100px") | n,unicode }
       <li class="active"><a href="#editor">${ _('Editor') }</a></li>
       <li class="active"><a href="#editor">${ _('Editor') }</a></li>
       <li><a href="#scripts">${ _('Scripts') }</a></li>
       <li><a href="#scripts">${ _('Scripts') }</a></li>
       <li><a href="#dashboard">${ _('Dashboard') }</a></li>
       <li><a href="#dashboard">${ _('Dashboard') }</a></li>
-      </ul>
+    </ul>
   </div>
   </div>
 </div>
 </div>
 
 
@@ -269,46 +269,67 @@ ${ commonheader(_('Pig'), "pig", user, "100px") | n,unicode }
         </div>
         </div>
 
 
       </div>
       </div>
-    </div>
-
   </div>
   </div>
 
 
   <div id="dashboard" class="row-fluid mainSection hide">
   <div id="dashboard" class="row-fluid mainSection hide">
-    <h3>${ _('Running') }</h3>
-    <div class="alert alert-info" data-bind="visible: runningScripts().length == 0">
-      ${_('There are currently no running scripts.')}
+    <ul class="nav nav-tabs">
+      <li class="active"><a href="#runningScripts" data-toggle="tab">${ _('Running') }</a></li>
+      <li><a href="#completedScripts" data-toggle="tab">${ _('Completed') }</a></li>
+    </ul>
+    <div class="tab-content">
+      <div class="tab-pane active" id="runningScripts">
+        <div class="alert alert-info" data-bind="visible: runningScripts().length == 0">
+          ${_('There are currently no running scripts.')}
+        </div>
+        <table class="table table-striped table-condensed datatables tablescroller-disable" data-bind="visible: runningScripts().length > 0">
+          <thead>
+          <tr>
+            <th width="20%">${_('Name')}</th>
+            <th width="40%">${_('Progress')}</th>
+            <th>${_('Created on')}</th>
+            <th width="30">&nbsp;</th>
+          </tr>
+          </thead>
+          <tbody data-bind="template: {name: 'runningTemplate', foreach: runningScripts}">
+
+          </tbody>
+        </table>
+      </div>
+      <div class="tab-pane" id="completedScripts">
+        <div class="alert alert-info" data-bind="visible: completedScripts().length == 0">
+          ${_('There are currently no completed scripts.')}
+        </div>
+        <table class="table table-striped table-condensed datatables tablescroller-disable" data-bind="visible: completedScripts().length > 0">
+          <thead>
+          <tr>
+            <th width="20%">${_('Name')}</th>
+            <th width="40%">${_('Status')}</th>
+            <th>${_('Created on')}</th>
+          </tr>
+          </thead>
+          <tbody data-bind="template: {name: 'completedTemplate', foreach: completedScripts}">
+
+          </tbody>
+        </table>
+      </div>
     </div>
     </div>
-    <table class="table table-striped table-condensed datatables tablescroller-disable" data-bind="visible: runningScripts().length > 0">
-      <thead>
-      <tr>
-        <th width="20%">${_('Name')}</th>
-        <th width="10%">${_('Status')}</th>
-        <th width="">${_('Created on')}</th>
-      </tr>
-      </thead>
-      <tbody data-bind="template: {name: 'dashboardTemplate', foreach: runningScripts}">
-
-      </tbody>
-    </table>
 
 
-    <h3>${ _('Completed') }</h3>
-    <div class="alert alert-info" data-bind="visible: completedScripts().length == 0">
-      ${_('There are currently no completed scripts.')}
-    </div>
-    <table class="table table-striped table-condensed datatables tablescroller-disable" data-bind="visible: completedScripts().length > 0">
-      <thead>
-      <tr>
-        <th width="20%">${_('Name')}</th>
-        <th width="20%">${_('Status')}</th>
-        <th width="">${_('Created on')}</th>
+    <script id="runningTemplate" type="text/html">
+      <tr style="cursor: pointer">
+        <td data-bind="click: $root.viewSubmittedScript" title="${_('Click to edit')}">
+          <strong><a data-bind="text: appName"></a></strong>
+        </td>
+        <td>
+          <div data-bind="css: {'progress': appName != '', 'progress-striped': appName != '', 'active': status == 'RUNNING'}">
+            <div data-bind="css: {'bar': appName != '', 'bar-success': status == 'SUCCEEDED' || status == 'OK', 'bar-warning': status == 'RUNNING' || status == 'PREP', 'bar-danger': status != 'RUNNING' && status != 'SUCCEEDED' && status != 'OK' && status != 'PREP'}, attr: {'style': 'width:' + progressPercent}"></div>
+          </div>
+        </td>
+        <td data-bind="text: created"></td>
+        <td data-bind="click: $root.showLogs"><i class="icon-tasks"></i></td>
       </tr>
       </tr>
-      </thead>
-      <tbody data-bind="template: {name: 'dashboardTemplate', foreach: completedScripts}">
-
-      </tbody>
-    </table>
+    </script>
 
 
-    <script id="dashboardTemplate" type="text/html">
+    <script id="completedTemplate" type="text/html">
       <tr style="cursor: pointer">
       <tr style="cursor: pointer">
         <td data-bind="click: $root.viewSubmittedScript" title="${_('Click to edit')}">
         <td data-bind="click: $root.viewSubmittedScript" title="${_('Click to edit')}">
           <strong><a data-bind="text: appName"></a></strong>
           <strong><a data-bind="text: appName"></a></strong>
@@ -316,9 +337,7 @@ ${ commonheader(_('Pig'), "pig", user, "100px") | n,unicode }
         <td>
         <td>
           <span data-bind="attr: {'class': statusClass}, text: status"></span>
           <span data-bind="attr: {'class': statusClass}, text: status"></span>
         </td>
         </td>
-        <td>
-          <strong><a data-bind="text: created, attr: {'href': absoluteUrl}" target="_blank"></a></strong>
-        </td>
+        <td data-bind="text: created"></td>
       </tr>
       </tr>
     </script>
     </script>
   </div>
   </div>
@@ -340,6 +359,19 @@ ${ commonheader(_('Pig'), "pig", user, "100px") | n,unicode }
   </div>
   </div>
 </div>
 </div>
 
 
+<div id="logsModal" class="modal hide fade">
+  <div class="modal-header">
+    <a href="#" class="close" data-dismiss="modal">&times;</a>
+    <h3>${_('Logs')}</h3>
+  </div>
+  <div class="modal-body">
+    <img src="/static/art/spinner.gif" class="hide" />
+    <pre class="scroll hide"></pre>
+  </div>
+  <div class="modal-footer">
+    <a class="btn" data-dismiss="modal">${_('Close')}</a>
+  </div>
+</div>
 
 
 <div id="submitModal" class="modal hide fade">
 <div id="submitModal" class="modal hide fade">
   <div class="modal-header">
   <div class="modal-header">
@@ -407,8 +439,6 @@ ${ commonheader(_('Pig'), "pig", user, "100px") | n,unicode }
     NEW_SCRIPT_RESOURCES: []
     NEW_SCRIPT_RESOURCES: []
   };
   };
 
 
-  var scripts = ${ scripts | n,unicode };
-
   var appProperties = {
   var appProperties = {
     labels: LABELS,
     labels: LABELS,
     listScripts: "${ url('pig:scripts') }",
     listScripts: "${ url('pig:scripts') }",
@@ -418,10 +448,12 @@ ${ commonheader(_('Pig'), "pig", user, "100px") | n,unicode }
     deleteUrl: "${ url('pig:delete') }"
     deleteUrl: "${ url('pig:delete') }"
   }
   }
 
 
-  var viewModel = new PigViewModel(scripts, appProperties);
+  var viewModel = new PigViewModel(appProperties);
   ko.applyBindings(viewModel);
   ko.applyBindings(viewModel);
 
 
   $(document).ready(function () {
   $(document).ready(function () {
+    viewModel.updateScripts();
+
     var scriptEditor = $("#scriptEditor")[0];
     var scriptEditor = $("#scriptEditor")[0];
 
 
     CodeMirror.commands.autocomplete = function(cm) {
     CodeMirror.commands.autocomplete = function(cm) {
@@ -522,13 +554,16 @@ ${ commonheader(_('Pig'), "pig", user, "100px") | n,unicode }
 
 
     var logsRefreshInterval;
     var logsRefreshInterval;
     $(document).on("startLogsRefresh", function () {
     $(document).on("startLogsRefresh", function () {
+      window.clearInterval(logsRefreshInterval);
+      $("#withLogs").text("");
+      refreshLogs();
       logsRefreshInterval = window.setInterval(function () {
       logsRefreshInterval = window.setInterval(function () {
         refreshLogs();
         refreshLogs();
       }, 1000);
       }, 1000);
     });
     });
 
 
     $(document).on("stopLogsRefresh", function () {
     $(document).on("stopLogsRefresh", function () {
-      window.clearTimeout(logsRefreshInterval);
+      window.clearInterval(logsRefreshInterval);
     });
     });
 
 
     var _resizeTimeout = -1;
     var _resizeTimeout = -1;
@@ -568,19 +603,18 @@ ${ commonheader(_('Pig'), "pig", user, "100px") | n,unicode }
     function refreshLogs() {
     function refreshLogs() {
       if (viewModel.currentScript().watchUrl() != "") {
       if (viewModel.currentScript().watchUrl() != "") {
         $.getJSON(viewModel.currentScript().watchUrl(), function (data) {
         $.getJSON(viewModel.currentScript().watchUrl(), function (data) {
+          if (data.logs.pig) {
+            $("#withoutLogs").addClass("hide");
+            $("#withLogs").removeClass("hide");
+            var _logsEl = $("#withLogs");
+            var newLines = data.logs.pig.split("\n").slice(_logsEl.text().split("\n").length);
+            _logsEl.text(_logsEl.text() + newLines.join("\n"));
+            if (logsAtEnd) {
+              _logsEl.scrollTop(_logsEl[0].scrollHeight - _logsEl.height());
+            }
+          }
           if (data.workflow && data.workflow.isRunning) {
           if (data.workflow && data.workflow.isRunning) {
             viewModel.currentScript().actions(data.workflow.actions);
             viewModel.currentScript().actions(data.workflow.actions);
-            if (data.logs.pig) {
-              $("#withoutLogs").addClass("hide");
-              $("#withLogs").removeClass("hide");
-              resizeLogs();
-              var _logsEl = $("#withLogs");
-              var newLines = data.logs.pig.split("\n").slice(_logsEl.text().split("\n").length);
-              _logsEl.text(_logsEl.text() + newLines.join("\n"));
-              if (logsAtEnd) {
-                _logsEl.scrollTop(_logsEl[0].scrollHeight - _logsEl.height());
-              }
-            }
           }
           }
           else {
           else {
             viewModel.currentScript().actions(data.workflow.actions);
             viewModel.currentScript().actions(data.workflow.actions);
@@ -595,12 +629,7 @@ ${ commonheader(_('Pig'), "pig", user, "100px") | n,unicode }
     }
     }
 
 
     $("#withLogs").scroll(function () {
     $("#withLogs").scroll(function () {
-      if ($(this).scrollTop() + $(this).height() + 20 >= $(this)[0].scrollHeight) {
-        logsAtEnd = true;
-      }
-      else {
-        logsAtEnd = false;
-      }
+      logsAtEnd = $(this).scrollTop() + $(this).height() + 20 >= $(this)[0].scrollHeight;
     });
     });
 
 
     function resizeLogs() {
     function resizeLogs() {

+ 1 - 4
apps/pig/src/pig/views.py

@@ -40,10 +40,7 @@ LOG = logging.getLogger(__name__)
 
 
 
 
 def app(request):
 def app(request):
-  return render('app.mako', request, {
-    'scripts': json.dumps(get_scripts(request.user))
-    }
-  )
+  return render('app.mako', request, {})
 
 
 
 
 def scripts(request):
 def scripts(request):

+ 12 - 0
apps/pig/static/css/pig.css

@@ -51,4 +51,16 @@
 
 
 .span2 .pull-right {
 .span2 .pull-right {
   display: none;
   display: none;
+}
+
+#logsModal {
+  width: 90%;
+  left: 5%;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.scroll {
+  overflow-y: scroll;
+  height: 300px;
 }
 }

+ 45 - 6
apps/pig/static/js/pig.ko.js

@@ -88,9 +88,11 @@ var Workflow = function (wf) {
     duration: wf.duration,
     duration: wf.duration,
     appName: wf.appName,
     appName: wf.appName,
     progress: wf.progress,
     progress: wf.progress,
+    progressPercent: wf.progressPercent,
     progressClass: "bar " + getStatusClass(wf.status, "bar-"),
     progressClass: "bar " + getStatusClass(wf.status, "bar-"),
     user: wf.user,
     user: wf.user,
     absoluteUrl: wf.absoluteUrl,
     absoluteUrl: wf.absoluteUrl,
+    watchUrl: wf.watchUrl,
     canEdit: wf.canEdit,
     canEdit: wf.canEdit,
     killUrl: wf.killUrl,
     killUrl: wf.killUrl,
     created: wf.created,
     created: wf.created,
@@ -99,7 +101,7 @@ var Workflow = function (wf) {
 }
 }
 
 
 
 
-var PigViewModel = function (scripts, props) {
+var PigViewModel = function (props) {
   var self = this;
   var self = this;
 
 
   self.LABELS = props.labels;
   self.LABELS = props.labels;
@@ -114,9 +116,7 @@ var PigViewModel = function (scripts, props) {
   self.allSelected = ko.observable(false);
   self.allSelected = ko.observable(false);
   self.submissionVariables = ko.observableArray([]);
   self.submissionVariables = ko.observableArray([]);
 
 
-  self.scripts = ko.observableArray(ko.utils.arrayMap(scripts, function (pigScript) {
-    return new PigScript(pigScript);
-  }));
+  self.scripts = ko.observableArray([]);
 
 
   self.filteredScripts = ko.observableArray(self.scripts());
   self.filteredScripts = ko.observableArray(self.scripts());
 
 
@@ -385,7 +385,46 @@ var PigViewModel = function (scripts, props) {
 
 
   self.viewSubmittedScript = function (workflow) {
   self.viewSubmittedScript = function (workflow) {
     self.loadScript(workflow.scriptId);
     self.loadScript(workflow.scriptId);
-    $(document).trigger("loadEditor");
-    $(document).trigger("showEditor");
+    self.currentScript().isRunning(true);
+    self.currentScript().watchUrl(workflow.watchUrl);
+    $(document).trigger("startLogsRefresh");
+    $(document).trigger("showLogs");
+  };
+
+  self.showLogsInterval = -1;
+  self.showLogsAtEnd = true;
+  self.showLogs = function (workflow) {
+    window.clearInterval(self.showLogsInterval);
+    $("#logsModal pre").scroll(function () {
+      self.showLogsAtEnd = $(this).scrollTop() + $(this).height() + 20 >= $(this)[0].scrollHeight;
+    });
+    if (workflow.isRunning) {
+      $("#logsModal img").removeClass("hide");
+      $("#logsModal pre").addClass("hide");
+      $("#logsModal").modal({
+        keyboard: true,
+        show: true
+      });
+      $("#logsModal").on("hide", function () {
+        window.clearInterval(self.showLogsInterval);
+      });
+      self.showLogsInterval = window.setInterval(function () {
+        $.getJSON(workflow.watchUrl, function (data) {
+          if (data.workflow && !data.workflow.isRunning) {
+            window.clearInterval(self.showLogsInterval);
+          }
+          if (data.logs.pig) {
+            $("#logsModal img").addClass("hide");
+            $("#logsModal pre").removeClass("hide");
+            var _logsEl = $("#logsModal pre");
+            var newLines = data.logs.pig.split("\n").slice(_logsEl.text().split("\n").length);
+            _logsEl.text(_logsEl.text() + newLines.join("\n"));
+            if (self.showLogsAtEnd) {
+              _logsEl.scrollTop(_logsEl[0].scrollHeight - _logsEl.height());
+            }
+          }
+        });
+      }, 1000);
+    }
   };
   };
 };
 };