Ver Fonte

HUE-2338 [oozie] Bulk operations on dashboards

Added support for bulk operations on workflows, coordinators and bundles
Enrico Berti há 11 anos atrás
pai
commit
3686abc336

+ 30 - 47
apps/oozie/src/oozie/templates/dashboard/list_oozie_bundles.mako

@@ -34,6 +34,13 @@ ${layout.menubar(section='bundles', dashboard=True)}
   <form>
     <input type="text" id="filterInput" class="input-xlarge search-query" placeholder="${ _('Search for username, name, etc...') }">
 
+    <div class="btn-toolbar" style="display: inline; vertical-align: middle; margin-left: 10px; font-size: 12px">
+      <span class="loader hide"><i class="fa fa-2x fa-spinner fa-spin muted"></i></span>
+      <button class="btn bulkToolbarBtn bulk-resume" data-operation="resume" title="${ _('Resume selected') }" disabled="disabled" type="button"><i class="fa fa-play"></i> ${ _('Resume') }</button>
+      <button class="btn bulkToolbarBtn bulk-suspend" data-operation="suspend" title="${ _('Suspend selected') }" disabled="disabled" type="button"><i class="fa fa-pause"></i> ${ _('Suspend') }</button>
+      <button class="btn bulkToolbarBtn btn-danger bulk-kill disable-feedback" data-operation="kill" title="${ _('Kill selected') }" disabled="disabled" type="button"><i class="fa fa-times"></i> ${ _('Kill') }</button>
+    </div>
+
     <span class="pull-right">
       <span style="padding-right:10px;float:left;margin-top:3px">
       ${ _('Show only') }
@@ -58,14 +65,14 @@ ${layout.menubar(section='bundles', dashboard=True)}
     <table class="table table-condensed" id="running-table">
       <thead>
         <tr>
-          <th width="15%">${ _('Kickoff Time') }</th>
+          <th width="1%"><div class="select-all hueCheckbox fa"></div></th>
+          <th width="14%">${ _('Kickoff Time') }</th>
           <th width="10%">${ _('Status') }</th>
           <th width="20%">${ _('Name') }</th>
           <th width="10%">${ _('Progress') }</th>
           <th width="10%">${ _('Submitter') }</th>
           <th width="10%">${ _('Created Time') }</th>
           <th width="15%">${ _('Id') }</th>
-          <th width="10%">${ _('Action') }</th>
         </tr>
       </thead>
       <tbody>
@@ -151,6 +158,8 @@ ${layout.menubar(section='bundles', dashboard=True)}
     }
   }
 
