소스 검색

[oozie] Preliminary work for new dashboard graph

Enrico Berti 10 년 전
부모
커밋
db5709cfa6

+ 60 - 19
apps/oozie/src/oozie/templates/dashboard/list_oozie_workflow.mako

@@ -23,6 +23,8 @@
 
 
 <%namespace name="layout" file="../navigation-bar.mako" />
 <%namespace name="layout" file="../navigation-bar.mako" />
 <%namespace name="utils" file="../utils.inc.mako" />
 <%namespace name="utils" file="../utils.inc.mako" />
+<%namespace name="workflow" file="../editor2/common_workflow.mako" />
+<%namespace name="dashboard" file="/common_dashboard.mako" />
 
 
 ${ commonheader(_("Workflow Dashboard"), "oozie", user) | n,unicode }
 ${ commonheader(_("Workflow Dashboard"), "oozie", user) | n,unicode }
 ${ layout.menubar(section='workflows', dashboard=True) }
 ${ layout.menubar(section='workflows', dashboard=True) }
@@ -133,7 +135,7 @@ ${ layout.menubar(section='workflows', dashboard=True) }
       </div>
       </div>
     </div>
     </div>
 
 
-    <div class="span10">
+    <div class="span10" style="margin-left: 2.127659574468085%;">
       <h1 class="card-heading simple card-heading-nopadding card-heading-noborder card-heading-blue" style="margin-bottom: 10px">
       <h1 class="card-heading simple card-heading-nopadding card-heading-noborder card-heading-blue" style="margin-bottom: 10px">
         % if oozie_bundle:
         % if oozie_bundle:
           ${ _('Bundle') } <a href="${ oozie_bundle.get_absolute_url() }">${ oozie_bundle.appName }</a> :
           ${ _('Bundle') } <a href="${ oozie_bundle.get_absolute_url() }">${ oozie_bundle.appName }</a> :
@@ -145,12 +147,8 @@ ${ layout.menubar(section='workflows', dashboard=True) }
         ${ _('Workflow') } ${ oozie_workflow.appName }
         ${ _('Workflow') } ${ oozie_workflow.appName }
       </h1>
       </h1>
       <ul class="nav nav-tabs">
       <ul class="nav nav-tabs">
-        % if workflow_graph:
-            <li class="active"><a href="#graph" data-toggle="tab">${ _('Graph') }</a></li>
-            <li><a href="#actions" data-toggle="tab">${ _('Actions') }</a></li>
-        % else:
-            <li class="active"><a href="#actions" data-toggle="tab">${ _('Actions') }</a></li>
-        % endif
+        <li class="active"><a href="#graph" data-toggle="tab">${ _('Graph') }</a></li>
+        <li><a href="#actions" data-toggle="tab">${ _('Actions') }</a></li>
         <li><a href="#details" data-toggle="tab">${ _('Details') }</a></li>
         <li><a href="#details" data-toggle="tab">${ _('Details') }</a></li>
         <li><a href="#configuration" data-toggle="tab">${ _('Configuration') }</a></li>
         <li><a href="#configuration" data-toggle="tab">${ _('Configuration') }</a></li>
         <li><a href="#log" data-toggle="tab">${ _('Log') }</a></li>
         <li><a href="#log" data-toggle="tab">${ _('Log') }</a></li>
@@ -160,15 +158,15 @@ ${ layout.menubar(section='workflows', dashboard=True) }
         % endif
         % endif
       </ul>
       </ul>
 
 
-      <div id="workflow-tab-content" class="tab-content" style="min-height:200px">
-
-        % if workflow_graph:
-            <div id="graph" class="tab-pane active">
-            ${ workflow_graph | n,unicode }
-            </div>
+      <div id="workflow-tab-content" class="tab-content" style="min-height:200px">    
+        <div id="graph" class="tab-pane active">
+        % if layout_json == '':
+        ${ workflow_graph | n,unicode }
+        % else:
+        ${ workflow.render() }
         % endif
         % endif
-
-        <div id="actions" class="tab-pane ${ utils.if_false(workflow_graph, 'active') }">
+        </div>
+        <div id="actions" class="tab-pane">
           <table class="table table-striped table-condensed selectable">
           <table class="table table-striped table-condensed selectable">
             <thead>
             <thead>
             <tr>
             <tr>
@@ -347,6 +345,7 @@ ${ layout.menubar(section='workflows', dashboard=True) }
 <script src="/oozie/static/js/dashboard-utils.js" type="text/javascript" charset="utf-8"></script>
 <script src="/oozie/static/js/dashboard-utils.js" type="text/javascript" charset="utf-8"></script>
 <link rel="stylesheet" href="/oozie/static/css/workflow.css">
 <link rel="stylesheet" href="/oozie/static/css/workflow.css">
 <script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/knockout.mapping-2.3.2.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>
 <script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/codemirror-3.11.js"></script>
 <script src="/static/ext/js/codemirror-3.11.js"></script>
 <link rel="stylesheet" href="/static/ext/css/codemirror.css">
 <link rel="stylesheet" href="/static/ext/css/codemirror.css">
@@ -361,6 +360,17 @@ ${ layout.menubar(section='workflows', dashboard=True) }
 <script src="/static/js/jquery.blueprint.js"></script>
 <script src="/static/js/jquery.blueprint.js"></script>
 %endif
 %endif
 
 
+% if layout_json != '':
+<link rel="stylesheet" href="/oozie/static/css/common-editor.css">
+<link rel="stylesheet" href="/oozie/static/css/workflow-editor.css">
+
+${ dashboard.import_layout() }
+
+<script src="/oozie/static/js/workflow-editor.ko.js" type="text/javascript" charset="utf-8"></script>
+<script src="/oozie/static/js/workflow-editor.utils.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/jquery/plugins/jquery.curvedarrow.js" type="text/javascript" charset="utf-8"></script>
+%endif
+
 <style type="text/css">
 <style type="text/css">
   .CodeMirror.cm-s-default {
   .CodeMirror.cm-s-default {
     height: 700px;
     height: 700px;
@@ -383,10 +393,28 @@ ${ layout.menubar(section='workflows', dashboard=True) }
   #slaTable {
   #slaTable {
     margin-top: 20px;
     margin-top: 20px;
   }
   }
+
+  % if not workflow_graph:
+  #graph {
+    margin-top: 0;
+  }
+  #graph, #new-node {
+    text-align: left;
+  }
+  % endif
+
 </style>
 </style>
 
 
 <script type="text/javascript">
 <script type="text/javascript">
 
 