+  var refreshRunning;
+
   $(document).ready(function () {
     var runningTable = $("#running-table").dataTable({
       "sPaginationType":"bootstrap",
@@ -158,14 +167,14 @@ ${layout.menubar(section='bundles', dashboard=True)}
       "bLengthChange":false,
       "sDom":"<'row'r>t<'row'<'span6'i><''p>>",
       "aoColumns":[
+        { "bSortable":false },
         { "sType":"date" },
         null,
         null,
         { "sSortDataType":"dom-sort-value", "sType":"numeric" },
         null,
         null,
-        null,
-        { "bSortable":false }
+        null
       ],
       "aaSorting":[
         [ 5, "desc" ]
@@ -321,13 +330,10 @@ ${layout.menubar(section='bundles', dashboard=True)}
       });
     });
 
-    refreshRunning();
-    refreshCompleted();
-    refreshProgress();
 
     var numRunning = 0;
 
-    function refreshRunning() {
+    refreshRunning = function () {
       $.getJSON(window.location.pathname + "?format=json&type=running", function (data) {
         if (data) {
           var nNodes = runningTable.fnGetNodes();
@@ -336,7 +342,7 @@ ${layout.menubar(section='bundles', dashboard=True)}
           $(nNodes).each(function (iNode, node) {
             var nodeFound = false;
             $(data).each(function (iBundle, currentItem) {
-              if ($(node).children("td").eq(6).text() == currentItem.id) {
+              if ($(node).children("td").eq(7).text() == currentItem.id) {
                 nodeFound = true;
               }
             });
@@ -350,51 +356,23 @@ ${layout.menubar(section='bundles', dashboard=True)}
             var bundle = new Bundle(item);
             var foundRow = null;
             $(nNodes).each(function (iNode, node) {
-              if ($(node).children("td").eq(6).text() == bundle.id) {
+              if ($(node).children("td").eq(7).text() == bundle.id) {
                 foundRow = node;
               }
             });
-            var killCell = "";
-            var suspendCell = "";
-            var resumeCell = "";
-            if (bundle.canEdit) {
-              killCell = '<a class="btn btn-mini btn-danger disable-feedback confirmationModal" ' +
-                      'href="javascript:void(0)" ' +
-                      'data-url="' + bundle.killUrl + '" ' +
-                      'title="${ _('Kill') } ' + bundle.id + '"' +
-                      'alt="${ _('Are you sure you want to kill bundle ')}' + bundle.id + '?" ' +
-                      'data-message="${ _('The bundle was killed!') }" ' +
-                      'data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }"' +
-                      '>${ _('Kill') }</a>';
-              suspendCell = '<a class="btn btn-mini confirmationModal" ' +
-                      'href="javascript:void(0)" ' +
-                      'data-url="' + bundle.suspendUrl + '" ' +
-                      'title="${ _('Suspend') } ' + bundle.id + '"' +
-                      'alt="${ _('Are you sure you want to suspend bundle ')}' + bundle.id + '?" ' +
-                      'data-message="${ _('The bundle was suspended!') }" ' +
-                      'data-confirmation-message="${ _('Are you sure you\'d like to suspend this job?') }"' +
-                      '>${ _('Suspend') }</a>';
-              resumeCell = '<a class="btn btn-mini confirmationModal" ' +
-                      'href="javascript:void(0)" ' +
-                      'data-url="' + bundle.resumeUrl + '" ' +
-                      'title="${ _('Resume') } ' + bundle.id + '"' +
-                      'alt="${ _('Are you sure you want to resume bundle ')}' + bundle.id + '?" ' +
-                      'data-message="${ _('The bundle was resumed!') }" ' +
-                      'data-confirmation-message="${ _('Are you sure you\'d like to resume this job?') }"' +
-                      '>${ _('Resume') }</a>';
-            }
+
             if (foundRow == null) {
               if (['RUNNING', 'PREP', 'WAITING', 'SUSPENDED', 'PREPSUSPENDED', 'PREPPAUSED', 'PAUSED', 'STARTED', 'FINISHING'].indexOf(bundle.status) > -1) {
                 try {
                   runningTable.fnAddData([
+                    bundle.canEdit ? '<div class="hueCheckbox fa" data-row-selector-exclude="true"></div>' : '',
                     emptyStringIfNull(bundle.kickoffTime),
                     '<span class="' + bundle.statusClass + '">' + bundle.status + '</span>',
                     bundle.appName,
                     '<div class="progress"><div class="bar bar-warning" style="width:1%"></div></div>',
                     bundle.user,
                     emptyStringIfNull(bundle.created),
-                    '<a href="' + bundle.absoluteUrl + '" data-row-selector="true">' + bundle.id + '</a>',
-                    killCell + " " + (['RUNNING', 'PREP', 'WAITING'].indexOf(bundle.status) > -1?suspendCell:resumeCell)
+                    '<a href="' + bundle.absoluteUrl + '" data-row-selector="true">' + bundle.id + '</a>'
                   ]);
                 }
                 catch (error) {
@@ -404,8 +382,7 @@ ${layout.menubar(section='bundles', dashboard=True)}
 
             }
             else {
-              runningTable.fnUpdate('<span class="' + bundle.statusClass + '">' + bundle.status + '</span>', foundRow, 1, false);
-              runningTable.fnUpdate(killCell + " " + (['RUNNING', 'PREP', 'WAITING'].indexOf(bundle.status) > -1?suspendCell:resumeCell), foundRow, 7, false);
+              runningTable.fnUpdate('<span class="' + bundle.statusClass + '">' + bundle.status + '</span>', foundRow, 2, false);
             }
           });
         }
@@ -452,17 +429,17 @@ ${layout.menubar(section='bundles', dashboard=True)}
             var bundle = new Bundle(item);
             var foundRow = null;
             $(nNodes).each(function (iNode, node) {
-              if ($(node).children("td").eq(6).text() == bundle.id) {
+              if ($(node).children("td").eq(7).text() == bundle.id) {
                 foundRow = node;
               }
             });
             if (foundRow != null) {
-              runningTable.fnUpdate('<span class="' + bundle.statusClass + '">' + bundle.status + '</span>', foundRow, 1, false);
+              runningTable.fnUpdate('<span class="' + bundle.statusClass + '">' + bundle.status + '</span>', foundRow, 2, false);
               if (bundle.progress == 0){
-                runningTable.fnUpdate('<div class="progress"><div class="bar bar-warning" style="width: 1%"></div></div>', foundRow, 3, false);
+                runningTable.fnUpdate('<div class="progress"><div class="bar bar-warning" style="width: 1%"></div></div>', foundRow, 4, false);
               }
               else {
-                runningTable.fnUpdate('<div class="progress"><div class="' + bundle.progressClass + '" style="width:' + bundle.progress + '%">' + bundle.progress + '%</div></div>', foundRow, 3, false);
+                runningTable.fnUpdate('<div class="progress"><div class="' + bundle.progressClass + '" style="width:' + bundle.progress + '%">' + bundle.progress + '%</div></div>', foundRow, 4, false);
               }
             }
           });
@@ -470,7 +447,13 @@ ${layout.menubar(section='bundles', dashboard=True)}
       });
     }
 
+    refreshRunning();
+    refreshCompleted();
+    refreshProgress();
+
   });
 </script>
 