+${ utils.slaGlobal() }
+
+  ko.bindingHandlers.editable = { // overwrite the editable bindings with a simple text filler
+    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
+      $(element).text(valueAccessor()());
+    }
+  }
+
   var Action = function (action) {
   var Action = function (action) {
     return {
     return {
       id: action.id,
       id: action.id,
@@ -416,8 +444,16 @@ ${ layout.menubar(section='workflows', dashboard=True) }
 
 
   };
   };
 
 
-  var viewModel = new RunningWorkflowActionsModel([]);
-  ko.applyBindings(viewModel);
+  var actionsViewModel = new RunningWorkflowActionsModel([]);
+  ko.applyBindings(actionsViewModel, $("#actions")[0]);
+
+  % if layout_json != '':
+  var viewModel = new WorkflowEditorViewModel(${ layout_json | n,unicode }, ${ workflow_json | n,unicode }, ${ credentials_json | n,unicode }, ${ workflow_properties_json | n,unicode }, ${ subworkflows_json | n,unicode }, ${ can_edit_json | n,unicode }); 
+  ko.applyBindings(viewModel, $("#graph")[0]);
+  viewModel.isViewer = ko.observable(true);
+  viewModel.init();
+  fullLayout(viewModel);
+  %endif
 
 
   var CHART_LABELS = {
   var CHART_LABELS = {
     NOMINAL_TIME: "${_('Nominal Time')}",
     NOMINAL_TIME: "${_('Nominal Time')}",
@@ -430,6 +466,10 @@ ${ layout.menubar(section='workflows', dashboard=True) }
   var slaTable;
   var slaTable;
 
 
   $(document).ready(function() {
   $(document).ready(function() {
+    % if layout_json != '':
+    drawArrows();
+    %endif
+
     var CURRENT_ZOOM = 1;
     var CURRENT_ZOOM = 1;
     $(document).keydown(function(e) {
     $(document).keydown(function(e) {
       if (e.ctrlKey){
       if (e.ctrlKey){
@@ -602,7 +642,7 @@ ${ layout.menubar(section='workflows', dashboard=True) }
       $.getJSON("${ oozie_workflow.get_absolute_url(format='json') }", function (data) {
       $.getJSON("${ oozie_workflow.get_absolute_url(format='json') }", function (data) {
 
 
         if (data.actions){
         if (data.actions){
-          viewModel.actions(ko.utils.arrayMap(data.actions, function (action) {
+          actionsViewModel.actions(ko.utils.arrayMap(data.actions, function (action) {
             return new Action(action);
             return new Action(action);
           }));
           }));
         }
         }
@@ -630,8 +670,9 @@ ${ layout.menubar(section='workflows', dashboard=True) }
         }
         }
 
 
         $("#progress .bar").text(data.progress + "%").css("width", data.progress + "%").attr("class", "bar " + getStatusClass(data.status, "bar-"));
         $("#progress .bar").text(data.progress + "%").css("width", data.progress + "%").attr("class", "bar " + getStatusClass(data.status, "bar-"));
-
+        %if layout_json == '':
         $("#graph").html(data.graph);
         $("#graph").html(data.graph);
+        %endif
 
 
         if (data.status != "RUNNING" && data.status != "PREP"){
         if (data.status != "RUNNING" && data.status != "PREP"){
           return;
           return;

+ 1540 - 0
apps/oozie/src/oozie/templates/editor2/common_workflow.mako

@@ -0,0 +1,1540 @@
+## -*- coding: utf-8 -*-
+## 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.
+
+
+#
+# Include this in order to use the functions:
+# <%namespace name="workflow" file="common_workflow.mako" />
+#
+
+<%!
+  from django.utils.translation import ugettext as _
+%>
+<%namespace name="utils" file="../utils.inc.mako" />
+
+<%def name="render()">
+  
+<div data-bind="css: {'dashboard': true, 'readonly': ! isEditing()}">
+  <!-- ko if: $root.workflow.properties.imported -->
+    <div class="alert alert-warn" style="margin-top: 93px; margin-bottom: 0; border: none; text-align: center">
+      ${ _('This workflow was imported from an old Hue version, save it to create a copy in the new format or') }
+      <a data-bind="attr: { href: '/oozie/edit_workflow/' + $root.workflow.properties.wf1_id() }">${ _('open it in the old editor.') }</a>
+    </div>
+  <!-- /ko -->
+  <div class="container-fluid">
+    <div class="row-fluid" data-bind="template: { name: 'column-template', foreach: oozieColumns}">
+    </div>
+    <div class="clearfix"></div>
+  </div>
+</div>
+
+
+<script type="text/html" id="column-template">
+  <div data-bind="css: klass()" style="min-height: 50px !important;">
+    <div data-bind="template: { name: 'row-template', data: oozieStartRow }, style:{'margin-top' : (typeof $root.workflow.properties.imported != 'undefined' && $root.workflow.properties.imported()) || typeof $root.isViewer != 'undefined' ? '0': '50px'}"></div>
+    <div class="container-fluid" data-bind="visible: $root.isEditing() && oozieRows().length > 0">
+      <div class="row-fluid">
+        <div data-bind="visible: enableOozieDropOnBefore, css: {'span4 offset4': true, 'drop-target': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}, droppable: {enabled: $root.isEditing, onDrop: function(x, y){ var _w = $root.addDraggedWidget($data, true); widgetDraggedAdditionalHandler(_w); } }"></div>
+        <div data-bind="visible: ! enableOozieDropOnBefore(), css: {'drop-target drop-target-disabled': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}"></div>
+      </div>
+    </div>
+    <div data-bind="template: { name: 'internal-row-template', foreach: oozieRows}">
+    </div>
+    <div class="container-fluid" data-bind="visible: $root.isEditing() && rows().length > 0">
+      <div class="row-fluid">
+        <div data-bind="visible: enableOozieDropOnAfter, css: {'span4 offset4': true, 'drop-target': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}, droppable: {enabled: $root.isEditing, onDrop: function(){ var _w = $root.addDraggedWidget($data, false); widgetDraggedAdditionalHandler(_w); } }">
+          <span data-bind="visible: oozieRows().length == 0">${ _('Drop your action here') }</span>
+        </div>
+        <div data-bind="visible: ! enableOozieDropOnAfter(), css: {'drop-target drop-target-disabled': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}"></div>
+      </div>
+    </div>
+
+    <div data-bind="template: { name: 'row-template', data: oozieEndRow }"></div>
+
+    <div data-bind="template: { name: 'row-template', data: oozieKillRow }" style="margin-top: 60px"></div>
+  </div>
+</script>
+
+
+<script type="text/html" id="internal-column-template">
+  <div data-bind="css: klass(), style: {'minHeight': '50px !important', 'width': percWidth() + '%' }">
+    <div class="container-fluid" data-bind="visible: $root.isEditing()">
+      <div data-bind="visible: ! enableOozieDropOnBefore(), css: {'drop-target drop-target-disabled': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}"></div>
+      <div data-bind="visible: enableOozieDropOnBefore, css: {'drop-target': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}, droppable: {enabled: $root.isEditing, onDrop: function(){ var _w = $root.addDraggedWidget($data, true); widgetDraggedAdditionalHandler(_w); } }"></div>
+    </div>
+    <div data-bind="template: { name: 'internal-row-template', foreach: rows}">
+    </div>
+    <div class="container-fluid" data-bind="visible: $root.isEditing()">
+      <div data-bind="visible: ! enableOozieDropOnAfter(), css: {'drop-target drop-target-disabled': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}"></div>
+      <div data-bind="visible: enableOozieDropOnAfter, css: {'drop-target': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}, droppable: {enabled: $root.isEditing, onDrop: function(){ var _w = $root.addDraggedWidget($data, false); widgetDraggedAdditionalHandler(_w); } }"></div>
+    </div>
+  </div>
+</script>
+
+
+<script type="text/html" id="row-template">
+  <div class="container-fluid">
+    <div class="row-fluid">
+      <div class="span4 offset4">
+        <div data-bind="css: {'row-fluid': true, 'row-container':true, 'is-editing': $root.isEditing},
+          sortable: { template: 'widget-template', data: widgets, allowDrop: $root.isEditing() && widgets().length < 1, isEnabled: $root.isEditing() && widgets().length < 1,
+          options: {'opacity': 0.7, 'placeholder': 'row-highlight', 'greedy': true,
+              'stop': function(event, ui){},
+              'helper': function(event){lastWindowScrollPosition = $(window).scrollTop(); var _par = $('<div>');_par.addClass('card card-widget');var _title = $('<h2>');_title.addClass('card-heading simple');_title.text($(event.toElement).text());_title.appendTo(_par);_par.height(80);_par.width(180);return _par;}},
+              dragged: function(widget){widgetDraggedAdditionalHandler(widget)}}">
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="internal-row-template">
+  <div class="container-fluid">
+    <div class="row-fluid" data-bind="visible: $index() > 0 && $root.isEditing() && ! $root.isRowBeforeJoin($data) && ! $root.isRowAfterFork($data)" style="margin-bottom: 10px">
+      <div data-bind="css: {'span1': true, 'offset3andhalf': ($root.isEditing() && $parents.length <= 2 && columns().length == 0), 'offset4': (!$root.isEditing() && $parents.length <= 2 && columns().length == 0), 'readonly': ! $root.isEditing()}"></div>
+      <div data-bind="css: {'span10': ($parents.length > 2 || columns().length > 0), 'span4': ($parents.length <= 2 && columns().length == 0), 'offset3andhalf': (!$root.isEditing() && $parents.length <= 2 && columns().length == 0), 'readonly': ! $root.isEditing()}">
+        <div data-bind="visible: $root.isEditing() && ! enableOozieDropOnBefore(), css: {'drop-target drop-target-disabled': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}"></div>
+        <div style="text-align: left" data-bind="visible: $root.isEditing() && enableOozieDropOnBefore(), css: {'drop-target': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}, droppable: {enabled: $root.isEditing, onDrop: function(){ var _w = $root.addDraggedWidget($data); widgetDraggedAdditionalHandler(_w); } }"></div>
+      </div>
+      <div data-bind="css: {'span1': true, 'readonly': ! $root.isEditing()}"></div>
+    </div>
+    <div class="row-fluid" data-bind="style: {'width': columns().length < 5 ? '100%' : (columns().length * 25)+'%' }">
+      <div data-bind="css: {'span1': true, 'offset3andhalf': ($root.isEditing() && $parents.length <= 2 && columns().length == 0), 'offset4': (!$root.isEditing() && $parents.length <= 2 && columns().length == 0), 'readonly': ! $root.isEditing()}">
+        <div data-bind="visible: $root.isEditing() && enableOozieDropOnSide() && !($data.widgets().length > 0 && ['join-widget', 'decision-widget'].indexOf($data.widgets()[0].widgetType()) > -1), css: {'drop-target drop-target-side': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}, droppable: {enabled: $root.isEditing, onDrop: function(){ var _w = $root.addSideDraggedWidget($data, true); widgetDraggedAdditionalHandler(_w); } }"></div>
+      </div>
+      <div  data-bind="css: {'span10': ($parents.length > 2 || columns().length > 0), 'span4': ($parents.length <= 2 && columns().length == 0), 'offset3andhalf': (!$root.isEditing() && $parents.length <= 2 && columns().length == 0), 'readonly': ! $root.isEditing()}">
+        <div data-bind="visible: columns().length == 0, css: {'row-fluid': true, 'row-container':true, 'is-editing': $root.isEditing},
+          sortable: { template: 'widget-template', data: widgets, allowDrop: enableOozieDrop, isEnabled: enableOozieDrop,
+          options: {'opacity': 0.7, 'placeholder': 'row-highlight', 'greedy': true,
+              'stop': function(event, ui){},
+              'helper': function(event){lastWindowScrollPosition = $(window).scrollTop();  var _par = $('<div>');_par.addClass('card card-widget');var _title = $('<h2>');_title.addClass('card-heading simple');_title.text($(event.toElement).text());_title.appendTo(_par);_par.height(80);_par.width(180);return _par;}},
+              dragged: function(widget){widgetDraggedAdditionalHandler(widget)}}">
+        </div>
+        <div class="container-fluid" data-bind="visible: columns().length > 0">
+          <div data-bind="css: {'row-fluid': true, 'row-container':true, 'is-editing': $root.isEditing}">
+            <div data-bind="template: { name: 'internal-column-template', foreach: columns}">
+            </div>
+          </div>
+        </div>
+      </div>
+      <div  data-bind="css: {'span1': true, 'readonly no-margin': ! $root.isEditing()}, style: {'margin': ! $root.isEditing() ? '0':''}">
+        <div data-bind="visible: $root.isEditing() && enableOozieDropOnSide() && !($data.widgets().length > 0 && ['join-widget', 'decision-widget'].indexOf($data.widgets()[0].widgetType()) > -1), css: {'drop-target drop-target-side': true, 'drop-target-dragging': $root.isDragging(), 'is-editing': $root.isEditing}, droppable: {enabled: $root.isEditing, onDrop: function(){ var _w = $root.addSideDraggedWidget($data, false); widgetDraggedAdditionalHandler(_w); } }"></div>
+      </div>
+    </div>
+  </div>
+
+</script>
+
+<script type="text/html" id="widget-template">
+  <div data-bind="attr: {'id': 'wdg_'+ id(),}, css: klass() + (ooziePropertiesExpanded()?' expanded-widget':'') + ($root.isRunning() && status() == ''?' widget-running':''),
+      draggable: {data: $data, isEnabled: true, options: {'handle': '.move-widget', 'opacity': 0.7, 'refreshPositions': true, 'start': function(event, ui){ $root.setCurrentlyDraggedWidget($data, event.toElement); $root.isDragging(true); }, 'stop': function(event, ui){ $root.enableSideDrop($data); $root.isDragging(false); }, 'helper': function(event){lastWindowScrollPosition = $(window).scrollTop();  var _par = $('<div>');_par.addClass('card card-widget');var _title = $('<h2>');_title.addClass('card-heading simple');_title.text($(event.currentTarget).find('h2').text());_title.appendTo(_par);_par.css('minHeight', '10px');_par.width(120);return _par;}}}">
+    <h2 class="card-heading simple" data-bind="visible: widgetType() != 'start-widget' && widgetType() != 'end-widget' &&
+        id() != '17c9c895-5a16-7443-bb81-f34b30b21548' && (['fork-widget', 'join-widget', 'decision-widget'].indexOf(widgetType()) == -1 || $root.isEditing())">
+
+      <span data-bind="visible: $root.isEditing() && oozieMovable() && ! oozieExpanded() && ! ooziePropertiesExpanded() && ($root.newAction() == null || $root.newAction().id() != id())">
+        <a href="javascript:void(0)" class="move-widget" title="${ _('Move node') }"><i class="fa fa-arrows"></i></a>
+        &nbsp;
+        <a href="javascript:void(0)" class="move-widget clone-widget" title="${ _('Copy node') }"><i class="fa fa-copy"></i></a>
+        &nbsp;
+      </span>
+
+      <!-- ko if: widgetType() == 'hive-widget' -->
+      <img src="/oozie/static/art/icon_beeswax_48.png" class="widget-icon">
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'hive2-widget' -->
+      <img src="/oozie/static/art/icon_beeswax_48.png" class="widget-icon"><sup style="color: #338bb8; margin-left: -4px">2</sup>
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'pig-widget' -->
+      <img src="/oozie/static/art/icon_pig_48.png" class="widget-icon">
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'java-widget' -->
+      <a class="widget-icon"><i class="fa fa-file-code-o"></i></a>
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'sqoop-widget' -->
+      <img src="/oozie/static/art/icon_sqoop_48.png" class="widget-icon">
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'mapreduce-widget' -->
+      <a class="widget-icon"><i class="fa fa-file-archive-o"></i></a>
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'subworkflow-widget' -->
+      <a class="widget-icon"><i class="fa fa-code-fork"></i></a>
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'shell-widget' -->
+      <a class="widget-icon"><i class="fa fa-terminal"></i></a>
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'ssh-widget' -->
+      <a class="widget-icon"><i class="fa fa-tty"></i></a>
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'fs-widget' -->
+      <a class="widget-icon"><i class="fa fa-file-o"></i></a>
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'email-widget' -->
+      <a class="widget-icon"><i class="fa fa-envelope-o"></i></a>
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'streaming-widget' -->
+      <a class="widget-icon"><i class="fa fa-exchange"></i></a>
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'distcp-widget' -->
+      <a class="widget-icon"><i class="fa fa-files-o"></i></a>
+      <!-- /ko -->
+
+      <!-- ko if: widgetType() == 'kill-widget' -->
+      <a class="widget-icon"><i class="fa fa-stop"></i></a>
+      <!-- /ko -->
+
+      <span data-bind="editable: name, editableOptions: {enabled: $root.isEditing(), placement: 'right'}, attr: {'title': id().slice(0, 4)}"></span>
+
+      <!-- ko if: widgetType() == 'decision-widget' -->
+        <div class="inline pull-right" data-bind="visible: $root.isEditing() && $root.workflow.getNodeById(id()) && $root.workflow.getNodeById(id()).children().length <= 1 && ! oozieExpanded() && ! ooziePropertiesExpanded()">
+          <a href="javascript:void(0)" data-bind="click: function(w){addActionDemiModalFieldCancel();$root.removeWidget(w);}"><i class="fa fa-times"></i></a>
+        </div>
+      <!-- /ko -->
+      <!-- ko if: widgetType() != 'decision-widget' -->
+        <div class="inline pull-right" data-bind="visible: $root.isEditing() && (['start-widget', 'end-widget', 'fork-widget', 'join-widget'].indexOf(widgetType()) == -1) && ! oozieExpanded() && ! ooziePropertiesExpanded()">
+          <a href="javascript:void(0)" data-bind="click: function(w){addActionDemiModalFieldCancel();$root.removeWidget(w);}"><i class="fa fa-times"></i></a>
+        </div>
+      <!-- /ko -->
+      <!-- ko if: ooziePropertiesExpanded() -->
+        <div class="inline pull-right">
+          <a href="javascript:void(0)" data-bind="click: toggleProperties"><i class="fa fa-caret-square-o-left"></i></a>
+        </div>
+      <!-- /ko -->
+    </h2>
+    <div class="card-body" style="padding: 0; position: relative">
+      <div class="advanced-triangle-container" data-bind="visible: $root.isEditing() && ! ooziePropertiesExpanded() && oozieMovable(), click: toggleProperties">
+        <div class="advanced-triangle">
+          <a href="javascript:void(0)"><i class="fa fa-cogs"></i></a>
+        </div>
+      </div>
+      <!-- ko if: id() == '17c9c895-5a16-7443-bb81-f34b30b21548' && ooziePropertiesExpanded() -->
+      <div class="advanced-triangle-container" data-bind="visible: $root.isEditing(), click: toggleProperties">
+        <div class="advanced-triangle">
+          <a href="javascript:void(0)"><i class="fa fa-caret-square-o-left"></i></a>
+        </div>
+      </div>
+      <!-- /ko -->
+      <div data-bind="template: { name: function() { return widgetType(); }}" class="widget-main-section"></div>
+      <div data-bind="css: {'widget-statusbar': true, 'widget-statusbar-running': status() == 'running', 'widget-statusbar-failed': status() == 'failed', 'widget-statusbar-success': status() == 'success'}, style: {'width': progress() + '%'}"></div>
+    </div>
+  </div>
+</script>
+
+
+<script type="text/html" id="fork-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="min-height: 40px">
+    <div class="big-icon"><i class="fa fa-sitemap"></i></div>
+    <div data-bind="visible: $root.isEditing" style="padding-left: 10px; padding-bottom: 10px">
+      <a class="pointer" data-bind="click: function() { $root.convertToDecision($parent, $data) }">${_('Convert to Decision')} <i class="fa fa-wrench"></i></a>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="join-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="min-height: 40px">
+    <div class="big-icon"><i class="fa fa-sitemap fa-rotate-180"></i></div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="decision-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="min-height: 40px">
+    <div class="big-icon" data-bind="visible: ! $root.isEditing()"><i class="fa fa-magic"></i></div>
+
+    <div data-bind="visible: $root.isEditing" style="padding: 10px">
+      <a class="custom-popover pull-right" href="javascript:void(0)" target="_blank" data-trigger="click" data-toggle="popover" data-placement="right" rel="popover" 
+        data-html="true" data-content="<strong>${ _('Examples of predicates:') }</strong><br/>
+                <br/>${'${'} fs:fileSize(secondjobOutputDir) gt 10 * GB }
+                <br/>
+                ${"${"} hadoop:counters('secondjob')[RECORDS][REDUCE_OUT] lt 1000000 }
+                <br/>
+                <a href='http://oozie.apache.org/docs/4.1.0/WorkflowFunctionalSpec.html#a4.2_Expression_Language_Functions'>${ _('Click for more') }</a>">
+          <i class="fa fa-question-circle" title="${ _('Click for more info') }"></i>
+      </a>    
+      <ul data-bind="foreach: children" class="unstyled">
+        <li>
+          ${ _('If') } <input type="text" data-bind="value: $data['condition']" />
+          ${ _('go to') }
+          <select data-bind="options: $root.workflow.nodeIds,
+                     optionsText: function(item) {return $root.workflow.nodeNamesMapping()[item]; },
+                     value: $data['to']
+                     ">
+          </select>
+          <a class="pointer" data-bind="click: function(){ $parent.children.remove(this);}">
+            <i class="fa fa-minus"></i>
+          </a>
+        </li>
+      </ul>
+      <a class="pointer" data-bind="click: function(){ children.push({'to': '', 'condition': '${'${'} 1 gt 0 }'});}">
+        ${ _('Jump to another node') } <i class="fa fa-plus"></i>
+      </a>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="kill-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="min-height: 40px">
+    <div class="big-icon" data-bind="visible: id() == '17c9c895-5a16-7443-bb81-f34b30b21548'" title="${ _('It is where we finish if failure!') }"><i class="fa fa-stop"></i></div>
+
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: $parent.ooziePropertiesExpanded">
+        <h6 class="field-title">${ _('Message') }</h6>
+        <textarea class="span12" data-bind="value: properties.message" />
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="start-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="min-height: 40px;">
+    <div class="big-icon" title="${ _('It is where we start!') }"><i class="fa fa-flag-checkered"></i></div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="end-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="min-height: 40px">
+    <div class="big-icon" title="${ _('It is where we successfully finish!') }"><i class="fa fa-dot-circle-o"></i></div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="common-action-properties">
+  <div class="properties">
+    <h6>${ _('Prepare') }</h6>
+    <ul data-bind="visible: properties.prepares().length > 0, foreach: properties.prepares" class="unstyled">
+      <li>
+        <div style="display: inline-block; width: 60px" data-bind="text: type"></div>
+        <input type="text" class="filechooser-input input-xlarge"
+            data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.prepares.help_text }"/>
+        <a href="#" data-bind="click: function(){ $parent.properties.prepares.remove(this); $(document).trigger('drawArrows') }">
+          <i class="fa fa-minus"></i>
+        </a>
+      </li>
+    </ul>
+    <a class="pointer" data-bind="click: function(){ properties.prepares.push({'type': 'mkdir', 'value': ''}); $(document).trigger('drawArrows') }">
+      ${ _('Directory') } <i class="fa fa-plus"></i>
+    </a>
+    <a class="pointer" data-bind="click: function(){ properties.prepares.push({'type': 'delete', 'value': ''}); $(document).trigger('drawArrows') }">
+      ${ _('Delete') } <i class="fa fa-plus"></i>
+    </a>
+
+    <!-- ko if: properties.job_xml -->
+      <h6>${ _('Job XML') }</h6>
+      <input type="text" class="input-xlarge filechooser-input" data-bind="filechooser: properties.job_xml, filechooserOptions: globalFilechooserOptions, attr: { placeholder: $root.workflow_properties.job_xml.help_text }"/>
+    <!-- /ko -->
+
+    <h6>
+      <a class="pointer" data-bind="click: function(){ properties.job_properties.push({'name': '', 'value': ''}); $(document).trigger('drawArrows') }">
+        ${ _('Properties') } <i class="fa fa-plus"></i>
+      </a>
+    </h6>
+    <ul data-bind="visible: properties.job_properties().length > 0, foreach: properties.job_properties" class="unstyled">
+      <li>
+        <input type="text" data-bind="value: name" placeholder="${ _('name, e.g. mapred.job.queue.name') }"/>
+        <input type="text" class="filechooser-input input-xlarge" data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.job_properties.help_text }"/>
+        <a href="#" data-bind="click: function(){ $parent.properties.job_properties.remove(this); $(document).trigger('drawArrows') }">
+          <i class="fa fa-minus"></i>
+        </a>
+      </li>
+    </ul>
+    <em data-bind="visible: properties.job_properties().length == 0">${ _('No properties defined.') }</em>
+
+    <h6>
+      <a class="pointer" data-bind="click: function(){ properties.archives.push(ko.mapping.fromJS({'name': ''})); $(document).trigger('drawArrows') }">
+        ${ _('Archives') } <i class="fa fa-plus"></i>
+      </a>
+    </h6>
+    <ul data-bind="visible: properties.archives().length > 0, foreach: properties.archives" class="unstyled">
+      <li>
+        <input type="text" class="filechooser-input input-xlarge" data-bind="filechooser: name(), filechooserFilter: 'zip,tar,tgz,tar.gz', filechooserOptions: globalFilechooserOptions, value: name, attr: { placeholder: $root.workflow_properties.archives.help_text }"/>
+        <span data-bind='template: { name: "common-fs-link", data: { path: name(), with_label: false} }'></span>
+        <a href="#" data-bind="click: function(){ $parent.properties.archives.remove(this); $(document).trigger('drawArrows') }">
+          <i class="fa fa-minus"></i>
+        </a>
+      </li>
+    </ul>
+    <em data-bind="visible: properties.archives().length == 0">${ _('No archives defined.') }</em>
+  </div>
+</script>
+
+
+<script type="text/html" id="common-properties-pig-arguments">
+  <h6>
+    <a class="pointer" data-bind="click: function(){ properties.arguments.push({'value': ''}); $(document).trigger('drawArrows') }">
+      ${ _('Arguments') } <i class="fa fa-plus"></i>
+    </a>
+  </h6>
+  <ul class="unstyled" data-bind="visible: properties.arguments().length > 0, foreach: properties.arguments">
+    <li>
+      <input type="text" class="input-medium" data-bind="value: value, attr: { placeholder: $root.workflow_properties.arguments.help_text }"/>
+      <a href="#" data-bind="click: function(){ $parent.properties.arguments.remove(this); $(document).trigger('drawArrows') }">
+        <i class="fa fa-minus"></i>
+      </a>
+    </li>
+  </ul>
+  <em data-bind="visible: properties.arguments().length == 0">${ _('No arguments defined.') }</em>
+</script>
+
+
+<script type="text/html" id="common-properties-arguments">
+  <h6>
+    <a class="pointer" data-bind="click: function(){ properties.arguments.push({'value': ''}); $(document).trigger('drawArrows') }">
+      ${ _('Arguments') } <i class="fa fa-plus"></i>
+    </a>
+  </h6>
+  <ul class="unstyled" data-bind="visible: properties.arguments().length > 0, foreach: properties.arguments">
+    <li style="margin-bottom: 3px">
+      <input type="text" class="span11" data-bind="value: value, attr: { placeholder: $root.workflow_properties.arguments.help_text }"/>
+      <a href="#" data-bind="click: function(){ $parent.properties.arguments.remove(this); $(document).trigger('drawArrows') }">
+        <i class="fa fa-minus"></i>
+      </a>
+    </li>
+  </ul>
+</script>
+
+
+<script type="text/html" id="common-properties-files">
+  <h6>
+    <a class="pointer" data-bind="click: function(){ properties.files.push(ko.mapping.fromJS({'value': ''})); $(document).trigger('drawArrows') }">
+      ${ _('Files') } <i class="fa fa-plus"></i>
+    </a>
+  </h6>
+  <ul class="unstyled" data-bind="foreach: properties.files">
+    <li style="margin-bottom: 3px">
+      <input type="text" class="filechooser-input" data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions"/>
+      <span data-bind='template: { name: "common-fs-link", data: { path: value(), with_label: false} }'></span>
+      <a href="#" data-bind="click: function(){ $parent.properties.files.remove(this); $(document).trigger('drawArrows') }">
+        <i class="fa fa-minus"></i>
+      </a>
+    </li>
+  </ul>
+</script>
+
+
+<script type="text/html" id="common-properties-parameters">
+  <h6>
+    <a class="pointer" data-bind="click: function(){ properties.parameters.push(ko.mapping.fromJS({'value': ''})); $(document).trigger('drawArrows')}">
+      ${ _('Parameters') } <i class="fa fa-plus"></i>
+    </a>
+  </h6>
+  <ul class="unstyled" data-bind="foreach: properties.parameters">
+    <li style="margin-bottom: 3px">
+      <input type="text" class="filechooser-input seventy" data-bind="value: value, filechooser: value, filechooserOptions: globalFilechooserOptions, filechooserPrefixSeparator: '=', attr: { placeholder: ' ${ _("Fill me up!") }' }, typeahead: { target: value, source: $parent.actionParametersUI, sourceSuffix: '=', triggerOnFocus: true }"/>
+      <span data-bind='template: { name: "param-fs-link", data: {path: value()} }'></span>
+      <a href="#" data-bind="click: function(){ $parent.properties.parameters.remove(this); $(document).trigger('drawArrows') }">
+        <i class="fa fa-minus"></i>
+      </a>
+    </li>
+  </ul>
+</script>
+
+
+<script type="text/html" id="common-action-transition">
+  <!-- ko if: children().length == 2 -->
+  OK -->
+  <select data-bind="options: $root.workflow.nodeIds,
+      optionsText: function(item) {return $root.workflow.nodeNamesMapping()[item]; },
+      value: children()[0]['to']
+      ">
+  </select>
+  <br/>
+  KO -->
+  <select data-bind="options: $root.workflow.nodeIds,
+     optionsText: function(item) {return $root.workflow.nodeNamesMapping()[item]; },
+   value: children()[1]['error']
+     ">
+  </select>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="common-action-credentials">
+  <em data-bind="visible: $root.credentials() == null || $root.credentials().length == 0">${ _('No available credentials.') }</em>
+  <select data-bind="visible: $root.credentials() != null && $root.credentials().length > 0, options: $root.credentials, value: properties.credentials" size="5" multiple="true"></select>
+</script>
+
+
+<script type="text/html" id="common-action-sla">
+  <div data-bind="with: properties">
+     ${ utils.slaForm() }
+  </div>
+</script>
+
+
+<script type="text/html" id="common-fs-link">
+<!-- ko if: $data.path.length > 0 -->
+  <!-- ko if: with_label -->
+    <a data-bind="attr: {href: '/filebrowser/view' + ($data.path.indexOf('/') == -1 ? $root.workflow.properties.deployment_dir() + '/' : '') + $data.path , title: '${ _('Open') } '+ $data.path }" target="_blank">
+      <span data-bind="text: $data.path.split('/').pop()"></span>
+    </a>
+  <!-- /ko -->
+
+   <!-- ko if: ! with_label -->
+     <a data-bind="attr: {href: '/filebrowser/view' + ($data.path.indexOf('/') == -1 ? $root.workflow.properties.deployment_dir() + '/' : '') + $data.path }" target="_blank" title="${ _('Open') }">
+       <i class="fa fa-external-link-square"></i>
+     </a>
+   <!-- /ko -->
+ <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="param-fs-link">
+  <!-- ko if: path.split('=', 2)[1] && path.split('=', 2)[1].charAt(0) == '/' -->
+    <a data-bind="attr: {href: '/filebrowser/view' + $data.path.split('=', 2)[1] }" target="_blank" title="${ _('Open') }">
+      <i class="fa fa-external-link-square"></i>
+    </a>
+  <!-- /ko -->
+</script>
+
+
+
+<script type="text/html" id="hive-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+
+    <div data-bind="visible: ! $root.isEditing()">
+      <span data-bind='template: { name: "common-fs-link", data: {path: properties.script_path(), with_label: true} }'></span>
+    </div>
+
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <input type="text" style="width:25%" class="filechooser-input" data-bind="filechooser: properties.script_path, filechooserOptions: globalFilechooserOptions, attr: { placeholder:  $root.workflow_properties.script_path.help_text }"/>
+        <span data-bind='template: { name: "common-fs-link", data: {path: properties.script_path(), with_label: false}}'></span>
+
+        &nbsp;&nbsp;
+
+        <span data-bind="text: $root.workflow_properties.hive_xml.label"></span>      
+        <input type="text" style="width:25%" class="input-large filechooser-input" data-bind="filechooser: properties.hive_xml, filechooserOptions: globalFilechooserOptions, attr: { placeholder: $root.workflow_properties.hive_xml.help_text }"/>
+
+        <div class="row-fluid">
+          <div class="span6" data-bind="template: { name: 'common-properties-parameters' }"></div>
+          <div class="span6" data-bind="template: { name: 'common-properties-files' }"></div>
+        </div>
+      </div>
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+          <span data-bind="template: { name: 'common-action-properties' }"></span>
+          <br/>
+          <br/>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="hive2-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+
+    <div data-bind="visible: ! $root.isEditing()">
+      <span data-bind='template: { name: "common-fs-link", data: {path: properties.script_path(), with_label: true} }'></span>
+    </div>
+
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <input type="text" class="filechooser-input" data-bind="filechooser: properties.script_path, filechooserOptions: globalFilechooserOptions, attr: { placeholder:  $root.workflow_properties.script_path.help_text }"/>
+        <span data-bind='template: { name: "common-fs-link", data: {path: properties.script_path(), with_label: false}}'></span>
+
+        <div class="row-fluid">
+          <div class="span6" data-bind="template: { name: 'common-properties-parameters' }"></div>
+          <div class="span6" data-bind="template: { name: 'common-properties-files' }"></div>
+        </div>
+      </div>
+    </div>
+
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+          <span data-bind="text: $root.workflow_properties.jdbc_url.label"></span>
+          <input type="text" data-bind="value: properties.jdbc_url" />
+          <br/>
+          <span data-bind="text: $root.workflow_properties.password.label"></span>
+          <input type="text" data-bind="value: properties.password" />
+          <br/>
+          <span data-bind="template: { name: 'common-action-properties' }"></span>
+          <br/>
+          <br/>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="pig-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+
+    <div data-bind="visible: ! $root.isEditing()">
+      <span data-bind='template: { name: "common-fs-link", data: {path: properties.script_path(), with_label: true} }'></span>
+    </div>
+
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <input type="text" class="filechooser-input" data-bind="filechooser: properties.script_path, filechooserOptions: globalFilechooserOptions, hdfsAutocomplete: properties.script_path, attr: { placeholder:  $root.workflow_properties.script_path.help_text }" />
+        <span data-bind='template: { name: "common-fs-link", data: {path: properties.script_path(), with_label: false}}'></span>
+
+        <div class="row-fluid">
+          <div class="span6" data-bind="template: { name: 'common-properties-parameters' }"></div>
+          <div class="span6" data-bind="template: { name: 'common-properties-files' }"></div>
+        </div>
+
+      </div>
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+          <span data-bind="template: { name: 'common-properties-pig-arguments' }"></span>
+          <br/>
+          <span data-bind="template: { name: 'common-action-properties' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="java-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+
+    <div data-bind="visible: ! $root.isEditing()">
+      <pan data-bind="text: properties.main_class" />
+    </div>
+
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <span data-bind="text: $root.workflow_properties.jar_path.label" style="display: inline-block; width: 75px"></span>
+        <input type="text" class="filechooser-input input-xlarge"
+            data-bind="filechooser: properties.jar_path, filechooserOptions: globalFilechooserOptions, attr: { placeholder: $root.workflow_properties.jar_path.help_text }"
+        />
+        <span data-bind='template: { name: "common-fs-link", data: {path: properties.jar_path(), with_label: false}}'></span>
+
+        <br/>
+        <span data-bind="text: $root.workflow_properties.main_class.label" style="display: inline-block; width: 75px"></span>
+        <input type="text" class="input-xlarge" data-bind="value: properties.main_class, attr: { placeholder: $root.workflow_properties.main_class.help_text }" />
+
+        <div class="row-fluid">
+          <div class="span6" data-bind="template: { name: 'common-properties-arguments' }"></div>
+          <div class="span6" data-bind="template: { name: 'common-properties-files' }"></div>
+        </div>
+
+      </div>
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+          <h6>
+            <a class="pointer" data-bind="click: function(){ properties.java_opts.push({'value': ''}); }">
+              <span data-bind="text: $root.workflow_properties.java_opts.label"></span> <i class="fa fa-plus"></i>
+            </a>
+          </h6>
+          <ul class="unstyled" data-bind="foreach: properties.java_opts">
+            <li>
+              <input type="text" data-bind="value: value, attr: { placeholder: $root.workflow_properties.java_opts.help_text }" class="input-xlarge"/>
+              <a href="#" data-bind="click: function(){ $parent.properties.java_opts.remove(this); }">
+                <i class="fa fa-minus"></i>
+              </a>
+            </li>
+          </ul>
+
+          <span data-bind="text: $root.workflow_properties.capture_output.label"></span>
+          <input type="checkbox" data-bind="checked: properties.capture_output" />
+          <br/>
+          <br/>
+
+          <span data-bind="template: { name: 'common-action-properties' }"></span>
+          <br/>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="sqoop-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <span data-bind="text: $root.workflow_properties.command.label"></span>
+        <input type="text" data-bind="value: properties.command" />
+
+        <div class="row-fluid">
+          <div class="span6" data-bind="template: { name: 'common-properties-parameters' }"></div>
+          <div class="span6" data-bind="template: { name: 'common-properties-files' }"></div>
+        </div>
+      </div>
+    </div>
+
+    <div data-bind="visible: ! $root.isEditing()">
+      <a href="javascript:void(0)">
+        <span type="text" data-bind="text: properties.command().slice(0, 70), attr: { title: properties.command() }" />...
+      </a>
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+          <span data-bind="template: { name: 'common-action-properties' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="mapreduce-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <span data-bind="text: $root.workflow_properties.jar_path.label"></span>
+        <input type="text" class="filechooser-input" data-bind="filechooser: properties.jar_path, filechooserOptions: globalFilechooserOptions, value: properties.jar_path" />
+        <span data-bind='template: { name: "common-fs-link", data: {path: properties.jar_path(), with_label: false} }'></span>
+
+        <h6>
+          <a class="pointer" data-bind="click: function(){ properties.job_properties.push({'name': '', 'value': ''}); $(document).trigger('drawArrows') }">
+            ${ _('Properties') } <i class="fa fa-plus"></i>
+          </a>
+        </h6>
+        <ul data-bind="visible: properties.job_properties().length > 0, foreach: properties.job_properties" class="unstyled">
+          <li>
+            <input type="text" class="span4" data-bind="value: name" placeholder="${ _('name, e.g. mapred.job.queue.name') }"/>
+            <input type="text" class="filechooser-input" data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.job_properties.help_text }"/>
+            <a href="#" data-bind="click: function(){ $parent.properties.job_properties.remove(this); $(document).trigger('drawArrows') }">
+              <i class="fa fa-minus"></i>
+            </a>
+          </li>
+        </ul>
+        
+        <div class="span12" data-bind="template: { name: 'common-properties-files' }"></div>
+      </div>
+    </div>
+
+    <div data-bind="visible: ! $root.isEditing()">
+      <span data-bind='template: { name: "common-fs-link", data: {path: properties.jar_path(), with_label: true} }'></span>
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+      <div class="properties">
+        <h6>${ _('Prepare') }</h6>
+        <ul data-bind="visible: properties.prepares().length > 0, foreach: properties.prepares" class="unstyled">
+          <li>
+            <div style="display: inline-block; width: 60px" data-bind="text: type"></div>
+            <input type="text" class="filechooser-input input-xlarge"
+                data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.prepares.help_text }"/>
+            <a href="#" data-bind="click: function(){ $parent.properties.prepares.remove(this); $(document).trigger('drawArrows') }">
+              <i class="fa fa-minus"></i>
+            </a>
+          </li>
+        </ul>
+        <a class="pointer" data-bind="click: function(){ properties.prepares.push({'type': 'mkdir', 'value': ''}); $(document).trigger('drawArrows') }">
+          ${ _('Directory') } <i class="fa fa-plus"></i>
+        </a>
+        <a class="pointer" data-bind="click: function(){ properties.prepares.push({'type': 'delete', 'value': ''}); $(document).trigger('drawArrows') }">
+          ${ _('Delete') } <i class="fa fa-plus"></i>
+        </a>
+    
+        <!-- ko if: properties.job_xml -->
+          <h6>${ _('Job XML') }</h6>
+          <input type="text" class="input-xlarge filechooser-input" data-bind="filechooser: properties.job_xml, filechooserOptions: globalFilechooserOptions, attr: { placeholder: $root.workflow_properties.job_xml.help_text }"/>
+        <!-- /ko -->
+    
+        <h6>
+          <a class="pointer" data-bind="click: function(){ properties.archives.push(ko.mapping.fromJS({'name': ''})); $(document).trigger('drawArrows') }">
+            ${ _('Archives') } <i class="fa fa-plus"></i>
+          </a>
+        </h6>
+        <ul data-bind="visible: properties.archives().length > 0, foreach: properties.archives" class="unstyled">
+          <li>
+            <input type="text" class="filechooser-input input-xlarge" data-bind="filechooser: name(), filechooserFilter: 'zip,tar,tgz,tar.gz', filechooserOptions: globalFilechooserOptions, value: name, attr: { placeholder: $root.workflow_properties.archives.help_text }"/>
+            <span data-bind='template: { name: "common-fs-link", data: { path: name(), with_label: false} }'></span>
+            <a href="#" data-bind="click: function(){ $parent.properties.archives.remove(this); $(document).trigger('drawArrows') }">
+              <i class="fa fa-minus"></i>
+            </a>
+          </li>
+        </ul>
+        <em data-bind="visible: properties.archives().length == 0">${ _('No archives defined.') }</em>
+      </div>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="subworkflow-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <select data-bind="options: $root.subworfklows, optionsText: 'name', optionsValue: 'value', value: properties.workflow"></select>
+        <span data-bind="visible: properties.workflow().length > 0">
+          <a href="#" data-bind="attr: { href: '${ url('oozie:edit_workflow') }' + '?workflow=' + properties.workflow() }" target="_blank" title="${ _('Open') }">
+            <i class="fa fa-external-link-square"></i>
+          </a>
+        </span>
+      </div>
+    </div>
+
+    <div data-bind="visible: ! $root.isEditing()">
+      <!-- ko if: $root.getSubWorkflow(properties.workflow()) -->
+        <span data-bind="with: $root.getSubWorkflow(properties.workflow())">
+          <a href="#" data-bind="attr: { href: '${ url('oozie:edit_workflow') }' + '?workflow=' + $data.value() }" target="_blank" title="${ _('Open') }">
+            <span data-bind="text: $data.name"></span>
+          </a>
+        </span>
+      <!-- /ko -->
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+          <span data-bind="text: $root.workflow_properties.propagate_configuration.label"></span>
+          <input type="checkbox" data-bind="checked: properties.propagate_configuration" />
+
+          <br/>
+
+          <h6>
+            <a class="pointer" data-bind="click: function(){ properties.job_properties.push({'name': '', 'value': ''}); $(document).trigger('drawArrows') }">
+              ${ _('Properties') } <i class="fa fa-plus"></i>
+            </a>
+          </h6>
+          <ul data-bind="visible: properties.job_properties().length > 0, foreach: properties.job_properties" class="unstyled">
+            <li>
+              <input type="text" data-bind="value: name"/>
+              <input type="text" data-bind="value: value"/>
+              <a href="#" data-bind="click: function(){ $parent.properties.job_properties.remove(this); $(document).trigger('drawArrows') }">
+                <i class="fa fa-minus"></i>
+              </a>
+            </li>
+          </ul>
+          <em data-bind="visible: properties.job_properties().length == 0">${ _('No properties defined.') }</em>
+
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="shell-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <input type="text" data-bind="value: properties.shell_command" />
+        <span data-bind='template: { name: "common-fs-link", data: {path: properties.shell_command(), with_label: false} }'></span>
+
+        <div class="row-fluid">
+          <div class="span6" data-bind="template: { name: 'common-properties-arguments' }"></div>
+          <div class="span6" data-bind="template: { name: 'common-properties-files' }"></div>
+        </div>
+      </div>
+    </div>
+
+    <div data-bind="visible: ! $root.isEditing()">
+      <span data-bind='template: { name: "common-fs-link", data: {path: properties.shell_command(), with_label: true} }'></span>
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+          <span data-bind="text: $root.workflow_properties.capture_output.label"></span>
+          <input type="checkbox" data-bind="checked: properties.capture_output" />
+          <br/>
+
+          <h6>
+            <a class="pointer" data-bind="click: function(){ properties.env_var.push({'value': ''}); }">
+              <span data-bind="text: $root.workflow_properties.env_var.label"></span> <i class="fa fa-plus"></i>
+            </a>
+          </h6>
+          <ul data-bind="foreach: properties.env_var" class="unstyled">
+            <li>
+              <input type="text" class="input-xlarge" data-bind="value: value, attr: { placeholder: $root.workflow_properties.env_var.help_text }"/>
+              <a href="#" data-bind="click: function(){ $parent.properties.env_var.remove(this); }">
+                <i class="fa fa-minus"></i>
+              </a>
+            </li>
+          </ul>
+
+          <span data-bind="template: { name: 'common-action-properties' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="ssh-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+    <div data-bind="visible: $root.isEditing">
+
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <span data-bind="text: $root.workflow_properties.host.label"></span>
+        <input type="text" data-bind="value: properties.host" />
+        <br/>
+
+        <span data-bind="text: $root.workflow_properties.ssh_command.label"></span>
+        <input type="text" data-bind="value: properties.ssh_command" />
+        <div class="row-fluid">
+          <div class="span6" data-bind="template: { name: 'common-properties-arguments' }"></div>
+        </div>
+      </div>
+    </div>
+
+    <div data-bind="visible: ! $root.isEditing()">
+      <span data-bind="text: properties.host" />
+      <span data-bind="text: properties.ssh_command().slice(0, 75)" />
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+          <span data-bind="text: $root.workflow_properties.capture_output.label"></span>
+          <input type="checkbox" data-bind="checked: properties.capture_output" />
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="fs-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+
+    <div data-bind="visible: ! $root.isEditing()">
+      <span data-bind="text: '${ _("Delete") }', visible: properties.deletes().length > 0"></span>
+      <ul data-bind="foreach: properties.deletes" class="unstyled">
+        <li>
+          <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: true} }, visible: value().length > 0'></span>
+        </li>
+      </ul>
+
+      <span data-bind="text: '${ _("Create") }', visible: properties.mkdirs().length > 0 || properties.touchzs().length > 0"></span>
+      <ul data-bind="foreach: properties.mkdirs" class="unstyled">
+        <li>
+          <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: true} }, visible: value().length > 0'></span>
+        </li>
+      </ul>
+      <ul data-bind="foreach: properties.touchzs" class="unstyled">
+        <li>
+          <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: true} }, visible: value().length > 0'></span>
+        </li>
+      </ul>
+
+      <span data-bind="text: '${ _("Move") }', visible: properties.moves().length > 0"></span>
+      <ul data-bind="foreach: properties.moves" class="unstyled">
+        <li>
+          <span data-bind='template: { name: "common-fs-link", data: {path: source(), with_label: true} }, visible: source().length > 0'></span>
+          ${ _('to') }
+          <span data-bind='template: { name: "common-fs-link", data: {path: destination(), with_label: true} }, visible: destination().length > 0'></span>
+        </li>
+      </ul>
+
+      <span data-bind="text: '${ _("Change permissions") }', visible: properties.chmods().length > 0"></span>
+      <ul data-bind="foreach: properties.chmods" class="unstyled">
+        <li>
+          <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: true} }, visible: value().length > 0'></span>
+          ${ _('to') }
+          <span data-bind="text: permissions"/>
+          <span data-bind="visible: ! dir_files(), text: '${ _('for directories') }'"/>
+          <span data-bind="visible: dir_files(), text: '${ _('for directories and files') }'"/>
+          <span data-bind="visible: recursive, text: '${ _('recursivelt') }'"/>
+        </li>
+      </ul>
+
+      <span data-bind="text: '${ _("Change groups") }', visible: properties.chgrps().length > 0"></span>
+      <ul data-bind="foreach: properties.chgrps" class="unstyled">
+        <li>
+          <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: true} }, visible: value().length > 0'></span>
+          ${ _('to') }
+          <span data-bind="text: group"/>
+          <span data-bind="visible: ! dir_files(), text: '${ _('for directories') }'"/>
+          <span data-bind="visible: dir_files(), text: '${ _('for directories and files') }'"/>
+          <span data-bind="visible: recursive, text: '${ _('recursively') }'"/>
+        </li>
+      </ul>
+    </div>
+
+
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <h6>
+          <a class="pointer" data-bind="click: function(){ properties.deletes.push(ko.mapping.fromJS({'value': ''})); }">
+            <span data-bind="text: $root.workflow_properties.deletes.label"></span> <i class="fa fa-plus"></i>
+          </a>
+        </h6>
+        <ul data-bind="foreach: properties.deletes" class="unstyled">
+          <li>
+            <input type="text" class="input-xlarge filechooser-input" data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.deletes.help_text }"/>
+            <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: false} }, visible: value().length > 0'></span>
+            <a href="#" data-bind="click: function(){ $parent.properties.deletes.remove(this); }">
+              <i class="fa fa-minus"></i>
+            </a>
+          </li>
+        </ul>
+
+        <h6>
+          <a class="pointer" data-bind="click: function(){ properties.mkdirs.push(ko.mapping.fromJS({'value': ''})); }">
+            <span data-bind="text: $root.workflow_properties.mkdirs.label"></span> <i class="fa fa-plus"></i>
+          </a>
+        </h6>
+        <ul data-bind="foreach: properties.mkdirs" class="unstyled">
+          <li>
+            <input type="text" class="input-xlarge filechooser-input" data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.mkdirs.help_text }"/>
+            <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: false} }, visible: value().length > 0'></span>
+            <a href="#" data-bind="click: function(){ $parent.properties.mkdirs.remove(this); }">
+              <i class="fa fa-minus"></i>
+            </a>
+          </li>
+        </ul>
+
+        <h6>
+          <a class="pointer" data-bind="click: function(){ properties.touchzs.push(ko.mapping.fromJS({'value': ''})); }">
+            <span data-bind="text: $root.workflow_properties.touchzs.label"></span> <i class="fa fa-plus"></i>
+          </a>
+        </h6>
+        <ul data-bind="foreach: properties.touchzs" class="unstyled">
+          <li>
+            <input type="text" class="input-xlarge filechooser-input" data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.touchzs.help_text }"/>
+            <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: false} }, visible: value().length > 0'></span>
+            <a href="#" data-bind="click: function(){ $parent.properties.touchzs.remove(this); }">
+              <i class="fa fa-minus"></i>
+            </a>
+          </li>
+        </ul>
+
+        <h6>
+          <a class="pointer" data-bind="click: function(){ properties.moves.push(ko.mapping.fromJS({'source': '', 'destination': ''})); }">
+            <span data-bind="text: $root.workflow_properties.moves.label"></span> <i class="fa fa-plus"></i>
+          </a>
+        </h6>
+        <ul data-bind="foreach: properties.moves" class="unstyled">
+          <li>
+            <input type="text" class="input-xlarge filechooser-input" data-bind="filechooser: source, filechooserOptions: globalFilechooserOptions, value: source" placeholder="${ _('Source path') }"/>
+            <span data-bind='template: { name: "common-fs-link", data: {path: source(), with_label: false} }, visible: source().length > 0'></span>
+
+            <input type="text" class="input-xlarge filechooser-input" data-bind="filechooser: destination, filechooserOptions: globalFilechooserOptions, value: destination" placeholder="${ _('New destination path') }"/>
+            <span data-bind='template: { name: "common-fs-link", data: {path: destination(), with_label: false} }, visible: destination().length > 0'></span>
+            <a href="#" data-bind="click: function(){ $parent.properties.moves.remove(this); }">
+              <i class="fa fa-minus"></i>
+            </a>
+          </li>
+        </ul>
+
+      </div>
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+
+        <h6>
+          <a class="pointer" data-bind="click: function(){ properties.chmods.push(ko.mapping.fromJS({'value': '', 'permissions': '755', 'dir_files': false, 'recursive': false})); }">
+            <span data-bind="text: $root.workflow_properties.chmods.label"></span> <i class="fa fa-plus"></i>
+          </a>
+        </h6>
+        <ul data-bind="foreach: properties.chmods" class="unstyled">
+          <li>
+            <input type="text" class="input-xlarge filechooser-input" data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.chmods.help_text }"/>
+            <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: false} }, visible: value().length > 0'></span>
+
+            <input type="text" class="input-small" data-bind="value: permissions" placeholder="${ _('755, -rwxrw-rw-') }"/>
+            ${ _('Only for directories') }
+            <input type="checkbox" data-bind="checked: dir_files"/>
+            ${ _('Recursive to sub directories') }
+            <input type="checkbox" data-bind="checked: recursive"/>
+            <a href="#" data-bind="click: function(){ $parent.properties.chmods.remove(this); }">
+              <i class="fa fa-minus"></i>
+            </a>
+          </li>
+        </ul>
+
+        <h6>
+          <a class="pointer" data-bind="click: function(){ properties.chgrps.push(ko.mapping.fromJS({'value': '', 'group': '', 'dir_files': false, 'recursive': false})); }">
+            <span data-bind="text: $root.workflow_properties.chgrps.label"></span> <i class="fa fa-plus"></i>
+          </a>
+        </h6>
+        <ul data-bind="foreach: properties.chgrps" class="unstyled">
+          <li>
+            <input type="text" class="input-xlarge filechooser-input" data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.chgrps.help_text }"/>
+            <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: false} }, visible: value().length > 0'></span>
+
+            <input type="text" class="input-small" data-bind="value: group" placeholder="${ _('e.g. newgroup') }"/>
+            ${ _('Only for directories') }
+            <input type="checkbox" data-bind="checked: dir_files"/>
+            ${ _('Recursive to sub directories') }
+            <input type="checkbox" data-bind="checked: recursive"/>
+            <a href="#" data-bind="click: function(){ $parent.properties.chgrps.remove(this); }">
+              <i class="fa fa-minus"></i>
+            </a>
+          </li>
+        </ul>
+
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="email-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <span data-bind="text: $root.workflow_properties.to.label" style="display: inline-block; width: 80px"></span>
+        <input type="text" data-bind="value: properties.to, attr: { placeholder: $root.workflow_properties.to.help_text }" />
+        <br/>
+
+        <span data-bind="text: $root.workflow_properties.subject.label" style="display: inline-block; width: 80px"></span>
+        <input type="text" data-bind="value: properties.subject, attr: { placeholder: $root.workflow_properties.subject.help_text }" />
+        <br/>
+
+        <span data-bind="text: $root.workflow_properties.body.label" style="display: inline-block; width: 80px"></span>
+        <textarea data-bind="value: properties.body, attr: { placeholder: $root.workflow_properties.body.help_text }"></textarea>
+      </div>
+    </div>
+
+    <div data-bind="visible: ! $root.isEditing()">
+      ${ _('To') }
+      <span data-bind="text: properties.to"/>
+      <br/>
+      ${ _('About') }
+      <span data-bind="text: properties.subject"/>
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+          <span data-bind="text: $root.workflow_properties.cc.label"></span>
+          <input type="text" data-bind="value: properties.cc, attr: { placeholder: $root.workflow_properties.cc.help_text }" />
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="streaming-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+        <span data-bind="text: $root.workflow_properties.mapper.label"></span>
+        <input type="text" data-bind="value: properties.mapper" />
+        <span data-bind='template: { name: "common-fs-link", data: {path: properties.mapper(), with_label: false} }'></span>
+        <br/>
+
+        <span data-bind="text: $root.workflow_properties.reducer.label"></span
+        <input type="text" data-bind="value: properties.reducer" />
+        <span data-bind='template: { name: "common-fs-link", data: {path: properties.reducer(), with_label: false} }'></span>
+        
+        <div data-bind="template: { name: 'common-properties-files' }"></div>
+      </div>
+    </div>
+
+    <div data-bind="visible: ! $root.isEditing()">
+      <span data-bind="text: $root.workflow_properties.mapper.label, visible: properties.mapper().length > 0"></span>
+      <span data-bind='template: { name: "common-fs-link", data: {path: properties.mapper(), with_label: true} }, visible: properties.mapper().length > 0'></span>
+      </br>
+      <span data-bind="text: $root.workflow_properties.reducer.label, visible: properties.reducer().length > 0"></span>
+      <span data-bind='template: { name: "common-fs-link", data: {path: properties.reducer(), with_label: true} }, visible: properties.reducer().length > 0'></span>
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+          <span data-bind="template: { name: 'common-action-properties' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+
+<script type="text/html" id="distcp-widget">
+  <!-- ko if: $root.workflow.getNodeById(id()) -->
+  <div class="row-fluid" data-bind="with: $root.workflow.getNodeById(id())" style="padding: 10px">
+    <div data-bind="visible: $root.isEditing">
+      <div data-bind="visible: ! $parent.ooziePropertiesExpanded()" class="nowrap">
+
+        <h6>
+          <a class="pointer" data-bind="click: function(){ properties.distcp_parameters.push(ko.mapping.fromJS({'value': ''}));}">
+            <span data-bind="text: $root.workflow_properties.distcp_parameters.label"></span> <i class="fa fa-plus"></i>
+          </a>
+        </h6>
+        <ul data-bind="foreach: properties.distcp_parameters" class="unstyled">
+          <li>
+            <input type="text" class="input-xlarge filechooser-input" data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.distcp_parameters.help_text }"/>
+            <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: false} }, visible: value().length > 0'></span>
+            <a href="#" data-bind="click: function(){ $parent.properties.distcp_parameters.remove(this);  }">
+              <i class="fa fa-minus"></i>
+            </a>
+          </li>
+        </ul>
+
+      </div>
+    </div>
+    
+    <div data-bind="visible: ! $root.isEditing()">
+      ${ _('Parameters') }
+      <ul data-bind="foreach: properties.distcp_parameters" class="unstyled">
+        <li>
+          <span data-bind='template: { name: "common-fs-link", data: {path: value(), with_label: true} }, visible: value().length > 0'></span>
+        </li>
+      </ul>
+    </div>
+
+    <div data-bind="visible: $parent.ooziePropertiesExpanded">
+      <ul class="nav nav-tabs">
+        <li class="active"><a data-bind="attr: { href: '#properties-' + id()}" data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: '#sla-' + id()}" href="#sla" data-toggle="tab">${ _('SLA') }</a></li>
+        <li><a data-bind="attr: { href: '#credentials-' + id()}" data-toggle="tab">${ _('Credentials') }</a></li>
+        <li><a data-bind="attr: { href: '#transitions-' + id()}" data-toggle="tab">${ _('Transitions') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: 'properties-' + id() }">
+
+     <h6>${ _('Prepare') }</h6>
+       <ul data-bind="visible: properties.prepares().length > 0, foreach: properties.prepares" class="unstyled">
+         <li>
+           <div style="display: inline-block; width: 60px" data-bind="text: type"></div>
+           <input type="text" class="filechooser-input input-xlarge"
+                data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.prepares.help_text }"/>
+            <a href="#" data-bind="click: function(){ $parent.properties.prepares.remove(this); $(document).trigger('drawArrows') }">
+              <i class="fa fa-minus"></i>
+            </a>
+          </li>
+       </ul>
+       <a class="pointer" data-bind="click: function(){ properties.prepares.push({'type': 'mkdir', 'value': ''}); $(document).trigger('drawArrows') }">
+         ${ _('Directory') } <i class="fa fa-plus"></i>
+       </a>
+       <a class="pointer" data-bind="click: function(){ properties.prepares.push({'type': 'delete', 'value': ''}); $(document).trigger('drawArrows') }">
+         ${ _('Delete') } <i class="fa fa-plus"></i>
+       </a>
+    
+       <h6>
+         <a class="pointer" data-bind="click: function(){ properties.job_properties.push({'name': '', 'value': ''}); $(document).trigger('drawArrows') }">
+           ${ _('Properties') } <i class="fa fa-plus"></i>
+         </a>
+       </h6>
+           <ul data-bind="visible: properties.job_properties().length > 0, foreach: properties.job_properties" class="unstyled">
+           <li>
+             <input type="text" data-bind="value: name" placeholder="${ _('name, e.g. mapred.job.queue.name') }"/>
+             <input type="text" class="filechooser-input input-xlarge" data-bind="filechooser: value, filechooserOptions: globalFilechooserOptions, value: value, attr: { placeholder: $root.workflow_properties.job_properties.help_text }"/>
+             <a href="#" data-bind="click: function(){ $parent.properties.job_properties.remove(this); $(document).trigger('drawArrows') }">
+               <i class="fa fa-minus"></i>
+              </a>
+             </li>
+           </ul>
+           <em data-bind="visible: properties.job_properties().length == 0">${ _('No properties defined.') }</em>
+
+           <h6>             
+             <span data-bind="text: $root.workflow_properties.java_opts.label"></span>             
+           </h6>
+           <input type="text" class="input-xlarge" data-bind="value: properties.java_opts, attr: { placeholder: $root.workflow_properties.java_opts.help_text }" />
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'sla-' + id() }">
+          <span data-bind="template: { name: 'common-action-sla' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'credentials-' + id() }">
+          <span data-bind="template: { name: 'common-action-credentials' }"></span>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: 'transitions-' + id() }">
+          <span data-bind="template: { name: 'common-action-transition' }"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- /ko -->
+</script>
+
+</%def>
+
+

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 8 - 1506
apps/oozie/src/oozie/templates/editor2/workflow_editor.mako