+${ utils.bulk_dashboard_functions() }
+
 ${ commonfooter(messages) | n,unicode }

+ 31 - 56
apps/oozie/src/oozie/templates/dashboard/list_oozie_coordinators.mako

@@ -34,6 +34,13 @@ ${layout.menubar(section='coordinators', dashboard=True)}
   <form>
     <input type="text" id="filterInput" class="input-xlarge search-query" placeholder="${ _('Search for username, name, etc...') }">
 
+    <div class="btn-toolbar" style="display: inline; vertical-align: middle; margin-left: 10px; font-size: 12px">
+      <span class="loader hide"><i class="fa fa-2x fa-spinner fa-spin muted"></i></span>
+      <button class="btn bulkToolbarBtn bulk-resume" data-operation="resume" title="${ _('Resume selected') }" disabled="disabled" type="button"><i class="fa fa-play"></i> ${ _('Resume') }</button>
+      <button class="btn bulkToolbarBtn bulk-suspend" data-operation="suspend" title="${ _('Suspend selected') }" disabled="disabled" type="button"><i class="fa fa-pause"></i> ${ _('Suspend') }</button>
+      <button class="btn bulkToolbarBtn btn-danger bulk-kill disable-feedback" data-operation="kill" title="${ _('Kill selected') }" disabled="disabled" type="button"><i class="fa fa-times"></i> ${ _('Kill') }</button>
+    </div>
+
     <span class="pull-right">
       <span style="padding-right:10px;float:left;margin-top:3px">
       ${ _('Show only') }
@@ -58,7 +65,8 @@ ${layout.menubar(section='coordinators', dashboard=True)}
     <table class="table table-condensed" id="running-table">
       <thead>
         <tr>
-          <th width="12%">${ _('Next Submission') }</th>
+          <th width="1%"><div class="select-all hueCheckbox fa"></div></th>
+          <th width="11%">${ _('Next Submission') }</th>
           <th width="5%">${ _('Status') }</th>
           <th width="20%">${ _('Name') }</th>
           <th width="5%">${ _('Progress') }</th>
@@ -71,7 +79,6 @@ ${layout.menubar(section='coordinators', dashboard=True)}
           % endif
           <th width="12%">${ _('Start Time') }</th>
           <th width="15%">${ _('Id') }</th>
-          <th width="10%">${ _('Action') }</th>
         </tr>
       </thead>
       <tbody>
@@ -80,6 +87,7 @@ ${layout.menubar(section='coordinators', dashboard=True)}
           <td></td>
           <td></td>
           <td></td>
+          <td></td>
           % if not enable_cron_scheduling:
           <td></td>
           % endif
@@ -87,7 +95,6 @@ ${layout.menubar(section='coordinators', dashboard=True)}
           <td></td>
           <td></td>
           <td></td>
-          <td></td>
         </tr>
       </tbody>
     </table>
@@ -181,6 +188,8 @@ ${layout.menubar(section='coordinators', dashboard=True)}
     }
   }
 