+ 42 - 6
apps/oozie/src/oozie/views/dashboard.py

@@ -37,15 +37,17 @@ from desktop.lib.i18n import smart_str, smart_unicode
 from desktop.lib.rest.http_client import RestException
 from desktop.lib.rest.http_client import RestException
 from desktop.lib.view_util import format_duration_in_millis
 from desktop.lib.view_util import format_duration_in_millis
 from desktop.log.access import access_warn
 from desktop.log.access import access_warn
+from desktop.models import Document, Document2
 
 
 from liboozie.oozie_api import get_oozie
 from liboozie.oozie_api import get_oozie
+from liboozie.credentials import Credentials
 from liboozie.submittion import Submission
 from liboozie.submittion import Submission
 from liboozie.types import Workflow as OozieWorkflow, Coordinator as CoordinatorWorkflow, Bundle as BundleWorkflow
 from liboozie.types import Workflow as OozieWorkflow, Coordinator as CoordinatorWorkflow, Bundle as BundleWorkflow
 
 
 from oozie.conf import OOZIE_JOBS_COUNT, ENABLE_CRON_SCHEDULING, ENABLE_V2
 from oozie.conf import OOZIE_JOBS_COUNT, ENABLE_CRON_SCHEDULING, ENABLE_V2
 from oozie.forms import RerunForm, ParameterForm, RerunCoordForm, RerunBundleForm, UpdateEndTimeForm
 from oozie.forms import RerunForm, ParameterForm, RerunCoordForm, RerunBundleForm, UpdateEndTimeForm
-from oozie.models import Workflow, Job, utc_datetime_format, Bundle, Coordinator, get_link, History as OldHistory
-from oozie.models2 import History
+from oozie.models import Workflow as OldWorkflow, Job, utc_datetime_format, Bundle, Coordinator, get_link, History as OldHistory
+from oozie.models2 import History, Workflow, WORKFLOW_NODE_PROPERTIES
 from oozie.settings import DJANGO_APPS
 from oozie.settings import DJANGO_APPS
 
 
 
 
@@ -55,6 +57,12 @@ def get_history():
   else:
   else:
     return OldHistory
     return OldHistory
 
 
+def get_workflow():
+  if ENABLE_V2.get():
+    return Workflow
+  else:
+    return OldWorkflow
+
 
 
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 MAX_COORD_ACTIONS = 250
 MAX_COORD_ACTIONS = 250
@@ -72,6 +80,15 @@ Permissions checking happens by calling:
   * check_job_edition_permission()
   * check_job_edition_permission()
 """
 """
 
 
+def _get_workflows(user):
+  return [{
+        'name': workflow.name,
+        'owner': workflow.owner.username,
+        'value': workflow.uuid,
+        'id': workflow.id
+      } for workflow in [d.content_object for d in Document.objects.get_docs(user, Document2, extra='workflow2')]
+    ]  
+
 
 
 def manage_oozie_jobs(request, job_id, action):
 def manage_oozie_jobs(request, job_id, action):
   if request.method != 'POST':
   if request.method != 'POST':