+  var refreshRunning;
+
   $(document).ready(function () {
     var runningTable = $("#running-table").dataTable({
       "sPaginationType":"bootstrap",
@@ -188,6 +197,7 @@ ${layout.menubar(section='coordinators', dashboard=True)}
       "bLengthChange":false,
       "sDom":"<'row'r>t<'row'<'span6'i><''p>>",
       "aoColumns":[
+        { "bSortable":false },
         { "sType":"date" },
         null,
         null,
@@ -198,8 +208,7 @@ ${layout.menubar(section='coordinators', dashboard=True)}
         % if not enable_cron_scheduling:
         null,
         % endif
-        null,
-        { "bSortable":false }
+        null
       ],
       "aaSorting":[
         [ 0, "desc" ]
@@ -365,13 +374,9 @@ ${layout.menubar(section='coordinators', dashboard=True)}
     % endif
 
 
-    refreshRunning();
-    refreshCompleted();
-    refreshProgress();
-
     var numRunning = 0;
 
-    function refreshRunning() {
+    refreshRunning = function () {
       $.getJSON(window.location.pathname + "?format=json&type=running", function (data) {
         if (data) {
           var nNodes = runningTable.fnGetNodes();
@@ -381,11 +386,11 @@ ${layout.menubar(section='coordinators', dashboard=True)}
             var nodeFound = false;
             $(data).each(function (iCoord, currentItem) {
               % if enable_cron_scheduling:
-              if ($(node).children("td").eq(7).text() == currentItem.id) {
+              if ($(node).children("td").eq(8).text() == currentItem.id) {
                  nodeFound = true;
               }
               % else:
-              if ($(node).children("td").eq(8).text() == currentItem.id) {
+              if ($(node).children("td").eq(9).text() == currentItem.id) {
                  nodeFound = true;
               }
               % endif
@@ -402,48 +407,20 @@ ${layout.menubar(section='coordinators', dashboard=True)}
             var foundRow = null;
             $(nNodes).each(function (iNode, node) {
               % if enable_cron_scheduling:
-              if ($(node).children("td").eq(7).text() == coord.id) {
+              if ($(node).children("td").eq(8).text() == coord.id) {
                 foundRow = node;
               }
               % else:
-              if ($(node).children("td").eq(8).text() == coord.id) {
+              if ($(node).children("td").eq(9).text() == coord.id) {
                 foundRow = node;
               }
               % endif
             });
-            var killCell = "";
-            var suspendCell = "";
-            var resumeCell = "";
-            if (coord.canEdit) {
-              killCell = '<a class="btn btn-mini btn-danger disable-feedback confirmationModal" ' +
-                      'href="javascript:void(0)" ' +
-                      'data-url="' + coord.killUrl + '" ' +
-                      'title="${ _('Kill') } ' + coord.id + '"' +
-                      'alt="${ _('Are you sure you want to kill coordinator ')}' + coord.id + '?" ' +
-                      'data-message="${ _('The coordinator was killed!') }" ' +
-                      'data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }"' +
-                      '>${ _('Kill') }</a>';
-              suspendCell = '<a class="btn btn-mini confirmationModal" ' +
-                      'href="javascript:void(0)" ' +
-                      'data-url="' + coord.suspendUrl + '" ' +
-                      'title="${ _('Suspend') } ' + coord.id + '"' +
-                      'alt="${ _('Are you sure you want to suspend coordinator ')}' + coord.id + '?" ' +
-                      'data-message="${ _('The coordinator was suspended!') }" ' +
-                      'data-confirmation-message="${ _('Are you sure you\'d like to suspend this job?') }"' +
-                      '>${ _('Suspend') }</a>';
-              resumeCell = '<a class="btn btn-mini confirmationModal" ' +
-                      'href="javascript:void(0)" ' +
-                      'data-url="' + coord.resumeUrl + '" ' +
-                      'title="${ _('Resume') } ' + coord.id + '"' +
-                      'alt="${ _('Are you sure you want to resume coordinator ')}' + coord.id + '?" ' +
-                      'data-message="${ _('The coordinator was resumed!') }" ' +
-                      'data-confirmation-message="${ _('Are you sure you\'d like to resume this job?') }"' +
-                      '>${ _('Resume') }</a>';
-            }
             if (foundRow == null) {
               if (['RUNNING', 'PREP', 'WAITING', 'SUSPENDED', 'PREPSUSPENDED', 'PREPPAUSED', 'PAUSED', 'STARTED', 'FINISHING'].indexOf(coord.status) > -1) {
                 try {
                   runningTable.fnAddData([
+                    coord.canEdit ? '<div class="hueCheckbox fa" data-row-selector-exclude="true"></div>' : '',
                     emptyStringIfNull(coord.nextMaterializedTime),
                     '<span class="' + coord.statusClass + '">' + coord.status + '</span>',
                     coord.appName,
@@ -456,8 +433,7 @@ ${layout.menubar(section='coordinators', dashboard=True)}
                     emptyStringIfNull(coord.timeUnit),
                     % endif
                     emptyStringIfNull(coord.startTime),
-                    '<a href="' + coord.absoluteUrl + '" data-row-selector="true">' + coord.id + '</a>',
-                    killCell + " " + (['RUNNING', 'PREP', 'WAITING'].indexOf(coord.status) > -1?suspendCell:resumeCell)
+                    '<a href="' + coord.absoluteUrl + '" data-row-selector="true">' + coord.id + '</a>'
                   ]);
                 }
                 catch (error) {
@@ -466,13 +442,7 @@ ${layout.menubar(section='coordinators', dashboard=True)}
               }
             }
             else {
-              runningTable.fnUpdate('<span class="' + coord.statusClass + '">' + coord.status + '</span>', foundRow, 1, false);
-              % if enable_cron_scheduling:
-              runningTable.fnUpdate(killCell + " " + (['RUNNING', 'PREP', 'WAITING'].indexOf(coord.status) > -1?suspendCell:resumeCell), foundRow, 9, false);
-              % else:
-              runningTable.fnUpdate(killCell + " " + (['RUNNING', 'PREP', 'WAITING'].indexOf(coord.status) > -1?suspendCell:resumeCell), foundRow, 10, false);
-              % endif
-
+              runningTable.fnUpdate('<span class="' + coord.statusClass + '">' + coord.status + '</span>', foundRow, 2, false);
             }
           });
         }
@@ -532,28 +502,33 @@ ${layout.menubar(section='coordinators', dashboard=True)}
             var foundRow = null;
             $(nNodes).each(function (iNode, node) {
               % if enable_cron_scheduling:
-              if ($(node).children("td").eq(7).text() == coord.id) {
+              if ($(node).children("td").eq(8).text() == coord.id) {
                 foundRow = node;
               }
               % else:
-              if ($(node).children("td").eq(8).text() == coord.id) {
+              if ($(node).children("td").eq(9).text() == coord.id) {
                 foundRow = node;
               }
               % endif
             });
             if (foundRow != null) {
               if (coord.progress == 0){
-                runningTable.fnUpdate('<div class="progress"><div class="bar bar-warning" style="width: 1%"></div></div>', foundRow, 3, false);
+                runningTable.fnUpdate('<div class="progress"><div class="bar bar-warning" style="width: 1%"></div></div>', foundRow, 4, false);
               }
               else {
-                runningTable.fnUpdate('<div class="progress"><div class="' + coord.progressClass + '" style="width:' + coord.progress + '%">' + coord.progress + '%</div></div>', foundRow, 3, false);
+                runningTable.fnUpdate('<div class="progress"><div class="' + coord.progressClass + '" style="width:' + coord.progress + '%">' + coord.progress + '%</div></div>', foundRow, 4, false);
               }
             }
           });
         window.setTimeout(refreshProgress, 30000);
       });
     }
+
+    refreshRunning();
+    refreshCompleted();
+    refreshProgress();
   });
 </script>
+${ utils.bulk_dashboard_functions() }
 
 ${ commonfooter(messages) | n,unicode }

+ 29 - 48
apps/oozie/src/oozie/templates/dashboard/list_oozie_workflows.mako

@@ -35,6 +35,13 @@ ${ layout.menubar(section='workflows', dashboard=True) }
   <form>
     <input type="text" id="filterInput" class="input-xlarge search-query" placeholder="${ _('Search for username, name, etc...') }">
 
+    <div class="btn-toolbar" style="display: inline; vertical-align: middle; margin-left: 10px; font-size: 12px">
+      <span class="loader hide"><i class="fa fa-2x fa-spinner fa-spin muted"></i></span>
+      <button class="btn bulkToolbarBtn bulk-resume" data-operation="resume" title="${ _('Resume selected') }" disabled="disabled" type="button"><i class="fa fa-play"></i> ${ _('Resume') }</button>
+      <button class="btn bulkToolbarBtn bulk-suspend" data-operation="suspend" title="${ _('Suspend selected') }" disabled="disabled" type="button"><i class="fa fa-pause"></i> ${ _('Suspend') }</button>
+      <button class="btn bulkToolbarBtn btn-danger bulk-kill disable-feedback" data-operation="kill" title="${ _('Kill selected') }" disabled="disabled" type="button"><i class="fa fa-times"></i> ${ _('Kill') }</button>
+    </div>
+
     <span class="pull-right">
       <span style="padding-right:10px;float:left;margin-top:3px">
       ${ _('Show only') }
@@ -59,14 +66,14 @@ ${ layout.menubar(section='workflows', dashboard=True) }
     <table class="table table-condensed" id="running-table">
       <thead>
         <tr>
-          <th width="15%">${ _('Submission') }</th>
+          <th width="1%"><div class="select-all hueCheckbox fa"></div></th>
+          <th width="14%">${ _('Submission') }</th>
           <th width="5%">${ _('Status') }</th>
           <th width="21%">${ _('Name') }</th>
           <th width="7%">${ _('Progress') }</th>
           <th width="7%">${ _('Submitter') }</th>
           <th width="15%">${ _('Last Modified') }</th>
           <th width="20%">${ _('Id') }</th>
-          <th width="10%">${ _('Action') }</th>
         </tr>
       </thead>
       <tbody>
@@ -128,6 +135,7 @@ ${ layout.menubar(section='workflows', dashboard=True) }
   </div>
 </div>
 
+
 <script src="/oozie/static/js/bundles.utils.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
 
@@ -155,6 +163,8 @@ ${ layout.menubar(section='workflows', dashboard=True) }
     }
   }
 
+  var refreshRunning;
+
   $(document).ready(function () {
     var runningTable = $("#running-table").dataTable({
       "sPaginationType":"bootstrap",
@@ -162,14 +172,14 @@ ${ layout.menubar(section='workflows', dashboard=True) }
       "bLengthChange":false,
       "sDom":"<'row'r>t<'row'<'span6'i><''p>>",
       "aoColumns":[
+        { "bSortable":false },
         { "sType":"date" },
         null,
         null,
         null,
         null,
         { "sSortDataType":"dom-sort-value", "sType":"numeric" },
-        null,
-        { "bSortable":false }
+        null
       ],
       "aaSorting":[
         [ 0, "desc" ]
@@ -321,13 +331,9 @@ ${ layout.menubar(section='workflows', dashboard=True) }
       });
     });
 
-    refreshRunning();
-    refreshCompleted();
-    refreshProgress();
-
     var numRunning = 0;
 
-    function refreshRunning() {
+    refreshRunning = function () {
       $.getJSON(window.location.pathname + "?format=json&type=running", function (data) {
         if (data) {
           var nNodes = runningTable.fnGetNodes();
@@ -336,7 +342,7 @@ ${ layout.menubar(section='workflows', dashboard=True) }
           $(nNodes).each(function (iNode, node) {
             var nodeFound = false;
             $(data).each(function (iWf, currentItem) {
-              if ($(node).children("td").eq(6).text() == currentItem.id) {
+              if ($(node).children("td").eq(7).text() == currentItem.id) {
                 nodeFound = true;
               }
             });
@@ -350,51 +356,22 @@ ${ layout.menubar(section='workflows', dashboard=True) }
             var wf = new Workflow(item);
             var foundRow = null;
             $(nNodes).each(function (iNode, node) {
-              if ($(node).children("td").eq(6).text() == wf.id) {
+              if ($(node).children("td").eq(7).text() == wf.id) {
                 foundRow = node;
               }
             });
-            var killCell = "";
-            var suspendCell = "";
-            var resumeCell = "";
-            if (wf.canEdit) {
-              killCell = '<a class="btn btn-mini btn-danger disable-feedback confirmationModal" ' +
-                      'href="javascript:void(0)" ' +
-                      'data-url="' + wf.killUrl + '" ' +
-                      'title="${ _('Kill') } ' + wf.id + '"' +
-                      'alt="${ _('Are you sure you want to kill workflow ')}' + wf.id + '?" ' +
-                      'data-message="${ _('The workflow was killed!') }" ' +
-                      'data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }"' +
-                      '>${ _('Kill') }</a>';
-              suspendCell = '<a class="btn btn-mini confirmationModal" ' +
-                      'href="javascript:void(0)" ' +
-                      'data-url="' + wf.suspendUrl + '" ' +
-                      'title="${ _('Suspend') } ' + wf.id + '"' +
-                      'alt="${ _('Are you sure you want to suspend workflow ')}' + wf.id + '?" ' +
-                      'data-message="${ _('The workflow was suspended!') }" ' +
-                      'data-confirmation-message="${ _('Are you sure you\'d like to suspend this job?') }"' +
-                      '>${ _('Suspend') }</a>';
-              resumeCell = '<a class="btn btn-mini confirmationModal" ' +
-                      'href="javascript:void(0)" ' +
-                      'data-url="' + wf.resumeUrl + '" ' +
-                      'title="${ _('Resume') } ' + wf.id + '"' +
-                      'alt="${ _('Are you sure you want to resume workflow ')}' + wf.id + '?" ' +
-                      'data-message="${ _('The workflow was resumed!') }" ' +
-                      'data-confirmation-message="${ _('Are you sure you\'d like to resume this job?') }"' +
-                      '>${ _('Resume') }</a>';
-            }
             if (foundRow == null) {
               if (['RUNNING', 'PREP', 'WAITING', 'SUSPENDED', 'PREPSUSPENDED', 'PREPPAUSED', 'PAUSED', 'STARTED', 'FINISHING'].indexOf(wf.status) > -1) {
                 try {
                   runningTable.fnAddData([
+                    wf.canEdit ? '<div class="hueCheckbox fa" data-row-selector-exclude="true"></div>':'',
                     emptyStringIfNull(wf.lastModTime),
                     '<span class="' + wf.statusClass + '">' + wf.status + '</span>',
                     wf.appName,
                     '<div class="progress"><div class="bar bar-warning" style="width: 1%"></div></div>',
                     wf.user,
                     emptyStringIfNull(wf.lastModTime),
-                    '<a href="' + wf.absoluteUrl + '" data-row-selector="true">' + wf.id + '</a>',
-                    killCell + " " + (['RUNNING', 'PREP', 'WAITING'].indexOf(wf.status) > -1 ? suspendCell : resumeCell)
+                    '<a href="' + wf.absoluteUrl + '" data-row-selector="true">' + wf.id + '</a>'
                   ]);
                 }
                 catch (error) {
@@ -403,8 +380,7 @@ ${ layout.menubar(section='workflows', dashboard=True) }
               }
             }
             else {
-              runningTable.fnUpdate('<span class="' + wf.statusClass + '">' + wf.status + '</span>', foundRow, 1, false);
-              runningTable.fnUpdate(killCell + " " + (['RUNNING', 'PREP', 'WAITING'].indexOf(wf.status) > -1?suspendCell:resumeCell), foundRow, 7, false);
+              runningTable.fnUpdate('<span class="' + wf.statusClass + '">' + wf.status + '</span>', foundRow, 2, false);
             }
           });
         }
@@ -451,17 +427,17 @@ ${ layout.menubar(section='workflows', dashboard=True) }
             var wf = new Workflow(item);
             var foundRow = null;
             $(nNodes).each(function (iNode, node) {
-              if ($(node).children("td").eq(6).text() == wf.id) {
+              if ($(node).children("td").eq(7).text() == wf.id) {
                 foundRow = node;
               }
             });
             if (foundRow != null) {
-              runningTable.fnUpdate('<span class="' + wf.statusClass + '">' + wf.status + '</span>', foundRow, 1, false);
+              runningTable.fnUpdate('<span class="' + wf.statusClass + '">' + wf.status + '</span>', foundRow, 2, false);
               if (wf.progress == 0){
-                runningTable.fnUpdate('<div class="progress"><div class="bar bar-warning" style="width: 1%"></div></div>', foundRow, 3, false);
+                runningTable.fnUpdate('<div class="progress"><div class="bar bar-warning" style="width: 1%"></div></div>', foundRow, 4, false);
               }
               else {
-                runningTable.fnUpdate('<div class="progress"><div class="' + wf.progressClass + '" style="width:' + wf.progress + '%">' + wf.progress + '%</div></div>', foundRow, 3, false);
+                runningTable.fnUpdate('<div class="progress"><div class="' + wf.progressClass + '" style="width:' + wf.progress + '%">' + wf.progress + '%</div></div>', foundRow, 4, false);
               }
             }
           });
@@ -469,8 +445,13 @@ ${ layout.menubar(section='workflows', dashboard=True) }
       });
     }
 
+    refreshRunning();
+    refreshCompleted();
+    refreshProgress();
+
   });
 
 </script>
+${ utils.bulk_dashboard_functions() }
 
 ${ commonfooter(messages) | n,unicode }

+ 130 - 0
apps/oozie/src/oozie/templates/utils.inc.mako

@@ -575,3 +575,133 @@ function renderCrons() {
 }
 </%def>
 
+<%def name="bulk_dashboard_functions()">
+
+<div id="bulkConfirmation" class="modal hide">
+  <div class="modal-header">
+    <a href="#" class="close" data-dismiss="modal">&times;</a>
+    <h3>${ _('Do you really want to kill the selected jobs?') }</h3>
+  </div>
+  <div class="modal-footer">
+    <a href="#" class="btn" data-dismiss="modal">${_('No')}</a>
+    <a class="btn btn-danger disable-feedback" href="javascript:void(0);">${_('Yes')}</a>
+  </div>
+</div>
+
+<script type="text/javascript">
+
+  $(document).ready(function(){
+    $(".bulkToolbarBtn").on("click", function(){
+      $(".btn-toolbar").find(".loader").removeClass("hide");
+      $(".bulkToolbarBtn").hide();
+      if ($(this).data("operation") == "kill"){
+        bulkOperationConfirmation($(this).data("operation"));
+      }
+      else {
+        bulkOperation($(this).data("operation"));
+      }
+    });
+
+    function toggleBulkButtons() {
+      if ($(".hueCheckbox.fa-check:not(.select-all)").length > 0){
+        var _allResume = true;
+        var _allSuspended = true;
+        $(".hueCheckbox.fa-check:not(.select-all)").each(function(){
+          if (['RUNNING', 'PREP', 'WAITING'].indexOf($(this).parents("tr").find(".label").text()) > -1){
+            _allResume = false;
+          }
+          else {
+            _allSuspended = false;
+          }
+        });
+        if (! _allResume) {
+          $(".bulk-suspend").removeAttr("disabled");
+        }
+        if (! _allSuspended) {
+          $(".bulk-resume").removeAttr("disabled");
+        }
+        $(".bulk-kill").removeAttr("disabled");
+      }
+      else {
+        $(".bulkToolbarBtn").attr("disabled", "disabled");
+      }
+    }
+
+    $("#bulkConfirmation").modal({
+      show: false
+    }).on("hidden", function(){
+      $(".btn-toolbar").find(".loader").addClass("hide");
+      $(".bulkToolbarBtn").show();
+    });
+
+
+    function bulkOperationConfirmation(what){
+      $("#bulkConfirmation").modal("show");
+      $("#bulkConfirmation a.btn-danger").on("click", function(){
+        bulkOperation(what);
+        $("#bulkConfirmation").modal("hide");
+      });
+    }
+
+    function bulkOperation(what) {
+      var _ids = [];
+      $(".hueCheckbox.fa-check:not(.select-all)").each(function(){
+        _ids.push($(this).parents("tr").find("a[data-row-selector='true']").text());
+      });
+
+      $.post("${ url('oozie:bulk_manage_oozie_jobs') }",
+        {
+          "job_ids": _ids.join(" "),
+          "action": what
+        },
+        function (response) {
+          ## we get this from each dashboard page
+          refreshRunning();
+          var _messages = {
+            suspend: "${ _('The selected jobs have been suspended correctly.') }",
+            suspendErrors: "${ _('Some of the selected jobs may not have been suspended correctly:') }",
+            resume: "${ _('The selected jobs have been resumed correctly.') }",
+            resumeErrors: "${ _('Some of the selected jobs may not have been resumed correctly:') }",
+            kill: "${ _('The selected jobs have been killed correctly.') }",
+            killErrors: "${ _('Some of the selected jobs may not have been killed correctly:') }"
+          }
+          if (response.totalErrors > 0){
+            $.jHueNotify.warn(_messages[what + "Errors"] + " " + response.messages);
+          }
+          else {
+            $.jHueNotify.info(_messages[what]);
+          }
+          $(".hueCheckbox").removeClass("fa-check");
+          toggleBulkButtons();
+          $(".btn-toolbar").find(".loader").addClass("hide");
+          $(".bulkToolbarBtn").show();
+      });
+    }
+
+    $(document).on("click", ".hueCheckbox", function(){
+      var _check = $(this);
+      if (_check.hasClass("select-all")){
+        if (_check.hasClass("fa-check")){
+          $(".hueCheckbox").removeClass("fa-check");
+        }
+        else {
+          $(".hueCheckbox").addClass("fa-check");
+        }
+      }
+      else {
+        if (_check.hasClass("fa-check")){
+          _check.removeClass("fa-check");
+        }
+        else {
+          _check.addClass("fa-check");
+        }
+      }
+      toggleBulkButtons();
+    });
+
+    toggleBulkButtons();
+  });
+
+</script>
+</%def>
+

+ 1 - 0
apps/oozie/src/oozie/urls.py

@@ -96,6 +96,7 @@ urlpatterns += patterns(
   url(r'^rerun_oozie_coord/(?P<job_id>[-\w]+)/(?P<app_path>.+?)$', 'rerun_oozie_coordinator', name='rerun_oozie_coord'),
   url(r'^rerun_oozie_bundle/(?P<job_id>[-\w]+)/(?P<app_path>.+?)$', 'rerun_oozie_bundle', name='rerun_oozie_bundle'),
   url(r'^manage_oozie_jobs/(?P<job_id>[-\w]+)/(?P<action>(start|suspend|resume|kill|rerun))$', 'manage_oozie_jobs', name='manage_oozie_jobs'),
+  url(r'^bulk_manage_oozie_jobs/$', 'bulk_manage_oozie_jobs', name='bulk_manage_oozie_jobs'),
 
   url(r'^submit_external_job/(?P<application_path>.+?)$', 'submit_external_job', name='submit_external_job'),
   url(r'^get_oozie_job_log/(?P<job_id>[-\w]+)$', 'get_oozie_job_log', name='get_oozie_job_log'),

+ 21 - 0
apps/oozie/src/oozie/views/dashboard.py

@@ -82,6 +82,27 @@ def manage_oozie_jobs(request, job_id, action):
 
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
+def bulk_manage_oozie_jobs(request):
+  if request.method != 'POST':
+    raise PopupException(_('Use a POST request to manage the Oozie jobs.'))
+
+  response = {'status': -1, 'data': ''}
+
+  if 'job_ids' in request.POST and 'action' in request.POST:
+    jobs = request.POST.get('job_ids').split()
+    response = {'totalRequests': len(jobs), 'totalErrors': 0, 'messages': ''}
+
+    for job_id in jobs:
+      job = check_job_access_permission(request, job_id)
+      check_job_edition_permission(job, request.user)
+      try:
+        get_oozie(request.user).job_control(job_id, request.POST.get('action'))
+      except RestException, ex:
+        response['totalErrors'] = response['totalErrors'] + 1
+        response['messages'] += str(ex)
+
+  return HttpResponse(json.dumps(response), mimetype="application/json")
+
 
 def show_oozie_error(view_func):
   def decorate(request, *args, **kwargs):