@@ -232,6 +249,10 @@ def list_oozie_workflow(request, job_id):
   if oozie_bundle is not None:
   if oozie_bundle is not None:
     setattr(oozie_workflow, 'oozie_bundle', oozie_bundle)
     setattr(oozie_workflow, 'oozie_bundle', oozie_bundle)
 
 
+  workflow_data = None
+  credentials = None
+  doc = None
+
   if ENABLE_V2.get():
   if ENABLE_V2.get():
     # To update with the new History document model
     # To update with the new History document model
     hue_coord = get_history().get_coordinator_from_config(oozie_workflow.conf_dict)
     hue_coord = get_history().get_coordinator_from_config(oozie_workflow.conf_dict)
@@ -243,8 +264,16 @@ def list_oozie_workflow(request, job_id):
     if hue_workflow:
     if hue_workflow:
       workflow_graph = hue_workflow.gen_status_graph(oozie_workflow)
       workflow_graph = hue_workflow.gen_status_graph(oozie_workflow)
       full_node_list = hue_workflow.nodes
       full_node_list = hue_workflow.nodes
+      workflow_id = hue_workflow.id  
+      wid = {
+        'id': workflow_id
+      }
+      doc = Document2.objects.get(type='oozie-workflow2', **wid)
+      new_workflow = get_workflow()(document=doc)
+      workflow_data = new_workflow.get_data()
+      credentials = Credentials()
     else:
     else:
-      workflow_graph, full_node_list = '', None    
+      workflow_graph, full_node_list = OldWorkflow.gen_status_graph_from_xml(request.user, oozie_workflow)
   else:
   else:
     history = get_history().cross_reference_submission_history(request.user, job_id)
     history = get_history().cross_reference_submission_history(request.user, job_id)
 
 
@@ -258,7 +287,7 @@ def list_oozie_workflow(request, job_id):
       workflow_graph = hue_workflow.gen_status_graph(oozie_workflow)
       workflow_graph = hue_workflow.gen_status_graph(oozie_workflow)
       full_node_list = hue_workflow.node_list
       full_node_list = hue_workflow.node_list
     else:
     else:
-      workflow_graph, full_node_list = Workflow.gen_status_graph_from_xml(request.user, oozie_workflow)
+      workflow_graph, full_node_list = get_workflow().gen_status_graph_from_xml(request.user, oozie_workflow)
 
 
   parameters = oozie_workflow.conf_dict.copy()
   parameters = oozie_workflow.conf_dict.copy()
 
 
@@ -295,7 +324,14 @@ def list_oozie_workflow(request, job_id):
     'hue_coord': hue_coord,
     'hue_coord': hue_coord,
     'parameters': parameters,
     'parameters': parameters,
     'has_job_edition_permission': has_job_edition_permission,
     'has_job_edition_permission': has_job_edition_permission,
-    'workflow_graph': workflow_graph
+    'workflow_graph': workflow_graph,
+    'layout_json': json.dumps(workflow_data['layout'], cls=JSONEncoderForHTML) if workflow_data else '',
+    'workflow_json': json.dumps(workflow_data['workflow'], cls=JSONEncoderForHTML) if workflow_data else '',
+    'credentials_json': json.dumps(credentials.credentials.keys(), cls=JSONEncoderForHTML) if credentials else '',
+    'workflow_properties_json': json.dumps(WORKFLOW_NODE_PROPERTIES, cls=JSONEncoderForHTML),
+    'doc1_id': doc.doc.get().id if doc else -1,
+    'subworkflows_json': json.dumps(_get_workflows(request.user), cls=JSONEncoderForHTML),
+    'can_edit_json': json.dumps(doc is None or doc.doc.get().is_editable(request.user))
   })
   })
 
 
 
 
@@ -688,7 +724,7 @@ def submit_external_job(request, application_path):
     if params_form.is_valid():
     if params_form.is_valid():
       mapping = dict([(param['name'], param['value']) for param in params_form.cleaned_data])
       mapping = dict([(param['name'], param['value']) for param in params_form.cleaned_data])
       application_name = os.path.basename(application_path)
       application_name = os.path.basename(application_path)
-      application_class = Bundle if application_name == 'bundle.xml' else Coordinator if application_name == 'coordinator.xml' else Workflow
+      application_class = Bundle if application_name == 'bundle.xml' else Coordinator if application_name == 'coordinator.xml' else get_workflow()
       mapping[application_class.get_application_path_key()] = application_path
       mapping[application_class.get_application_path_key()] = application_path
 
 
       try:
       try:

+ 3 - 3
apps/oozie/src/oozie/views/editor2.py

@@ -37,7 +37,7 @@ from liboozie.submission2 import Submission
 
 
 from oozie.decorators import check_document_access_permission, check_document_modify_permission
 from oozie.decorators import check_document_access_permission, check_document_modify_permission
 from oozie.forms import ParameterForm
 from oozie.forms import ParameterForm
-from oozie.models import Workflow as OlfWorklow, Coordinator as OldCoordinator, Bundle as OldBundle, Job
+from oozie.models import Workflow as OldWorklow, Coordinator as OldCoordinator, Bundle as OldBundle, Job
 from oozie.models2 import Node, Workflow, Coordinator, Bundle, NODES, WORKFLOW_NODE_PROPERTIES, import_workflow_from_hue_3_7,\
 from oozie.models2 import Node, Workflow, Coordinator, Bundle, NODES, WORKFLOW_NODE_PROPERTIES, import_workflow_from_hue_3_7,\
     find_dollar_variables, find_dollar_braced_variables
     find_dollar_variables, find_dollar_braced_variables
 from oozie.views.editor import edit_workflow as old_edit_workflow, edit_coordinator as old_edit_coordinator, edit_bundle as old_edit_bundle
 from oozie.views.editor import edit_workflow as old_edit_workflow, edit_coordinator as old_edit_coordinator, edit_bundle as old_edit_bundle
@@ -50,7 +50,7 @@ LOG = logging.getLogger(__name__)
 def list_editor_workflows(request):  
 def list_editor_workflows(request):  
   workflows = [d.content_object.to_dict() for d in Document.objects.get_docs(request.user, Document2, extra='workflow2')]
   workflows = [d.content_object.to_dict() for d in Document.objects.get_docs(request.user, Document2, extra='workflow2')]
 
 
-  workflows_v1 = [job.doc.get().to_dict() for job in Document.objects.available(OlfWorklow, request.user) if job.managed]
+  workflows_v1 = [job.doc.get().to_dict() for job in Document.objects.available(OldWorklow, request.user) if job.managed]
   if workflows_v1:
   if workflows_v1:
     workflows.extend(workflows_v1)
     workflows.extend(workflows_v1)
   
   
@@ -134,7 +134,7 @@ def delete_job(request):
     else: # Old version
     else: # Old version
       job = Job.objects.can_read_or_exception(request, job['object_id'])
       job = Job.objects.can_read_or_exception(request, job['object_id'])
       Job.objects.can_edit_or_exception(request, job)
       Job.objects.can_edit_or_exception(request, job)
-      OlfWorklow.objects.destroy(job, request.fs)      
+      OldWorklow.objects.destroy(job, request.fs)      
 
 
   response = {}
   response = {}
   request.info(_('Document deleted.') if len(jobs) > 1 else _('Document deleted.'))
   request.info(_('Document deleted.') if len(jobs) > 1 else _('Document deleted.'))

+ 2 - 6
apps/oozie/static/css/workflow-editor.css

@@ -75,11 +75,11 @@
   margin-left: 0.5%;
   margin-left: 0.5%;
 }
 }
 
 
-.row-fluid .span10 {
+.dashboard .row-fluid .span10 {
   width: 90%;
   width: 90%;
 }
 }
 
 
-.row-fluid .span1 {
+.dashboard .row-fluid .span1 {
   width: 4.5%;
   width: 4.5%;
 }
 }
 
 
@@ -182,10 +182,6 @@
   padding: 5px;
   padding: 5px;
 }
 }
 
 
-body {
-  background-color: #FFFFFF;
-}
-
 .ui-sortable {
 .ui-sortable {
   min-height: 10px !important;
   min-height: 10px !important;
 }
 }

+ 105 - 0
apps/oozie/static/js/workflow-editor.utils.js

@@ -0,0 +1,105 @@
+// 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.
+
+function linkWidgets(fromId, toId) {
+  var _from = $("#wdg_" + (typeof fromId == "function" ? fromId() : fromId));
+  var _to = $("#wdg_" + (typeof toId == "function" ? toId() : toId));
+  if (_from.length > 0 && _to.length > 0) {
+    var _fromCenter = {
+      x: _from.position().left + _from.outerWidth() / 2,
+      y: _from.position().top + _from.outerHeight() + 3
+    }
+
+    var _toCenter = {
+      x: _to.position().left + _to.outerWidth() / 2,
+      y: _to.position().top - 5
+    }
+
+    var _curveCoords = {};
+
+    if (_fromCenter.x == _toCenter.x) {
+      _curveCoords.x = _fromCenter.x;
+      _curveCoords.y = _fromCenter.y + (_toCenter.y - _fromCenter.y) / 2;
+    } else {
+      if (_fromCenter.x > _toCenter.x) {
+        _fromCenter.x = _fromCenter.x - 5;
+        _toCenter.x = _toCenter.x + 5;
+      } else {
+        _fromCenter.x = _fromCenter.x + 5;
+        _toCenter.x = _toCenter.x - 5;
+      }
+      _curveCoords.x = _fromCenter.x - (_fromCenter.x - _toCenter.x) / 4;
+      _curveCoords.y = _fromCenter.y + (_toCenter.y - _fromCenter.y) / 2;
+    }
+
+    $(document.body).curvedArrow({
+      p0x: _fromCenter.x,
+      p0y: _fromCenter.y,
+      p1x: _curveCoords.x,
+      p1y: _curveCoords.y,
+      p2x: _toCenter.x,
+      p2y: _toCenter.y,
+      lineWidth: 2,
+      size: 10,
+      strokeStyle: viewModel.isEditing() ? '#e5e5e5' : '#dddddd'
+    });
+  }
+}
+
+function drawArrows() {
+  $("canvas").remove();
+  if (viewModel.oozieColumns()[0].rows().length > 3) {
+    var _links = viewModel.workflow.linkMapping();
+    Object.keys(_links).forEach(function(id) {
+      if (_links[id].length > 0) {
+        _links[id].forEach(function(nextId) {
+          linkWidgets(id, nextId);
+        });
+      }
+    });
+  }
+}
+
+function toggleProperties(widget) {
+  if (widget.oozieMovable()) {
+    var _el = $("#wdg_" + widget.id());
+    if (!widget.ooziePropertiesExpanded()) {
+      setLastExpandedWidget(widget);
+      _el.find(".prop-editor").show();
+      widget.ooziePropertiesExpanded(true);
+    } else {
+      if (widget.oozieExpanded()) {
+        exposeOverlayClickHandler();
+      } else {
+        _el.find(".prop-editor").hide();
+        widget.ooziePropertiesExpanded(false);
+      }
+    }
+    $(document).trigger("drawArrows");
+  }
+}
+
+var _linkMappingTimeout = -1;
+$(document).on("drawArrows", function(){
+  window.clearTimeout(_linkMappingTimeout);
+  if (typeof renderChangeables != 'undefined'){
+  	_linkMappingTimeout = window.setTimeout(renderChangeables, 25);	
+  }
+});
+
+$(document).on("removeArrows", function(){
+  $("canvas").remove();
+});

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.