Просмотр исходного кода

AMBARI-17213. Create ambari workflow designer contrib view. (Venkat Ranganathan via yusaku)

Yusaku Sako 9 лет назад
Родитель
Сommit
f66b4c0b39
100 измененных файлов с 10637 добавлено и 0 удалено
  1. 4 0
      contrib/views/pom.xml
  2. 304 0
      contrib/views/wfmanager/pom.xml
  3. 26 0
      contrib/views/wfmanager/src/assembly/assembly.xml
  4. 61 0
      contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/FileServices.java
  5. 569 0
      contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/OozieProxyImpersonator.java
  6. 22 0
      contrib/views/wfmanager/src/main/resources/WEB-INF/web.xml
  7. 117 0
      contrib/views/wfmanager/src/main/resources/ui/README.md
  8. 39 0
      contrib/views/wfmanager/src/main/resources/ui/app/app.js
  9. 39 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/action-credential-config.js
  10. 26 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/action-version-select.js
  11. 41 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/archive-config.js
  12. 45 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/arg-config.js
  13. 32 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/bundle-job-details.js
  14. 32 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/coord-job-details.js
  15. 189 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/credentials-config.js
  16. 87 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/decision-add-branch.js
  17. 50 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/decision-config.js
  18. 21 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/designer-errors.js
  19. 63 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/distcp-action.js
  20. 66 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/email-action.js
  21. 27 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/field-error.js
  22. 60 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/file-config.js
  23. 48 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/file-upload.js
  24. 643 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/flow-designer.js
  25. 102 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/fs-action.js
  26. 70 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/global-config.js
  27. 114 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/hdfs-browser.js
  28. 19 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/header-logo-user-bar.js
  29. 22 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/help-icon.js
  30. 104 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/hive-action.js
  31. 109 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/hive2-action.js
  32. 101 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/java-action.js
  33. 21 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/jdbc-url.js
  34. 141 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/job-details.js
  35. 44 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/job-log.js
  36. 87 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/job-row.js
  37. 38 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/jobxml-config.js
  38. 88 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/map-red-action.js
  39. 48 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/name-value-config.js
  40. 32 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/named-properties.js
  41. 21 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/pass-word.js
  42. 76 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/pig-action.js
  43. 209 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/prepare-config-fs.js
  44. 66 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/prepare-config.js
  45. 202 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/search-create-new-bar.js
  46. 128 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/search-table.js
  47. 74 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/shell-action.js
  48. 147 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/sla-info.js
  49. 119 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/spark-action.js
  50. 85 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/sqoop-action.js
  51. 88 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/ssh-action.js
  52. 58 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/sub-workflow.js
  53. 67 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/transition-config.js
  54. 61 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/version-settings.js
  55. 190 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-action-editor.js
  56. 28 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-actions.js
  57. 228 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-config.js
  58. 92 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-credentials.js
  59. 31 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-job-details.js
  60. 91 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-node.js
  61. 80 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-parameters.js
  62. 50 0
      contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-sla.js
  63. 21 0
      contrib/views/wfmanager/src/main/resources/ui/app/controllers/application.js
  64. 81 0
      contrib/views/wfmanager/src/main/resources/ui/app/controllers/dashboard.js
  65. 26 0
      contrib/views/wfmanager/src/main/resources/ui/app/controllers/design.js
  66. 60 0
      contrib/views/wfmanager/src/main/resources/ui/app/controllers/job.js
  67. 411 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/actionjob_hanlder.js
  68. 50 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/default-layout-manager.js
  69. 103 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/findnode-mixin.js
  70. 35 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/id-gen.js
  71. 88 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/layout-manager1.js
  72. 87 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/layout-manager2.js
  73. 254 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/mapping-utils.js
  74. 120 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/node-factory.js
  75. 251 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/node-handler.js
  76. 38 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/node-visitor.js
  77. 212 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/node.js
  78. 70 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/schema-versions.js
  79. 59 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/sla-info.js
  80. 53 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/transition.js
  81. 34 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow-context.js
  82. 134 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow-importer.js
  83. 144 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow-xml-generator.js
  84. 266 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow.js
  85. 135 0
      contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow_xml_mapper.js
  86. 29 0
      contrib/views/wfmanager/src/main/resources/ui/app/helpers/date-helper.js
  87. 77 0
      contrib/views/wfmanager/src/main/resources/ui/app/index.html
  88. 25 0
      contrib/views/wfmanager/src/main/resources/ui/app/initializers/init.js
  89. 20 0
      contrib/views/wfmanager/src/main/resources/ui/app/resolver.js
  90. 33 0
      contrib/views/wfmanager/src/main/resources/ui/app/router.js
  91. 35 0
      contrib/views/wfmanager/src/main/resources/ui/app/routes/connection-error.js
  92. 146 0
      contrib/views/wfmanager/src/main/resources/ui/app/routes/dashboard.js
  93. 25 0
      contrib/views/wfmanager/src/main/resources/ui/app/routes/design.js
  94. 65 0
      contrib/views/wfmanager/src/main/resources/ui/app/routes/designtest.js
  95. 24 0
      contrib/views/wfmanager/src/main/resources/ui/app/routes/index.js
  96. 84 0
      contrib/views/wfmanager/src/main/resources/ui/app/routes/job.js
  97. 27 0
      contrib/views/wfmanager/src/main/resources/ui/app/services/file-browser.js
  98. 31 0
      contrib/views/wfmanager/src/main/resources/ui/app/services/history.js
  99. 61 0
      contrib/views/wfmanager/src/main/resources/ui/app/services/property-extractor.js
  100. 1281 0
      contrib/views/wfmanager/src/main/resources/ui/app/styles/app.css

+ 4 - 0
contrib/views/pom.xml

@@ -46,6 +46,7 @@
     <module>zeppelin</module>
     <module>hueambarimigration</module>
     <module>hive-next</module>
+    <module>wfmanager</module>
   </modules>
   <build>
     <pluginManagement>
@@ -93,8 +94,11 @@
             <exclude>**/vendor/**</exclude>
             <exclude>**/public/**</exclude>
             <exclude>**/dist/**</exclude>
+            <exclude>**/robots.txt</exclude>
+            <exclude>**/externaladdons/**</exclude>
             <exclude>**/bower_components/**</exclude>
             <exclude>**/bower/**</exclude>
+            <exclude>**/.bowerrc</exclude>
             <exclude>**/node/**</exclude>
             <exclude>**/runner.js</exclude>
             <exclude>**/assets/javascripts/**</exclude>

+ 304 - 0
contrib/views/wfmanager/pom.xml

@@ -0,0 +1,304 @@
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+	license agreements. See the NOTICE file distributed with this work for additional
+	information regarding copyright ownership. The ASF 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. -->
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns="http://maven.apache.org/POM/4.0.0"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<artifactId>wfmanager</artifactId>
+	<groupId>org.apache.ambari.contrib.views</groupId>
+	<version>0.1.0.0-SNAPSHOT</version>
+	<name>WF Manager View</name>
+	<parent>
+		<groupId>org.apache.ambari.contrib.views</groupId>
+		<artifactId>ambari-contrib-views</artifactId>
+		<version>2.4.0.0.0</version>
+	</parent>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.ambari.contrib.views</groupId>
+			<artifactId>ambari-views-utils</artifactId>
+			<version>2.4.0.0.0</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.ambari</groupId>
+			<artifactId>ambari-views</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.ambari.contrib.views</groupId>
+			<artifactId>ambari-views-commons</artifactId>
+			<version>2.4.0.0.0</version>
+		</dependency>
+		<dependency>
+			<groupId>com.sun.jersey</groupId>
+			<artifactId>jersey-core</artifactId>
+		</dependency>
+		<!-- <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId>
+			<version>2.2.2</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId>
+			<artifactId>jaxb-impl</artifactId> <version>2.1.2</version> </dependency> -->
+		<dependency>
+			<groupId>com.sun.jersey</groupId>
+			<artifactId>jersey-client</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.sun.jersey</groupId>
+			<artifactId>jersey-json</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.google.code.gson</groupId>
+			<artifactId>gson</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>2.4</version>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>javax.inject</groupId>
+			<artifactId>javax.inject</artifactId>
+			<version>1</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.hadoop</groupId>
+			<artifactId>hadoop-common</artifactId>
+			<version>${hadoop.version}</version>
+			<exclusions>
+				<exclusion>
+					<groupId>tomcat</groupId>
+					<artifactId>jasper-runtime</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.hadoop</groupId>
+			<artifactId>hadoop-hdfs</artifactId>
+			<version>${hadoop.version}</version>
+			<exclusions>
+				<exclusion>
+					<groupId>tomcat</groupId>
+					<artifactId>jasper-runtime</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+
+
+	</dependencies>
+	<properties>
+		<uicode.dir>../</uicode.dir>
+		<checkstyle.skip>true</checkstyle.skip>
+                <ambari.dir>${project.parent.parent.parent.basedir}</ambari.dir>
+                <ui.directory>${basedir}/src/main/resources/ui</ui.directory>
+	</properties>
+	<build>
+		<plugins>
+			<plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-clean-plugin</artifactId>
+        <version>2.5</version>
+        <configuration>
+          <filesets>
+            <fileset>
+              <directory>${ui.directory}</directory>
+              <followSymlinks>false</followSymlinks>
+              <includes>
+                <include>tmp/**</include>
+                <!--
+                <include>node_modules/**</include>
+                <include>bower_components/**</include>
+                -->
+                <include>node/**</include>
+              </includes>
+            </fileset>
+          </filesets>
+        </configuration>
+      </plugin>
+	      <plugin>
+	        <groupId>org.apache.maven.plugins</groupId>
+	        <artifactId>maven-clean-plugin</artifactId>
+	        <version>2.5</version>
+	        <configuration>
+	          <filesets>
+	            <fileset>
+	              <directory>${ui.directory}</directory>
+	              <followSymlinks>false</followSymlinks>
+	              <includes>
+	                <include>tmp/**</include>
+	                <!--
+	                <include>node_modules/**</include>
+	                <include>bower_components/**</include>
+	                -->
+	                <include>node/**</include>
+	              </includes>
+	            </fileset>
+	          </filesets>
+	        </configuration>
+	      </plugin>
+			<plugin>
+				<groupId>com.github.eirslett</groupId>
+				<artifactId>frontend-maven-plugin</artifactId>
+				<version>1.0</version>
+				<configuration>
+					<nodeVersion>v0.12.2</nodeVersion>
+					<npmVersion>1.4.8</npmVersion>
+					<workingDirectory>src/main/resources/ui/</workingDirectory>
+				</configuration>
+				<executions>
+					<execution>
+						<id>install node and npm</id>
+						<phase>generate-sources</phase>
+						<goals>
+							<goal>install-node-and-npm</goal>
+						</goals>
+					</execution>
+					<execution>
+						<id>npm install</id>
+						<phase>generate-sources</phase>
+						<goals>
+							<goal>npm</goal>
+						</goals>
+						<configuration>
+							<arguments>install
+								--python="${project.basedir}/../src/main/unix/ambari-python-wrap"
+								--unsafe-perm</arguments>
+						</configuration>
+					</execution>
+				</executions>
+				</plugin>
+               <plugin>
+                    <artifactId>exec-maven-plugin</artifactId>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <version>1.3.2</version>
+                    <executions>
+					<execution>
+						<id>Bower install</id>
+						<phase>generate-sources</phase>
+						<goals>
+						  <goal>exec</goal>
+						</goals>
+						<configuration>
+						  <workingDirectory>${ui.directory}</workingDirectory>
+						  <executable>${ui.directory}/node/${node.executable}</executable>
+						  <arguments>
+							<argument>${ui.directory}/node_modules/bower/bin/bower</argument>
+							<argument>install</argument>
+							<argument>--allow-root</argument>
+						  </arguments>
+						</configuration>
+					  </execution>
+					<execution>
+						<id>Files build</id>
+						<phase>generate-sources</phase>
+						<goals>
+							<goal>exec</goal>
+						</goals>
+						<configuration>
+							<workingDirectory>${basedir}/src/main/resources/ui</workingDirectory>
+							<executable>node/node</executable>
+							<arguments>
+								<argument>node_modules/.bin/ember</argument>
+								<argument>build</argument>
+
+							</arguments>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>3.1</version>
+				<configuration>
+					<source>1.7</source>
+					<target>1.7</target>
+				</configuration>
+			</plugin>
+			<plugin>
+				<artifactId>maven-dependency-plugin</artifactId>
+				<executions>
+					<execution>
+						<phase>generate-resources</phase>
+						<goals>
+							<goal>copy-dependencies</goal>
+						</goals>
+						<configuration>
+							<outputDirectory>${project.build.directory}/lib</outputDirectory>
+							<includeScope>runtime</includeScope>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+		<resources>
+			<resource>
+				<directory>src/main/resources</directory>
+				<filtering>false</filtering>
+				<includes>
+					<include>META-INF/**/*</include>
+					<include>view.xml</include>
+				</includes>
+			</resource>
+			<resource>
+				<directory>src/main/resources/ui/dist</directory>
+				<filtering>false</filtering>
+			</resource>
+			<!-- <resource> <directory>src/main/resources/ui/</directory> <filtering>false</filtering>
+				</resource> -->
+			<resource>
+				<directory>${uicode.dir}/dist</directory>
+				<filtering>false</filtering>
+			</resource>
+			<resource>
+				<targetPath>WEB-INF/lib</targetPath>
+				<filtering>false</filtering>
+				<directory>target/lib</directory>
+			</resource>
+		</resources>
+	</build>
+    <profiles>
+      <profile>
+        <id>windows</id>
+        <activation>
+          <os>
+            <family>win</family>
+          </os>
+        </activation>
+        <properties>
+          <node.executable>node.exe</node.executable>
+          <skip.nodegyp.chmod>true</skip.nodegyp.chmod>
+        </properties>
+      </profile>
+      <profile>
+        <id>linux</id>
+        <activation>
+          <os>
+            <family>unix</family>
+          </os>
+        </activation>
+        <properties>
+          <node.executable>node</node.executable>
+          <skip.nodegyp.chmod>false</skip.nodegyp.chmod>
+        </properties>
+      </profile>
+    </profiles>
+</project>

+ 26 - 0
contrib/views/wfmanager/src/assembly/assembly.xml

@@ -0,0 +1,26 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF 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.
+-->
+<assembly
+	xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+	<containerDescriptorHandlers>
+		<containerDescriptorHandler>
+			<handlerName>metaInf-services</handlerName>
+		</containerDescriptorHandler>
+	</containerDescriptorHandlers>
+</assembly>

+ 61 - 0
contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/FileServices.java

@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.oozie.ambari.view;
+
+import javax.ws.rs.Path;
+
+import org.apache.ambari.view.ViewContext;
+import org.apache.ambari.view.commons.hdfs.FileOperationService;
+import org.apache.ambari.view.commons.hdfs.UploadService;
+import org.apache.ambari.view.commons.hdfs.UserService;
+
+public class FileServices {
+
+	private ViewContext context;
+
+	public FileServices(ViewContext viewContext) {
+		this.context=viewContext;
+	}
+
+	/**
+	 * @see UploadService
+	 * @return service
+	 */
+	@Path("/upload")
+	public UploadService upload() {
+		return new UploadService(context);
+	}
+
+	/**
+	 * @see org.apache.ambari.view.commons.hdfs.FileOperationService
+	 * @return service
+	 */
+	@Path("/fileops")
+	public FileOperationService fileOps() {
+		return new FileOperationService(context);
+	}
+
+	/**
+	 * @see org.apache.ambari.view.commons.hdfs.UserService
+	 * @return service
+	 */
+	@Path("/user")
+	public UserService userService() {
+		return new UserService(context);
+	}
+}

+ 569 - 0
contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/OozieProxyImpersonator.java

@@ -0,0 +1,569 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.oozie.ambari.view;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.StreamingOutput;
+import javax.ws.rs.core.UriInfo;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.ambari.view.URLStreamProvider;
+import org.apache.ambari.view.ViewContext;
+import org.apache.ambari.view.utils.ambari.AmbariApi;
+import org.apache.ambari.view.utils.hdfs.HdfsApi;
+import org.apache.ambari.view.utils.hdfs.HdfsUtil;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.fs.FSDataInputStream;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * This is a class used to bridge the communication between the and the Oozie
+ * API executing inside ambari.
+ */
+public class OozieProxyImpersonator {
+
+  private static final String OOZIE_WF_APPLICATION_PATH_CONF_KEY = "oozie.wf.application.path";
+  private static final String OOZIE_WF_RERUN_FAILNODES_CONF_KEY = "oozie.wf.rerun.failnodes";
+  private static final String OOZIE_USE_SYSTEM_LIBPATH_CONF_KEY = "oozie.use.system.libpath";
+  private static final String XML_INDENT_SPACES = "4";
+  private static final String XML_INDENT_AMT_PROP_NAME = "{http://xml.apache.org/xslt}indent-amount";
+  private ViewContext viewContext;
+  private AmbariApi ambariApi;
+  private HdfsApi _hdfsApi = null;
+
+  private static final String USER_NAME_HEADER = "user.name";
+  private static final String USER_OOZIE_SUPER = "oozie";
+  private static final String DO_AS_HEADER = "doAs";
+
+  private static final String SERVICE_URI_PROP = "oozie.service.uri";
+  private static final String DEFAULT_SERVICE_URI = "http://sandbox.hortonworks.com:11000/oozie";
+
+  private final static Logger LOGGER = LoggerFactory
+    .getLogger(OozieProxyImpersonator.class);
+
+  @Inject
+  public OozieProxyImpersonator(ViewContext viewContext) {
+    this.viewContext = viewContext;
+    this.ambariApi = new AmbariApi(viewContext);
+    LOGGER.info(String.format(
+      "OozieProxyImpersonator initialized for instance: %s",
+      viewContext.getInstanceName()));
+  }
+
+  @Path("/fileServices")
+  public FileServices fileServices() {
+    return new FileServices(viewContext);
+  }
+
+  @POST
+  @Path("/submitWorkflow")
+  @Consumes({MediaType.TEXT_PLAIN + "," + MediaType.TEXT_XML})
+  public Response submitWorkflow(String postBody, @Context HttpHeaders headers,
+                                 @Context UriInfo ui, @QueryParam("app.path") String appPath,
+                                 @DefaultValue("false") @QueryParam("overwrite") Boolean overwrite) {
+    LOGGER.info("submit workflow job called");
+    try {
+      if (StringUtils.isEmpty(appPath)) {
+        throw new RuntimeException("app path can't be empty.");
+      }
+      appPath = appPath.trim();
+      if (!overwrite) {
+        boolean fileExists = getHdfsgetApi().exists(appPath);
+        LOGGER.info("FILE exists for [" + appPath + "] returned [" + fileExists
+          + "]");
+        if (fileExists) {
+          HashMap<String, String> resp = new HashMap<String, String>();
+          resp.put("status", "workflow.folder.exists");
+          resp.put("message", "Workflow Folder exists");
+          return Response.status(Response.Status.BAD_REQUEST).entity(resp)
+            .build();
+        }
+      }
+      String workflowFile = null;
+      if (appPath.endsWith(".xml")) {
+        workflowFile = appPath;
+      } else {
+        workflowFile = appPath + (appPath.endsWith("/") ? "" : "/")
+          + "workflow.xml";
+      }
+      postBody = formatXml(postBody);
+      try {
+        String filePath = createWorkflowFile(postBody, workflowFile, overwrite);
+        LOGGER.info(String.format("submit workflow job done. filePath=[%s]",
+          filePath));
+      } catch (org.apache.hadoop.security.AccessControlException ace) {
+        HashMap<String, String> resp = new HashMap<String, String>();
+        resp.put("status", "workflow.oozie.error");
+        resp.put("message", "You dont seem to have access to folder path.");
+        return Response.status(Response.Status.BAD_REQUEST).entity(resp)
+          .build();
+      }
+
+      String response = submitWorkflowJobToOozie(headers, appPath,
+        ui.getQueryParameters());
+      if (response != null && response.trim().startsWith("{")) {
+        // dealing with oozie giving error but with 200 response.
+        return Response.status(Response.Status.OK).entity(response).build();
+      } else {
+        HashMap<String, String> resp = new HashMap<String, String>();
+        resp.put("status", "workflow.oozie.error");
+        resp.put("message", response);
+        return Response.status(Response.Status.BAD_REQUEST).entity(resp)
+          .build();
+      }
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    } catch (Exception e) {
+      LOGGER.error("Error in submit workflow", e);
+      throw new RuntimeException(e);
+    }
+  }
+
+  @GET
+  @Path("/readWorkflowXml")
+  public Response readWorkflowXxml(
+      @QueryParam("workflowXmlPath") String workflowPath) {
+    if (StringUtils.isEmpty(workflowPath)) {
+      throw new RuntimeException("workflowXmlPath can't be empty.");
+    }
+    try {
+      final FSDataInputStream is = getHdfsgetApi().open(workflowPath);
+      StreamingOutput streamer = new StreamingOutput() {
+
+        @Override
+        public void write(OutputStream os) throws IOException,
+            WebApplicationException {
+          IOUtils.copy(is, os);
+          is.close();
+          os.close();
+        }
+      };
+      return Response.ok(streamer).status(200).build();
+    } catch (org.apache.hadoop.security.AccessControlException ace) {
+      HashMap<String, String> resp = new HashMap<String, String>();
+      resp.put("status", "workflow.oozie.error");
+      resp.put("message", "Access denied to file path");
+      return Response.status(Response.Status.FORBIDDEN).entity(resp).build();
+    } catch (IOException e) {
+      LOGGER.error("Error in read worfklow file", e);
+      throw new RuntimeException(e);
+    } catch (InterruptedException e) {
+      LOGGER.error("Error in read worfklow file", e);
+      throw new RuntimeException(e);
+    }
+  }
+
+  @GET
+  @Path("/getDag")
+  @Produces("image/png")
+  public Response submitWorkflow(@Context HttpHeaders headers,
+                                 @Context UriInfo ui, @QueryParam("jobid") String jobid) {
+    String imgUrl = getServiceUri() + "/v2/job/" + jobid + "?show=graph";
+    Map<String, String> newHeaders = getHeaders(headers);
+    final InputStream is = readFromOozie(headers, imgUrl, HttpMethod.GET, null,
+      newHeaders);
+    StreamingOutput streamer = new StreamingOutput() {
+
+      @Override
+      public void write(OutputStream os) throws IOException,
+        WebApplicationException {
+        IOUtils.copy(is, os);
+        is.close();
+        os.close();
+      }
+
+    };
+    return Response.ok(streamer).status(200).build();
+  }
+
+  @GET
+  @Path("/{path: .*}")
+  public Response handleGet(@Context HttpHeaders headers, @Context UriInfo ui) {
+    try {
+      String serviceURI = buildURI(ui);
+      return consumeService(headers, serviceURI, HttpMethod.GET, null);
+    } catch (Exception ex) {
+      LOGGER.error("Error in GET proxy", ex);
+      return Response.status(Response.Status.BAD_REQUEST).entity(ex.toString())
+        .build();
+    }
+  }
+
+  @POST
+  @Path("/{path: .*}")
+  public Response handlePost(String xml, @Context HttpHeaders headers, @Context UriInfo ui) {
+    try {
+      String serviceURI = buildURI(ui);
+      return consumeService(headers, serviceURI, HttpMethod.POST, xml);
+    } catch (Exception ex) {
+      LOGGER.error("Error in POST proxy", ex);
+      return Response.status(Response.Status.BAD_REQUEST).entity(ex.toString())
+        .build();
+    }
+  }
+
+  @DELETE
+  @Path("/{path: .*}")
+  public Response handleDelete(@Context HttpHeaders headers, @Context UriInfo ui) {
+    try {
+      String serviceURI = buildURI(ui);
+      return consumeService(headers, serviceURI, HttpMethod.POST, null);
+    } catch (Exception ex) {
+      LOGGER.error("Error in DELETE proxy", ex);
+      return Response.status(Response.Status.BAD_REQUEST).entity(ex.toString())
+        .build();
+    }
+  }
+
+  @PUT
+  @Path("/{path: .*}")
+  public Response handlePut(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
+
+    try {
+      String serviceURI = buildURI(ui);
+      return consumeService(headers, serviceURI, HttpMethod.PUT, body);
+    } catch (Exception ex) {
+      LOGGER.error("Error in PUT proxy", ex);
+      return Response.status(Response.Status.BAD_REQUEST).entity(ex.toString())
+        .build();
+    }
+  }
+
+  private String submitWorkflowJobToOozie(HttpHeaders headers, String filePath,
+                                          MultivaluedMap<String, String> queryParams) {
+    String nameNode = "hdfs://" + viewContext.getCluster().getConfigurationValue("hdfs-site", "dfs.namenode.rpc-address");
+
+    if (!queryParams.containsKey("config.nameNode")) {
+      ArrayList<String> nameNodes = new ArrayList<String>();
+      LOGGER.info("Namenode===" + nameNode);
+      nameNodes.add(nameNode);
+      queryParams.put("config.nameNode", nameNodes);
+    }
+
+    HashMap<String, String> workflowConigs = new HashMap<String, String>();
+    if (queryParams.containsKey("resourceManager")
+      && "useDefault".equals(queryParams.getFirst("resourceManager"))) {
+      String jobTrackerNode = viewContext.getCluster().getConfigurationValue(
+        "yarn-site", "yarn.resourcemanager.address");
+      LOGGER.info("jobTrackerNode===" + jobTrackerNode);
+      workflowConigs.put("resourceManager", jobTrackerNode);
+      workflowConigs.put("jobTracker", jobTrackerNode);
+    }
+    if (queryParams != null) {
+      for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
+        if (entry.getKey().startsWith("config.")) {
+          if (entry.getValue() != null && entry.getValue().size() > 0) {
+            workflowConigs.put(entry.getKey().substring(7), entry.getValue()
+              .get(0));
+          }
+        }
+      }
+    }
+
+    if (queryParams.containsKey("oozieconfig.useSystemLibPath")) {
+      String useSystemLibPath = queryParams
+        .getFirst("oozieconfig.useSystemLibPath");
+      workflowConigs.put(OOZIE_USE_SYSTEM_LIBPATH_CONF_KEY, useSystemLibPath);
+    } else {
+      workflowConigs.put(OOZIE_USE_SYSTEM_LIBPATH_CONF_KEY, "true");
+    }
+    if (queryParams.containsKey("oozieconfig.rerunOnFailure")) {
+      String rerunFailnodes = queryParams
+        .getFirst("oozieconfig.rerunOnFailure");
+      workflowConigs.put(OOZIE_WF_RERUN_FAILNODES_CONF_KEY, rerunFailnodes);
+    } else {
+      workflowConigs.put(OOZIE_WF_RERUN_FAILNODES_CONF_KEY, "true");
+    }
+
+    workflowConigs.put("user.name", viewContext.getUsername());
+    workflowConigs.put(OOZIE_WF_APPLICATION_PATH_CONF_KEY, nameNode + filePath);
+    String configXMl = generateConigXml(workflowConigs);
+    LOGGER.info("Config xml==" + configXMl);
+    HashMap<String, String> customHeaders = new HashMap<String, String>();
+    customHeaders.put("Content-Type", "application/xml;charset=UTF-8");
+    Response serviceResponse = consumeService(headers, getServiceUri()
+      + "/v2/jobs", HttpMethod.POST, configXMl, customHeaders);
+
+    LOGGER
+      .info("REsp from oozie status entity==" + serviceResponse.getEntity());
+    if (serviceResponse.getEntity() instanceof String) {
+      return (String) serviceResponse.getEntity();
+    } else {
+      return "success";
+    }
+
+  }
+
+  private String createWorkflowFile(String postBody, String workflowFile, boolean overwrite) throws IOException, InterruptedException {
+    FSDataOutputStream fsOut = getHdfsgetApi().create(workflowFile, overwrite);
+    fsOut.write(postBody.getBytes());
+    fsOut.close();
+    return workflowFile;
+  }
+
+  private String buildURI(UriInfo ui) {
+    String uiURI = ui.getAbsolutePath().getPath();
+    int index = uiURI.indexOf("proxy/") + 5;
+    uiURI = uiURI.substring(index);
+    String serviceURI = getServiceUri();
+    serviceURI += uiURI;
+
+    MultivaluedMap<String, String> parameters = ui.getQueryParameters();
+    StringBuilder urlBuilder = new StringBuilder(serviceURI);
+    boolean firstEntry = true;
+    for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
+      if ("user.name".equals(entry.getKey())) {
+        ArrayList<String> vals = new ArrayList<String>();
+        vals.add(viewContext.getUsername());
+        entry.setValue(vals);
+      }
+      if (firstEntry) {
+        urlBuilder.append("?");
+      } else {
+        urlBuilder.append("&");
+      }
+      boolean firstVal = true;
+      for (String val : entry.getValue()) {
+        urlBuilder.append(firstVal ? "" : "&").append(entry.getKey())
+          .append("=").append(val);
+        firstVal = false;
+      }
+      firstEntry = false;
+    }
+    return urlBuilder.toString();
+  }
+
+  private String getServiceUri() {
+    String serviceURI = viewContext.getProperties().get(SERVICE_URI_PROP) != null ? viewContext
+      .getProperties().get(SERVICE_URI_PROP) : DEFAULT_SERVICE_URI;
+    return serviceURI;
+  }
+
+  public Response consumeService(HttpHeaders headers, String urlToRead,
+                                 String method, String body, Map<String, String> customHeaders) {
+    Response response = null;
+    InputStream stream = readFromOozie(headers, urlToRead, method, body,
+      customHeaders);
+    String stringResponse = null;
+    try {
+      stringResponse = IOUtils.toString(stream);
+    } catch (IOException e) {
+      LOGGER.error("Error while converting stream to string", e);
+      throw new RuntimeException(e);
+    }
+    if (stringResponse.contains(Response.Status.BAD_REQUEST.name())) {
+      response = Response.status(Response.Status.BAD_REQUEST)
+        .entity(stringResponse).type(MediaType.TEXT_PLAIN).build();
+    } else {
+      response = Response.status(Response.Status.OK).entity(stringResponse)
+        .type(deduceType(stringResponse)).build();
+    }
+    return response;
+  }
+
+  private InputStream readFromOozie(HttpHeaders headers, String urlToRead,
+                                    String method, String body, Map<String, String> customHeaders) {
+    URLStreamProvider streamProvider = viewContext.getURLStreamProvider();
+    Map<String, String> newHeaders = getHeaders(headers);
+    newHeaders.put(USER_NAME_HEADER, USER_OOZIE_SUPER);
+
+    newHeaders.put(DO_AS_HEADER, viewContext.getUsername());
+    newHeaders.put("Accept", MediaType.APPLICATION_JSON);
+    if (customHeaders != null) {
+      newHeaders.putAll(customHeaders);
+    }
+    LOGGER.info(String.format("Proxy request for url: [%s] %s", method,
+      urlToRead));
+    boolean securityEnabled = isSecurityEnabled();
+    LOGGER.debug(String.format("IS security enabled:[%b]", securityEnabled));
+    InputStream stream = null;
+    try {
+      if (securityEnabled) {
+        stream = streamProvider.readAsCurrent(urlToRead, method, body, newHeaders);
+
+      } else {
+        stream = streamProvider.readFrom(urlToRead, method, body, newHeaders);
+      }
+    } catch (IOException e) {
+      LOGGER.error("error talking to oozie", e);
+      throw new RuntimeException(e);
+    }
+    return stream;
+  }
+
+  public Response consumeService(HttpHeaders headers, String urlToRead,
+                                 String method, String body) throws Exception {
+    return consumeService(headers, urlToRead, method, body, null);
+  }
+
+  public Map<String, String> getHeaders(HttpHeaders headers) {
+    MultivaluedMap<String, String> requestHeaders = headers.getRequestHeaders();
+    Set<Entry<String, List<String>>> headerEntrySet = requestHeaders.entrySet();
+    HashMap<String, String> headersMap = new HashMap<String, String>();
+    for (Entry<String, List<String>> headerEntry : headerEntrySet) {
+      String key = headerEntry.getKey();
+      List<String> values = headerEntry.getValue();
+      headersMap.put(key, strJoin(values, ","));
+    }
+    return headersMap;
+  }
+
+  public String strJoin(List<String> strings, String separator) {
+    StringBuilder stringBuilder = new StringBuilder();
+    for (int i = 0, il = strings.size(); i < il; i++) {
+      if (i > 0) {
+        stringBuilder.append(separator);
+      }
+      stringBuilder.append(strings.get(i));
+    }
+    return stringBuilder.toString();
+  }
+
+  private MediaType deduceType(String stringResponse) {
+    if (stringResponse.startsWith("{")) {
+      return MediaType.APPLICATION_JSON_TYPE;
+    } else if (stringResponse.startsWith("<")) {
+      return MediaType.TEXT_XML_TYPE;
+    } else {
+      return MediaType.APPLICATION_JSON_TYPE;
+    }
+  }
+
+  private HdfsApi getHdfsgetApi() {
+    if (_hdfsApi == null) {
+      try {
+        _hdfsApi = HdfsUtil.connectToHDFSApi(viewContext);
+      } catch (Exception ex) {
+        LOGGER.error("Error in getting HDFS Api", ex);
+        throw new RuntimeException("HdfsApi connection failed. Check \"webhdfs.url\" property", ex);
+      }
+    }
+    return _hdfsApi;
+  }
+
+  private String generateConigXml(Map<String, String> map) {
+    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+    DocumentBuilder db;
+    try {
+      db = dbf.newDocumentBuilder();
+      Document doc = db.newDocument();
+      Element configElement = doc.createElement("configuration");
+      doc.appendChild(configElement);
+      for (Map.Entry<String, String> entry : map.entrySet()) {
+        Element propElement = doc.createElement("property");
+        configElement.appendChild(propElement);
+        Element nameElem = doc.createElement("name");
+        nameElem.setTextContent(entry.getKey());
+        Element valueElem = doc.createElement("value");
+        valueElem.setTextContent(entry.getValue());
+        propElement.appendChild(nameElem);
+        propElement.appendChild(valueElem);
+      }
+      DOMSource domSource = new DOMSource(doc);
+      StringWriter writer = new StringWriter();
+      StreamResult result = new StreamResult(writer);
+      TransformerFactory tf = TransformerFactory.newInstance();
+      Transformer transformer = tf.newTransformer();
+      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+      transformer
+        .setOutputProperty(XML_INDENT_AMT_PROP_NAME, XML_INDENT_SPACES);
+      transformer.transform(domSource, result);
+      return writer.toString();
+    } catch (ParserConfigurationException | TransformerException e) {
+      LOGGER.error("error in generating config xml", e);
+      throw new RuntimeException(e);
+    }
+
+  }
+
+  private String formatXml(String xml) {
+    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+    try {
+      DocumentBuilder db = dbf.newDocumentBuilder();
+      StreamResult result = new StreamResult(new StringWriter());
+      Document document = db.parse(new InputSource(new StringReader(xml)));
+      Transformer transformer = TransformerFactory.newInstance()
+        .newTransformer();
+      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+      transformer
+        .setOutputProperty(XML_INDENT_AMT_PROP_NAME, XML_INDENT_SPACES);
+      DOMSource source = new DOMSource(document);
+      transformer.transform(source, result);
+      return result.getWriter().toString();
+    } catch (ParserConfigurationException | SAXException | IOException
+      | TransformerFactoryConfigurationError | TransformerException e) {
+      LOGGER.error("Error in formatting xml", e);
+      throw new RuntimeException(e);
+    }
+  }
+
+  private boolean isSecurityEnabled() {
+    boolean securityEnabled = Boolean.valueOf(getHadoopConfigs().get(
+      "security_enabled"));
+    return securityEnabled;
+  }
+
+  private Map<String, String> getHadoopConfigs() {
+    return viewContext.getInstanceData();
+  }
+
+}

+ 22 - 0
contrib/views/wfmanager/src/main/resources/WEB-INF/web.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF 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. Kerberos, LDAP, Custom. Binary/Htt
+-->
+<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
+         version="3.1">
+</web-app>

+ 117 - 0
contrib/views/wfmanager/src/main/resources/ui/README.md

@@ -0,0 +1,117 @@
+<!---
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF 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](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.
+-->
+# Workflow Manager-UI
+
+This is the repository for the Workflow Manager web UI . This has Dashboard where Oozie jobs can be monitored, run, stip etc..It also has a desiger which allows
+you to develop a oozie work flow in a graphical interface.
+
+Technologies Used.
+it uses Ember as underline JS framework.
+JsPlbumb for designer.
+Dagre for layout in designer.
+bootstrap for css.
+
+## Prerequisites
+
+If you are not building using maven, you will need the following things properly installed on your computer. Maven build would download and setup the prerequisites for you.
+
+* [Git](http://git-scm.com/)
+* [Node.js](http://nodejs.org/) (with NPM)
+* [Bower](http://bower.io/)
+* [Ember CLI](http://www.ember-cli.com/)
+* [PhantomJS](http://phantomjs.org/)
+
+## Installation
+
+* `git clone <repository-url>` this repository
+* change into the new directory
+* To build the deployable war file, run `mvn clean package`
+
+in the main directory, just do mvn install.
+
+For doing local development
+==============================
+Go to folder under wfmanager\src\main\resources\ui.
+* `npm install`
+* `bower install`
+
+
+## Running / Development using node and ember-cli
+
+In development mode (and in non Ambari View mode), you might want to connect the UI with already running remote/local oozie server. To do so you can run the proxy so that UI can route the requests through it.
+
+* `node proxy.js <local-port> <remote-oozie-host> <remote-oozie-port>`
+
+By default local port is `11002`, default oozie host is `localhost` with default port as `11000`
+
+* `node proxy.js`
+* `Proxy on: localhost:11002 -> localhost:11000`
+
+If you want to use different local port (other than `11002`) then update the API_URL in `environment.js` accordingly.
+
+* `ember server`
+* Visit your app at [http://localhost:4300](http://localhost:4300).
+
+### Building and Running through Maven
+
+* `mvn clean package` (Build web ui and puts into /dist)
+* `mvn test -Pproxy` (start proxy for the oozie API)
+* In another console tab `mvn test -Pserver` (does ember server)
+* Wheather you use maven or ember, your app would be accessible at [http://localhost:4300](http://localhost:4300).
+
+### Setup and Run sample Oozie workflows, bundles and coordinators
+
+* Get and start the latest Hortonworks Sandbox (if you haven't yet)
+* From the host machine, `ssh root@127.0.0.1 -p 2222` to get into Sandbox
+* Switch to a `guest` user using `su - guest`
+* Inside the sandbox, `tar -xvf /usr/hdp/current/oozie-client/doc/oozie-examples.tar.gz`
+* Correct job.properties `find ./examples/apps/ -iname "job.properties" | xargs sed -i 's/localhost/sandbox.hortonworks.com/g'`
+* Set hadoop user: `export HADOOP_USER_NAME=guest`
+* Move the examples into HDFS: `hdfs dfs -put ./examples/ /user/guest/examples`
+* Set oozie user `export OOZIE_USER_NAME=guest`
+* Submit and run all the jobs: `find ./examples/apps/ -iname "job.properties" | xargs -i oozie job -oozie http://localhost:11000/oozie -config  {} -run`
+
+### Oozie Ambari View
+
+This UI can be built and deployed as an Ambari view. Below are the steps to build the Ambari view.
+
+* `cd oozie-ambari-view`
+* `mvn clean package` - This builds `target/oozie-ambari-view-0.0.0.1-SNAPSHOT.jar`
+* `cp target/oozie-ambari-view-0.0.0.1-SNAPSHOT.jar </var/lib/ambari-server/resources/views/>`
+* restart your ambari server and Oozie Amabri View would be available in Ambari
+
+### Code Generators
+
+Make use of the many generators for code, try `ember help generate` for more details
+
+### Running Tests
+
+* `ember test`
+* `ember test --server`
+
+## Further Reading / Useful Links
+
+* [ember.js](http://emberjs.com/)
+* [ember-cli](http://www.ember-cli.com/)
+* Development Browser Extensions
+  * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
+  * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/)
+
+How the code is organized.
+There are 2 main components- Dashboard and Designer.
+1) Designer
+
+
+2) Dashboard.

+ 39 - 0
contrib/views/wfmanager/src/main/resources/ui/app/app.js

@@ -0,0 +1,39 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import Resolver from './resolver';
+import loadInitializers from 'ember-load-initializers';
+import config from './config/environment';
+
+let App;
+
+Ember.MODEL_FACTORY_INJECTIONS = true;
+
+App = Ember.Application.extend({
+  modulePrefix: config.modulePrefix,
+  podModulePrefix: config.podModulePrefix,
+  Resolver
+});
+Ember.$.ajaxSetup({
+  beforeSend: function(xhr) {
+    xhr.setRequestHeader("X-XSRF-HEADER", Math.round(Math.random()*100000));
+  }
+});
+loadInitializers(App, config.modulePrefix);
+
+export default App;

+ 39 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/action-credential-config.js

@@ -0,0 +1,39 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  elementsInserted : function(){
+    if(this.get('actionCredentials')){
+      var credentials = this.get('actionCredentials').split(",");
+      credentials.forEach((credential)=>{
+        this.$('input[name="' + credential +'"]').prop('checked','checked');
+      });
+    }
+  }.on('didInsertElement'),
+  actions : {
+    onClick (name){
+      var checked = this.$('.cbox:checked');
+      var selectedCredentials = [];
+      checked.each((index, checkbox)=>{
+        selectedCredentials.push(this.$(checkbox).prop('name'));
+      });
+      this.set('actionCredentials', selectedCredentials.join());
+    }
+  }
+});

+ 26 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/action-version-select.js

@@ -0,0 +1,26 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  actions: {
+    versionChanged (version){
+      this.sendAction('versionChanged',this.get('actionVersion.name'), version);
+    }
+  }
+});

+ 41 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/archive-config.js

@@ -0,0 +1,41 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations,{
+  fileBrowser : Ember.inject.service('file-browser'),
+  initialize : function(){
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+  }.on('init'),
+  actions : {
+    addArchive () {
+      this.get('archives').unshiftObjects(this.get('archive'));
+      this.set('archive', "");
+    },
+    deleteArchive(index){
+      this.get('archives').removeAt(index);
+    },
+    openFileBrowser(model){
+      this.set('filePathModel', model);
+      this.sendAction("openFileBrowser", model, this);
+    }
+  }
+});

+ 45 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/arg-config.js

@@ -0,0 +1,45 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  multivalued : true,
+  initialize : function(){
+    this.sendAction('register', this, this);
+    this.on('bindInputPlaceholder',function () {
+      this.set('addUnboundValue', true);
+    }.bind(this));
+  }.on('init'),
+  bindInputPlaceholder : function () {
+    if(this.get('addUnboundValue') && !Ember.isBlank(this.get('arg'))){
+      this.addArg();
+    }
+  }.on('willDestroyElement'),
+  addArg (){
+    this.get('args').pushObject({value:this.get('arg')});
+    this.set('arg', "");
+  },
+  actions : {
+    addArg () {
+      this.addArg();
+    },
+    deleteArg (index) {
+      this.get('args').removeAt(index);
+    }
+  }
+});

+ 32 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/bundle-job-details.js

@@ -0,0 +1,32 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  actions :{
+    getJobLog (params) {
+      this.sendAction('getJobLog', params);
+    },
+    getActionDetails(action){
+      this.sendAction('getActionDetails',action);
+    },
+    showCoord(id){
+      this.sendAction('showCoord',id);
+    }
+  }
+});

+ 32 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/coord-job-details.js

@@ -0,0 +1,32 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  actions :{
+    getJobLog (params) {
+      this.sendAction('getJobLog', params);
+    },
+    getCoordActionReruns(){
+      this.sendAction('getCoordActionReruns',this.get('rerunActionList'));
+    },
+    showWorkflow(id){
+      this.sendAction('showWorkflow',id);
+    }
+  }
+});

+ 189 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/credentials-config.js

@@ -0,0 +1,189 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, {
+  childComponents : new Map(),
+  initialize : function(){
+    if(this.get('mode') === 'edit'){
+      this.sendAction('register', this, this);
+    }
+    this.get('childComponents').clear();
+    this.set('credentialType',Ember.A([]));
+    this.get('credentialType').pushObject({value:'',displayName:'Select'});
+    this.get('credentialType').pushObject({value:'hcat',displayName:'HCat'});
+    this.get('credentialType').pushObject({value:'hive2',displayName:'Hive2'});
+    this.get('credentialType').pushObject({value:'hbase',displayName:'HBase'});
+
+    Ember.addObserver(this, 'credential.type', this, this.credentialTypeObserver);
+
+    this.initializeCredentialDetails();
+
+    if(this.get('mode') === 'create'){
+      this.set("credential", {});
+      this.set("credential.property", Ember.A([]));
+    }
+    if(this.get('credential.type') && this.get('credential.property')){
+      this.set('staticProps', Ember.copy(this.get('credentialDetails').findBy('name',this.get('credential.type')).staticProps));
+      var configProperties = this.get('credential.property');
+      configProperties.forEach((property)=>{
+        var existingStaticProp = this.get('staticProps').findBy('name',property.name);
+        if (existingStaticProp) {
+          Ember.set(existingStaticProp,'value', property.value);
+          Ember.set(property,'static', true);
+        }
+      });
+    }
+  }.on('init'),
+  rendered : function(){
+    if(this.get('mode') === 'create'){
+      this.$('.collapse').collapse('show');
+    }else if(this.get('mode') === 'edit'){
+      this.$('.collapse').collapse('hide');
+    }
+  }.on('didInsertElement'),
+  initializeCredentialDetails : function(){
+    this.set('credentialDetails', Ember.A([]));
+    this.get('credentialDetails').pushObject({
+      name:'hcat',
+      staticProps:
+      [{name:'hcat.metastore.principal',displayName:'Hcat Metastore principal', value:'', belongsTo:'credential.property'},
+      {name:'hcat.metastore.uri',displayName:'Hcat Metastore uri', value:'', belongsTo:'credential.property'}]
+    });
+    this.get('credentialDetails').pushObject({
+      name:'hive2',
+      staticProps:
+      [{name:'hive2.jdbc.url',displayName:'Hive2 Jdbc Url', value:'', belongsTo:'credential.property'},
+      {name:'hive2.server.principal',displayName:'Hive2 Server principal', value:'', belongsTo:'credential.property'}]
+    });
+    this.get('credentialDetails').pushObject({
+      name:'hbase',
+      staticProps:
+      [{name:'hadoop.security.authentication',displayName:'Hadoop security auth', value:'', belongsTo:'credential.property'},
+      {name:'hbase.security.authentication',displayName:'Hbase security auth', value:'', belongsTo:'credential.property'},
+      {name:'hbase.master.kerberos.principal',displayName:'Hbase Master kerberos principal', value:'', belongsTo:'credential.property'},
+      {name:'hbase.regionserver.kerberos.principal',displayName:'Hbase regionserver kerberos principal', value:'', belongsTo:'credential.property'},
+      {name:'hbase.zookeeper.quorum',displayName:'Hbase zookeeper quorum', value:'', belongsTo:'credential.property'},
+      {name:'hadoop.rpc.protection',displayName:'Hadoop Rpc protection', value:'', belongsTo:'credential.property'},
+      {name:'hbase.rpc.protection',displayName:'Hbase Rpc protection', value:'', belongsTo:'credential.property'}]
+    });
+  },
+  credentialTypeObserver : function(){
+    var credentialType = this.get('credential.type');
+    if(!credentialType){
+      return;
+    }
+    this.set('staticProps', Ember.copy(this.get('credentialDetails').findBy('name',credentialType).staticProps));
+  },
+  processMultivaluedComponents(){
+    this.get('childComponents').forEach((childComponent)=>{
+      if(childComponent.get('multivalued')){
+        childComponent.trigger('bindInputPlaceholder');
+      }
+    });
+  },
+  resetForm : function(){
+    this.set('credential', {});
+    this.set('credential.property',Ember.A([]));
+    this.get('staticProps').clear();
+    this.initializeCredentialDetails();
+  },
+  validateChildrenComponents(){
+    var validationPromises = [];
+    var deferred = Ember.RSVP.defer();
+    if(this.get('childComponents').size === 0){
+      deferred.resolve(true);
+    }else{
+      this.get('childComponents').forEach((childComponent)=>{
+        if(!childComponent.validations){
+          return;
+        }
+        var validationDeferred = Ember.RSVP.defer();
+        childComponent.validate().then(()=>{
+          validationDeferred.resolve();
+        }).catch((e)=>{
+          validationDeferred.reject(e);
+        });
+        validationPromises.push(validationDeferred.promise);
+      });
+      Ember.RSVP.Promise.all(validationPromises).then(function(){
+        deferred.resolve(true);
+      }).catch(function(e){
+        deferred.reject(e);
+      });
+    }
+    return deferred;
+  },
+  validations : {
+    'credential.name': {
+      presence: {
+        'message' : 'Required',
+      }
+    },
+    'credential.type': {
+      presence: {
+        'message' : 'Required',
+      }
+    }
+  },
+  actions : {
+    register(component, context){
+      if(this.get('mode') === 'edit'){
+        this.sendAction('register', component, context);
+      }
+      this.get('childComponents').set(component, context);
+    },
+    add(){
+      var isFormValid = this.validateChildrenComponents();
+      isFormValid.promise.then(function(){
+        this.validate().then(function(){
+          var staticProps = this.get('staticProps');
+          var index = 0;
+          staticProps.forEach((property)=>{
+            var existingStaticProp = this.get('credential.property').findBy('name',property.name);
+            if (existingStaticProp) {
+              Ember.set(existingStaticProp,'value',property.value);
+              index++;
+            } else {
+              var propObj = {name : property.name, value:property.value, static:true};
+              this.get('credential.property').insertAt(index++, propObj);
+            }
+          });
+          this.processMultivaluedComponents();
+          this.sendAction('add',this.get('credential'));
+          this.resetForm();
+        }.bind(this)).catch(function(e){
+        }.bind(this));
+      }.bind(this)).catch(function (e) {
+      });
+
+    },
+    delete(name){
+      this.sendAction('delete',name);
+    },
+    togglePanel (){
+      this.$('.collapse').collapse('toggle');
+      if(this.$('.collapse').hasClass('in')){
+        this.set('isOpen', true);
+      }else{
+        this.set('isOpen', false);
+      }
+    }
+  }
+
+});

+ 87 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/decision-add-branch.js

@@ -0,0 +1,87 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import {FindNodeMixin} from '../domain/findnode-mixin';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, FindNodeMixin,{
+  isInsertAction: false,
+  condition:"",
+  targetNode:"",
+  newNodeType:null,
+  initialize : function(){
+    var self=this;
+
+    this.on("showBranchOptions",function(){
+      if (self.$("#selector-content").is(":visible")){
+        self.$("#selector-content").hide();
+      }else{
+        self.set("isInsertAction",false);
+        this.set("newNodeType",null);
+        this.set('descendantNodes',this.getDesendantNodes(this.get('node')));
+        self.$("#selector-content").show();
+      }
+    });
+  }.on('init'),
+  setup : function(){
+    this.sendAction('registerAddBranchAction',this);
+  }.on('didInsertElement'),
+  validations : {
+    'condition': {
+      presence: {
+        'if' : 'canValidate',
+        'message' : 'Required',
+      }
+    },
+    'targetNode': {
+      presence: {
+        'if' : 'canValidate',
+        'message' : 'Required',
+      }
+    }
+  },
+  actions:{
+    addNewNode(type){
+      this.set("newNodeType",type);
+    },
+    onTargetNodeChange(value){
+      var node = this.get('descendantNodes').findBy('id',value);
+      this.set('targetNode', node);
+    },
+    save(){
+      this.set('canValidate', true);
+      this.validate().then(function(){
+        this.sendAction("addDecisionBranch",{
+          sourceNode: this.get("node"),
+          condition:this.get("condition"),
+          targetNode:this.get("targetNode"),
+          newNodeType:this.get("newNodeType")
+        });
+        this.$("#selector-content").hide();
+        this.set('canValidate', false);
+        this.set('condition',"");
+        this.set('targetNode',"");
+        this.$('#target-node-select').prop('selectedIndex', 0);
+      }.bind(this)).catch(function(e){
+      }.bind(this));
+    },
+    cancel(){
+      this.$("#selector-content").hide();
+    }
+  }
+});

+ 50 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/decision-config.js

@@ -0,0 +1,50 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations,{ validator } from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations,{
+  initialize : function(){
+    this.sendAction('register','decision',this);
+  }.on('init'),
+  validations: {
+    'actionModel': {
+      inline : validator(function() {
+        var hasDefaultCond = false;
+        this.get('actionModel').forEach(function(item, index){
+          if(item.condition === "default"){
+            hasDefaultCond = true;
+            return;
+          }
+        });
+        if(!hasDefaultCond){
+          return "Decision Should have one default condition";
+        }
+        var hasEmptyCond = false;
+        this.get('actionModel').forEach(function(item, index){
+          if(item.condition === '' || item.condition === undefined || Ember.$.trim(item.condition).length === 0){
+            hasEmptyCond = true;
+            return;
+          }
+        });
+        if(hasEmptyCond){
+          return "Condition cannot be blank";
+        }
+      })}
+    }
+  });

+ 21 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/designer-errors.js

@@ -0,0 +1,21 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+});

+ 63 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/distcp-action.js

@@ -0,0 +1,63 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, {
+  fileBrowser : Ember.inject.service('file-browser'),
+  setUp : function(){
+    if(this.get('actionModel.args') === undefined){
+      this.set("actionModel.args", Ember.A([]));
+    }
+
+    if(this.get('actionModel.prepare') === undefined){
+      this.set("actionModel.prepare", Ember.A([]));
+    }
+    if(this.get('actionModel.configuration') === undefined){
+      this.set("actionModel.configuration",{});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+  }.on('init'),
+  initialize : function(){
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+    this.sendAction('register','distcpAction', this);
+  }.on('didInsertElement'),
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  validations : {
+
+  },
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    },
+
+  }
+});

+ 66 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/email-action.js

@@ -0,0 +1,66 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, {
+  fileBrowser : Ember.inject.service('file-browser'),
+  setUp : function(){
+
+  }.on('init'),
+  initialize : function(){
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+    this.sendAction('register','emailAction', this);
+  }.on('didInsertElement'),
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  validations : {
+    'actionModel.to': {
+      presence: {
+        'message' : 'You need to provide a value for Email To',
+      }
+    },
+    'actionModel.subject': {
+      presence: {
+        'message' : 'You need to provide a value for subject',
+      }
+    },
+    'actionModel.body': {
+      presence: {
+        'message' : 'You need to provide a value for body',
+      }
+    }
+  },
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    }
+  }
+});

+ 27 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/field-error.js

@@ -0,0 +1,27 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  errorMessage : null,
+  watchError : Ember.observer('error',function(){
+    if(this.get('error')){
+      this.set('errorMessage',this.get('error')[0]);
+    }
+  })
+});

+ 60 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/file-config.js

@@ -0,0 +1,60 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations,{
+  multivalued: true,
+  fileBrowser : Ember.inject.service('file-browser'),
+  initialize : function(){
+    this.on('fileSelected', function(fileName){
+      if(!Ember.isBlank(this.get('filePathModel'))){
+        var file = this.get('files').objectAt(this.get('filePathModel'));
+        Ember.set(file,"value", fileName);
+        this.get('files').replace(this.get('filePathModel'), 1, file);
+      }else{
+        this.set('file', fileName);
+      }
+    }.bind(this));
+    this.on('bindInputPlaceholder',function () {
+      this.set('addUnboundValue', true);
+    }.bind(this));
+    this.sendAction('register', this, this);
+  }.on('init'),
+  bindInputPlaceholder :function (){
+    if(this.get('addUnboundValue') && !Ember.isBlank(this.get('file'))){
+      this.addFile();
+    }
+  }.on('willDestroyElement'),
+  addFile (){
+    this.get('files').pushObject({value:this.get('file')});
+    this.set('file', "");
+  },
+  actions : {
+    addFile () {
+      this.addFile();
+    },
+    deleteFile(index){
+      this.get('files').removeAt(index);
+    },
+    openFileBrowser(index){
+      this.set('filePathModel', index);
+      this.sendAction("openFileBrowser", index, this);
+    }
+  }
+});

+ 48 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/file-upload.js

@@ -0,0 +1,48 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberUploader from 'ember-uploader';
+export default EmberUploader.FileField.extend({
+  filesDidChange: function(files) {
+    if (!this.get("selectedPath") || this.get("selectedPath")===""){
+      this.sendAction("uploadValidation","No file selected");
+    }
+    const uploader = EmberUploader.Uploader.create({
+      url: Ember.ENV.FILE_API_URL+"/upload",
+      method: 'PUT',
+      ajaxSettings: {
+        headers: {
+          'X-Requested-By': 'workflow designer'
+        }
+      }
+    });
+    uploader.on('progress', e => {
+      this.sendAction("uploadProgress",e);
+    });
+    uploader.on('didUpload', (e) => {
+      this.sendAction("uploadSuccess",e);
+    });
+    uploader.on('didError', (jqXHR, textStatus, errorThrown) => {
+
+      this.sendAction("uploadFailure",textStatus,errorThrown);
+    });
+    if (!Ember.isEmpty(files)) {
+      uploader.upload(files[0], { path: "/user/admin/"});
+    }
+  }
+});

+ 643 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/flow-designer.js

@@ -0,0 +1,643 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import {Workflow} from '../domain/workflow';
+import Constants from '../utils/constants';
+import {WorkflowGenerator} from '../domain/workflow-xml-generator';
+import {WorkflowImporter} from '../domain/workflow-importer';
+import {WorkflowContext} from '../domain/workflow-context';
+import {DefaultLayoutManager as LayoutManager} from '../domain/default-layout-manager';
+import EmberValidations,{ validator } from 'ember-validations';
+
+
+export default Ember.Component.extend(EmberValidations,{
+  workflowContext : WorkflowContext.create({}),
+  workflowTitle:"",
+  previewXml:"",
+  supportedActionTypes:["java", "hive", "pig", "sqoop", "shell", "spark", "map-reduce", "hive2", "sub-workflow", "distcp", "ssh", "FS"],
+  workflow:null,
+  showingConfirmationNewWorkflow:false,
+  showingWorkflowConfigProps:false,
+  workflowSubmitConfigs:{},
+  showingPreview:false,
+  currentTransition:null,
+  currentNode:null,
+  domain:{},
+  showActionEditor : false,
+  flattenedNodes: [],
+
+  workflowImporter:WorkflowImporter.create({}),
+  designerPlumb:null,
+  propertyExtractor : Ember.inject.service('property-extractor'),
+  showGlobalConfig : false,
+  showParameterSettings : false,
+  globalConfig : {},
+  parameters : {},
+  clonedDomain : {},
+  clonedErrorNode : {},
+  validationErrors : [],
+  layoutManager:null,
+  showingFileBrowser : false,
+  killNode : {},
+  isWorkflowImporting: false,
+  isImportingSuccess: true,
+  initialize :function(){
+    this.designerPlumb=jsPlumb.getInstance({});
+    this.layoutManager=LayoutManager.create({});
+    this.setConentWidth();
+    this.set('workflow',Workflow.create({}));
+    if(this.get("xmlAppPath")){
+      var workflowXmlPath = this.get("xmlAppPath"), relXmlPath = "", tempArr;
+      if(workflowXmlPath.indexOf("://") === -1 && workflowXmlPath.indexOf(":") === -1){
+        relXmlPath = workflowXmlPath;
+      } else{
+        tempArr = workflowXmlPath.split("//")[1].split("/");
+        tempArr.splice(0, 1);
+        relXmlPath = "/" + tempArr.join("/");
+        if(!(relXmlPath.indexOf(".xml") === relXmlPath.length-4)) {
+          if(relXmlPath.charAt(relXmlPath.length-1) !== "/"){
+            relXmlPath = relXmlPath+ "/" +"workflow.xml";
+          } else{
+            relXmlPath = relXmlPath+"workflow.xml";
+          }
+        }
+      }
+      this.importWorkflow(relXmlPath);
+      return;
+    }else{
+      this.workflow.initialize();
+      this.initAndRenderWorkflow();
+      this.$('#wf_title').focus();
+      this.restoreWorkinProgress();
+    }
+  }.on('didInsertElement'),
+  validations: {
+    'flattenedNodes': {
+      inline : validator(function() {
+        var nodeNames = new Map();
+        this.get("validationErrors").clear();
+        this.get('flattenedNodes').forEach((item)=>{
+          Ember.set(item, "errors", false);
+          if(nodeNames.get(item.name)){
+            Ember.set(item, "errors", true);
+            this.get("validationErrors").pushObject({node:item,message:"Node name should be unique"});
+          }else{
+            nodeNames.set(item.name, item);
+            Ember.set(item, "errors", false);
+          }
+          if(this.get("supportedActionTypes").indexOf(item.actionType) === -1 && item.type === "action"){
+            this.get('validationErrors').pushObject({node : item ,message : item.actionType+" is unsupported"});
+          }
+          var nodeErrors=item.validateCustom();
+          if (nodeErrors.length>0){
+            Ember.set(item, "errors", true);
+            nodeErrors.forEach(function(errMsg){
+              this.get("errors").pushObject({node:item,message:errMsg });
+            }.bind(this));
+          }
+        }.bind(this));
+
+        if(this.get('flattenedNodes').length !== nodeNames.size || this.get("errors").length>0){
+          return true;
+        }
+      })
+    },
+    "workflow.killnodes": {
+      inline : validator(function() {
+        let killNodes = [], flag;
+        if(this.get("workflow") && this.get("workflow").killNodes){
+          killNodes = this.get("workflow").killNodes;
+          for(let i=0; i<killNodes.length; i++){
+            for(let j=0; j<killNodes.length; j++){
+              if(killNodes[i].name === killNodes[j].name && i !== j){
+                this.get('validationErrors').pushObject({node : killNodes[j] ,message : "Duplicate killnode"});
+                flag = true;
+                break;
+              }
+            }
+            if(flag){
+              break;
+            }
+          }
+        }
+        if (flag){
+          return true;
+        }
+      })
+    }
+  },
+  setConentWidth(){
+    var offset = 120;
+    if (Ember.ENV.instanceInfo) {
+      offset = 0;
+    }
+    Ember.$(window).resize(function() {
+      return;
+    });
+  },
+  nodeRendered: function(){
+    var self=this;
+    if(this.get('renderNodeTransitions')){
+      var connections=[];
+      var visitedNodes=[];
+      this.renderTransitions(this.get("workflow").startNode,connections,visitedNodes);
+      this.workflowConnections=connections;
+      this.layout();
+      this.designerPlumb.setSuspendDrawing(true);
+      this.designerPlumb.batch(function(){
+        connections.forEach(function(conn){
+          self.designerPlumb.connect(conn);
+        });
+      });
+      this.designerPlumb.setSuspendDrawing(false,true);
+      this.set('renderNodeTransitions',false);
+    }
+    this.persistWorkInProgress();
+  }.on('didUpdate'),
+  cleanUpJsplumb:function(){
+    this.get('flattenedNodes').clear();
+    this.set('renderNodeTransitions',false);
+    this.designerPlumb.detachEveryConnection();
+  }.on('willDestroyElement'),
+  initAndRenderWorkflow(){
+    this.designerPlumb.ready(function() {
+      this.renderWorkflow();
+    }.bind(this));
+  },
+  renderWorkflow(){
+    this.get('flattenedNodes').clear();
+    this.set('renderNodeTransitions', true);
+    var visitedNodes=[];
+    this.renderNodes(this.get("workflow").startNode,visitedNodes);
+  },
+  rerender(){
+    this.designerPlumb.detachEveryConnection();
+    this.renderWorkflow(this.get("workflow"));
+  },
+  setCurrentTransition(transition){
+    this.set("currentTransition",transition);
+  },
+  renderNodes(node,visitedNodes){
+    if (!node || node.isKillNode()){
+      return;
+    }
+    if (visitedNodes.contains(node)){
+      return;
+    }
+    visitedNodes.push(node);
+    if(!this.get('flattenedNodes').contains(node)){
+      this.get('flattenedNodes').pushObject(node);
+    }
+    if (node.transitions.length > 0){
+      node.transitions.forEach(function(transition) {
+        var target = transition.targetNode;
+        this.renderNodes(target,visitedNodes);
+      }.bind(this));
+    }
+  },
+  createConnection(sourceNode,target,transition){
+    var connectionColor="#777";
+    var lineWidth=1;
+    if (transition.condition){
+      if(transition.condition==="default"){
+        lineWidth=2;
+      }else if (transition.condition==="error"|| transition.errorPath){
+        connectionColor=Constants.globalSetting.errorTransitionColor;
+      }
+    }
+    var connectionObj={
+      source:sourceNode.id,
+      target:target.id,
+      connector:["Straight"],
+      paintStyle:{lineWidth:lineWidth,strokeStyle:connectionColor},
+      endpointStyle:{fillStyle:'rgb(243,229,0)'},
+      endpoint: ["Dot", {
+        radius: 1
+      }],
+      alwaysRespectStubs:true,
+      anchors: [["Bottom"],["Top"]],
+      overlays:[]
+    };
+    return connectionObj;
+  },
+  deleteTransition(transition){
+    this.get("workflow").deleteTransition(transition);
+    this.rerender();
+  },
+  renderTransitions(sourceNode,connections,visitedNodes){
+    var self=this;
+    if(!sourceNode){
+      return;
+    }
+    if (visitedNodes.contains(sourceNode)){
+      return;
+    }
+    if (sourceNode.hasTransition() ){
+      var transitionCount=sourceNode.transitions.length;
+      sourceNode.transitions.forEach(function(transition) {
+        var target = transition.targetNode;
+        if (target.isKillNode() || !Constants.showErrorTransitions && transition.isOnError()){
+          return;
+        }
+        var connectionObj=self.createConnection(sourceNode,target,transition);
+
+        if (transition.condition){
+          var conditionHTML = "<div class='decision-condition' title='"+transition.condition+"'>"+ transition.condition+"</div>";
+          connectionObj.overlays.push([ "Label", {label:conditionHTML, location:0.75, id:"myLabel" } ]);
+        }
+        if (!target.isPlaceholder()){
+          connectionObj.overlays.push(["PlainArrow",{location:-0.1,width: 7,length: 7}]);
+        }
+        if (!(sourceNode.isPlaceholder() || target.isKillNode())){
+          var location=target.type==="placeholder"?1:0.5;
+          var addNodeoverlay=["Custom" , {
+            id: sourceNode.id+"_"+target.id+"_"+"connector",
+            location:location,
+            create:function(component) {
+              var container=Ember.$('<div />');
+              var plus= Ember.$('<div class="fa fa-plus connector_overlay_new"></div>');
+              if ((sourceNode.isDecisionNode() && transitionCount>1 ||sourceNode.isForkNode() && transitionCount>2 ) &&
+                target.isPlaceholder() &&
+                !transition.isDefaultCasePath()){
+                var trash=Ember.$('<div class="node_actions node_left"><i class="fa fa-trash-o"></i></div>');
+                trash.on("click",function(){
+                  self.deleteTransition(transition);
+                });
+                plus.append(trash);
+              }
+              container.append(plus);
+              return container;
+            },
+            events:{
+              click:function(labelOverlay, originalEvent) {
+                var element = originalEvent.target;
+                self.set('popOverElement', element);
+                self.setCurrentTransition(transition);
+                self.$('.popover').popover('destroy');
+                Ember.$(element).parents(".jsplumb-overlay").css("z-index", "4");
+                self.$(element).attr('data-toggle','popover');
+                self.$(element).popover({
+                  html : true,
+                  title : "Add Node <button type='button' class='close'>&times;</button>",
+                  placement: 'right',
+                  trigger : 'focus',
+                  content : function(){
+                    return self.$('#workflow-actions').html();
+                  }
+                });
+                self.$(element).popover("show");
+                self.$('.popover .close').on('click',function(){
+                  Ember.$(".jsplumb-overlay").css("z-index", "");
+                  self.$('.popover').popover('destroy');
+                });
+              }
+            }
+          }];
+          connectionObj.overlays.push(addNodeoverlay);
+        }
+        connections.push(connectionObj);
+        self.renderTransitions(target,connections,visitedNodes);
+      });
+    }
+  },
+  layout(){
+    var nodes = Ember.$(".nodecontainer");
+    //var edges = this.designerPlumb.getConnections();
+    var edges=this.workflowConnections;
+    this.layoutManager.doLayout(this,nodes,edges,this.get("workflow"));
+    this.designerPlumb.repaintEverything();
+    var endNodeTop=this.$("#node-end").offset().top;
+    var endNodeLeft=this.$("#node-end").offset().left;
+    this.$("#killnodes-container").offset({top:endNodeTop+50,left:endNodeLeft-50});
+    var top = this.$("#killnodes-container").offset().top + 40;
+    var left = this.$("#killnodes-container").offset().left - 28;
+    this.$('.kill').each(function(index,value){
+      this.$(value).offset({top:top,left:left});
+      top = this.$(value).offset().top+70 ;
+    }.bind(this));
+  },
+  doValidation(){
+    this.set('validationErrors',[]);
+    this.validate().then(() => {
+      this.set('validationErrors',[]);
+    }).catch(() => {
+      this.get('flattenedNodes').filterBy('errors',true).forEach((node)=>{
+        this.get('validationErrors').pushObjects(node.errorMsgs);
+      }.bind(this));
+
+    }.bind(this));
+  },
+  importWorkflow(filePath){
+    var self = this;
+    this.set("isWorkflowImporting", true);
+    this.set("workflowFilePath", filePath);
+    this.resetDesigner();
+    this.set("isWorkflowImporting", true);
+    var workflowXmlDefered=this.getWorkflowFromHdfs(filePath);
+    workflowXmlDefered.promise.then(function(data){
+      this.importWorkflowFromString(data);
+      this.set("isWorkflowImporting", false);
+    }.bind(this)).catch(function(e){
+      self.set("isWorkflowImporting", false);
+      self.set("isImportingSuccess", false);
+    });
+  },
+  importWorkflowFromString(data){
+    var workflow=this.get("workflowImporter").importWorkflow(data);
+    this.resetDesigner();
+    this.set("workflow",workflow);
+    this.rerender();
+    this.doValidation();
+  },
+  getWorkflowFromHdfs(filePath){
+    var url = Ember.ENV.API_URL + "/readWorkflowXml?workflowXmlPath="+filePath;
+    var deferred = Ember.RSVP.defer();
+    Ember.$.ajax({
+      url: url,
+      method: 'GET',
+      dataType: "text",
+      beforeSend: function (xhr) {
+        xhr.setRequestHeader("X-XSRF-HEADER", Math.round(Math.random()*100000));
+        xhr.setRequestHeader("X-Requested-By", "Ambari");
+      }
+    }).done(function(data){
+      deferred.resolve(data);
+    }).fail(function(){
+      deferred.reject();
+    });
+    return deferred;
+  },
+  resetDesigner(){
+    this.set("isImportingSuccess", true);
+    this.set("xmlAppPath", null)
+    this.set('errors',{});
+    this.set('validationErrors',{});
+    this.set('workflowFilePath',"");
+    this.get("workflow").resetWorfklow();
+    this.set('globalConfig', {});
+    this.set('parameters', {});
+    if(this.get('workflow.parameters') !== null){
+      this.set('workflow.parameters', {});
+    }
+    this.set('parameters', {});
+    this.designerPlumb.reset();
+  },
+  resetZoomLevel(){
+    this.set("zoomLevel", 1);
+  },
+  incZoomLevel(){
+    this.set("zoomLevel", this.get("zoomLevel")+0.1);
+  },
+  decZoomLevel(){
+    this.set("zoomLevel", this.get("zoomLevel")-0.1);
+  },
+  importSampleWorkflow (){
+    var deferred = Ember.RSVP.defer();
+    Ember.$.ajax({
+      url: "/sampledata/workflow.xml",
+      dataType: "text",
+      cache:false,
+      success: function(data) {
+        var workflow=this.get("workflowImporter").importWorkflow(data);
+        deferred.resolve(workflow);
+      }.bind(this),
+      failure : function(data){
+        deferred.reject(data);
+      }
+    });
+    return deferred;
+  },
+  persistWorkInProgress(){
+    //TODO later
+  },
+  restoreWorkinProgress(){
+    //TODO later
+  },
+  actions:{
+    showWorkflowSla (value) {
+      this.set('showWorkflowSla', value);
+    },
+    showCreateKillNode (value){
+      this.set('showCreateKillNode', value);
+    },
+    showVersionSettings(value){
+      this.set('showVersionSettings', value);
+    },
+    showParameterSettings(value){
+      if(this.get('workflow.parameters') !== null){
+        this.set('parameters', Ember.copy(this.get('workflow.parameters')));
+      }else{
+        this.set('globalConfig', {});
+      }
+      this.set('showParameterSettings', value);
+    },
+    showCredentials(value){
+      this.set('showCredentials', value);
+    },
+    createKillNode(){
+      this.set("createKillnodeError",null);
+      var existingKillNode=this.get('workflow').get("killNodes").findBy("name",this.get('killNode.name'));
+      if (existingKillNode){
+        this.set("createKillnodeError","The kill node already exists");
+        return;
+      }
+      if (Ember.isBlank(this.get('killNode.name'))){
+        this.set("createKillnodeError","The kill node cannot be empty");
+        return;
+      }
+      this.get("workflow").createKillNode(this.get('killNode.name'),this.get('killNode.message'));
+      this.set('killNode',{});
+      this.rerender();
+      this.layout();
+      this.doValidation();
+      this.$("#kill-node-dialog").modal("hide");
+      this.set('showCreateKillNode', false);
+    },
+    addNode(type){
+      var currentTransition=this.get("currentTransition");
+      var newNode=this.get("workflow").addNode(currentTransition,type);
+      if(currentTransition.targetNode.isPlaceholder()){
+        this.designerPlumb.remove(currentTransition.targetNode.id);
+      }
+      this.rerender();
+      this.doValidation();
+      var scroll = $(window).scrollTop();
+      Ember.$('html, body')
+      .animate({
+        scrollTop: scroll+200
+      }, 1000);
+    },
+    nameChanged(){
+      this.doValidation();
+    },
+    deleteNode(node){
+      if(node.isKillNode()){
+        var result=this.get("workflow").deleteKillNode(node);
+        if (result && result.status===false){
+          this.get('validationErrors').pushObject({node : node ,message :result.message});
+        }
+      } else {
+        this.get("workflow").deleteNode(node);
+      }
+      this.rerender();
+      this.doValidation();
+    },
+    openEditor(node){
+      this.set('showActionEditor', true);
+      this.set('currentAction', node.actionType);
+      var domain = node.getNodeDetail();
+      this.set('clonedDomain',Ember.copy(domain));
+      this.set('clonedErrorNode', node.errorNode);
+      this.set('clonedKillMessage',node.get('killMessage'));
+      node.set("domain", domain);
+      this.set('currentNode', node);
+    },
+    addBranch(node){
+      this.get("workflow").addBranch(node);
+      this.rerender();
+    },
+    addDecisionBranch(settings){
+      this.get("workflow").addDecisionBranch(settings);
+      this.rerender();
+    },
+    addKillNode(errorNode){
+      var currentNode= this.get("currentNode");
+      if(errorNode && errorNode.isNew){
+        this.get("workflow").addKillNode(currentNode,errorNode);
+        this.get("workflow.killNodes").push(errorNode);
+      }else {
+        this.set('currentNode.errorNode', errorNode);
+      }
+    },
+    submitWorkflow(){
+      this.get('workflowContext').clearErrors();
+      var workflowGenerator=WorkflowGenerator.create({workflow:this.get("workflow"),
+      workflowContext:this.get('workflowContext')});
+      var workflowXml=workflowGenerator.process();
+      if(this.get('workflowContext').hasErrors()){
+        this.set('errors',this.get('workflowContext').getErrors());
+      }else{
+        var dynamicProperties = this.get('propertyExtractor').getDynamicProperties(workflowXml);
+        var configForSubmit={props:dynamicProperties,xml:workflowXml,params:this.get('workflow.parameters')};
+        this.set("workflowSubmitConfigs",configForSubmit);
+        this.set("showingWorkflowConfigProps",true);
+      }
+
+    },
+    previewWorkflow(){
+      this.set("showingPreview",false);
+      this.get('workflowContext').clearErrors();
+      var workflowGenerator=WorkflowGenerator.create({workflow:this.get("workflow"),
+      workflowContext:this.get('workflowContext')});
+      var workflowXml=workflowGenerator.process();
+      if(this.get('workflowContext').hasErrors()){
+        this.set('errors',this.get('workflowContext').getErrors());
+      }else{
+        this.set("previewXml",vkbeautify.xml(workflowXml));
+        this.set("showingPreview",true);
+      }
+    },
+    closeWorkflowSubmitConfigs(){
+      this.set("showingWorkflowConfigProps",false);
+    },
+    importWorkflowTest(){
+      var deferred = this.importSampleWorkflow();
+      deferred.promise.then(function(data){
+        this.resetDesigner();
+        this.set("workflow",data);
+        this.rerender();
+        this.doValidation();
+      }.bind(this)).catch(function(e){
+      });
+    },
+    closeFileBrowser(){
+      this.set("showingFileBrowser",false);
+      if(this.get('workflowFilePath')){
+        this.importWorkflow(this.get('workflowFilePath'));
+      }
+    },
+    showFileBrowser(){
+      this.set('showingFileBrowser', true);
+    },
+    createNewWorkflow(){
+      this.resetDesigner();
+      this.rerender();
+      this.set("workflowFilePath", "");
+      this.$('#wf_title').focus();
+    },
+    conirmCreatingNewWorkflow(){
+      this.set('showingConfirmationNewWorkflow', true);
+    },
+    showWorkflowGlobalProps(){
+      if(this.get('workflow.globalSetting') !== null){
+        this.set('globalConfig', Ember.copy(this.get('workflow.globalSetting')));
+      }else{
+        this.set('globalConfig', {});
+      }
+      this.set("showGlobalConfig", true);
+    },
+    closeWorkflowGlobalProps(){
+      this.set("showGlobalConfig", false);
+    },
+    saveGlobalConfig(){
+      this.set('workflow.globalSetting', Ember.copy(this.get('globalConfig')));
+      this.set("showGlobalConfig", false);
+    },
+    closeWorkFlowParam(){
+      this.set("showParameterSettings", false);
+    },
+    saveWorkFlowParam(){
+      this.set('workflow.parameters', Ember.copy(this.get('parameters')));
+      this.set("showParameterSettings", false);
+    },
+    zoomIn(){
+      if(!this.get("zoomLevel")){
+        this.resetZoomLevel();
+      }
+      this.decZoomLevel();
+      var lev = this.get("zoomLevel") <= 0 ? 0.1 : this.get("zoomLevel");
+      this.$("#flow-designer").css("transform", "scale(" + lev + ")");
+    },
+    zoomOut(){
+      if(!this.get("zoomLevel")){
+        this.resetZoomLevel();
+      }
+      this.incZoomLevel();
+      var lev = this.get("zoomLevel") >= 1 ? 1 : this.get("zoomLevel");
+      this.$("#flow-designer").css("transform", "scale(" + lev + ")");
+    },
+    zoomReset(){
+      this.resetZoomLevel();
+      this.$("#flow-designer").css("transform", "scale(" + 1 + ")");
+    },
+    closeActionEditor (isSaved){
+      if(isSaved){
+        this.currentNode.onSave();
+        this.doValidation();
+      }	else {
+        this.set('currentNode.domain',Ember.copy(this.get('clonedDomain')));
+        this.set('currentNode.errorNode', this.get('clonedErrorNode'));
+        if(this.currentNode.type === 'kill'){
+          this.set('currentNode.killMessage', this.get('clonedKillMessage'));
+        }
+      }
+      this.set('showActionEditor', false);
+      this.rerender();
+    }
+  }
+});

+ 102 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/fs-action.js

@@ -0,0 +1,102 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+import EmberValidations, {
+  validator
+} from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, {
+  fileBrowser: Ember.inject.service('file-browser'),
+  setUp: function() {
+    if (this.get('actionModel.fsOps') === undefined) {
+      this.set("actionModel.fsOps", Ember.A([]));
+    }
+    if (this.get('actionModel.configuration') === undefined) {
+      this.set("actionModel.configuration", {});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+    this.sendAction('register', 'fsAction', this);
+  }.on('init'),
+  initialize: function() {
+    this.on('fileSelected', function(fileName) {
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+  }.on('didInsertElement'),
+  observeError: function() {
+    if (this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")) {
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  validations: {
+    'actionModel': {
+      inline: validator(function() {
+        var isValidated = true,
+        msg = "";
+        if (!this.get('actionModel.fsOps')) {
+          return;
+        }
+        this.get('actionModel.fsOps').forEach(function(item, index) {
+          switch (item.type) {
+            case "mkdir":
+            case "delete":
+            case "touchz":
+            if (!item.settings.path) {
+              isValidated = false;
+              msg = "path is mandatory";
+            }
+            break;
+            case "chmod":
+            if (!item.settings.path) {
+              isValidated = false;
+              msg = "path and permissions are mandatory";
+            }
+            break;
+            case "chgrp":
+            if (!item.settings.path || !item.settings.group) {
+              isValidated = false;
+              msg = "path and group are mandatory";
+            }
+            break;
+            case "move":
+            if (!item.settings.source || !item.settings.target) {
+              isValidated = false;
+              msg = "source and target are mandatory";
+            }
+            break;
+          }
+        });
+        if (!isValidated) {
+          return "   ";
+        }
+
+      })
+    }
+  },
+  
+  actions: {
+    openFileBrowser(model, context) {
+      if (undefined === context) {
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register(name, context) {
+      this.sendAction('register', name, context);
+    }
+  }
+});

+ 70 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/global-config.js

@@ -0,0 +1,70 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+export default Ember.Component.extend({
+  showingFileBrowser : false,
+  fileBrowser : Ember.inject.service('file-browser'),
+  setUp : function(){
+    if(this.get('actionModel') === undefined || this.get('actionModel') === null){
+      this.set('actionModel',{});
+    }
+    if(this.get('actionModel.jobXml') === undefined){
+      this.set("actionModel.jobXml", Ember.A([]));
+    }
+    if(this.get('actionModel.configuration') === undefined){
+      this.set("actionModel.configuration",{});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+  }.on('init'),
+  saveClicked : false,
+  initialize : function(){
+    this.$('#global_properties_dialog').modal('show');
+    this.$('#global_properties_dialog').modal().on('hidden.bs.modal', function() {
+      if(this.get('saveClicked')){
+        this.sendAction('saveGlobalConfig');
+      }else{
+        this.sendAction('closeGlobalConfig');
+      }
+    }.bind(this));
+    this.get('fileBrowser').on('fileBrowserOpened',function(context){
+      this.get('fileBrowser').setContext(context);
+    }.bind(this));
+  }.on('didInsertElement'),
+  actions : {
+    close (){
+      this.$('#global_properties_dialog').modal('hide');
+      this.set('saveClicked', false);
+    },
+    save(){
+      this.$('#global_properties_dialog').modal('hide');
+      this.set('saveClicked', true);
+    },
+    openFileBrowser(model, context){
+      if(!context){
+        context = this;
+      }
+      this.get('fileBrowser').trigger('fileBrowserOpened',context);
+      this.set('filePathModel', model);
+      this.set('showingFileBrowser',true);
+    },
+    closeFileBrowser(){
+      this.get('fileBrowser').getContext().trigger('fileSelected', this.get('filePath'));
+      this.set("showingFileBrowser", false);
+    }
+  }
+});

+ 114 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/hdfs-browser.js

@@ -0,0 +1,114 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import HdfsViewerConfig from '../utils/hdfsviewer';
+export default Ember.Component.extend({
+  config: HdfsViewerConfig.create(),
+  initialize:function(){
+    var self=this;
+    self.$("#filediv").modal("show");
+    self.$("#filediv").on('hidden.bs.modal', function (e) {
+      self.sendAction('closeWorkflowSubmitConfigs');
+      self.sendAction("closeFileBrowser");
+    });
+
+  }.on('didInsertElement'),
+  selectFileType: "all",//can be all/file/folder
+  selectedPath:"",
+  isDirectory:false,
+  callback: null,
+  alertMessage:null,
+  alertDetails:null,
+  alertType:null,
+  uploadSelected: false,
+  isFilePathInvalid: Ember.computed('selectedPath',function() {
+    return this.get("selectedPath").indexOf("<")>-1;
+  }),
+  showNotification(data){
+    if (!data){
+      return;
+    }
+    if (data.type==="success"){
+      this.set("alertType","success");
+    }
+    if (data.type==="error"){
+      this.set("alertType","danger");
+    }
+    this.set("alertDetails",data.details);
+    this.set("alertMessage",data.message);
+  },
+  actions: {
+    viewerError() {
+    },
+    createFolder(){
+      var self=this;
+      var $elem=this.$("#selectedPath");
+      //$elem.val($elem.val()+"/");
+      var folderHint="<enter folder here>";
+      this.set("selectedPath",this.get("selectedPath")+"/"+folderHint);
+      setTimeout(function(){
+        $elem[0].selectionStart = $elem[0].selectionEnd = self.get("selectedPath").length-folderHint.length;
+      },10);
+
+      $elem.focus();
+
+    },
+    viewerSelectedPath(data) {
+      this.set("selectedPath",data.path);
+      this.set("isDirectory",data.isDirectory);
+      this.set("alertMessage",null);
+    },
+    selectFile(){
+      if (this.get("selectedPath")===""){
+        this.showNotification( {"type": "error", "message": "Please fill the settings value"});
+        return false;
+      }
+      if (this.get("selectFileType")==="folder" && !this.get("isDirectory")){
+        this.showNotification( {"type": "error", "message": "Only folders can be selected"});
+        return false;
+      }
+      this.set("filePath",this.get("selectedPath"));
+      this.$("#filediv").modal("hide");
+    },
+    uploadSelect(){
+      this.set("uploadSelected",true);
+    },
+
+    closeUpload(){
+      this.set("uploadSelected",false);
+    },
+    uploadSuccess(e){
+    },
+    uploadFailure(textStatus,errorThrown){
+      this.showNotification({
+        "type": "error",
+        "message": "Upload Failed",
+        "details":textStatus
+      });
+    },
+    uploadProgress(e){
+    },
+    uploadValidation(e){
+      this.showNotification({
+        "type": "error",
+        "message": "Upload Failed",
+        "details":e
+      });
+    }
+  }
+});

+ 19 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/header-logo-user-bar.js

@@ -0,0 +1,19 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+export default Ember.Component.extend({});

+ 22 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/help-icon.js

@@ -0,0 +1,22 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  tagName: ''
+});

+ 104 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/hive-action.js

@@ -0,0 +1,104 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations,{
+  hiveOptionObserver : Ember.observer('isScript',function(){
+    if(this.get('isScript')){
+      this.set("actionModel.query", undefined);
+    }else{
+      this.set("actionModel.script",  undefined);
+    }
+  }),
+  setUp : function(){
+    if(this.get('actionModel.script')){
+      this.set('isScript', true);
+    }else if(this.get('actionModel.query')){
+      this.set('isScript', false);
+    }else{
+      this.set('isScript', true);
+    }
+    if(this.get('actionModel.jobXml') === undefined){
+      this.set("actionModel.jobXml", Ember.A([]));
+    }
+    if(this.get('actionModel.args') === undefined){
+      this.set("actionModel.args", Ember.A([]));
+    }
+    if(this.get('actionModel.params') === undefined){
+      this.set("actionModel.params", Ember.A([]));
+    }
+    if(this.get('actionModel.files') === undefined){
+      this.set("actionModel.files", Ember.A([]));
+    }
+    if(this.get('actionModel.archives') === undefined){
+      this.set("actionModel.archives", Ember.A([]));
+    }
+    if(this.get('actionModel.prepare') === undefined){
+      this.set("actionModel.prepare", Ember.A([]));
+    }
+    if(this.get('actionModel.configuration') === undefined){
+      this.set("actionModel.configuration",{});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+  }.on('init'),
+  initialize : function(){
+    this.sendAction('register','hiveAction', this);
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+  }.on('didInsertElement'),
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  validations : {
+    'actionModel.script': {
+      presence: {
+        'if':'isScript',
+        'message' : 'You need to provide a value for Script'
+      }
+    },
+    'actionModel.query': {
+      presence: {
+        unless :'isScript',
+        'message' : 'You need to provide a value for Query'
+      }
+    }
+  },
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    },
+    onHiveOptionChange(value){
+      if(value === "script"){
+        this.set('isScript',true);
+      }else{
+        this.set('isScript',false);
+      }
+    }
+  }
+});

+ 109 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/hive2-action.js

@@ -0,0 +1,109 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations,{
+  hiveOptionObserver : Ember.observer('isScript',function(){
+    if(this.get('isScript')){
+      this.set("actionModel.query", undefined);
+    }else{
+      this.set("actionModel.script",  undefined);
+    }
+  }),
+  setUp : function(){
+    if(this.get('actionModel.script')){
+      this.set('isScript', true);
+    }else if(this.get('actionModel.query')){
+      this.set('isScript', false);
+    }else{
+      this.set('isScript', true);
+    }
+    if(this.get('actionModel.jobXml') === undefined){
+      this.set("actionModel.jobXml", Ember.A([]));
+    }
+    if(this.get('actionModel.args') === undefined){
+      this.set("actionModel.args", Ember.A([]));
+    }
+    if(this.get('actionModel.params') === undefined){
+      this.set("actionModel.params", Ember.A([]));
+    }
+    if(this.get('actionModel.files') === undefined){
+      this.set("actionModel.files", Ember.A([]));
+    }
+    if(this.get('actionModel.archives') === undefined){
+      this.set("actionModel.archives", Ember.A([]));
+    }
+    if(this.get('actionModel.prepare') === undefined){
+      this.set("actionModel.prepare", Ember.A([]));
+    }
+    if(this.get('actionModel.configuration') === undefined){
+      this.set("actionModel.configuration",{});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+  }.on('init'),
+  initialize : function(){
+    this.sendAction('register','hiveAction', this);
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+  }.on('didInsertElement'),
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  validations : {
+    'actionModel.script': {
+      presence: {
+        'if':'isScript',
+        'message' : 'You need to provide a value for Script'
+      }
+    },
+    'actionModel.query': {
+      presence: {
+        unless :'isScript',
+        'message' : 'You need to provide a value for Query'
+      }
+    },
+    'actionModel.jdbc-url': {
+      presence: {
+        'message' : 'You need to provide a value for jdbc url'
+      }
+    }
+  },
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    },
+    onHiveOptionChange(value){
+      if(value === "script"){
+        this.set('isScript',true);
+      }else{
+        this.set('isScript',false);
+      }
+    }
+  }
+});

+ 101 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/java-action.js

@@ -0,0 +1,101 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, {
+  fileBrowser : Ember.inject.service('file-browser'),
+  javaOptsObserver : Ember.observer('isSingle',function(){
+    if(this.get('isSingle')){
+      this.set("actionModel.javaOpt", undefined);
+    }else{
+      this.set("actionModel.javaOpts", undefined);
+    }
+  }),
+  setUp : function(){
+    if(this.get('actionModel.args') === undefined){
+      this.set("actionModel.args", Ember.A([]));
+    }
+    if(this.get('actionModel.javaOpt') === undefined && !this.get('actionModel.javaOpts')){
+      this.set("actionModel.javaOpt", Ember.A([]));
+      this.set('isSingle', false);
+    }else if(this.get('actionModel.javaOpt') === undefined && this.get('actionModel.javaOpts')){
+      this.set('isSingle', true);
+    }else{
+      this.set('isSingle', false);
+    }
+    if(this.get('actionModel.files') === undefined){
+      this.set("actionModel.files", Ember.A([]));
+    }
+    if(this.get('actionModel.jobXml') === undefined){
+      this.set("actionModel.jobXml", Ember.A([]));
+    }
+    if(this.get('actionModel.archives') === undefined){
+      this.set("actionModel.archives", Ember.A([]));
+    }
+    if(this.get('actionModel.prepare') === undefined){
+      this.set("actionModel.prepare", Ember.A([]));
+    }
+    if(this.get('actionModel.configuration') === undefined){
+      this.set("actionModel.configuration",{});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+  }.on('init'),
+  initialize : function(){
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+    this.sendAction('register','javaAction', this);
+  }.on('didInsertElement'),
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  validations : {
+    'actionModel.mainClass': {
+      presence: {
+        'message' : 'You need to provide a value for Main Class',
+      },
+      format: {
+        with: /([a-z][a-z_0-9]*\.)*[A-Za-z_]($[A-Za-z_]|[\w_])*/,
+        allowBlank: false,
+        message: 'You need to provide a valid value'
+      }
+    }
+  },
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    },
+    onJavaOptChange(value){
+      if(value === "single"){
+        this.set('isSingle',true);
+      }else{
+        this.set('isSingle',false);
+      }
+    }
+  }
+});

+ 21 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/jdbc-url.js

@@ -0,0 +1,21 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF 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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+});

+ 141 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/job-details.js

@@ -0,0 +1,141 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  error : {},
+  errorMessage : Ember.computed('error', function() {
+    if(this.get('error').status === 400){
+      return 'Remote API Failed';
+    }else if(this.get('error').status === 401){
+      return 'User Not Authorized';
+    }
+  }),
+  id : Ember.computed('model.jobType', function() {
+    if (this.get('model.jobType') === 'wf'){
+      return this.get('model.id');
+    } else if(this.get('model.jobType') === 'coords'){
+      return this.get('model.coordJobId');
+    } else if(this.get('model.jobType') === 'bundles'){
+      return this.get('model.bundleJobId');
+    }
+  }),
+  name : Ember.computed('model.jobType', function() {
+    if (this.get('model.jobType') === 'wf'){
+      return this.get('model.appName');
+    } else if(this.get('model.jobType') === 'coords'){
+      return this.get('model.coordJobName');
+    } else if(this.get('model.jobType') === 'bundles'){
+      return this.get('model.bundleJobName');
+    }
+  }),
+  displayType : Ember.computed('model.jobType', function() {
+    if(this.get('jobType') === 'wf'){
+      return "Workflow";
+    }else if(this.get('jobType') === 'coords'){
+      return "Coordinator";
+    }
+    else if(this.get('jobType') === 'bundles'){
+      return "Bundle";
+    }
+    return "Workflow";
+  }),
+  initialize : function(){
+    if(this.get('currentTab')){
+      this.$('.nav-tabs a[href="'+this.get('currentTab').attr("href")+'"]').tab('show');
+      if(this.get('model.actions')){
+        this.set('model.actionDetails', this.get('model.actions')[0]);
+      }
+    }
+    this.$('.nav-tabs').on('shown.bs.tab', function(event){
+      this.sendAction('onTabChange', this.$(event.target));
+    }.bind(this));
+  }.on('didInsertElement'),
+  actions : {
+    back (){
+      this.sendAction('back');
+    },
+    close : function(){
+      this.sendAction('close');
+    },
+    doRefresh : function(){
+      this.sendAction('doRefresh');
+    },
+    getJobDefinition : function () {
+      Ember.$.get(Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=definition&timezone=GMT',function(response){
+        this.set('model.jobDefinition', (new XMLSerializer()).serializeToString(response).trim());
+      }.bind(this)).fail(function(error){
+        this.set('error',error);
+      }.bind(this));
+    },
+    showFirstActionDetail : function(){
+      this.set('model.actionDetails', this.get('model.actions')[0]);
+    },
+    getJobLog : function (params){
+      var url = Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=log';
+      if(params && params.logFilter){
+        url = url + '&logfilter=' + params.logFilter;
+      }
+      if(params && params.logActionList){
+        url = url + '&type=action&scope='+ params.logActionList;
+      }
+      Ember.$.get(url,function(response){
+        this.set('model.jobLog', response);
+      }.bind(this)).fail(function(error){
+        this.set('error', error);
+      }.bind(this));
+    },
+    getErrorLog : function (){
+      Ember.$.get(Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=errorlog',function(response){
+        this.set('model.errorLog', response);
+      }.bind(this)).fail(function(error){
+        this.set('error', error);
+      }.bind(this));
+    },
+    getAuditLog : function (){
+      Ember.$.get(Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=auditlog',function(response){
+        this.set('model.auditLog', response);
+      }.bind(this)).fail(function(error){
+        this.set('error', error);
+      }.bind(this));
+    },
+    getJobDag : function (){
+      this.set('model.jobDag', Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=graph');
+    },
+    getCoordActionReruns : function () {
+      var url = Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=allruns&type=action';
+      if(this.get('rerunActionList')){
+        url = url + '&scope=' + this.get('rerunActionList');
+      }
+      Ember.$.get(url, function(response){
+        this.set('model.coordActionReruns', response.workflows);
+      }.bind(this)).fail(function(error){
+        this.set('error', error);
+      }.bind(this));
+    },
+    getActionDetails : function (actionInfo) {
+      this.set('model.actionDetails', actionInfo);
+    },
+    showWorkflow : function(workflowId){
+      this.sendAction('showWorkflow', workflowId);
+    },
+    showCoord : function(coordId){
+      this.sendAction('showCoord', coordId);
+    }
+  }
+});

+ 44 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/job-log.js

@@ -0,0 +1,44 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  logFilter :"",
+  logActionList : "",
+  initialize : function(){
+    this.$('[data-toggle="popover"]').popover();
+    /*jshint multistr: true */
+    var filterHelpContent = 'You can filter using search filters as opt1=val1;opt2=val1;opt3=val1. Available options are recent,start,end,loglevel,text,limit,debug\
+    Visit <a href="https://oozie.apache.org/docs/4.2.0/DG_CommandLineTool.html#Filtering_the_server_logs_with_logfilter_options" target="_blank">here</a> for more details';
+    this.$('#logFilterHelp [data-toggle="popover"]').attr('data-content', filterHelpContent);
+    var actionListHelpContent = 'Enter list of actions in the format similiar to 1,3-4,7-40 to get logs for specific coordinator actions.';
+    this.$('#actionlogHelp [data-toggle="popover"]').attr('data-content', actionListHelpContent);
+  }.on('didInsertElement'),
+  actions : {
+    onGetJobLog (){
+      var params = {};
+      if(this.get('logFilter')){
+        params.logFilter = this.get('logFilter');
+      }
+      if(this.get('logActionList')){
+        params.logActionList = this.get('logActionList');
+      }
+      this.sendAction('getJobLog', params);
+    }
+  }
+});

+ 87 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/job-row.js

@@ -0,0 +1,87 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  tagName: 'tr',
+  onRender : function(){
+    this.$('#actions').hide();
+  }.on('didInsertElement'),
+  getJobDetails : function(id){
+    var deferred = Ember.RSVP.defer();
+    Ember.$.get(Ember.ENV.API_URL+'/v2/job/'+id+'?show=info&timezone=GMT&offset=1&len='+Ember.ENV.PAGE_SIZE).done(function(response){
+      if (typeof response === "string") {
+        response = JSON.parse(response);
+      }
+      deferred.resolve(response);
+    }).fail(function(){
+      deferred.reject();
+    });
+    return deferred.promise;
+  },
+  actions : {
+    doAction(action, id) {
+      var deferred = Ember.RSVP.defer();
+      deferred.promise.then(function(){
+        if(action === 'start'){
+          this.set('job.status','RUNNING');
+        }else if(action === 'suspend'){
+          this.set('job.status','SUSPENDED');
+        }else if(action === 'resume'){
+          this.set('job.status','RUNNING');
+        }else if(action === 'stop'){
+          this.set('job.status','STOPPED');
+        }else if(action === 'rerun'){
+          this.set('job.status','RUNNING');
+        }else if(action === 'kill'){
+          this.set('job.status','KILLED');
+        }
+      }.bind(this),function(){
+      }.bind(this));
+      if(action === 'rerun' && this.get('job').bundleJobId){
+        action = 'bundle-'+action;
+      }else if(action === 'rerun' && this.get('job').coordJobId){
+        action = 'coord-'+action;
+      }
+      var params = {id: id, action:action };
+      if(action.indexOf('rerun') > -1){
+        var jobDetailsPromise = this.getJobDetails(id);
+        jobDetailsPromise.then(function(jobInfo){
+          params.conf = jobInfo.conf;
+          this.sendAction("onAction", params, deferred);
+        }.bind(this));
+      }else{
+        this.sendAction("onAction", params, deferred);
+      }
+    },
+    showJobDetails : function(jobId){
+      this.sendAction('showJobDetails', jobId);
+    },
+    showActions () {
+      this.$('#actions-div').hide();
+      this.$('#actions').show();
+    },
+    hideActions () {
+      this.$('#actions-div').show();
+      this.$('#actions').hide();
+    },
+    rowSelected(){
+      this.sendAction('rowSelected');
+    }
+  }
+});

+ 38 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/jobxml-config.js

@@ -0,0 +1,38 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  initialize : function(){
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+  }.on('init'),
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    }
+  }
+});

+ 88 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/map-red-action.js

@@ -0,0 +1,88 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, {
+  hasStaticProps : true,
+  fileBrowser : Ember.inject.service('file-browser'),
+  staticProps : Ember.A([]),
+  setUp : function(){
+    if(this.get('actionModel.files') === undefined){
+      this.set("actionModel.files", Ember.A([]));
+    }
+    if(this.get('actionModel.jobXml') === undefined){
+      this.set("actionModel.jobXml", Ember.A([]));
+    }
+    if(this.get('actionModel.archives') === undefined){
+      this.set("actionModel.archives", Ember.A([]));
+    }
+    if(this.get('actionModel.prepare') === undefined){
+      this.set("actionModel.prepare", Ember.A([]));
+    }
+    if(this.get('actionModel.configuration') === undefined){
+      this.set("actionModel.configuration",{});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+    this.get('staticProps').clear();
+    this.get('staticProps').pushObjects([
+      {name:'mapred.mapper.class',displayName:'Mapper class', value:'', belongsTo:'actionModel.configuration.property'},
+      {name:'mapred.reducer.class',displayName:'Reducer class', value:'', belongsTo:'actionModel.configuration.property'},
+      {name:'mapred.map.tasks',displayName:'No of Tasks', value:'', belongsTo:'actionModel.configuration.property'},
+      {name:'mapred.input.dir',displayName:'Input dir', value:'', belongsTo:'actionModel.configuration.property'},
+      {name:'mapred.output.dir',displayName:'Output dir', value:'', belongsTo:'actionModel.configuration.property'}
+    ]);
+    this.get('staticProps').forEach((property)=>{
+      var propertyExists = this.get(property.belongsTo);
+      if(!propertyExists){
+        return;
+      }
+      var existingStaticProp = this.get(property.belongsTo).findBy('name',property.name);
+      if(existingStaticProp){
+        Ember.set(property,'value',existingStaticProp.value);
+        Ember.set(existingStaticProp,'static', true);
+      }
+    });
+  }.on('init'),
+  initialize : function(){
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+    this.sendAction('register','mapRedAction', this);
+  }.on('didInsertElement'),
+  validations : {
+
+  },
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    }
+  }
+});

+ 48 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/name-value-config.js

@@ -0,0 +1,48 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  multivalued: true,
+  propertyName:'',
+  propertyValue:'',
+  initialize : function(){
+    this.sendAction('register', this, this);
+    this.on('bindInputPlaceholder',function () {
+      this.set('addUnboundValue', true);
+    }.bind(this));
+  }.on('init'),
+  bindInputPlaceholder : function() {
+    if(this.get('addUnboundValue') && this.get('propertyName')){
+      this.addProperty();
+    }
+  }.on('willDestroyElement'),
+  addProperty (){
+    this.get('configuration.property').pushObject({name:this.get('propertyName'),value:this.get('propertyValue')});
+    this.set('propertyName', "");
+    this.set('propertyValue', "");
+  },
+  actions : {
+    addProperty () {
+      this.addProperty();
+    },
+    deleteProperty(index){
+      this.get('configuration.property').removeAt(index);
+    }
+  }
+});

+ 32 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/named-properties.js

@@ -0,0 +1,32 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, {
+  initialize : function () {
+    this.sendAction('register', this, this);
+  }.on('init'),
+  validations : {
+    'property.value': {
+      presence: {
+        'if' :'required',
+        'message' : 'Required'
+      }
+    }
+  }
+});

+ 21 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/pass-word.js

@@ -0,0 +1,21 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF 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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+});

+ 76 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/pig-action.js

@@ -0,0 +1,76 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations,{
+  setUp : function(){
+    if(this.get('actionModel.jobXml') === undefined){
+      this.set("actionModel.jobXml", Ember.A([]));
+    }
+    if(this.get('actionModel.args') === undefined){
+      this.set("actionModel.args", Ember.A([]));
+    }
+    if(this.get('actionModel.files') === undefined){
+      this.set("actionModel.files", Ember.A([]));
+    }
+    if(this.get('actionModel.archives') === undefined){
+      this.set("actionModel.archives", Ember.A([]));
+    }
+    if(this.get('actionModel.prepare') === undefined){
+      this.set("actionModel.prepare", Ember.A([]));
+    }
+    if(this.get('actionModel.param') === undefined){
+      this.set("actionModel.param", Ember.A([]));
+    }
+    if(this.get('actionModel.configuration') === undefined){
+      this.set("actionModel.configuration",{});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+  }.on('init'),
+  initialize : function(){
+    this.sendAction('register','pigAction', this);
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+  }.on('didInsertElement'),
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  validations : {
+    'actionModel.script': {
+      presence: {
+        'message' : 'You need to provide a value for Script',
+      }
+    }
+  },
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    }
+  }
+});

+ 209 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/prepare-config-fs.js

@@ -0,0 +1,209 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, {
+  mkdirORdeleteORtouchz: true,
+  mkdir: 1,
+  delete: 0,
+  touchz: 0,
+  chmod: 0,
+  move: 0,
+  chgrp: 0,
+  multivalued: true,
+  prepareType: 'mkdir',
+  fileBrowser: Ember.inject.service('file-browser'),
+  initialize: function() {
+    this.on('fileSelected', (fileName)=>{
+      var filePathModel = this.get('filePathModel');
+      if(filePathModel && filePathModel.hasOwnProperty("index") && filePathModel.hasOwnProperty("property")){
+        var fileOperation = this.get('fsOps').objectAt(filePathModel.index);
+        var settings = fileOperation.settings;
+        Ember.set(settings, filePathModel.property, fileName);
+        Ember.set(fileOperation, "settings", settings);
+      }else{
+        this.set(this.get('filePathModel'), fileName);
+      }
+    }.bind(this));
+    this.on('bindInputPlaceholder', function() {
+      this.set('addUnboundValue', true);
+    }.bind(this));
+    this.sendAction('register', 'fsOps', this);
+  }.on('init'),
+  bindInputPlaceholder: function() {
+    let type = this.get("prepareType");
+    if (this.validateOperations(type)) {
+      let value = this.get("prepareType");
+      if (value === "chgrp" && this.get('path') && this.get('group')) {
+        this.addPrepare();
+      } else if (value === "move" && this.get('source') && this.get('target')) {
+        this.addPrepare();
+      } else if (value === "chmod" && this.get('path')) {
+        this.addPrepare();
+      } else if (value === "mkdir" || value === "delete" || value === "touchz" && this.get('path')) {
+        this.addPrepare();
+      }
+    }
+  }.on('willDestroyElement'),
+  formPermissions(r, w, e, type){
+    var perm = 0, permObj = {};
+    if(r){
+      perm = perm+4;
+      permObj[type+"read"] = true;
+    }
+    if(w){
+      perm = perm+2;
+      permObj[type+"write"] = true;
+    }
+    if(e){
+      perm = perm+1;
+      permObj[type+"execute"] = true;
+    }
+    permObj[type+"perm"] = perm;
+    return permObj;
+  },
+  addPrepare: function() {
+
+    let value = this.get("prepareType");
+    switch (value) {
+      case "mkdir":
+      case "delete":
+      case "touchz":
+      this.get('fsOps').pushObject({
+        settings: {
+          path: this.get('path')
+        },
+        type: value
+      });
+      break;
+      case "chmod":
+      var oPerm = this.formPermissions(this.get("oread"), this.get("owrite"), this.get("oexecute"), "o");
+      var gPerm = this.formPermissions(this.get("gread"), this.get("gwrite"), this.get("gexecute"), "g");
+      var rPerm = this.formPermissions(this.get("rread"), this.get("rwrite"), this.get("rexecute"), "r");
+      var permissionsObj = {};
+      permissionsObj = $.extend(true, oPerm, gPerm);
+      permissionsObj = $.extend(true, permissionsObj, rPerm);
+      var perm = oPerm.operm + ""+ gPerm.gperm + ""+ rPerm.rperm;
+      this.get('fsOps').pushObject({
+        settings: {
+          path: this.get('path'),
+          permissions: perm,
+          permissionsObj: permissionsObj,
+          recursive: this.get('recursive'),
+          dirfiles: this.get('dirFiles')
+        },
+        type: value
+      });
+      break;
+      case "chgrp":
+      this.get('fsOps').pushObject({
+        settings: {
+          path: this.get('path'),
+          group: this.get('group'),
+          recursive: this.get('recursive'),
+          dirfiles: this.get('dirFiles')
+        },
+        type: value
+      });
+      break;
+      case "move":
+      this.get('fsOps').pushObject({
+        settings: {
+          source: this.get('source'),
+          target: this.get('target')
+        },
+        type: value
+      });
+      break;
+    }
+    this.resetFields();
+  },
+  resetFields: function() {
+    this.set('prepareType', "mkdir");
+    this.set('path', "");
+    this.set('source', "");
+    this.set('target', "");
+    this.set('group', "");
+    this.set('permissions', "");
+    this.set('recursive', false);
+    this.set('dirFiles', false);
+  },
+  toggleAllFields: function() {
+    this.set("mkdir", 0);
+    this.set("delete", 0);
+    this.set("chmod", 0);
+    this.set("touchz", 0);
+    this.set("chmod", 0);
+    this.set("move", 0);
+    this.set("chgrp", 0);
+  },
+  validateOperations: function(type) {
+    var flag = true;
+    switch (type) {
+      case "mkdir":
+      case "delete":
+      case "touchz":
+      if (!this.get('path')) {
+        flag = false;
+      }
+      break;
+      case "chmod":
+      if (!this.get('path')) {
+        flag = false;
+      }
+      break;
+      case "chgrp":
+      if (!this.get('path') || !this.get('group')) {
+        flag = false;
+      }
+      break;
+      case "move":
+      if (!this.get('source') || !this.get('target')) {
+        flag = false;
+      }
+      break;
+    }
+    return flag;
+  },
+  actions: {
+    onPrepareTypeChange(value) {
+      this.set('prepareType', value);
+      this.toggleAllFields();
+      this.set(value, 1);
+      if (value === "mkdir" || value === "delete" || value === "touchz") {
+        this.set("mkdirORdeleteORtouchz", true);
+      } else {
+        this.set("mkdirORdeleteORtouchz", false);
+      }
+    },
+    addPrepare() {
+      this.addPrepare();
+    },
+    deletePrepare(index) {
+      this.get('fsOps').removeAt(index);
+    },
+    openFileBrowser(model) {
+      this.set('filePathModel', model);
+      this.sendAction("openFileBrowser", model, this);
+    },
+    openFileBrowserForListItem(index, property){
+      this.set('filePathModel',{index:index, property:property});
+      this.sendAction("openFileBrowser", this.get('filePathModel'), this);
+    }
+  }
+});

+ 66 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/prepare-config.js

@@ -0,0 +1,66 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations,{
+  multivalued: true,
+  prepareType : 'mkdir',
+  fileBrowser : Ember.inject.service('file-browser'),
+  initialize : function(){
+    this.on('fileSelected',function(fileName){
+      if(!Ember.isBlank(this.get('filePathModel'))){
+        var prepareObj = this.get('prepare').objectAt(this.get('filePathModel'));
+        Ember.set(prepareObj,"path", fileName);
+        this.get('prepare').replace(this.get('filePathModel'), 1, prepareObj);
+      }else{
+        this.set('preparePath', fileName);
+      }
+    }.bind(this));
+    this.on('bindInputPlaceholder',function () {
+      this.set('addUnboundValue', true);
+    }.bind(this));
+    this.sendAction('register', 'prepare', this);
+  }.on('init'),
+  bindInputPlaceholder : function(){
+    if(this.get('addUnboundValue') && this.get('prepareType') && this.get('preparePath')){
+      this.addPrepare();
+    }
+  }.on('willDestroyElement'),
+  addPrepare : function (){
+    this.get('prepare').pushObject({type:this.get('prepareType'),path:this.get('preparePath')});
+    this.set('prepareType', "mkdir");
+    this.set('preparePath', "");
+    this.$('#prepare-type-select').prop('selectedIndex', 0);
+  },
+  actions : {
+    onPrepareTypeChange (value) {
+      this.set('prepareType', value);
+    },
+    addPrepare () {
+      this.addPrepare();
+    },
+    deletePrepare(index){
+      this.get('prepare').removeAt(index);
+    },
+    openFileBrowser(model){
+      this.set('filePathModel', model);
+      this.sendAction("openFileBrowser", model, this);
+    }
+  }
+});

+ 202 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/search-create-new-bar.js

@@ -0,0 +1,202 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF 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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend(Ember.Evented,{
+    filter : {},
+    history: Ember.inject.service(),
+    startDate : '',
+    endDate : '',
+    tags : Ember.A([]),
+    jobTypeChanged : Ember.observer('jobType', function() {
+      this.$('#search-field').tagsinput('removeAll');
+      this.set('startDate','');
+      this.set('endDate','');
+    }),
+    populateFilters : function (){
+      this.set('tags', Ember.A([]));
+      var previousFilters = this.get('history').getSearchParams();
+      if(!previousFilters || !previousFilters.filter){
+        return;
+      }else{
+        var filterArray = previousFilters.filter.split(";");
+        filterArray.forEach(function(value){
+          if (value.length < 1) {
+            return;
+          }
+          var valueArr = value.split("=");
+          if(valueArr[0] !== 'startCreatedTime' && valueArr[0] !== 'endCreatedTime'){
+            this.get('tags').pushObject(valueArr[0] + ":" + valueArr[1]);
+          }else if(valueArr[0] === 'startCreatedTime'){
+            this.set('startDate',valueArr[1]);
+          }else if(valueArr[0] === 'endCreatedTime'){
+            this.set('endDate',valueArr[1]);
+          }
+        }.bind(this));
+      }
+    }.on('init'),
+
+    displayType : Ember.computed('jobType', function() {
+      if(this.get('jobType') === 'wf'){
+          return "Workflows";
+      }else if(this.get('jobType') === 'coords'){
+          return "Coordinators";
+      }
+      else if(this.get('jobType') === 'bundles'){
+          return "Bundles";
+      }
+      return "Workflows";
+    }),
+    initializeDatePickers : function(){
+      this.$('#startDate').datetimepicker({
+        useCurrent: false,
+        showClose : true,
+        defaultDate : this.get('startDate')
+      });
+      this.$('#endDate').datetimepicker({
+          useCurrent: false, //Important! See issue #1075
+          showClose : true,
+            defaultDate : this.get('endDate')
+      });
+      this.$("#startDate").on("dp.change", function (e) {
+          this.$('#endDate').data("DateTimePicker").minDate(e.date);
+          this.filterByDate(e.date,'start');
+      }.bind(this));
+      this.$("#endDate").on("dp.change", function (e) {
+          this.$('#startDate').data("DateTimePicker").maxDate(e.date);
+          this.filterByDate(e.date,'end');
+      }.bind(this));
+    }.on('didInsertElement'),
+    refresh : function(){
+      var self = this;
+      var source = ['Status:RUNNING',
+                    'Status:SUSPENDED',
+                    'Status:SUCCEEDED',
+                    'Status:KILLED',
+                    'Status:FAILED'];
+      var substringMatcher = function(strs) {
+        return function findMatches(q, cb) {
+          var searchTerm =  self.$('#search-field').tagsinput('input').val();
+          var originalLength = strs.length;
+          if(self.get('jobType') === 'wf'){
+            strs.push('Status:PREP');
+          }
+          strs.push('Name:'+ searchTerm);
+          strs.push('User:'+ searchTerm);
+          var newLength = strs.length;
+          var matches, substrRegex;
+          matches = [];
+          substrRegex = new RegExp(q, 'i');
+          strs.forEach(function(str) {
+            if (substrRegex.test(str)) {
+              matches.push(str);
+            }
+          });
+          strs.splice(originalLength, newLength - originalLength);
+          cb(matches);
+        };
+      };
+      this.$('#search-field').tagsinput({
+          typeaheadjs: {
+            name: 'source',
+            source: substringMatcher(source),
+            highlight : true
+          }
+      });
+      this.get('tags').forEach(function(value){
+        this.$('#search-field').tagsinput('add', value);
+      }.bind(this));
+      this.$('#search-field').tagsinput('refresh');
+      this.$('#search-field').on('itemAdded itemRemoved',function(){
+        var searchTerms = this.$('#search-field').tagsinput('items');
+        var filter = searchTerms.map(function(value){
+          var eachTag = value.split(":");
+          return eachTag[0].toLowerCase()+"="+eachTag[1];
+        });
+        if(filter.length > 0){
+          this.filter.tags = filter.join(";");
+        }else {
+          this.filter.tags = [];
+        }
+        this.sendAction('onSearch', { type: this.get('jobType'), filter: this.getAllFilters()});
+      }.bind(this));
+      this.$('#search-field').on('beforeItemAdd',function(event){
+        var tag = event.item.split(":");
+        if(tag.length < 2){
+          event.cancel = true;
+          this.$('#search-field').tagsinput('add', 'Name:'+tag[0]);
+        }
+      }.bind(this));
+    }.on('didInsertElement'),
+
+    filterByDate(date, dateType){
+      var queryParam;
+      if(dateType === 'start'){
+        queryParam = "startCreatedTime";
+      }else{
+        queryParam = "endCreatedTime";
+      }
+      if (date._isAMomentObject) {
+        var dateFilter = queryParam +"="+ date.format("YYYY-MM-DDThh:mm")+'Z';
+        this.filter[queryParam] = dateFilter;
+      } else {
+        delete this.filter[queryParam];
+      }
+      this.sendAction('onSearch', { type: this.get('jobType'), filter: this.getAllFilters() });
+    },
+
+    getAllFilters(){
+      var allFilters = [];
+      Object.keys(this.filter).forEach(function(value){
+        allFilters.push(this.filter[value]);
+      }.bind(this));
+      return allFilters.join(";");
+    },
+
+    actions: {
+        launchDesign() {
+            this.sendAction('onCreate');
+        },
+        search(type) {
+            var filter = this.get('filterValue'),
+                elem = this.$("#" + type + "_btn");
+            this.$(".scope-btn").removeClass("btn-primary");
+            elem.addClass("btn-primary");
+            this.sendAction('onSearch', { type: type, filter: filter });
+        },
+        refresh(){
+          this.sendAction('onSearch', this.get('history').getSearchParams());
+        },
+        showDatePicker(dateType) {
+          if (dateType === 'start') {
+            this.$("#startDate").trigger("dp.show");
+          } else {
+            this.$("#endDate").trigger("dp.show");
+          }
+        },
+        onClear(type) {
+          if (type ==='start' && this.get('startDate') === "") {
+            this.filterByDate("", type);
+          } else if (type ==='start' && this.get('endDate') === "") {
+            this.filterByDate("", type);
+          }
+
+        }
+    }
+
+});

+ 128 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/search-table.js

@@ -0,0 +1,128 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  showBulkAction : false,
+  history: Ember.inject.service(),
+  currentPage : Ember.computed('jobs.start',function(){
+    if(Ember.isBlank(this.get('jobs.start'))){
+      return 1;
+    }
+    var roundedStart = this.get('jobs.start') - this.get('jobs.start') % 10;
+    return (roundedStart / this.get('jobs.pageSize'))+1;
+  }),
+  rendered : function(){
+    this.sendAction('onSearch', this.get('history').getSearchParams());
+  }.on('didInsertElement'),
+  actions: {
+    selectAll() {
+      this.$(".cbox").click();
+      this.$('#ba_buttons').toggleClass('shown');
+    },
+    onAction(params, deferred) {
+      this.sendAction("onAction", params, deferred);
+    },
+    refresh (){
+      this.sendAction("doRefresh");
+    },
+    doBulkAction(action){
+      var filter = '';
+      var deferred = Ember.RSVP.defer();
+      this.$('.cbox:checked').each(function(index, element){
+        filter = filter + "name=" + this.$(element).attr('name')+ ";";
+      }.bind(this));
+      var params = {};
+      this.$('#bulk-action-loader').removeClass('hidden');
+      params.action = action;
+      params.filter = filter;
+      params.jobType = this.get('jobs.jobTypeValue');
+      params.start = this.get('jobs.start');
+      params.len = this.get('jobs.pageSize');
+      this.sendAction('onBulkAction', params, deferred);
+      deferred.promise.then(function(){
+        this.sendAction("doRefresh");
+        this.$('#bulk-action-loader').addClass('hidden');
+        this.set('showBulkAction', false);
+      }.bind(this), function(){
+        this.$('#bulk-action-loader').addClass('hidden');
+      }.bind(this));
+    },
+    page: function (page) {
+      var size = this.get('jobs.pageSize'),
+      jobType = this.get('jobs.jobTypeValue'),
+      filter = this.get('jobs.filterValue'),
+      start = (size * (page - 1) + 1);
+      start = start || 1;
+      this.sendAction("onSearch", { type: jobType, start: start, filter: filter });
+    },
+    prev: function (page) {
+      page = page - 1;
+      var size = this.get('jobs.pageSize'),
+      jobType = this.get('jobs.jobTypeValue'),
+      filter = this.get('jobs.filterValue'),
+      start = (size * (page - 1) + 1);
+
+      if (page >= 0) {
+        start = start || 1;
+        this.sendAction("onSearch", { type: jobType, start: start, filter: filter });
+      }
+    },
+    next: function (page) {
+      page = page + 1;
+      var size = this.get('jobs.pageSize'),
+      jobType = this.get('jobs.jobTypeValue'),
+      filter = this.get('jobs.filterValue'),
+      total = this.get('jobs.totalValue'),
+      start = (size * (page - 1) + 1);
+      if (start < total) {
+        start = start || 1;
+        this.sendAction("onSearch", { type: jobType, start: start, filter: filter });
+      }
+      this.sendAction("onSearch", { type: jobType, start: start, filter: filter });
+    },
+    showJobDetails : function(jobId){
+      this.sendAction('onShowJobDetails',{type:this.get('jobs.jobTypeValue'), id:jobId});
+    },
+    rowSelected : function(){
+      if(this.$('.cbox:checked').length > 0){
+        this.set('showBulkAction', true);
+        var status = [];
+        this.$('.cbox:checked').each((index, element)=>{
+          status.push(this.$(element).attr('data-status'));
+        }.bind(this));
+        var isSame = status.every(function(value, idx, array){
+          return idx === 0 || value === array[idx - 1];
+        });
+        if(isSame && status[0] === 'SUSPENDED'){
+          this.set('toggleResume', 'enabled');
+          this.set('toggleSuspend', 'disabled');
+        }else if(isSame && status[0] === 'RUNNING'){
+          this.set('toggleSuspend', 'enabled');
+          this.set('toggleResume', 'disabled');
+        }else{
+          this.set('toggleKill', 'enabled');
+          this.set('toggleSuspend', 'disabled');
+          this.set('toggleResume', 'disabled');
+        }
+      }else{
+        this.set('showBulkAction', false);
+      }
+    }
+  }
+});

+ 74 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/shell-action.js

@@ -0,0 +1,74 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations,{
+  initialize : function(){
+    this.sendAction('register','shellAction', this);
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+    if(this.get('actionModel.jobXml') === undefined){
+      this.set("actionModel.jobXml", Ember.A([]));
+    }
+    if(this.get('actionModel.args') === undefined){
+      this.set("actionModel.args", Ember.A([]));
+    }
+    if(this.get('actionModel.envVar') === undefined){
+      this.set("actionModel.envVar", Ember.A([]));
+    }
+    if(this.get('actionModel.files') === undefined){
+      this.set("actionModel.files", Ember.A([]));
+    }
+    if(this.get('actionModel.archives') === undefined){
+      this.set("actionModel.archives", Ember.A([]));
+    }
+    if(this.get('actionModel.prepare') === undefined){
+      this.set("actionModel.prepare", Ember.A([]));
+    }
+    if(this.get('actionModel.configuration') === undefined){
+      this.set("actionModel.configuration",{});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+  }.on('didInsertElement'),
+  validations : {
+    'actionModel.exec': {
+      presence: {
+        'message' : 'You need to provide a value for Exec'
+      }
+    }
+  },
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    }
+  }
+});

+ 147 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/sla-info.js

@@ -0,0 +1,147 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, {
+  alertEvents: Ember.A([]),
+  timeUnitOptions : Ember.A([]),
+  nominalTime : '',
+  initialize : function(){
+    this.set('alertEvents', Ember.A([]));
+    this.get('alertEvents').pushObject({eventType:'start_miss', alertEnabled:false, displayName :'Start Miss'});
+    this.get('alertEvents').pushObject({eventType:'end_miss', alertEnabled:false, displayName : 'End Miss'});
+    this.get('alertEvents').pushObject({eventType:'duration_miss', alertEnabled:false, displayName:'Duration Miss'});
+
+    Ember.addObserver(this, 'alertEvents.@each.alertEnabled', this, this.alertEventsObserver);
+
+    this.set('timeUnitOptions',Ember.A([]));
+    this.get('timeUnitOptions').pushObject({value:'',displayName:'Select'});
+    this.get('timeUnitOptions').pushObject({value:'MINUTES',displayName:'Minutes'});
+    this.get('timeUnitOptions').pushObject({value:'HOURS',displayName:'Hours'});
+    this.get('timeUnitOptions').pushObject({value:'DAYS',displayName:'Days'});
+
+    if(this.get('slaInfo.alertEvents')){
+      var alertsFor = this.get('slaInfo.alertEvents').split(",");
+      alertsFor.forEach((alert)=>{
+        Ember.set(this.get('alertEvents').findBy('eventType', alert),'alertEnabled', true);
+      });
+    }
+    if(this.get('slaEnabled') === undefined){
+      this.set('slaEnabled', false);
+    }
+    Ember.addObserver(this, 'slaEnabled', this, this.slaObserver);
+    if(this.get('slaInfo.nominalTime')){
+      var date = new Date(this.get('slaInfo.nominalTime'));
+      if(date && !isNaN(date.getTime())){
+        var utcDate = new Date(date.getTime() + date.getTimezoneOffset()*60*1000);
+        this.set('nominalTime', moment(utcDate).format("MM/DD/YYYY hh:mm A"));
+      }
+    }
+    Ember.addObserver(this, 'nominalTime', this, this.nominalTimeObserver);
+  }.on('init'),
+  alertEventsObserver : function(){
+    var alerts = this.get('alertEvents').filterBy('alertEnabled',true).mapBy('eventType');
+    this.set('slaInfo.alertEvents', alerts.join());
+  },
+  onDestroy : function(){
+    Ember.removeObserver(this, 'alertEvents.@each.alertEnabled', this, this.alertEventsObserver);
+    Ember.removeObserver(this, 'slaEnabled', this, this.slaObserver);
+    Ember.removeObserver(this, 'nominalTime', this, this.nominalTimeObserver);
+  }.on('willDestroyElement'),
+  elementsInserted : function() {
+    this.$('#nominalTime').datetimepicker({
+      useCurrent: false,
+      showClose : true,
+      defaultDate : this.get('slaInfo.nominalTime')
+    });
+    this.sendAction('register','slaInfo', this);
+    if(this.get('slaEnabled')){
+      this.$('#slaCollapse').collapse('show');
+    }
+  }.on('didInsertElement'),
+  nominalTimeObserver : function(){
+    var date = new Date(this.get('nominalTime'));
+    this.set('slaInfo.nominalTime',moment(date).format("YYYY-MM-DDTHH:mm")+'Z');
+  },
+  shouldEnd : Ember.computed.alias('slaInfo.shouldEnd'),
+  shouldStart : Ember.computed.alias('slaInfo.shouldStart'),
+  maxDuration : Ember.computed.alias('slaInfo.maxDuration'),
+  validations : {
+    'nominalTime': {
+      presence: {
+        'if': 'slaEnabled',
+        'message' : 'Required',
+      }
+    },
+    'shouldEnd.time': {
+      presence: {
+        'if': 'slaEnabled',
+        'message' : 'Required',
+      },
+      numericality: {
+        'if': 'slaEnabled',
+        onlyInteger: true,
+        greaterThan: 0,
+        'message' : 'Number Only'
+      }
+    },
+    'shouldStart.time': {
+      numericality: {
+        'if': 'slaEnabled',
+        allowBlank :true,
+        onlyInteger: true,
+        greaterThan: 0,
+        message : 'Number Only'
+      }
+    },
+    'maxDuration.time': {
+      numericality: {
+        'if': 'slaEnabled',
+        allowBlank :true,
+        onlyInteger: true,
+        greaterThan: 0,
+        message : 'Number Only'
+      }
+    },
+    'shouldStart.unit': {
+      presence: {
+        'if': 'shouldStart.time',
+        'message' : 'Required',
+      }
+    },
+    'shouldEnd.unit': {
+      presence: {
+        'if': 'slaEnabled',
+        'message' : 'Required',
+      }
+    },
+    'maxDuration.unit': {
+      presence: {
+        'if': 'maxDuration.time',
+        'message' : 'Required',
+      }
+    }
+  },
+  slaObserver : function(){
+    if(this.get('slaEnabled')){
+      this.$('#slaCollapse').collapse('show');
+    }else{
+      this.$('#slaCollapse').collapse('hide');
+    }
+  }
+});

+ 119 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/spark-action.js

@@ -0,0 +1,119 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+import Constants from '../utils/constants';
+
+export default Ember.Component.extend(EmberValidations,{
+  setup : function(){
+    if(this.get('actionModel.jobXml') === undefined){
+      this.set("actionModel.jobXml", Ember.A([]));
+    }
+    if(this.get('actionModel.args') === undefined){
+      this.set("actionModel.args", Ember.A([]));
+    }
+    if(this.get('actionModel.files') === undefined){
+      this.set("actionModel.files", Ember.A([]));
+    }
+    if(this.get('actionModel.archives') === undefined){
+      this.set("actionModel.archives", Ember.A([]));
+    }
+    if(this.get('actionModel.prepare') === undefined){
+      this.set("actionModel.prepare", Ember.A([]));
+    }
+    if(this.get('actionModel.configuration') === undefined){
+      this.set("actionModel.configuration",{});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+    this.set('mastersList',Ember.copy(Constants.sparkMasterList));
+    this.sendAction('register','sparkAction', this);
+  }.on('init'),
+  initialize : function(){
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+  }.on('didInsertElement'),
+  rendered : function(){
+    if(this.get('actionModel.master')){
+      var master = Constants.sparkMasterList.findBy('value',this.get('actionModel.master'));
+      if(master){
+        this.$("input[name=master][value=" + this.get('actionModel.master') + "]").prop('checked','checked');
+        this.set('disableCustomMaster','disabled');
+      }else{
+        this.$("input[name=master][value=other]").prop('checked','checked');
+        this.set('customMaster',Ember.copy(this.get('actionModel.master')));
+        this.set('disableCustomMaster', false);
+      }
+    }
+  }.on('didInsertElement'),
+  jarObserver : Ember.observer('actionModel.jar',function(){
+    var isJar =  this.get('actionModel.jar') && this.get('actionModel.jar').endsWith('.jar');
+    this.set('isJar', isJar);
+    if(!isJar){
+      this.set('actionModel.class', undefined);
+    }
+  }),
+  validations : {
+    'actionModel.master': {
+      presence: {
+        'message' : 'You need to provide a value for Runs on (Master)'
+      }
+    },
+    'actionModel.jar': {
+      presence: {
+        'message' : 'You need to provide a value for Application'
+      },
+      format : {
+        'with' : /\.jar$|\.py$/i,
+        'message' : 'You need to provide a .jar or .py file'
+      }
+    },
+    'actionModel.sparkName': {
+      presence: {
+        'message' : 'You need to provide a value for Name'
+      }
+    }
+  },
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    },
+    onMasterChange (elt){
+      var value = this.$(elt).val();
+      if(value !== 'other'){
+        this.set('actionModel.master',value);
+        this.set('customMaster','');
+        this.set('disableCustomMaster','disabled');
+      }else{
+        this.set('disableCustomMaster', false);
+      }
+    }
+  }
+});

+ 85 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/sqoop-action.js

@@ -0,0 +1,85 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations,{
+  sqoopSendType : Ember.observer('isArg',function(){
+    if(this.get('isArg')){
+      this.set("actionModel.command", undefined);
+    }else{
+      this.set("actionModel.args",  Ember.A([]));
+    }
+  }),
+  initialize : function(){
+    this.sendAction('register','sqoopAction', this);
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+    if(this.get('actionModel.jobXml') === undefined){
+      this.set("actionModel.jobXml", Ember.A([]));
+    }
+    if(this.get('actionModel.args') === undefined && !this.get('actionModel.command')){
+      this.set("actionModel.args", Ember.A([]));
+      this.set('isArg', false);
+    }else if(this.get('actionModel.args') && this.get('actionModel.args').length > 0){
+      this.set('isArg', true);
+    }else{
+      this.set('isArg', false);
+    }
+    if(this.get('actionModel.files') === undefined){
+      this.set("actionModel.files", Ember.A([]));
+    }
+    if(this.get('actionModel.archives') === undefined){
+      this.set("actionModel.archives", Ember.A([]));
+    }
+    if(this.get('actionModel.prepare') === undefined){
+      this.set("actionModel.prepare", Ember.A([]));
+    }
+    if(this.get('actionModel.configuration') === undefined){
+      this.set("actionModel.configuration",{});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+  }.on('init'),
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  validations : {
+  },
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    },
+    onSendTypeChange(value){
+      if(value === "arg"){
+        this.set('isArg',true);
+      }else{
+        this.set('isArg',false);
+      }
+    }
+  }
+});

+ 88 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/ssh-action.js

@@ -0,0 +1,88 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, {
+  fileBrowser : Ember.inject.service('file-browser'),
+  javaOptsObserver : Ember.observer('isSingle',function(){
+    if(this.get('isSingle')){
+      this.set("actionModel.arg", Ember.A([]));
+    }else{
+      this.set("actionModel.args", Ember.A([]));
+    }
+  }),
+  setUp : function(){
+    if(this.get('actionModel.args') === undefined){
+      this.set("actionModel.args", Ember.A([]));
+    }
+    if(this.get('actionModel.arg') === undefined){
+      this.set("actionModel.arg", Ember.A([]));
+    }
+    if(this.get('actionModel.arg') === undefined && !this.get('actionModel.args')){
+      this.set("actionModel.arg", Ember.A([]));
+      this.set('isSingle', false);
+    }else if(this.get('actionModel.arg') === undefined && this.get('actionModel.args')){
+      this.set('isSingle', true);
+    }else{
+      this.set('isSingle', false);
+    }
+  }.on('init'),
+  initialize : function(){
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+    this.sendAction('register','sshAction', this);
+  }.on('didInsertElement'),
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  validations : {
+    'actionModel.host': {
+      presence: {
+        'message' : 'You need to provide a value for host',
+      }
+    },
+    'actionModel.command': {
+      presence: {
+        'message' : 'You need to provide a value for command',
+      }
+    }
+  },
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    },
+    onJavaOptChange(value){
+      if(value === "single"){
+        this.set('isSingle',true);
+      }else{
+        this.set('isSingle',false);
+      }
+    }
+  }
+});

+ 58 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/sub-workflow.js

@@ -0,0 +1,58 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations,{
+  setUp : function(){
+    if(this.get('actionModel.configuration') === undefined){
+      this.set("actionModel.configuration",{});
+      this.set("actionModel.configuration.property", Ember.A([]));
+    }
+  }.on('init'),
+  initialize : function(){
+    this.sendAction('register','hiveAction', this);
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+  }.on('didInsertElement'),
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  validations : {
+    'actionModel.appPath': {
+      presence: {
+        'message' : 'You need to provide a value for app path'
+      }
+    }
+  },
+  actions : {
+    openFileBrowser(model, context){
+      if(undefined === context){
+        context = this;
+      }
+      this.set('filePathModel', model);
+      this.sendAction('openFileBrowser', model, context);
+    },
+    register (name, context){
+      this.sendAction('register',name , context);
+    }
+  }
+});

+ 67 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/transition-config.js

@@ -0,0 +1,67 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations,{ validator } from 'ember-validations';
+import {FindNodeMixin} from '../domain/findnode-mixin';
+
+export default Ember.Component.extend(FindNodeMixin, EmberValidations, {
+  selectedKillNode : '',
+  initialize : function(){
+    this.set('descendantNodes',this.getDesendantNodes(this.get('currentNode')));
+    this.set('okToNode', this.getOKToNode(this.get('currentNode')));
+    this.sendAction('register','transition', this);
+    if(Ember.isBlank(this.get('transition.errorNode.name'))){
+      this.set('transition.errorNode', this.get('killNodes').objectAt(0));
+    }
+  }.on('init'),
+  //Work-around : Issue in ember-validations framework
+  errorNode : Ember.computed.alias('transition.errorNode'),
+  validations : {
+    'errorNode.name': {
+      inline : validator(function() {
+        if(!this.get('transition.errorNode.name') || this.get('transition.errorNode.name') === ""){
+          return "You need to provide an error-to transition";
+        }
+      })
+    }
+  },
+  actions : {
+    onSelectChange (value){
+      this.set('selectedKillNode', value);
+      if(this.get('selectedKillNode') === 'createNew'){
+        this.set('transition.errorNode.name', "");
+        this.set('transition.errorNode.message', "");
+        this.set('transition.errorNode.isNew', true);
+      }else if(value === ""){
+        this.set('transition.errorNode', null);
+      }else{
+        this.set('transition.errorNode.isNew',false);
+        var node = this.get('descendantNodes').findBy('name',value);
+        if(node){
+          this.set('transition.errorNode', node);
+        }else{
+          node = this.get('killNodes').findBy('name',value);
+          this.set('transition.errorNode', node);
+        }
+      }
+    },
+    okNodeHandler (value){
+
+    }
+  }
+});

+ 61 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/version-settings.js

@@ -0,0 +1,61 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import Constants from '../utils/constants';
+
+export default Ember.Component.extend({
+  initialize : function(){
+    this.set('currentWorkflowVersion', this.get('schemaVersions').getCurrentWorkflowVersion());
+    this.set('workflowSchemaVersions', this.get('schemaVersions').getWorkflowVersions());
+    this.get('schemaVersions').createCopy();
+    this.set('actionSchemaVersions', Constants.actions);
+    var actionVersions = Ember.A([]);
+    Object.keys(this.get('actionSchemaVersions')).forEach((key)=>{
+      var action = this.get('actionSchemaVersions')[key];
+      if(action.supportsSchema){
+        actionVersions.push({name:action.name, supporedVersions :this.get('schemaVersions').getActionVersions(action.name),selectedVersion:this.get('schemaVersions').getActionVersion(action.name)});
+      }
+    });
+    this.set('actionVersions',actionVersions);
+  }.on('init'),
+  WorkflowVersionObserver : Ember.observer('currentWorkflowVersion',function(){
+    this.get('schemaVersions').setCurrentWorkflowVersion(this.get('currentWorkflowVersion'));
+  }),
+  rendered : function(){
+    this.$('#version-settings-dialog').modal({
+      backdrop: 'static',
+      keyboard: false
+    });
+    this.$('#version-settings-dialog').modal('show');
+    this.$('#version-settings-dialog').modal().on('hidden.bs.modal', function() {
+      this.sendAction('showVersionSettings', false);
+    }.bind(this));
+  }.on('didInsertElement'),
+  actions : {
+    versionChanged : function(actionName, version){
+      this.get('schemaVersions').setActionVersion(actionName, version);
+    },
+    save (){
+      this.$('#version-settings-dialog').modal('hide');
+    },
+    cancel (){
+      this.get('schemaVersions').rollBack();
+      this.$('#version-settings-dialog').modal('hide');
+    }
+  }
+});

+ 190 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-action-editor.js

@@ -0,0 +1,190 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import EmberValidations from 'ember-validations';
+import Constants from '../utils/constants';
+import {SlaInfo} from '../domain/sla-info';
+
+export default Ember.Component.extend(EmberValidations, Ember.Evented,{
+  actionIcons : {
+    "hive": "server",
+    "hive2": "server",
+    "pig": "product-hunt",
+    "sqoop": "database",
+    "hdfs": "copy",
+    "java": "code",
+    "shell": "terminal",
+    "distcp": "clone",
+    "map-reduce": "cubes",
+    "spark": "star",
+    "ssh": "terminal",
+    "sub-workflow":"share-alt-square",
+    "stream": "exchange",
+    "email": "envelope",
+    "fs":"folder-o"
+  },
+  clonedActionModel : {},
+  showingFileBrowser : false,
+  childComponents : new Map(),
+  isActionNode : Ember.computed('nodeType',function(){
+    if(this.get('nodeType') === 'action'){
+      return true;
+    }else{
+      return false;
+    }
+  }),
+  type : Ember.computed('nodeType','actionType',function(){
+    if(this.get('nodeType') === 'action'){
+      return this.get('actionType');
+    }else if(this.get('nodeType') === 'decision' || this.get('nodeType') === 'kill'){
+      return  this.get('nodeType');
+    }
+  }),
+  icon : Ember.computed('actionIcons', 'actionType',function(){
+    return this.get('actionIcons')[this.get('actionType')];
+  }),
+  saveClicked : false,
+  fileBrowser : Ember.inject.service('file-browser'),
+  onDestroy : function(){
+    this.set('transition',{});
+    this.get('childComponents').clear();
+  }.on('willDestroyElement'),
+  setUp : function () {
+    var errorNode = Ember.Object.extend(Ember.Copyable).create({
+      name : "",
+      isNew : false,
+      message : ""
+    });
+    var errorNodeOfCurrentNode = this.get('currentNode').get('errorNode');
+    if(errorNodeOfCurrentNode){
+      errorNode.set('name', errorNodeOfCurrentNode.get('name'));
+      errorNode.set('message', errorNodeOfCurrentNode.get('killMessage'));
+    }
+    var transition = Ember.Object.extend(Ember.Copyable).create({
+      errorNode : errorNode
+    });
+    this.set('transition',transition);
+    if (Ember.isBlank(this.get("actionModel.jobTracker"))){
+      this.set('actionModel.jobTracker',Constants.rmDefaultValue);
+    }
+    if (Ember.isBlank(this.get("actionModel.nameNode"))){
+      this.set('actionModel.nameNode','${nameNode}');
+    }
+    if(this.get('nodeType') === 'action' && this.get('actionModel.slaInfo') === undefined){
+      this.set('actionModel.slaInfo', SlaInfo.create({}));
+    }
+  }.on('init'),
+  initialize : function(){
+    this.$('#action_properties_dialog').modal({
+      backdrop: 'static',
+      keyboard: false
+    });
+    this.$('#action_properties_dialog').modal('show');
+    this.$('#action_properties_dialog').modal().on('hidden.bs.modal', function() {
+      this.sendAction('closeActionEditor', this.get('saveClicked'));
+    }.bind(this));
+    this.get('fileBrowser').on('fileBrowserOpened',function(context){
+      this.get('fileBrowser').setContext(context);
+    }.bind(this));
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+  }.on('didInsertElement'),
+  observeError :function(){
+    if(this.$('#collapseOne label.text-danger').length > 0 && !this.$('#collapseOne').hasClass("in")){
+      this.$('#collapseOne').collapse('show');
+    }
+  }.on('didUpdate'),
+  validateChildrenComponents(){
+    var validationPromises = [];
+    var deferred = Ember.RSVP.defer();
+    if(this.get('childComponents').size === 0){
+      deferred.resolve(true);
+    }else{
+      this.get('childComponents').forEach((childComponent)=>{
+        if(!childComponent.validations){
+          return;
+        }
+        var validationDeferred = Ember.RSVP.defer();
+        childComponent.validate().then(()=>{
+          validationDeferred.resolve();
+        }).catch((e)=>{
+          validationDeferred.reject(e);
+        });
+        validationPromises.push(validationDeferred.promise);
+      });
+      Ember.RSVP.Promise.all(validationPromises).then(function(){
+        deferred.resolve(true);
+      }).catch(function(e){
+        deferred.reject(e);
+      });
+    }
+    return deferred;
+  },
+  processMultivaluedComponents(){
+    this.get('childComponents').forEach((childComponent)=>{
+      if(childComponent.get('multivalued')){
+        childComponent.trigger('bindInputPlaceholder');
+      }
+    });
+  },
+  processStaticProps(){
+    this.get('childComponents').forEach((childComponent)=>{
+      if(childComponent.get('hasStaticProps')){
+        childComponent.get('staticProps').forEach((property)=>{
+          this.get(property.belongsTo).push({name:property.name,value:property.value});
+        });
+      }
+    });
+  },
+  actions : {
+    closeEditor (){
+      this.sendAction('close');
+    },
+    save () {
+      var isFormValid = this.validateChildrenComponents();
+      isFormValid.promise.then(function(){
+        this.validate().then(function(){
+          this.processMultivaluedComponents();
+          this.processStaticProps();
+          this.$('#action_properties_dialog').modal('hide');
+          this.sendAction('addKillNode', this.get('transition.errorNode'));
+          this.set('saveClicked', true);
+        }.bind(this)).catch(function(e){
+        }.bind(this));
+      }.bind(this)).catch(function (e) {
+      });
+
+    },
+    openFileBrowser(model, context){
+      if(!context){
+        context = this;
+      }
+      this.get('fileBrowser').trigger('fileBrowserOpened',context);
+      this.set('filePathModel', model);
+      this.set('showingFileBrowser',true);
+    },
+    closeFileBrowser(){
+      this.get('fileBrowser').getContext().trigger('fileSelected', this.get('filePath'));
+      this.set("showingFileBrowser",false);
+    },
+    registerChild (name, context){
+      this.get('childComponents').set(name, context);
+    }
+  }
+});

+ 28 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-actions.js

@@ -0,0 +1,28 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+export default Ember.Component.extend({
+  actions : {
+    addAction : function(type){
+      this.$(".dr_action").css("background-color", "#fff");
+      this.$("[data-type="+type+"]").css("background-color", "#538EC0");
+      this.$(this.get('element')).popover('hide');
+      this.sendAction("addNode", type);
+    }
+  }
+});

+ 228 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-config.js

@@ -0,0 +1,228 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import Constants from '../utils/constants';
+export default Ember.Component.extend({
+  systemConfigs : Ember.A([]),
+  initialize :function(){
+    this.set("configPropsExists",this.get("workflowSubmitConfigs").props.size>0);
+    var workflowProps =[];
+    var workflowParams = this.get("workflowSubmitConfigs").params;
+    if(workflowParams && workflowParams.configuration && workflowParams.configuration.property){
+      workflowParams.configuration.property.forEach((param)=>{
+        if(param && !param.value){
+          var prop= Ember.Object.create({
+            name: param.name,
+            value: null,
+            isRequired : true
+          });
+          workflowProps.push(prop);
+        }
+      });
+    }
+    this.get("workflowSubmitConfigs").props.forEach(function(value) {
+      if (value!=="${nameNode}" && value!==Constants.rmDefaultValue){
+        var propName = value.trim().substring(2, value.length-1);
+        var isRequired = true;
+        if(workflowParams && workflowParams.configuration && workflowParams.configuration.property){
+          var param = workflowParams.configuration.property.findBy('name', propName);
+          if(param && param.value){
+            isRequired = false;
+          }else {
+            isRequired = true;
+          }
+        }
+        var prop= Ember.Object.create({
+          name: propName,
+          value: null,
+          isRequired : isRequired
+        });
+        workflowProps.push(prop);
+      }
+    });
+    this.set("configMap",workflowProps);
+    this.set("workflowXml",this.get("workflowSubmitConfigs").xml);
+    this.set('systemConfigs', Ember.A([]));
+    this.get('systemConfigs').pushObjects([
+      {displayName: 'Run on submit',name : 'runOnSubmit', value: false},
+      {displayName: 'Use system lib path', name :'useSystemLibPath', value:true},
+      {displayName: 'Rerun on Failure', name : 'rerunOnFailure', value:true}
+    ]);
+    this.set('filePath', Ember.copy(this.get('workflowFilePath')));
+  }.on('init'),
+  rendered : function(){
+    this.$("#configureWorkfowModal").on('hidden.bs.modal', function () {
+      this.sendAction('closeWorkflowSubmitConfigs');
+    }.bind(this));
+    this.$("#configureWorkfowModal").modal("show");
+  }.on('didInsertElement'),
+  showingFileBrowser: false,
+  workflowXml:"",
+  workflowName: "",
+  overwriteWorkflowPath: false,
+  configMap : Ember.A([]),
+  configPropsExists: false,
+  savingInProgress: false,
+  alertType: "",
+  alertMessage:"",
+  alertDetails:"",
+  filePath : "",
+  showNotification(data){
+    if (!data){
+      return;
+    }
+    if (data.type==="success"){
+      this.set("alertType","success");
+    }
+    if (data.type==="error"){
+      this.set("alertType","danger");
+    }
+    this.set("alertDetails",data.details);
+    this.set("alertMessage",data.message);
+  },
+  submitWorkflow(){
+    var self=this;
+    this.set('workflowFilePath', Ember.copy(this.get('filePath')));
+    var url = Ember.ENV.API_URL + "/submitWorkflow?app.path=" +this.get("filePath")+"&overwrite="+this.get("overwriteWorkflowPath");
+    if (this.get("filePath").trim() === ""){//TODO later proper validations.
+      self.showNotification({
+        "type": "error",
+        "message": "Workflow File Path cannot be empty"
+      });
+      return;
+    }
+    var submitConfigs=this.get("configMap");
+    var missingConfig=false;
+    submitConfigs.forEach(function(item) {
+      if (item.isRequired && (!item || !item.value || item.value==="")){
+        missingConfig = true;
+      }else if(!item.isRequired && (!item || !item.value || item.value==="")){
+        return;
+      }else{
+        url = url + "&config." + item.name + "=" + item.value;
+      }
+    }, this);
+    this.get('systemConfigs').forEach((config)=>{
+      url = url + "&oozieconfig." + config.name + "=" + config.value;
+    });
+    if ( this.get("workflowSubmitConfigs").props.has("${resourceManager}")){
+      url= url+"&resourceManager=useDefault";
+    }
+    if (missingConfig){
+      self.showNotification({
+        "type": "error",
+        "message": "You need to fill all the mandatory job properties."
+      });
+      return;
+    }
+
+    this.set("savingInProgress",true);
+    Ember.$.ajax({
+      url: url,
+      method: "POST",
+      dataType: "text",
+      contentType: "text/plain;charset=utf-8",
+      beforeSend: function(request) {
+        request.setRequestHeader("X-XSRF-HEADER", Math.round(Math.random()*100000));
+        request.setRequestHeader("X-Requested-By", "workflow-designer");
+      },
+      data: this.get("workflowXml"),
+      success: function(response) {
+        var result=JSON.parse(response);
+        this.showNotification({
+          "type": "success",
+          "message": "Workflow saved.",
+          "details": "Job id :"+result.id
+        });
+        this.set("savingInProgress",false);
+        var runOnSubmit = this.get('systemConfigs').findBy('name','runOnSubmit');
+        if(runOnSubmit.value){
+          this.startJob(result.id);
+        }
+      }.bind(this),
+      error: function(response) {
+        self.set("savingInProgress",false);
+        self.showNotification({
+          "type": "error",
+          "message": "Error occurred while saving workflow.",
+          "details": this.getParsedErrorResponse(response)
+        });
+      }.bind(this)
+    });
+  },
+  startJob (jobId){
+    this.set('startingInProgress', true);
+    var url = [Ember.ENV.API_URL,
+      "/v2/job/", jobId, "?action=", 'start','&user.name=oozie'
+    ].join("");
+    Ember.$.ajax({
+      url: url,
+      method: 'PUT',
+      beforeSend: function (xhr) {
+        xhr.setRequestHeader("X-XSRF-HEADER", Math.round(Math.random()*100000));
+        xhr.setRequestHeader("X-Requested-By", "Ambari");
+      }
+    }).done(function(){
+      this.set('startingInProgress', false);
+      this.showNotification({
+        "type": "success",
+        "message": "Workflow Started",
+        "details": jobId
+      });
+    }.bind(this)).fail(function(response){
+      this.set('startingInProgress', false);
+      this.showNotification({
+        "type": "error",
+        "message": "Error occurred while starting workflow.",
+        "details": this.getParsedErrorResponse(response)
+      });
+    }.bind(this));
+  },
+  getParsedErrorResponse (response){
+    var detail;
+    if (response.responseText && response.responseText.charAt(0)==="{"){
+      var jsonResp=JSON.parse(response.responseText);
+      if (jsonResp.status==="workflow.oozie.error"){
+        detail="Oozie error. Please check the workflow.";
+      }else{
+        detail=jsonResp.message;
+      }
+    }else{
+      detail=response;
+    }
+    return detail;
+  },
+  actions: {
+    selectWorflowFile(){
+      this.set("showingFileBrowser",true);
+    },
+    closeFileBrowser(){
+      this.set("showingFileBrowser",false);
+    },
+    save(){
+      this.submitWorkflow();
+      return false;
+    },
+    previewXml(){
+      this.set("showingPreview",true);
+    },
+    closePreview(){
+      this.set("showingPreview",false);
+    }
+  }
+});

+ 92 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-credentials.js

@@ -0,0 +1,92 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+
+export default Ember.Component.extend(Ember.Evented, {
+  credentialsList : Ember.A([]),
+  credentialsInfo : {},
+  childComponents : new Map(),
+  initialize : function(){
+    this.get('credentialsList').clear();
+    this.get('childComponents').clear();
+    this.set('credentialsList', Ember.copy(this.get('workflowCredentials')));
+  }.on('init'),
+  rendered : function(){
+    this.$('#workflow_credentials_dialog').modal({
+      backdrop: 'static',
+      keyboard: false
+    });
+    this.$('#workflow_credentials_dialog').modal('show');
+    this.$('#workflow_credentials_dialog').modal().on('hidden.bs.modal', function() {
+      this.sendAction('showCredentials', false);
+    }.bind(this));
+  }.on('didInsertElement'),
+  processMultivaluedComponents(){
+    this.get('childComponents').forEach((childComponent)=>{
+      if(childComponent.get('multivalued')){
+        childComponent.trigger('bindInputPlaceholder');
+      }
+    });
+  },
+  validateChildrenComponents(){
+    var validationPromises = [];
+    var deferred = Ember.RSVP.defer();
+    if(this.get('childComponents').size === 0){
+      deferred.resolve(true);
+    }else{
+      this.get('childComponents').forEach((childComponent)=>{
+        if(!childComponent.validations){
+          return;
+        }
+        var validationDeferred = Ember.RSVP.defer();
+        childComponent.validate().then(()=>{
+          validationDeferred.resolve();
+        }).catch((e)=>{
+          validationDeferred.reject(e);
+        });
+        validationPromises.push(validationDeferred.promise);
+      });
+      Ember.RSVP.Promise.all(validationPromises).then(function(){
+        deferred.resolve(true);
+      }).catch(function(e){
+        deferred.reject(e);
+      });
+    }
+    return deferred;
+  },
+  actions : {
+    register(component, context){
+      this.get('childComponents').set(component, context);
+    },
+    addCredentials (credentialsInfo){
+      this.get('credentialsList').pushObject(credentialsInfo);
+    },
+    deleteCredentials(name){
+      var credentials = this.get('credentialsList').findBy('name', name);
+      this.get('credentialsList').removeObject(credentials);
+    },
+    saveCredentials (){
+      var isFormValid = this.validateChildrenComponents();
+      isFormValid.promise.then(function(){
+        this.processMultivaluedComponents();
+        this.set('workflowCredentials', Ember.copy(this.get('credentialsList')));
+        this.$('#workflow_credentials_dialog').modal('hide');
+      }.bind(this)).catch(function (e) {
+      });
+    }
+  }
+});

+ 31 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-job-details.js

@@ -0,0 +1,31 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+export default Ember.Component.extend({
+  dagUrl:Ember.computed('model.id',function(){
+    return Ember.ENV.API_URL+'/getDag?jobid='+this.get('model.id');
+  }),
+  actions :{
+    getJobLog (params) {
+      this.sendAction('getJobLog', params);
+    },
+    getActionDetails(action){
+      this.sendAction('getActionDetails',action);
+    }
+  }
+});

+ 91 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-node.js

@@ -0,0 +1,91 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Component.extend(Ember.Evented,{
+  actionIcons : {
+    "hive": "server",
+    "hive2": "server",
+    "pig": "product-hunt",
+    "sqoop": "database",
+    "hdfs": "copy",
+    "java": "code",
+    "shell": "terminal",
+    "distcp": "clone",
+    "map-reduce": "cubes",
+    "spark": "star",
+    "ssh": "terminal",
+    "sub-workflow":"share-alt-square",
+    "stream": "exchange",
+    "email": "envelope",
+    "fs":"folder-o"
+  },
+  icon : Ember.computed('actionIcons',function(){
+    return this.get('actionIcons')[this.get('node.actionType')];
+  }),
+  nodeSpecificClasses : Ember.computed('node.type',function(){
+    if(this.get('node.type') === 'start'){
+      return "start";
+    }else if(this.get('node.type') === 'end'){
+      return "end";
+    }else if(this.get('node.type') === 'kill'){
+      return "kill";
+    }else if(this.get('node.type') === 'decision'){
+      return "decision_node";
+    }else if(this.get('node.type') === 'decision_end'){
+      return "decision_end";
+    }else if(this.get('node.type') === 'fork'){
+      return "fa fa-sitemap fork";
+    }else if(this.get('node.type') === 'placeholder'){
+      return "placeholder-node";
+    }else if(this.get('node.type') === 'join'){
+      return "fa fa-sitemap fa-rotate-180 control_flow_node join";
+    }else if(this.get('node.type') === 'action'){
+      return "action-node";
+    }
+  }),
+  rendered : function(){
+    if(this.get('node.type') === 'action') {
+      this.$('input[name="actionName"]').focus();
+      this.$('input[name="actionName"]').select();
+    }
+  }.on('didInsertElement'),
+  nameChanged : function(){
+    this.sendAction("onNameChange");
+  }.observes('node.name'),
+  actions : {
+    registerAddBranchAction(component){
+      this.set("addBranchListener",component);
+    },
+    openEditor (){
+      this.sendAction("openEditor", this.get('node'));
+    },
+    showAddBranch(){
+      this.get("addBranchListener").trigger("showBranchOptions");
+    },
+    addBranch(){
+      this.sendAction("addBranch", this.get('node'));
+    },
+    deleteNode(){
+      this.sendAction("deleteNode", this.get('node'));
+    },
+    addDecisionBranch(settings){
+      this.sendAction("addDecisionBranch",settings);
+    }
+  }
+});

+ 80 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-parameters.js

@@ -0,0 +1,80 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+import EmberValidations,{ validator } from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations, {
+  initialize : function(){
+    if(this.get('parameters') === undefined || this.get('parameters') === null){
+      this.set('parameters',{});
+    }
+    if(this.get('parameters.configuration') === undefined){
+      this.set("parameters.configuration",{});
+      this.set("parameters.configuration.property", Ember.A([]));
+    }
+    this.sendAction('register','workflowParameters',this);
+
+  }.on('init'),
+  validations : {
+    'parameters': {
+      inline : validator(function() {
+        var nameMap = [], errorMsg = undefined;
+        if(this.get('parameters.configuration.property')){
+          this.get('parameters.configuration.property').forEach(function(item, index){
+            if(!item.name){
+              errorMsg = "Name cannot be blank";
+            } else if(nameMap.indexOf(item.name) > -1){
+              errorMsg = "Name cannot be duplicate";
+            } else{
+              nameMap.push(item.name);
+            }
+          });
+          if(errorMsg){
+            return errorMsg;
+          }
+        }
+      })
+    }
+  },
+  rendered : function(){
+    this.$('#workflow_parameters_dialog').modal({
+      backdrop: 'static',
+      keyboard: false
+    });
+    this.$('#workflow_parameters_dialog').modal('show');
+    this.$('#workflow_parameters_dialog').modal().on('hidden.bs.modal', function() {
+      if(this.get('saveClicked')){
+        this.sendAction('saveWorkFlowParam');
+      }else{
+        this.sendAction('closeWorkFlowParam');
+      }
+    }.bind(this));
+  }.on('didInsertElement'),
+  actions : {
+    register(component, context){
+      this.set('nameValueContext', context);
+    },
+    saveParameters (){
+      this.get("nameValueContext").trigger("bindInputPlaceholder");
+      this.validate().then(function(){
+        this.set('saveClicked', true);
+        this.$('#workflow_parameters_dialog').modal('hide');
+      }.bind(this)).catch(function(e){
+      }.bind(this));
+    }
+  }
+});

+ 50 - 0
contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-sla.js

@@ -0,0 +1,50 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+import {SlaInfo} from '../domain/sla-info'
+import EmberValidations from 'ember-validations';
+
+export default Ember.Component.extend(EmberValidations,{
+  slaInfo : {},
+  initialize : function(){
+    this.set('slaInfo',Ember.copy(this.get('workflowSla')));
+  }.on('init'),
+  rendered : function(){
+    this.$('#workflow_sla_dialog').modal({
+      backdrop: 'static',
+      keyboard: false
+    });
+    this.$('#workflow_sla_dialog').modal('show');
+    this.$('#workflow_sla_dialog').modal().on('hidden.bs.modal', function() {
+      this.sendAction('showWorkflowSla', false);
+    }.bind(this));
+  }.on('didInsertElement'),
+  actions : {
+    saveWorkflowSla () {
+      this.get('slaContext').validate().then(()=>{
+        this.set('workflowSla', this.get('slaInfo'));
+        this.$('#workflow_sla_dialog').modal('hide');
+      }.bind(this)). catch(()=>{
+
+      });
+
+    },
+    register (name, context) {
+      this.set('slaContext', context);
+    }
+  }
+});

+ 21 - 0
contrib/views/wfmanager/src/main/resources/ui/app/controllers/application.js

@@ -0,0 +1,21 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Controller.extend({
+});

+ 81 - 0
contrib/views/wfmanager/src/main/resources/ui/app/controllers/dashboard.js

@@ -0,0 +1,81 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Controller.extend({
+  actions: {
+    launchDesign: function () {
+      this.transitionToRoute('design');
+    },
+    doRefresh : function(){
+      this.get('target.router').refresh();
+    },
+    onJobAction: function (params, deferred) {
+      if (Ember.ENV.API_FAILED) {
+        return { error: "Remote API Failed." };
+      }
+      var url = [Ember.ENV.API_URL,
+        "/v2/job/", params.id, "?action=", params.action,'&user.name=oozie'
+      ].join("");
+      var jobActionParams = {
+        url: url,
+        method: 'PUT',
+        beforeSend: function (xhr) {
+          xhr.setRequestHeader("X-XSRF-HEADER", Math.round(Math.random()*100000));
+          xhr.setRequestHeader("X-Requested-By", "Ambari");
+          if(params.action.indexOf('rerun') > -1){
+            xhr.setRequestHeader("Content-Type","application/xml");
+          }
+        }
+      };
+      if(params.action.indexOf('rerun') > -1){
+        jobActionParams.data = params.conf;
+      }
+      Ember.$.ajax(jobActionParams).done(function(){
+        deferred.resolve();
+      }).fail(function(){
+        deferred.reject();
+      });
+    },
+    onBulkAction : function(params, deferred){
+      if (Ember.ENV.API_FAILED) {
+        return { error: "Remote API Failed." };
+      }
+      var url = [Ember.ENV.API_URL,
+        "/v2/jobs?jobtype=", params.jobType,
+        "&offset=", params.start,
+        "&len=", params.len,
+        "&filter=", params.filter,
+        "&action=", params.action,
+        "&user.name=oozie"
+      ].join("");
+      Ember.$.ajax({
+        url: url,
+        method: 'PUT',
+        beforeSend: function (xhr) {
+          xhr.setRequestHeader("X-XSRF-HEADER", Math.round(Math.random()*100000));
+          xhr.setRequestHeader("X-Requested-By", "Ambari");
+        }
+      }).done(function(response){
+        deferred.resolve(response);
+      }).fail(function(response){
+        deferred.reject(response);
+      });
+    }
+  }
+});

+ 26 - 0
contrib/views/wfmanager/src/main/resources/ui/app/controllers/design.js

@@ -0,0 +1,26 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Controller.extend({
+  queryParams: "appPath",
+  appPath : null,
+  model: function(params) {
+    return {};
+  }
+});

+ 60 - 0
contrib/views/wfmanager/src/main/resources/ui/app/controllers/job.js

@@ -0,0 +1,60 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+
+export default Ember.Controller.extend({
+  from : null,
+  fromType : null,
+  actions : {
+    close : function(){
+      this.sendAction('onCloseJobDetails');
+    },
+    doRefresh : function(){
+      this.get('target.router').refresh();
+    },
+    showWorkflow : function(workflowId){
+      this.transitionToRoute('job', {
+        queryParams: {
+          jobType: 'wf',
+          id: workflowId,
+          from : this.get('model.coordJobId'),
+          fromType : this.get('model.jobType')
+        }
+      });
+    },
+    showCoord : function(coordJobId){
+      this.transitionToRoute('job', {
+        queryParams: {
+          jobType: 'coords',
+          id: coordJobId,
+          from : this.get('model.bundleJobId'),
+          fromType : this.get('model.jobType')
+        }
+      });
+    },
+    back : function (){
+      this.transitionToRoute('job', {
+        queryParams: {
+          jobType: this.get('fromType'),
+          id: this.get('from'),
+          from : null,
+          fromType : null
+        }
+      });
+    },
+  }
+});

+ 411 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/actionjob_hanlder.js

@@ -0,0 +1,411 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import CommonUtils from "../utils/common-utils";
+import {MappingMixin,ConfigurationMapper,PrepareMapper} from "../domain/mapping-utils";
+var ActionJobHandler=Ember.Object.extend(MappingMixin,{
+  type:"actionJob",
+  context : {},
+  configurationMapper:ConfigurationMapper.create({}),
+  prepareMapper:PrepareMapper.create({}),
+  setContext(context){
+    this.context = context;
+  },
+  getContext(){
+    return this.context;
+  },
+  handle(nodeDomain,nodeObj,nodeName){
+    var actionObj={};
+    nodeObj[this.get("actionType")]=actionObj;
+    if (this.get("nameSpace")){
+      var schemaVersion=this.schemaVersions.getActionVersion(this.get("actionType"));
+      if (this.get("nameSpace")){
+        var schema=this.get("nameSpace");
+        if (schemaVersion){
+          schema=CommonUtils.extractSchema(schema)+":"+schemaVersion;
+        }
+        nodeObj[this.get("actionType")]["_xmlns"]=schema;
+      }
+    }
+    this.handleMapping(nodeDomain,actionObj,this.mapping,nodeName);
+  },
+  validate(nodeDomain){
+    //overwrite in implmentations and return array of errors object.
+  },
+  handleImport(actionNode,json){
+    this.handleImportMapping(actionNode,json,this.mapping);
+  }
+});
+var JavaActionJobHandler=ActionJobHandler.extend({
+  actionType:"java",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"job-tracker",domain:"jobTracker"},
+      {xml:"name-node",domain:"nameNode"},
+      {xml:"prepare",customHandler:this.prepareMapper},
+      {xml:"job-xml",domain:"jobXml",occurs:"many",domainProperty:"value"},
+      {xml:"configuration",customHandler:this.configurationMapper},
+      {xml:"main-class",domain:"mainClass",mandatory:true},
+      {xml:"java-opts",domain:"javaOpts"},
+      {xml:"java-opt",domain:"javaOpt",occurs:"many", domainProperty:"value"},
+      {xml:"arg",domain:"args",occurs:"many",domainProperty:"value"},
+      {xml:"file",domain:"files",occurs:"many",domainProperty:"value"},
+      {xml:"archive",domain:"archives",occurs:"many",domainProperty:"value"},
+      {xml:"capture-output",domain:"captureOutput",ignoreValue:true}
+    ];
+  },
+
+  handleImport(actionNode,json){
+    this._super(actionNode,json);
+  }
+});
+var PigActionJobHandler=ActionJobHandler.extend({
+  actionType:"pig",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"job-tracker",domain:"jobTracker"},
+      {xml:"name-node",domain:"nameNode"},
+      {xml:"prepare",customHandler:this.prepareMapper},
+      {xml:"job-xml",domain:"jobXml",occurs:"many",domainProperty:"value"},
+      {xml:"configuration",customHandler:this.configurationMapper},
+      {xml:"script",domain:"script",mandatory:true},
+      {xml:"param",domain:"param",domainProperty:"value",occurs:"many"},
+      {xml:"argument",domain:"args",occurs:"many",domainProperty:"value"},
+      {xml:"file",domain:"files",occurs:"many",domainProperty:"value"},
+      {xml:"archive",domain:"archives",occurs:"many",domainProperty:"value"}
+    ];
+  }
+});
+var HiveActionJobHandler=ActionJobHandler.extend({
+  actionType:"hive",
+  nameSpace:"uri:oozie:hive-action:0.6",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"job-tracker",domain:"jobTracker"},
+      {xml:"name-node",domain:"nameNode"},
+      {xml:"prepare",customHandler:this.prepareMapper},
+      {xml:"job-xml",domain:"jobXml",occurs:"many",domainProperty:"value"},
+      {xml:"configuration",customHandler:this.configurationMapper},
+      {xml:"script",domain:"script"},
+      {xml:"query",domain:"query"},
+      {xml:"param",domain:"params",domainProperty:"value",occurs:"many"},
+      {xml:"argument",domain:"args",occurs:"many",domainProperty:"value"},
+      {xml:"file",domain:"files",occurs:"many",domainProperty:"value"},
+      {xml:"archive",domain:"archives",occurs:"many",domainProperty:"value"}
+    ];
+  },
+  validate(nodeDomain){
+    if (Ember.isBlank(nodeDomain.script) && Ember.isBlank(nodeDomain.query)){
+      return [{message : "Either script or query to be set."}];
+    }
+  }
+});
+var Hive2ActionJobHandler=ActionJobHandler.extend({
+  actionType:"hive2",
+  nameSpace:"uri:oozie:hive2-action:0.2",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"job-tracker",domain:"jobTracker"},
+      {xml:"name-node",domain:"nameNode"},
+      {xml:"prepare",customHandler:this.prepareMapper},
+      {xml:"job-xml",domain:"jobXml",occurs:"many",domainProperty:"value"},
+      {xml:"configuration",customHandler:this.configurationMapper},
+      {xml:"jdbc-url",domain:"jdbc-url",mandatory:true},
+      {xml:"password",domain:"password"},
+      {xml:"script",domain:"script"},
+      {xml:"query",domain:"query"},
+      {xml:"param",domain:"params",domainProperty:"value",occurs:"many"},
+      {xml:"argument",domain:"args",occurs:"many",domainProperty:"value"},
+      {xml:"file",domain:"files",occurs:"many",domainProperty:"value"},
+      {xml:"archive",domain:"archives",occurs:"many",domainProperty:"value"}
+    ];
+  },
+  validate(nodeDomain){
+    if (Ember.isBlank(nodeDomain.script) && Ember.isBlank(nodeDomain.query)){
+      return [{message : "Either script or query to be set."}];
+    }
+  }
+});
+
+var SqoopActionJobHandler=ActionJobHandler.extend({
+  actionType:"sqoop",
+  nameSpace:"uri:oozie:sqoop-action:0.4",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"job-tracker",domain:"jobTracker"},
+      {xml:"name-node",domain:"nameNode"},
+      {xml:"prepare",customHandler:this.prepareMapper},
+      {xml:"job-xml",domain:"jobXml",occurs:"many",domainProperty:"value"},
+      {xml:"configuration",customHandler:this.configurationMapper},
+      {xml:"command",domain:"command"},
+      {xml:"argument",domain:"args",occurs:"many",domainProperty:"value"},
+      {xml:"file",domain:"files",occurs:"many",domainProperty:"value"},
+      {xml:"archive",domain:"archives",occurs:"many",domainProperty:"value"}
+    ];
+  },
+  validate(nodeDomain){
+    if (Ember.isBlank(nodeDomain.command) && nodeDomain.args.length<1){
+      return [{message : "Either command or arguments have to be set."}];
+    }
+  }
+});
+var ShellActionJobHandler=ActionJobHandler.extend({
+  actionType:"shell",
+  nameSpace:"uri:oozie:shell-action:0.3",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"job-tracker",domain:"jobTracker"},
+      {xml:"name-node",domain:"nameNode"},
+      {xml:"prepare",customHandler:this.prepareMapper},
+      {xml:"job-xml",domain:"jobXml",occurs:"many",domainProperty:"value"},
+      {xml:"configuration",customHandler:this.configurationMapper},
+      {xml:"exec",domain:"exec",mandatory:true},
+      {xml:"argument",domain:"args",occurs:"many",domainProperty:"value"},
+      {xml:"env-var",domain:"envVar",occurs:"many",domainProperty:"value"},
+      {xml:"file",domain:"files",occurs:"many",domainProperty:"value"},
+      {xml:"archive",domain:"archives",occurs:"many",domainProperty:"value"},
+      {xml:"capture-output",domain:"captureOutput",ignoreValue:true}
+    ];
+  },
+
+  handleImport(actionNode,json){
+    this._super(actionNode,json);
+  }
+});
+var SparkActionJobHandler=ActionJobHandler.extend({
+  actionType:"spark",
+  nameSpace:"uri:oozie:spark-action:0.2",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"job-tracker",domain:"jobTracker"},
+      {xml:"name-node",domain:"nameNode"},
+      {xml:"prepare",customHandler:this.prepareMapper},
+      {xml:"job-xml",domain:"jobXml",occurs:"many",domainProperty:"value"},
+      {xml:"configuration",customHandler:this.configurationMapper},
+      {xml:"master",domain:"master",mandatory:true,displayName:"Runs On"},
+      {xml:"mode",domain:"mode"},
+      {xml:"name",domain:"sparkName",mandatory:true},
+      {xml:"class",domain:"class"},
+      {xml:"jar",domain:"jar",mandatory:true,displayName:"Application"},
+      {xml:"spark-opts",domain:"sparkOpts"},
+      {xml:"arg",domain:"args",occurs:"many",domainProperty:"value"},
+      {xml:"file",domain:"files",occurs:"many",domainProperty:"value"},
+      {xml:"archive",domain:"archives",occurs:"many",domainProperty:"value"}
+    ];
+  },
+  handleImport(actionNode,json){
+    this._super(actionNode,json);
+  }
+});
+var SubWFActionJobHandler=ActionJobHandler.extend({
+  actionType:"sub-workflow",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"app-path",domain:"appPath",mandatory:true},
+      {xml:"propagate-configuration",domain:"propagate-configuration", ignoreValue:true},
+      {xml:"configuration",customHandler:this.configurationMapper}
+    ];
+  }
+});
+var DistCpJobHandler=ActionJobHandler.extend({
+  actionType:"distcp",
+  nameSpace:"uri:oozie:distcp-action:0.2",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"job-tracker",domain:"jobTracker"},
+      {xml:"name-node",domain:"nameNode"},
+      {xml:"prepare",customHandler:this.handlePrepare},
+      {xml:"configuration",customHandler:this.configurationMapper},
+      {xml:"java-opts",domain:"javaOpts"},
+      {xml:"arg",domain:"args",occurs:"many",domainProperty:"value"},
+    ];
+  },
+
+});
+
+var SshActionJobHandler=ActionJobHandler.extend({
+  actionType:"ssh",
+  nameSpace:"uri:oozie:ssh-action:0.2",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"host",domain:"host"},
+      {xml:"command",domain:"command"},
+      {xml:"args",domain:"args",occurs:"many",domainProperty:"value"},
+      {xml:"arg",domain:"arg",occurs:"many",domainProperty:"value"},
+      {xml:"capture-output",domain:"captureOutput",ignoreValue:true}
+    ];
+  },
+
+  handleImport(actionNode,json){
+    this._super(actionNode,json);
+  }
+});
+
+var EmailActionJobHandler=ActionJobHandler.extend({
+  actionType:"email",
+  nameSpace:"uri:oozie:email-action:0.2",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"to",domain:"to",mandatory:true},
+      {xml:"cc",domain:"cc"},
+      {xml:"bcc",domain:"bcc"},
+      {xml:"subject",domain:"subject",mandatory:true},
+      {xml:"body",domain:"body",mandatory:true},
+      {xml:"content_type",domain:"content_type"},
+      {xml:"attachment",domain:"attachment"}
+
+    ];
+  },
+
+  handleImport(actionNode,json){
+    this._super(actionNode,json);
+  }
+});
+
+
+var MapRedActionJobHandler=ActionJobHandler.extend({
+  actionType:"map-reduce",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"job-tracker",domain:"jobTracker"},
+      {xml:"name-node",domain:"nameNode"},
+      {xml:"prepare",customHandler:this.prepareMapper},
+      {xml:"job-xml",domain:"jobXml",occurs:"many",domainProperty:"value"},
+      {xml:"config-class", domain:"config-class"},
+      {xml:"configuration",customHandler:this.configurationMapper},
+      {xml:"file",domain:"files",occurs:"many",domainProperty:"value"},
+      {xml:"archive",domain:"archives",occurs:"many",domainProperty:"value"}
+    ];
+  },
+
+  handleImport(actionNode,json){
+    this._super(actionNode,json);
+  }
+});
+
+var FSActionJobHandler=ActionJobHandler.extend({
+  actionType:"fs",
+  mapping:null,
+  init(){
+    this.mapping=[
+      {xml:"name-node",domain:"nameNode"},
+      {xml:"configuration",customHandler:this.configurationMapper}
+    ];
+  },
+  handle(nodeDomain,nodeObj,nodeName){
+    this._super(nodeDomain,nodeObj,nodeName);
+    if (!nodeDomain.fsOps){
+      return;
+    }
+    nodeDomain.fsOps.forEach(function(fsop){
+      if (!nodeObj.fs[fsop.type]){
+        nodeObj.fs[fsop.type]=[];
+      }
+      switch (fsop.type) {
+        case "delete":
+        nodeObj.fs["delete"].push({"_path":fsop.settings.path});
+        break;
+        case "mkdir":
+        nodeObj.fs["mkdir"].push({"_path":fsop.settings.path});
+        break;
+        case "move":
+        nodeObj.fs["move"].push({"_source":fsop.settings.source,"_target":fsop.settings.target});
+        break;
+        case "touchz":
+        nodeObj.fs["touchz"].push({"_path":fsop.settings.path});
+        break;
+        case "chmod":
+        var conf={"_path":fsop.settings.path,"_permissions":fsop.settings.permissions,"_dir-files":fsop.settings.dirfiles};
+        if (fsop.settings.recursive){
+          conf["recursive"]="";
+        }
+        nodeObj.fs["chmod"].push(conf);
+        break;
+        case "chgrp":
+        var conf={"_path":fsop.settings.path,"_group":fsop.settings.group,"_dir-files":fsop.settings.dirfiles};
+        if (fsop.settings.recursive){
+          conf["recursive"]="";
+        }
+        nodeObj.fs["chgrp"].push(conf);
+        break;
+        default:
+      }
+    });
+  },
+  handleImport(actionNode,json){
+    this._super(actionNode,json);
+    var commandKeys=["delete","mkdir","move","chmod","touchz","chgrp"];
+    var fsOps=actionNode.domain.fsOps=[];
+    Object.keys(json).forEach(function(key){
+      if (commandKeys.contains(key)){
+        var fileOpsJson=null;
+        if (!Ember.isArray(json[key])){
+          fileOpsJson=[json[key]];
+        }else{
+          fileOpsJson=json[key];
+        }
+        fileOpsJson.forEach(function (fileOpJson) {
+          var fsConf={};
+          fsOps.push(fsConf);
+          fsConf.type=key;
+          var settings=fsConf.settings={};
+          switch (key) {
+            case "delete":
+            settings.path=fileOpJson._path;
+            break;
+            case "mkdir":
+            settings.path=fileOpJson._path;
+            break;
+            case "touchz":
+            settings.path=fileOpJson._path;
+            break;
+            case "move":
+            settings.source=fileOpJson._source;
+            settings.target=fileOpJson._target;
+            break;
+            case "chmod":
+            settings.path=fileOpJson._path;
+            settings.permissions=fileOpJson._permissions;
+            settings.dirfiles=fileOpJson["_dir-files"];
+            settings.recursive=fileOpJson["recursive"]?true:false;
+            break;
+            case "chgrp":
+            settings.path=fileOpJson._path;
+            settings.group=fileOpJson._group;
+            settings.dirfiles=fileOpJson["_dir-files"];
+            settings.recursive=fileOpJson["recursive"]?true:false;
+            break;
+          }
+        });
+      }
+    });
+  }
+});
+export{ActionJobHandler,JavaActionJobHandler,PigActionJobHandler,HiveActionJobHandler,SqoopActionJobHandler,ShellActionJobHandler, EmailActionJobHandler,SparkActionJobHandler,MapRedActionJobHandler, Hive2ActionJobHandler, SubWFActionJobHandler, DistCpJobHandler, SshActionJobHandler, FSActionJobHandler};

+ 50 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/default-layout-manager.js

@@ -0,0 +1,50 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+var DefaultLayoutManager= Ember.Object.extend({
+  doDagreLayout(nodes,edges){
+    var g = new dagre.graphlib.Graph();
+    g.setGraph({rankdir:"TB", nodesep:100,edgesep:200,marginx:40,ranksep:130});
+    g.setDefaultEdgeLabel(function() { return {}; });
+
+    for (var i = 0; i < nodes.length; i++) {
+      var n = Ember.$(nodes[i]);
+      g.setNode(n.attr("id"), {width: n.width(), height: n.height()});
+    }
+
+    for (var i = 0; i < edges.length; i++) {
+      var c = edges[i];
+      g.setEdge(c.source,c.target);
+    }
+    dagre.layout(g);
+    return g;
+  },
+  doLayout(component,nodes,edges){
+    var g=this.doDagreLayout(nodes,edges);
+    g.nodes().forEach(function(v) {
+      try{
+        var nodeWidth=component.$("#" + v).width();
+        var displacement=150-Math.floor(nodeWidth/2);
+        Ember.$("#" + v).css("left", g.node(v).x+displacement + "px");
+        Ember.$("#" + v).css("top",g.node(v).y+ "px");
+      }catch(err){
+      }
+    });
+  }
+});
+export {DefaultLayoutManager};

+ 103 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/findnode-mixin.js

@@ -0,0 +1,103 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+var FindNodeMixin= Ember.Mixin.create({
+  findNodeById(startNode,id){
+
+    return this.findNodeByIdInternal(startNode,id);
+  },
+  findNodeByIdInternal(node,id){
+    var self=this;
+    if (node.get("id")===id){
+      return node;
+    }else{
+      if (node.transitions){
+        for (var i = 0; i < node.transitions.length; i++) {
+          var transition=node.transitions[i];
+          var result=self.findNodeByIdInternal(transition.getTargetNode(true),id);
+          if (result){
+            return result;
+          }
+        }
+      }else{
+        return null;
+      }
+    }
+  },
+  getOKToNode(node){
+    var okToNode;
+    if (!node){
+      okToNode = null;
+    }else{
+      var transitions = node.transitions;
+      transitions.forEach(function(trans){
+        if (!trans.condition){
+          okToNode = trans.targetNode;
+          return;
+        }
+      });
+    }
+    return okToNode;
+  },
+  getDesendantNodes(node, ignoreEndNode){
+    if (!node){
+      return null;
+    }
+    var currNode=null;
+    var nodes = [], nxtPath = node.getTargets();
+    for(var i =0; i< nxtPath.length; i++){
+      currNode = nxtPath[i];
+      do {
+        if(this.insertUniqueNodes(currNode, nodes) && currNode){
+          nodes.push(currNode);
+        }
+        var nodesList = currNode.getTargets();
+        if(nodesList.length > 1){
+          for(var j=0; j<nodesList.length; j++) {
+            if(nodesList[j].getTargets().length>1){
+              var tmp = this.getDesendantNodes(nodesList[j]);
+              if(tmp.length){
+                nodes = nodes.concat(tmp);
+              }
+            } else if(this.insertUniqueNodes(nodesList[j], nodes) && nodesList[j]){
+              nodes.push(nodesList[j]);
+              currNode = nodesList[j];
+            } else {
+              currNode = nodesList[j];
+            }
+          }
+        } else {
+          currNode = nodesList[0];
+        }
+      } while(currNode && currNode.get("id") && currNode.get("id") !== "node-end");
+    }
+    if(!ignoreEndNode && currNode){
+      nodes.push(currNode);
+    }
+    return nodes;
+  },
+  insertUniqueNodes(currNode, nodes){
+    if(nodes.indexOf(currNode) > -1){
+    } else {
+      if (!( currNode.isKillNode() || currNode.isPlaceholder() || currNode.isJoinNode() || currNode.isDecisionEnd())){
+        return true;
+      }
+    }
+  },
+});
+export{FindNodeMixin};

+ 35 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/id-gen.js

@@ -0,0 +1,35 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+var IdGen = Ember.Object.extend({
+  idCount: 0,
+  nameCount: 0,
+  usedIds: [],
+  generateNodeId(){
+    return "node_"+(this.idCount++);
+  },
+  generateNodeName(){
+    return this.nameCount++;
+  },
+  reset(){
+    this.nameCount=0;
+    this.idCount=0;
+  }
+});
+var idGen=IdGen.create({});
+export {idGen};

+ 88 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/layout-manager1.js

@@ -0,0 +1,88 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+var LayoutManager1= Ember.Object.extend({
+  doLayout(component,nodes,edges,workflow){
+    var levelMatrix = [];
+    var adjancencyMatrix = {};
+    for (var i = 0; i < edges.length; i++) {
+      var c = edges[i];
+      if(!adjancencyMatrix[c.source.id]){
+        adjancencyMatrix[c.source.id] = [];
+      }
+      adjancencyMatrix[c.source.id].push(c.target.id);
+    }
+    var bfsArray = this.doBFS(nodes[0].id, adjancencyMatrix);
+    var level = 0;
+    bfsArray.forEach((item, index)=>{
+      if(!adjancencyMatrix[item]){
+        return;
+      }
+      adjancencyMatrix[item].forEach((value)=>{
+        if(!levelMatrix[level]){
+          levelMatrix[level] = [];
+        }
+        levelMatrix[level].push(value);
+      });
+      level++;
+    });
+    var startNodeOffset = component.$("#node-start").offset();
+    var top = Math.floor(startNodeOffset.top);
+    var left = Math.floor(startNodeOffset.left);
+    levelMatrix.forEach((nodeArray, level)=>{
+      var levelLength = nodeArray.length;
+      var levelSplit = left/levelLength;
+      nodeArray.forEach((node, idx, array)=>{
+        if(levelLength == 1){
+          Ember.$("#" + node).css("top", top+(level*100)+ "px");
+        }else{
+          Ember.$("#" + node).css("top", top+ "px");
+          if(idx < levelLength/2){
+            Ember.$("#" + node).css("left", left-(idx*100) + "px");
+          }else if(idx === levelLength/2){
+            Ember.$("#" + node).css("left", left + "px");
+          }else{
+            Ember.$("#" + node).css("left", left+(idx*100) + "px");
+          }
+        }
+      });
+    });
+  },
+  doBFS (root, adjancencyMatrix){
+    var bfsResult = [];
+    var level = 0;
+    var visited = {};
+    visited[root] = true;
+    var queue = [];
+    queue.push(root);
+    while(queue.length !== 0){
+      root = queue.shift();
+      bfsResult.push(root);
+      if(!adjancencyMatrix[root]){
+        continue;
+      }
+      adjancencyMatrix[root].forEach(function(node){
+        if(!visited[node]){
+          visited[node] = true;
+          queue.push(node);
+        }
+      });
+    }
+    return bfsResult;
+  },
+});
+export {LayoutManager1};

+ 87 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/layout-manager2.js

@@ -0,0 +1,87 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+var LayoutManager1= Ember.Object.extend({
+  doLayout(component,nodes,edges,workflow){
+    var levelMatrix = [];
+    var adjancencyMatrix = {};
+    for (var i = 0; i < edges.length; i++) {
+      var c = edges[i];
+      if(!adjancencyMatrix[c.source.id]){
+        adjancencyMatrix[c.source.id] = [];
+      }
+      adjancencyMatrix[c.source.id].push(c.target.id);
+    }
+    var bfsArray = this.doBFS(nodes[0].id, adjancencyMatrix);
+    var level = 0;
+    levelMatrix[level] = [];
+    levelMatrix[level++].push(nodes[0].id);
+    bfsArray.forEach((item, index)=>{
+      if(!adjancencyMatrix[item]){
+        return;
+      }
+      adjancencyMatrix[item].forEach((value)=>{
+        if(!levelMatrix[level]){
+          levelMatrix[level] = [];
+        }
+        levelMatrix[level].push(value);
+      });
+      level++;
+    });
+    var top = 0;
+    var left = 400;
+    var startNodeWidth = component.$("#"+nodes[0].id).width();
+    var center = left+(150-Math.floor(startNodeWidth/2));
+    levelMatrix.forEach((nodeArray, level)=>{
+      var levelLength = nodeArray.length;
+      nodeArray.forEach((node, idx, array)=>{
+        Ember.$("#" + node).css("top", top+(level*100)+ "px");
+        var nodeWidth=Math.round(component.$("#" + node).width()/10) * 10;
+        var avgPositionChange = 0;
+        var totalPositions = ((levelLength-1)*(levelLength)/2)*100;
+        var displacement = 150-Math.floor(nodeWidth/2);
+        var avgPositionChange = (totalPositions/levelLength);
+        var eltPosition = idx*100 - avgPositionChange;
+        var total = left + eltPosition + displacement;
+        Ember.$("#" + node).css("left", total + "px");
+      });
+    });
+  },
+  doBFS (root, adjancencyMatrix){
+    var bfsResult = [];
+    var level = 0;
+    var visited = {};
+    visited[root] = true;
+    var queue = [];
+    queue.push(root);
+    while(queue.length !== 0){
+      root = queue.shift();
+      bfsResult.push(root);
+      if(!adjancencyMatrix[root]){
+        continue;
+      }
+      adjancencyMatrix[root].forEach(function(node){
+        if(!visited[node]){
+          visited[node] = true;
+          queue.push(node);
+        }
+      });
+    }
+    return bfsResult;
+  },
+});
+export {LayoutManager1};

+ 254 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/mapping-utils.js

@@ -0,0 +1,254 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import CommonUtils from "../utils/common-utils";
+import {SlaInfo} from '../domain/sla-info';
+var MappingMixin= Ember.Mixin.create({
+  handleMapping(nodeDomain,nodeObj,mappings,nodeName){
+    var self=this;
+    mappings.forEach(function(mapping){
+      var nodeVals=[];
+      if (mapping.mandatory){
+        if (!(nodeDomain[mapping.domain] || mapping.customHandler)){
+          var msgForVal=mapping.domain;
+          if (mapping.displayName){
+            msgForVal=mapping.displayName;
+          }
+          self.getContext().addError({node:{name:nodeName}, message:"Mandatory element missing for "+msgForVal});
+        }
+      }
+      if (nodeDomain && mapping.domain && nodeDomain[mapping.domain]){
+        if (!mapping.occurs){
+          mapping.occurs="once";
+        }
+        var objs=[];
+        if (mapping.occurs==="once"){
+          objs.push(mapping.ignoreValue?"":nodeDomain[mapping.domain]);
+        }else{
+          if (mapping.domainProperty){
+            var tempObjs=[];
+            nodeDomain[mapping.domain].forEach(function(value){
+              tempObjs.push(value[mapping.domainProperty]);
+            });
+            objs=tempObjs;
+          }else{
+            objs=mapping.ignoreValue?"":nodeDomain[mapping.domain];
+          }
+        }
+        if (!Ember.isArray(objs) || Ember.isArray(objs)&& objs.length>0){
+          nodeObj[mapping.xml]=objs;
+        }
+      }else if (mapping.customHandler){
+        var result=mapping.customHandler.hanldeGeneration(nodeDomain,nodeObj);
+        if (result){
+          nodeObj[mapping.xml]=result;
+        }
+      }
+    });
+  },
+
+  handleImportMapping(actionNode,json,mappings){
+    var domain={};
+    if (json._xmlns){
+      var version=CommonUtils.extractSchemaVersion(json._xmlns);
+      this.schemaVersions.setActionVersion(actionNode.actionType,version);
+    }
+    actionNode.set("domain",domain);
+    mappings.forEach(function(mapping){
+      if (!mapping.occurs) {
+        mapping.occurs = "once";
+      }
+      if (mapping.domain && (json[mapping.xml] ||  json[mapping.xml]==="")){
+        if (mapping.occurs==="once"){
+          if (mapping.ignoreValue){
+            domain[mapping.domain]=json[mapping.xml]!==null || json[mapping.xml]!==undefined;
+          }else{
+            domain[mapping.domain]=json[mapping.xml];
+          }
+        }else{
+          if (!domain[mapping.domain]){
+            domain[mapping.domain]=Ember.A([]);
+          }
+          if (Ember.isArray(json[mapping.xml])){
+            if (mapping.domainProperty){
+              json[mapping.xml].forEach(function(mappingVal){
+                var obj={};
+                obj[mapping.domainProperty]=  mappingVal;
+                domain[mapping.domain].pushObject(obj);
+              });
+            }else{
+              domain[mapping.domain].pushObjects(json[mapping.xml]);
+            }
+          }else{
+            if(mapping.domainProperty){
+              var obj = {};
+              obj[mapping.domainProperty]=  json[mapping.xml];
+              domain[mapping.domain].pushObject(obj);
+            }else{
+              domain[mapping.domain].pushObject(json[mapping.xml]);
+            }
+          }
+        }
+      }else if (mapping.customHandler){
+        if (json[mapping.xml]){
+          mapping.customHandler.handleImport(domain,json[mapping.xml]);
+        }
+      }
+    });
+  }
+});
+var ConfigurationMapper= Ember.Object.extend({
+  hanldeGeneration(node,nodeObj){
+    if (!node || !node.configuration || !node.configuration.property){
+      return;
+    }
+    var props=[];
+    node.configuration.property.forEach(function(config){
+      props.push({name:config.name,value:config.value});
+    });
+    if (props.length>0){
+      var configuration={"property":props};
+      return configuration;
+    }
+  },
+  handleImport(domain,nodeObj){
+    if (!nodeObj.property){
+      return;
+    }
+    var configs=Ember.A([]);
+    domain.configuration={property:configs};
+    if (Ember.isArray(nodeObj.property)){
+      nodeObj.property.forEach(function(prop){
+        var propObj=Ember.Object.create({
+          name: prop.name,
+          value: prop.value
+        });
+        configs.pushObject(propObj);
+      });
+    }else{
+      var propObj=Ember.Object.create({
+        name: nodeObj.property.name,
+        value: nodeObj.property.value
+      });
+      configs.pushObject(propObj);
+    }
+  }
+});
+
+var PrepareMapper= Ember.Object.extend({
+  hanldeGeneration(node,nodeObj){
+    if (!node){
+      return;
+    }
+    if (node.prepare && node.prepare.length>0){
+      node.prepare.sort(function(a,b){
+        if (a.type==="delete"){
+          return -1;
+        }else{
+          return 1;
+        }
+      });
+      var prepareObjs={};
+      nodeObj["prepare"]=prepareObjs;
+      node.prepare.forEach(function(prep){
+        if (!prepareObjs[prep.type]){
+          prepareObjs[prep.type]=[];
+        }
+        prepareObjs[prep.type].push({"_path":prep.path});
+      });
+    }
+  },
+  handleImport(domain,nodeObj){
+    domain.prepare=[];
+    if (nodeObj.delete){
+      this.handlePrepActionInternal(domain.prepare,nodeObj.delete,"delete");
+    }
+    if (nodeObj.mkdir){
+      this.handlePrepActionInternal(domain.prepare,nodeObj.mkdir,"mkdir");
+    }
+
+  },
+  handlePrepActionInternal(prepareDomain,actionObjs,type){
+    if (Ember.isArray(actionObjs)){
+      actionObjs.forEach(function(actionObj){
+        var obj=Ember.Object.create({
+          path: actionObj._path,
+          type: type
+        });
+        prepareDomain.push(obj);
+      });
+    }else{
+      var obj=Ember.Object.create({
+        path: actionObjs._path,
+        type: type
+      });
+      prepareDomain.push(obj);
+    }
+  }
+});
+var SLAMapper= Ember.Object.extend({
+  hanldeGeneration(sla,nodeObj){
+    if (sla){
+      var slaInfo=nodeObj["info"]={};
+      slaInfo["__prefix"]="sla";
+      if (sla.nominalTime){
+        slaInfo["nominal-time"]=sla.nominalTime;
+      }
+      if (sla.shouldStart){
+        slaInfo["should-start"]="${"+sla.shouldStart.time+ "*"+sla.shouldStart.unit+"}";
+      }
+      if (sla.shouldEnd){
+        slaInfo["should-end"]="${"+sla.shouldEnd.time+ "*"+sla.shouldEnd.unit+"}";
+      }
+      if (sla.maxDuration){
+        slaInfo["max-duration"]="${"+sla.maxDuration.time+ "*"+sla.maxDuration.unit+"}";
+      }
+      if (sla.alertEvents){
+        slaInfo["alert-events"]=sla.alertEvents;
+      }
+      if (sla.alertContact){
+        slaInfo["alert-contact"]=sla.alertContact;
+      }
+
+    }
+    return nodeObj;
+  },
+  handleImport(domain,infoJson,key){
+    var sla=domain[key]=SlaInfo.create({});
+    if (infoJson["nominal-time"]){
+      sla.nominalTime=infoJson["nominal-time"];
+    }
+    sla.alertContact=infoJson["alert-contact"];
+    sla.alertEvents=infoJson["alert-events"];
+    this.processTimePeriods(sla,infoJson,"should-start","shouldStart");
+    this.processTimePeriods(sla,infoJson,"should-end","shouldEnd");
+    this.processTimePeriods(sla,infoJson,"max-duration","maxDuration");
+  },
+  processTimePeriods(sla,infoJson,key,domainKey){
+    if (infoJson[key]){
+      var timeParts=this.parseSlaTime(infoJson[key],key);
+      sla[domainKey].time=timeParts[0];
+      sla[domainKey].unit=timeParts[1];
+    }
+  },
+  parseSlaTime(str,key){
+    var timePeriod= str.substring(str.indexOf("{")+1,str.indexOf("}"));
+    return timePeriod.split("*");
+  }
+});
+export {MappingMixin,ConfigurationMapper,PrepareMapper,SLAMapper};

+ 120 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/node-factory.js

@@ -0,0 +1,120 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import Constants from '../utils/constants';
+import {Node} from '../domain/node';
+import {idGen} from '../domain/id-gen';
+var NodeFactory= Ember.Object.extend({
+  createStartNode(){
+    return this.createNode({id:'node-start', type:'start',name:"Start"});
+  },
+  createEndNode(name){
+    return this.createNode({id:'node-end', type:'end', name:name});
+  },
+  createKillNode(name,message){
+    return this.createNode({id:this.generateNodeId(), type:"kill", name:name,killMessage:message});
+  },
+  createPlaceholderNode(target,errorPath){
+    var placeholderNode= this.createNode({
+      id:this.generateNodeId(), type:"placeholder",
+      name:"placeholder" + this.generateName(),
+      errorPath:errorPath
+    });
+    placeholderNode.addTransitionTo(target);
+    return placeholderNode;
+  },
+  createActionNode(actionType,name){
+    if (!name){
+      name=actionType;
+    }
+    return this.createNode({id:this.generateNodeId(),type:"action",actionType:actionType, name:name});
+  },
+  generateDecisionNodeWithJoinPlaceHolder(target){
+    var id=this.generateNodeId();
+    var decisionNode=this.createEmptyDecisionNode("decision" + this.generateName(),id);
+    var joinPlaceholder = this.createNode({id:"decision_end_"+id, type:"decision_end", name:"decision_end" + this.generateName()});
+    joinPlaceholder.addTransitionTo(target);
+    var leftPlaceholder =this.createPlaceholderNode(joinPlaceholder) ;
+    decisionNode.addTransitionTo(leftPlaceholder,"default");
+    var rightPlaceholder = this.createPlaceholderNode(joinPlaceholder);
+    decisionNode.addTransitionTo(rightPlaceholder);
+    return decisionNode;
+  },
+  generateDecisionNodeWithoutJoinPlaceHolder(target){
+    var decisionNode=this.createEmptyDecisionNode("decision");
+    var leftPlaceholder =this.createPlaceholderNode(target) ;
+    decisionNode.addTransitionTo(leftPlaceholder,"default");
+    if (Constants.globalSetting.useAdditionalPlaceholderFlowForDecision){
+      var rightPlaceholder = this.createPlaceholderNode(target);
+      decisionNode.addTransitionTo(rightPlaceholder);
+    }
+    return decisionNode;
+  },
+  generateDecisionNode(target){
+    if (Constants.globalSetting.useJoinNodeForDecision){
+      return this.generateDecisionNodeWithJoinPlaceHolder(target);
+    }else{
+      return this.generateDecisionNodeWithoutJoinPlaceHolder(target);
+    }
+  },
+  createEmptyDecisionNode(name,id){
+    if (!id){
+      id=this.generateNodeId();
+    }
+    var decisionNode = this.createNode({id:id, type:"decision", name:name});
+    return decisionNode;
+  },
+  createEmptyForkNode(name,id){
+    if (!id){
+      id=this.generateNodeId();
+    }
+    var forkNode = this.createNode({id:id, type:"fork", name:name});
+    return forkNode;
+  },
+  createEmptyJoinNode(name,id){
+    if (!id){
+      id=this.generateNodeId();
+    }
+    var joinNode=this.createNode({id:id, type:"join", name:name});
+    return joinNode;
+  },
+  generateForkNode(target){
+    var forkId=this.generateNodeId();
+    var forkNode= this.createEmptyForkNode("fork" + this.generateName(),forkId);
+    var joinNode= this.createEmptyJoinNode("join" + this.generateName(),"join_"+forkId);
+    joinNode.addTransitionTo(target);
+
+    var leftPlaceholder =this.createPlaceholderNode(joinNode) ;
+    forkNode.addTransitionTo(leftPlaceholder);
+
+    var rightPlaceholder = this.createPlaceholderNode(joinNode);
+    forkNode.addTransitionTo(rightPlaceholder);
+    return forkNode;
+  },
+  createNode(settings){
+    settings.factory=this;
+    return Node.create(settings);
+  },
+  generateNodeId(){
+    return idGen.generateNodeId();
+  },
+  generateName(){
+    return idGen.generateNodeName();
+  }
+});
+export{NodeFactory};

+ 251 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/node-handler.js

@@ -0,0 +1,251 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import {NodeFactory} from '../domain/node-factory';
+import * as actionJobHandler from '../domain/actionjob_hanlder';
+import {SlaInfo} from '../domain/sla-info';
+import {SLAMapper} from "../domain/mapping-utils";
+var ActionTypeResolver=Ember.Object.extend({
+  actionJobHandlerMap:null,
+  validStandardActionProps:["ok","error","info"],
+  init(){
+    var settings={schemaVersions:this.schemaVersions};
+    this.actionJobHandlerMap=new Map();
+    this.actionJobHandlerMap.set("java",actionJobHandler.JavaActionJobHandler.create(settings));
+    this.actionJobHandlerMap.set("pig",actionJobHandler.PigActionJobHandler.create(settings));
+    this.actionJobHandlerMap.set("hive",actionJobHandler.HiveActionJobHandler.create(settings));
+    this.actionJobHandlerMap.set("hive2",actionJobHandler.Hive2ActionJobHandler.create(settings));
+    this.actionJobHandlerMap.set("sqoop",actionJobHandler.SqoopActionJobHandler.create(settings));
+    this.actionJobHandlerMap.set("shell",actionJobHandler.ShellActionJobHandler.create(settings));
+    this.actionJobHandlerMap.set("spark",actionJobHandler.SparkActionJobHandler.create(settings));
+    this.actionJobHandlerMap.set("map-reduce",actionJobHandler.MapRedActionJobHandler.create(settings));
+    this.actionJobHandlerMap.set("sub-workflow",actionJobHandler.SubWFActionJobHandler.create(settings));
+    this.actionJobHandlerMap.set("distcp",actionJobHandler.DistCpJobHandler.create(settings));
+    this.actionJobHandlerMap.set("ssh",actionJobHandler.SshActionJobHandler.create(settings));
+    this.actionJobHandlerMap.set("email",actionJobHandler.EmailActionJobHandler.create(settings));
+    this.actionJobHandlerMap.set("fs",actionJobHandler.FSActionJobHandler.create(settings));
+  },
+  getActionType(json){
+    var self=this;
+    var resolvedType=null;
+    var problaleActionsTypes=[];
+    Object.keys(json).forEach(function functionName(key) {
+      if (!self.validStandardActionProps.contains(key) && !key.startsWith("_")){
+        problaleActionsTypes.push(key);
+      }
+    });
+    if (problaleActionsTypes.length===1){
+      return problaleActionsTypes[0];
+    }else{
+      console.error("Invalid Action spec..",json);
+    }
+    return resolvedType;
+  },
+  getActionJobHandler(jobType){
+    return this.actionJobHandlerMap.get(jobType);
+  }
+});
+var NodeHandler=Ember.Object.extend({
+  nodeFactory:NodeFactory.create({}),
+  context : {},
+  setContext(context){
+    this.context = context;
+  },
+  getContext(){
+    return this.context;
+  },
+  hasMany(){
+    return true;
+  },
+  handleNode(node){
+    return {"_name":node.get("name")};
+  },
+
+  handleTransitions(transitions,nodeObj){
+
+  },
+  handleImportNode(type,node){
+  },
+  handleImportTransitions(node,json,nodeMap){
+  }
+});
+var StartNodeHandler= NodeHandler.extend({
+  hasMany(){
+    return false;
+  },
+  handleNode(node){
+    return {};
+  },
+  handleTransitions(transitions,nodeObj){
+    if (transitions.length!==1){
+      this.context.addError({node:nodeObj, message:"Invalid Start Node"});
+    }
+    nodeObj["_to"]=transitions[0].targetNode.getName();
+  },
+  handleImportNode(type,node,workflow){
+    return this.nodeFactory.createStartNode();
+  },
+  handleImportTransitions(node,json,nodeMap){
+    node.addTransitionTo(nodeMap.get(json._to).node);
+  }
+});
+var EndNodeHandler= NodeHandler.extend({
+  hasMany(){
+    return false;
+  },
+  handleImportNode(type,node,workflow){
+    return this.nodeFactory.createEndNode("end");
+  },
+
+});
+var KillNodeHandler= NodeHandler.extend({
+  type:"kill",
+  handleImportNode(type,node,workflow){
+    return this.nodeFactory.createKillNode(node._name,node.message);
+  },
+  handleNode(node){
+    var obj= {"_name":node.get("name")};
+    if (!Ember.isBlank(node.get("killMessage"))){
+      obj["message"]=node.get("killMessage");
+    }
+    return obj;
+  }
+});
+var ActionNodeHandler= NodeHandler.extend({
+  type:"action",
+  actionTypeResolver:null,
+  schemaVersions: null,
+  slaMapper: SLAMapper.create({}),
+  init(){
+  },
+  handleNode(node){
+    var nodeObj=this._super(node);
+    if (node.domain && !Ember.isBlank(node.domain.credentials)){
+      nodeObj._cred=node.domain.credentials;
+    }
+    return nodeObj;
+  },
+  handleSla(domain,nodeObj){
+    if (domain && domain.slaEnabled){
+      return this.slaMapper.hanldeGeneration(domain.slaInfo,nodeObj);
+    }
+  },
+
+  handleTransitions(transitions,nodeObj){
+    transitions.forEach(function(tran){
+      if (!tran.condition){
+        nodeObj["ok"]={"_to":tran.getTargetNode().getName()};
+      }else if (tran.condition="error"){
+        nodeObj["error"]={"_to":tran.getTargetNode().getName()};
+      }
+    });
+  },
+  handleImportNode(type,nodeJson,workflow){
+    var actionType=this.get("actionTypeResolver").getActionType(nodeJson);
+
+    var actionNode = this.nodeFactory.createActionNode(actionType,nodeJson._name);
+    if (actionType===null){
+      console.error("cannot handle unsupported node:"+nodeJson);//TODO error handling...
+      return actionNode;
+    }
+    var actionJobHandler=this.get("actionTypeResolver").getActionJobHandler(actionType);
+    if (!actionJobHandler){
+      console.error("cannot handle unsupported action type:"+actionType+" for "+nodeJson._name);//TODO error handling...
+      return actionNode;
+    }
+    actionJobHandler.handleImport(actionNode,nodeJson[actionType]);
+    if (nodeJson.info && nodeJson.info.__prefix==="sla") {
+      actionNode.domain.slaEnabled=true;
+      this.slaMapper.handleImport(actionNode.domain,nodeJson.info,"slaInfo");
+    }
+    actionNode.domain.credentials=nodeJson._cred;
+    return actionNode;
+  },
+  handleImportTransitions(node,json,nodeMap){
+    node.addTransitionTo(nodeMap.get(json.ok._to).node);
+    if (json.error && json.error._to){
+      node.addTransitionTo(nodeMap.get(json.error._to).node,"error");
+    }
+  }
+});
+var DecisionNodeHandler= NodeHandler.extend({
+  type:"decision",
+  handleTransitions(transitions,nodeObj){
+    var swithCaseObj={"case":[]};
+    nodeObj["switch"]=swithCaseObj;
+    var caseObjects=swithCaseObj["case"];
+    transitions.forEach(function(tran){
+      if (tran.condition!=="default"){
+        caseObjects.push({"_to":tran.getTargetNode().getName(),"__text":tran.condition});
+      }else{
+        swithCaseObj['default']={};
+        swithCaseObj['default']["_to"]=tran.getTargetNode().getName();
+      }
+
+    });
+  },
+  handleImportNode(type,node,workflow){
+    return this.nodeFactory.createEmptyDecisionNode(node._name);
+  },
+  handleImportTransitions(node,json,nodeMap){
+    var defaultPath=json.switch.default._to;
+    node.addTransitionTo(nodeMap.get(defaultPath).node,"default");
+    var cases=[];
+    if (Ember.isArray(json.switch.case)){
+      cases=json.switch.case;
+    }else{
+      cases.push(json.switch.case);
+    }
+    cases.forEach(function(caseExpr){
+      node.addTransitionTo(nodeMap.get(caseExpr._to).node,caseExpr.__text);
+    });
+  }
+});
+var ForkNodeHandler= NodeHandler.extend({
+  type:"fork",
+  handleTransitions(transitions,nodeObj){
+    var pathObjects=[];
+    nodeObj["path"]=pathObjects;
+    transitions.forEach(function(tran){
+      pathObjects.push({"_start":tran.getTargetNode().getName()});
+    });
+  },
+  handleImportNode(type,node,workflow){
+    return this.nodeFactory.createEmptyForkNode(node._name);
+  },
+  handleImportTransitions(node,json,nodeMap){
+    json.path.forEach(function(path){
+      node.addTransitionTo(nodeMap.get(path._start).node);
+    });
+  }
+});
+var JoinNodeHandler= NodeHandler.extend({
+  type:"join",
+  handleTransitions(transitions,nodeObj){
+    transitions.forEach(function(tran){
+      nodeObj["_to"]=tran.getTargetNode().getName();
+    });
+  },
+  handleImportNode(type,node,workflow){
+    return this.nodeFactory.createEmptyJoinNode(node._name);
+  },
+  handleImportTransitions(node,json,nodeMap){
+    node.addTransitionTo(nodeMap.get(json._to).node);
+  }
+});
+export{ActionTypeResolver,NodeHandler,StartNodeHandler,EndNodeHandler,KillNodeHandler,ActionNodeHandler,DecisionNodeHandler,ForkNodeHandler,JoinNodeHandler};

+ 38 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/node-visitor.js

@@ -0,0 +1,38 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+var NodeVisitor = Ember.Object.extend({
+  process(node,callback,context){
+    var visitedNodes=[];
+    return this.visitNode(node,callback,context,visitedNodes);
+  },
+  visitNode(node,callback,context,visitedNodes){
+    if (visitedNodes.contains(node.get("id"))){
+      return;
+    }
+    visitedNodes.push(node.get("id"));
+    callback(node,context);
+    var self=this;
+    if (node.transitions){
+      node.transitions.forEach(function(tran){
+        return self.visitNode(tran.targetNode,callback,context,visitedNodes);
+      });
+    }
+  }
+});
+export {NodeVisitor};

+ 212 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/node.js

@@ -0,0 +1,212 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import {FindNodeMixin} from '../domain/findnode-mixin';
+import {Transition} from '../domain/transition';
+import Constants from '../utils/constants';
+var Node = Ember.Object.extend(FindNodeMixin,{
+  id:null,
+  name:null,
+  type:null,
+  actionType:null,/*for action nodes*/
+  transitions:null,
+  domain:null,
+  errorNode:null,
+  killnodeName:"",
+  factory:null,
+  errorMsgs:[],
+  init(){
+    this.transitions = [];
+  },
+  onSave(){
+    var self=this;
+    if (this.isDecisionNode()){
+      var i=0;
+      this.get("domain").forEach(function(tran){
+        self.get("transitions")[i].condition=tran.condition;
+        i++;
+      });
+    }else if (this.isActionNode()){
+      var errorNode=this.get("errorNode");
+      if(errorNode && errorNode.id){
+        this.setErrorTransitionTo(errorNode);
+      }
+    }
+  },
+  validateCustom(){
+    this.errorMsgs=[];
+    //TODO Custom validations
+    return this.errorMsgs;
+  },
+  deleteCurrentKillNode(){
+    var self=this;
+    this.get("transitions").forEach(function(tran){
+      if (tran.condition==="error"){
+        self.removeTransition(tran);
+      }
+    });
+  },
+  getNodeDetail(){
+    var domain={};
+    if (this.isDecisionNode()){
+      var flows=[];
+      this.get("transitions").forEach(function(tran){
+        flows.push({condition: tran.condition, targetName: tran.getTargetNode().getName()});
+      });
+      this.set("domain",flows);
+      return this.get("domain");
+    }else if (this.get("domain")){
+      return this.get("domain");
+    }else{
+      return domain;
+    }
+  },
+  getName(){
+    return this.get("name")?this.get("name"):this.get("id");
+  },
+  findTransitionTo(target){
+    var oldTrans = null;
+    var transitions=this.get("transitions");
+    transitions.forEach(function(tran){
+      if (tran.targetNode === target) {
+        oldTrans = tran;
+        return false;
+      }
+    });
+    return oldTrans;
+  },
+  addTransition(transition) {
+    var transitions=this.get("transitions");
+    if (transitions && transitions.indexOf(transition) === -1) {
+      if (transition.condition==="error"){
+        this.set("errorNode",transition.getTargetNode());
+      }
+      transitions.push(transition);
+    }
+  },
+  addTransitionTo(target,condition){
+    var transition = Transition.create({targetNode:target,sourceNode:this,condition:condition});
+    this.addTransition(transition);
+    return transition;
+  },
+  setErrorTransitionTo(target){
+    var errorTrans=this.getErrorTransition();
+    if (!errorTrans){
+      if (target){
+        if (target.isKillNode()){
+          this.addTransitionTo(target,"error");
+        }else{
+          this.addTransitionTo(target,"error");
+        }
+      }
+    }else{
+      errorTrans.set("targetNode",target);
+    }
+  },
+
+  removeTransition(transition){
+    var transitions=this.get("transitions");
+    if (transition && this.transitions.indexOf(transition) > -1) {
+      this.transitions.splice(this.transitions.indexOf(transition), 1);
+    }
+  },
+  hasTransition(){
+    var transitions=this.get("transitions");
+    if (!transitions){
+      return false;
+    }else{
+      return transitions.length>0;
+    }
+  },
+  getErrorTransition(){
+    var errorTrans=null;
+    this.get("transitions").forEach(function(tran){
+      if (tran.condition==="error"){
+        errorTrans=tran;
+        return;
+      }
+    });
+    return errorTrans;
+  },
+  getOkTransitionCount(){
+    var count=0;
+    this.get("transitions").forEach(function(tran){
+      if (tran.condition!=="error"){
+        count++;
+      }
+    });
+    return count;
+  },
+  getDefaultTransitionTarget(){
+    if (this.isForkNode()){
+      return this.findNodeById(this,"join_"+this.get("id"));
+    }
+    var transitions=this.get("transitions");
+    if (transitions.length===0){
+      return this;
+    }else if (transitions.length===1){
+      return transitions[0].targetNode;
+    }
+    var target=transitions[0].targetNode;
+    transitions.forEach(function(tran){
+      if (tran.condition==="default"){
+        target=tran.targetNode;
+      }
+    });
+    if (target.isPlaceholder()){
+      return target.getDefaultTransitionTarget();
+    }
+    return target;
+  },
+  getTargets(){
+    var targets=[];
+    var transitions=this.get("transitions");
+    transitions.forEach(function(tran){
+      targets.push(tran.get("targetNode"));
+    });
+    return targets;
+  },
+  addError(obj){
+    this.errorMsgs.push(obj);
+  },
+  isPlaceholder(){
+    return this.get("type")==="placeholder";
+  },
+  isDecisionNode(){
+    return this.get("type")==="decision";
+  },
+  isForkNode(){
+    return this.get("type")==="fork";
+  },
+  isJoinNode(){
+    return this.get("type")==="join";
+  },
+  isActionNode(){
+    return this.get("type")==="action";
+  },
+  isKillNode(){
+    return this.get("type")==="kill";
+  },
+  isDecisionEnd(){
+    return this.get("type")==="decision_end";
+  },
+  isDefaultKillNode(){
+    return this.isKillNode() && this.get("name")===Constants.defaultKillNodeName;
+  }
+});
+export {Node};

+ 70 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/schema-versions.js

@@ -0,0 +1,70 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+export default Ember.Object.extend({
+  actionVersions: Ember.Map.create(),
+  currentActionVersion:Ember.Map.create(),
+  clone : {},
+  createCopy(){
+    this.clone.workflowVersion = this.workflowVersion;
+    this.clone.currentActionVersion = this.currentActionVersion.copy();
+  },
+  rollBack(){
+    this.workflowVersion = this.clone.workflowVersion;
+    this.currentActionVersion = this.clone.currentActionVersion.copy();
+  },
+  init(){
+    this.workflowVersion = "0.5";
+    this.workflowVersions = ["0.5","0.4.5","0.4","0.3","0.2.5","0.2","0.1"];
+    this.actionVersions.set("hive",["0.6","0.5","0.4","0.3","0.2","0.1"]);
+    this.actionVersions.set("hive2",["0.2","0.1"]);
+    this.actionVersions.set("pig",["0.3","0.2","0.1"]);
+    this.actionVersions.set("sqoop",["0.3","0.2","0.1"]);
+    this.actionVersions.set("shell",["0.3","0.2","0.1"]);
+    this.actionVersions.set("spark",["0.2","0.1"]);
+    this.actionVersions.set("distcp",["0.2","0.1"]);
+    this.actionVersions.set("email",["0.2","0.1"]);
+
+    this.currentActionVersion.set("hive","0.6");
+    this.currentActionVersion.set("hive2","0.2");
+    this.currentActionVersion.set("pig","0.3");
+    this.currentActionVersion.set("sqoop","0.3");
+    this.currentActionVersion.set("shell","0.3");
+    this.currentActionVersion.set("spark","0.2");
+    this.currentActionVersion.set("distcp","0.2");
+    this.currentActionVersion.set("email","0.2");
+  },
+  getActionVersions(type){
+    return this.actionVersions.get(type);
+  },
+  getActionVersion(type){
+    return this.currentActionVersion.get(type);
+  },
+  getCurrentWorkflowVersion(){
+    return this.workflowVersion;
+  },
+  getWorkflowVersions(){
+    return this.workflowVersions;
+  },
+  setActionVersion(type, version){
+    this.currentActionVersion.set(type, version);
+  },
+  setCurrentWorkflowVersion(version){
+    return this.workflowVersion = version;
+  }
+
+});

+ 59 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/sla-info.js

@@ -0,0 +1,59 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+import Ember from 'ember';
+var SlaInfo = Ember.Object.extend(Ember.Copyable,{
+  copy (){
+    var slaInfo = {}
+    for (let key in this) {
+      slaInfo[key] = Ember.copy(this[key]) ;
+    }
+    return slaInfo;
+  },
+  init (){
+    this.nominalTime='';
+    this.shouldStart = {
+      time : '',
+      unit : ''
+    };
+    this.shouldEnd = {
+      time : '',
+      unit : ''
+    };
+    this.maxDuration = {
+      time : '',
+      unit : ''
+    };
+    this.alertEvents = '';
+    this.alertContacts = '';
+  },
+  nominalTime:'',
+  shouldStart : {
+    time : '',
+    unit : ''
+  },
+  shouldEnd : {
+    time : '',
+    unit : ''
+  },
+  maxDuration : {
+    time : '',
+    unit : ''
+  },
+  alertEvents : '',
+  alertContacts : ''
+});
+export {SlaInfo};

+ 53 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/transition.js

@@ -0,0 +1,53 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+var Transition = Ember.Object.extend({
+  id:null,
+  sourceNode:null,
+  targetNode:null,
+  type:null,
+  condition:null,
+  errorPath:false,
+  init(){
+
+  },
+  copyAttribs(transition){
+    this.condition=transition.condition;
+  },
+  isOnError(){
+    return this.condition==="error";
+  },
+  isDefaultCasePath(){
+    return this.condition==="default";
+  },
+  getSourceNode(){
+    return this.get("sourceNode");
+  },
+  getTargetNode(skipPlaceholder){
+    var currNode=this.targetNode;
+    if (skipPlaceholder===false){
+      return currNode;
+    }
+    while(currNode.isPlaceholder()){
+      var targets=currNode.getTargets();
+      currNode=targets[0];
+    }
+    return currNode;
+  }
+});
+export {Transition};

+ 34 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow-context.js

@@ -0,0 +1,34 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+var WorkflowContext = Ember.Object.extend({
+  errors : [],
+  addError (error){
+    this.errors.pushObject(error);
+  },
+  getErrors (){
+    return this.errors;
+  },
+  hasErrors (){
+    return this.errors.length > 0;
+  },
+  clearErrors (){
+    this.errors.clear();
+  }
+});
+export{WorkflowContext};

+ 134 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow-importer.js

@@ -0,0 +1,134 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import CommonUtils from "../utils/common-utils";
+import {Workflow} from '../domain/workflow';
+import {WorkflowXmlMapper} from '../domain/workflow_xml_mapper';
+var WorkflowImporter= Ember.Object.extend({
+  workflowMapper:null,
+  x2js : new X2JS(),
+  importWorkflow(workflowXml){
+    var workflow=Workflow.create({});
+    workflow.initialize();
+    this.workflowMapper=WorkflowXmlMapper.create({schemaVersions:workflow.schemaVersions});
+    return this.processWorkflowXml(workflowXml,workflow);
+  },
+  processWorkflowXml(workflowXml,workflow){
+    var workflowJson= this.get("x2js").xml_str2json(workflowXml);
+    if (!workflowJson["workflow-app"]){
+      throw "Invalid workflow";
+    }
+    var workflowAppJson=workflowJson["workflow-app"];
+    var workflowVersion=CommonUtils.extractSchemaVersion(workflowAppJson._xmlns);
+    workflow.schemaVersions.setCurrentWorkflowVersion(workflowVersion);
+    if (workflowAppJson.info && workflowAppJson.info.__prefix==="sla") {
+      workflow.slaEnabled=true;
+      this.workflowMapper.handleSLAImport(workflow,workflowAppJson.info);
+    }
+    this.workflowMapper.handleCredentialImport(workflow,workflowAppJson.credentials);
+    this.workflowMapper.handleParametersImport(workflow,workflowAppJson.parameters);
+    var nodeMap=this.setupNodeMap(workflowAppJson,workflow);
+    this.setupTransitions(workflowAppJson,nodeMap);
+    workflow.set("startNode",nodeMap.get("start").node);
+    this.populateKillNodes(workflow,nodeMap);
+    return workflow;
+  },
+  processActionNode(nodeMap,action){
+    var actionMapper=this.get("workflowMapper").getNodeHandler("action");
+    var actionNode=actionMapper.handleImportNode(action);
+    nodeMap.set(actionNode.getName(),actionNode);
+  },
+  setupNodeMap(workflowAppJson,workflow){
+    var self=this;
+    workflow.set("name",workflowAppJson["_name"]);
+    var nodeMap=new Map();
+    Object.keys(workflowAppJson).forEach(function (key) {
+      var nodeHandler=self.workflowMapper.getNodeHandler(key);
+      if (nodeHandler){
+        if (Ember.isArray(workflowAppJson[key])){
+          workflowAppJson[key].forEach(function(jsonObj){
+            var node=nodeHandler.handleImportNode(key,jsonObj,workflow);
+            nodeMap.set(jsonObj._name,{json:jsonObj,node:node});
+          });
+        }else{
+          var node=nodeHandler.handleImportNode(key,workflowAppJson[key],workflow);
+          if (!workflowAppJson[key]._name){
+            nodeMap.set(key,{json:workflowAppJson[key],node:node});
+          }else{
+            nodeMap.set(workflowAppJson[key]._name,{json:workflowAppJson[key],node:node});
+          }
+        }
+      }
+    });
+    return nodeMap;
+  },
+  setupTransitions(workflowAppJson,nodeMap){
+    var self=this;
+    nodeMap.forEach(function(entry,key){
+      var node=entry.node;
+      if (!node){
+        console.error("could not process:",key);//TODO error handling...
+        return;
+      }
+      var json=entry.json;
+      var nodeHandler=self.workflowMapper.getNodeHandler(node.get("type"));
+      if (!nodeHandler){
+        console.error("could not process:",node.get("type"));//TODO error handling...
+      }
+      nodeHandler.handleImportTransitions(node,json,nodeMap);
+    });
+  },
+  getNodeIds(nodeMap){
+    var ids=[];
+    nodeMap.forEach(function(entry,key){
+      var node=entry.node;
+      ids.push(node.id);
+    });
+    return ids;
+  },
+  getNodeNames(nodeMap){
+    var names=[];
+    nodeMap.forEach(function(entry,key){
+      var node=entry.node;
+      names.push(node.id);
+    });
+    return names;
+  },
+  populateKillNodes(workflow,nodeMap){
+    if (this.containsKillNode(nodeMap)){
+      workflow.resetKillNodes();
+    }
+    nodeMap.forEach(function(entry,key){
+      var node=entry.node;
+      if (node.isKillNode()){
+        workflow.get("killNodes").pushObject(node);
+      }
+    });
+  },
+  containsKillNode(nodeMap){
+    var containsKillNode=false;
+    nodeMap.forEach(function(entry,key){
+      var node=entry.node;
+      if (node.isKillNode()){
+        containsKillNode=true;
+      }
+    });
+    return containsKillNode;
+  }
+});
+export {WorkflowImporter};

+ 144 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow-xml-generator.js

@@ -0,0 +1,144 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF 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.
+ */
+
+import Ember from 'ember';
+import {WorkflowXmlMapper} from '../domain/workflow_xml_mapper';
+import {NodeVisitor} from '../domain/node-visitor';
+import Constants from '../utils/constants';
+var WorkflowGenerator= Ember.Object.extend({
+  workflowMapper:null,
+  x2js : new X2JS({useDoubleQuotes:true}),
+  workflow:null,
+  workflowContext : {},
+  nodeVisitor:null,
+  ignoreErrors:false,
+  init(){
+    this.workflowMapper=WorkflowXmlMapper.create({schemaVersions:this.workflow.schemaVersions});
+    this.nodeVisitor=NodeVisitor.create({});
+  },
+  process(){
+    if (!this.ignoreErrors && (!this.workflow.get("name") || this.workflow.get("name").trim()==="")){
+      this.workflowContext.addError({message : "Workflow name is mandatory"});
+      return;
+    }
+    var workflowObj={"workflow-app":{}};
+    this.get("workflowMapper").getGlobalConfigHandler().handle(this.workflow.get("globalSetting"),workflowObj["workflow-app"]);
+    this.visitNode(workflowObj,this.workflow.startNode);
+    if (this.workflow.slaEnabled===true){
+      this.get("workflowMapper").handleSLAMapping(this.workflow.sla,workflowObj["workflow-app"]);
+    }
+    this.get("workflowMapper").handleCredentialsGeneration(this.workflow.credentials,workflowObj["workflow-app"]);
+    this.get("workflowMapper").hanldeParametersGeneration(this.workflow.parameters,workflowObj["workflow-app"]);
+    if (!this.ignoreErrors && (!workflowObj["workflow-app"].action || workflowObj["workflow-app"].action.length<1)){
+      this.workflowContext.addError({message : "Miniumum of one action node must exist"});
+      return;
+    }
+    var reordered={"workflow-app":{}};
+    var srcWorkflowApp=workflowObj["workflow-app"];
+    var targetWorkflowApp=reordered["workflow-app"];
+    targetWorkflowApp["_name"]=this.workflow.get("name");
+    this.copyProp(srcWorkflowApp,targetWorkflowApp,["parameters","global","credentials","start","decision","fork","join","action","kill","end","info"]);
+    targetWorkflowApp["_xmlns"]="uri:oozie:workflow:"+this.workflow.get("schemaVersions").getCurrentWorkflowVersion();
+    if (this.slaInfoExists(targetWorkflowApp)){
+      targetWorkflowApp["_xmlns:sla"]="uri:oozie:sla:0.2";
+    }
+    var xmlAsStr = this.get("x2js").json2xml_str(reordered);
+    return xmlAsStr;
+  },
+  slaInfoExists(workflowApp){
+    if (workflowApp.info){
+      return true;
+    }
+    var slaExists= false;
+    if (!workflowApp.action){
+      return false;
+    }
+    workflowApp.action.forEach(function(action){
+      if (action.info){
+        slaExists=true;
+      }
+    });
+    return slaExists;
+  },
+  copyProp(src,dest,props){
+    props.forEach(function(prop){
+      if (src[prop]){
+        dest[prop]=src[prop];
+      }
+    });
+  },
+
+  visitNode(workflowObj,node,visitedNodes){
+    if (!visitedNodes){
+      visitedNodes=[];
+    }
+    if (visitedNodes.contains(node.get("id"))){
+      return;
+    }
+    visitedNodes.push(node.get("id"));
+    var self=this;
+    var workflowApp=workflowObj["workflow-app"];
+    if (node.isPlaceholder()){
+      return self.visitNode(workflowObj,node.transitions[0].targetNode,visitedNodes);
+    }
+    var nodeHandler=this.get("workflowMapper").getNodeHandler(node.type);
+    nodeHandler.setContext(this.workflowContext);
+    var nodeObj=nodeHandler.handleNode(node);
+    if (node.type==='action'){
+      var jobHandler=this.get("workflowMapper").getActionJobHandler(node.actionType);
+      if (jobHandler){
+
+        jobHandler.setContext(this.workflowContext);
+        if (!self.ignoreErrors && !node.get("domain")){
+            this.workflowContext.addError({node : node, message : "Action Properties are empty"});
+        }else{
+          jobHandler.handle(node.get("domain"),nodeObj,node.get("name"));
+          if (!self.ignoreErrors){
+            var errors=jobHandler.validate(node.get("domain"));
+            if (errors && errors.length>0){
+              errors.forEach(function(err){
+                this.workflowContext.addError({node : node, message : err.message});
+              }.bind(this));
+            }
+          }
+        }
+      }else{
+        if (!self.ignoreErrors){
+          this.workflowContext.addError({node : node, message : "Unknown action:"+node.actionType});
+        }
+      }
+    }
+    if (nodeHandler.hasMany()){
+        if (!workflowApp[node.type]){
+            workflowApp[node.type]=[];
+        }
+      workflowApp[node.type].push(nodeObj);
+    }else{
+      workflowApp[node.type]=nodeObj;
+    }
+    nodeHandler.handleTransitions(node.transitions,nodeObj);
+    if (node.transitions){
+      node.transitions.forEach(function(tran){
+        self.visitNode(workflowObj,tran.targetNode,visitedNodes);
+      });
+    }
+    if (node.isActionNode()){
+      nodeHandler.handleSla(node.domain,nodeObj);
+    }
+  }
+});
+export {WorkflowGenerator};

+ 266 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow.js

@@ -0,0 +1,266 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import Constants from '../utils/constants';
+import {FindNodeMixin} from '../domain/findnode-mixin';
+import {NodeFactory} from '../domain/node-factory';
+import SchemaVersions from '../domain/schema-versions';
+import {NodeVisitor} from '../domain/node-visitor';
+import {idGen} from '../domain/id-gen';
+import {SlaInfo} from '../domain/sla-info'
+var Workflow= Ember.Object.extend(FindNodeMixin,{
+  name:"",
+  startNode:null,
+  globalSetting:null,
+  parameters: null,
+  usePlaceholders: true,
+  killNodes : null,
+  nodeVisitor : null,
+  nodeFactory:NodeFactory.create({}),
+  schemaVersions:SchemaVersions.create({}),
+  sla : SlaInfo.create({}),
+  credentials : Ember.A([]),
+  initialize(){
+    this.nodeVisitor=NodeVisitor.create({});
+    var src =this.nodeFactory.createStartNode();
+    var dest =this.nodeFactory.createEndNode("end");
+    this.set("startNode", src);
+    this.set("killNodes",Ember.A([]));
+    this.set("globalSetting",null);
+    this.set("name","");
+    this.appendDefaultKillNode();
+    src.addTransitionTo(dest);
+  },
+  appendDefaultKillNode(){
+    this.createKillNode(Constants.defaultKillNodeName,"${wf:errorMessage(wf:lastErrorNode())}");
+  },
+  createKillNode(name, message){
+    var killNode=this.nodeFactory.createKillNode(name,message);
+    this.get("killNodes").pushObject(killNode);
+  },
+  resetKillNodes(){
+    this.set("killNodes",Ember.A([]));
+  },
+  resetWorfklow(){
+    //TODO idGen.reset();
+    this.initialize();
+  },
+  findCommonTargetNodeId(node){
+    var nodeIds = {}, targ, decPath = node.getTargets(), tempId = 0;
+    for(var i =0; i< decPath.length; i++){
+      var currNode = decPath[i];
+      do {
+        if(nodeIds.hasOwnProperty(currNode.get("id"))){
+          nodeIds[currNode.get("id")] = nodeIds[currNode.get("id")] + 1;
+        } else {
+          nodeIds[currNode.get("id")] = 1;
+        }
+        if(currNode.get("id") === "node-end"){
+          break;
+        }
+        currNode = currNode.getTargets()[0];
+      } while(currNode && currNode.get("id"));
+    }
+    for(var j in nodeIds){
+      if(tempId < nodeIds[j]){
+        targ = j;
+        tempId = nodeIds[j];
+      }
+    }
+    return targ;
+  },
+  findJoinNode(node){
+    var commonTargetId=null;
+    var commonTarget=null;
+    if (node.isDecisionNode()){
+      if (Constants.globalSetting.useJoinNodeForDecision){
+        var target=this.findNodeById(node,"decision_end_"+node.get("id"));
+        if (!target){
+          commonTargetId=this.findCommonTargetNodeId(node);
+          commonTarget=this.findNodeById(this.startNode,commonTargetId);
+          return commonTarget;
+        }else{
+          return target;
+        }
+      }else{
+        commonTargetId=this.findCommonTargetNodeId(node);
+        commonTarget=this.findNodeById(this.startNode,commonTargetId);
+        return commonTarget;
+      }
+    }else if (node.isForkNode()) {
+      commonTargetId=this.findCommonTargetNodeId(node);
+      commonTarget=this.findNodeById(this.startNode,commonTargetId);
+      return commonTarget;
+    }else{
+      return null;
+    }
+  },
+  addBranch(sourceNode){
+    var target=this.findJoinNode(sourceNode);
+    if (this.get("usePlaceholders")){
+      var placeholderNode=this.nodeFactory.createPlaceholderNode(target) ;
+      sourceNode.addTransitionTo(placeholderNode);
+    }else{
+      sourceNode.addTransitionTo(target);
+    }
+  },
+  addDecisionBranch(settings){
+    if (!settings.targetNode){
+      return;
+    }
+    var sourceNode=settings.sourceNode;
+    var insertNodeOnPath=settings.newNodeType?true:false;
+    var target=settings.targetNode;
+    if (!insertNodeOnPath){
+      if (this.get("usePlaceholders")){
+        var placeholderNode=this.nodeFactory.createPlaceholderNode(target) ;
+        sourceNode.addTransitionTo(placeholderNode,settings.condition);
+      }else{
+        sourceNode.addTransitionTo(target,settings.condition);
+      }
+    }else{
+    }
+  },
+  generatedNode(target,type){
+    var generatedNode=null;
+    if ("decision" === type){
+      generatedNode=this.nodeFactory.generateDecisionNode(target);
+    }else  if ("fork" === type){
+      generatedNode=this.nodeFactory.generateForkNode(target);
+    }else  if ("kill" === type){
+      generatedNode = this.nodeFactory.createKillNode(settings.name);
+      source.deleteCurrentKillNode();
+    }else{
+      generatedNode = this.nodeFactory.createActionNode(type);
+      generatedNode.addTransitionTo(target);
+    }
+    return generatedNode;
+  },
+  addKillNode(node,settings){
+    var generatedNode=this.generatedNode(null,"kill");
+    return source.addTransitionTo(generatedNode,"error");
+  },
+  addNode(transition,type,settings) {
+    var source=transition.sourceNode;
+    var target=transition.targetNode;
+    var computedTarget=target;
+    if (target && target.isPlaceholder()){
+      computedTarget=target.getTargets()[0];
+    }
+    var generatedNode=this.generatedNode(computedTarget,type);
+    transition.targetNode=generatedNode;
+    return generatedNode;
+  },
+  deleteKillNode(node){
+    let killNodes = this.get("killNodes");
+    var killNodeReferenced=false;
+    this.nodeVisitor.process(this.startNode,function(n,ctx){
+      if (n.errorNode && n.errorNode.name===node.name){
+        killNodeReferenced=true;
+      }
+    });
+    if (killNodeReferenced){
+      return{
+        status: false,
+        message: "Kill node is being referenced by other nodes."
+      };
+    }
+    for(var i=0; i<killNodes.length; i++){
+      if(node.id === killNodes[i].id){
+        this.get("killNodes").removeObject(killNodes[i]);
+        break;
+      }
+    }
+    return {
+      status:true
+    };
+  },
+  deleteNode(node){
+    var self=this;
+    var target=node.getDefaultTransitionTarget();
+    if (node.isForkNode()|| node.isDecisionNode()){
+      target=this.findJoinNode(node);
+      if (target.isJoinNode()){
+        target=target.getDefaultTransitionTarget();
+      }
+    }
+    var transitionslist=this.findTransistionsToNode(node);
+    transitionslist.forEach(function(tran){
+      if (tran.getSourceNode().isDecisionNode()){
+        var joinNode=self.findJoinNode(tran.getSourceNode());
+        if (joinNode===target){
+          if (tran.isDefaultCasePath()){
+            var placeholderNode=self.nodeFactory.createPlaceholderNode(target);
+            tran.targetNode=placeholderNode;
+          }else   if (tran.getSourceNode().getOkTransitionCount()>2){
+            tran.getSourceNode().removeTransition(tran);
+          }else{
+            var placeholderNode=self.nodeFactory.createPlaceholderNode(target);
+            tran.targetNode=placeholderNode;
+          }
+        }else{
+          tran.targetNode=target;
+        }
+      }else if (tran.getSourceNode().isForkNode()){
+        var joinNode=self.findJoinNode(tran.getSourceNode());
+        if (joinNode===target){
+          if (tran.getSourceNode().getOkTransitionCount()>2){
+            tran.getSourceNode().removeTransition(tran);
+          }else{
+            var placeholderNode=self.nodeFactory.createPlaceholderNode(target);
+            tran.targetNode=placeholderNode;
+          }
+        }else{
+          tran.targetNode=target;
+        }
+      }else{
+        tran.targetNode=target;
+      }
+    });
+  },
+  deleteTransition(transition){
+    var src=transition.getSourceNode();
+    src.removeTransition(transition);
+  },
+  deleteEmptyTransitions(transitionslist){
+    transitionslist.forEach(function(tran){
+      if (tran.getSourceNode().isForkNode()&& tran.getTargetNode().isJoinNode()){
+        tran.getSourceNode().removeTransition(tran);
+      }
+    });
+  },
+  findTransistionsToNode(matchingNode){
+    var transitionslist=[];
+    this.findTransistionsToNodeInternal(this.startNode,matchingNode,transitionslist);
+    return transitionslist;
+  },
+  findTransistionsToNodeInternal(node,matchingNode,transitionslist){
+    var self=this;
+    if (node.transitions){
+      node.transitions.forEach(function(tran){
+        if (tran.getTargetNode()===matchingNode){
+          transitionslist.push(tran);
+        }
+        self.findTransistionsToNodeInternal(tran.getTargetNode(),matchingNode,transitionslist);
+      });
+    }
+  }
+});
+
+
+export {Workflow};

+ 135 - 0
contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow_xml_mapper.js

@@ -0,0 +1,135 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import * as nodeHandler from '../domain/node-handler';
+import {SLAMapper} from "../domain/mapping-utils";
+
+import {MappingMixin,ConfigurationMapper} from "../domain/mapping-utils";
+var WorkflowXmlMapper= Ember.Object.extend({
+  nodeHandlerMap:null,
+  globalConfigHandler:null,
+  actionTypeResolver:null,
+  slaMapper: SLAMapper.create({}),
+  schemaVersions:null,
+  init: function() {
+    this.actionTypeResolver=nodeHandler.ActionTypeResolver.create({schemaVersions:this.schemaVersions});
+    this.set("globalConfigHandler",GlobalConfigHandler.create({}));
+    this.set("slaMapper",SLAMapper.create({}));
+    this.nodeHandlerMap=new Map();
+    this.nodeHandlerMap.set("start",nodeHandler.StartNodeHandler.create({}));
+    this.nodeHandlerMap.set("end",nodeHandler.EndNodeHandler.create({}));
+    this.nodeHandlerMap.set("action",nodeHandler.ActionNodeHandler.create({actionTypeResolver:this.actionTypeResolver}));
+    this.nodeHandlerMap.set("decision",nodeHandler.DecisionNodeHandler.create({}));
+    this.nodeHandlerMap.set("fork",nodeHandler.ForkNodeHandler.create({}));
+    this.nodeHandlerMap.set("join",nodeHandler.JoinNodeHandler.create({}));
+    this.nodeHandlerMap.set("kill",nodeHandler.KillNodeHandler.create({}));
+  },
+  getNodeHandler(type){
+    return this.nodeHandlerMap.get(type);
+  },
+  getGlobalConfigHandler(){
+    return this.globalConfigHandler;
+  },
+  getActionJobHandler(jobType){
+    return this.actionTypeResolver.getActionJobHandler(jobType);
+  },
+  handleSLAMapping(sla,workflowObj){
+    this.get("slaMapper").hanldeGeneration(sla,workflowObj);
+  },
+  handleSLAImport(workflow,infoJson){
+    this.slaMapper.handleImport(workflow,infoJson,"sla");
+  },
+  handleCredentialsGeneration(credentials,workflowObj){
+    if (credentials && credentials.length>0){
+      workflowObj["credentials"]={"credential":[]};
+      credentials.forEach(function(credential){
+        var credJson={_name:credential.name,_type:credential.type,property:[]};
+        if (credential.property){
+          credential.property.forEach(function(prop){
+            credJson.property.push({"name":prop.name,"value":prop.value});
+          });
+        }
+        workflowObj.credentials.credential.push(credJson);
+      });
+    }
+  },
+  handleCredentialImport(workflow,credJson){
+    if (!credJson || !credJson.credential){
+      return;
+    }
+    workflow.credentials.clear();
+    var credntialsJson=Ember.isArray(credJson.credential)?credJson.credential:[credJson.credential];
+    credntialsJson.forEach(function(cred){
+      var credential={
+        name:cred._name,
+        type:cred._type,
+        property:Ember.A([])
+      };
+      if (cred.property){
+        if (!Ember.isArray(cred.property)){
+          cred.property=[cred.property];
+        }
+        cred.property.forEach(function(property){
+          credential.property.push({"name":property.name,"value":property.value});
+        });
+      }
+      workflow.credentials.push(credential);
+    });
+  },
+  hanldeParametersGeneration(parameters,workflowObj){
+    if (!parameters || !parameters.configuration){
+      return;
+    }
+    if (parameters.configuration.property.length>0){
+      workflowObj.parameters={property:[]};
+    }
+    parameters.configuration.property.forEach(function(prop){
+      workflowObj.parameters.property.push({"name":prop.name,"value":prop.value});
+    });
+  },
+  handleParametersImport(workflow,parameters){
+    if (!parameters|| !parameters.property){
+      return;
+    }
+    workflow.parameters={"configuration":{property:[]}}
+    parameters.property.forEach(function(prop){
+      workflow.parameters.configuration.property.push({"name":prop.name,"value":prop.value});
+    });
+  }
+});
+var GlobalConfigHandler=Ember.Object.extend(MappingMixin,{
+  mapping:null,
+  configurationMapper:ConfigurationMapper.create({}),
+  init(){
+    this.mapping=[
+      {xml:"job-tracker",domain:"jobTracker"},
+      {xml:"name-node",domain:"nameNode"},
+      {xml:"configuration",customHandler:this.configurationMapper}
+    ];
+  },
+
+  handle(domainObject,nodeObj){
+    if (!domainObject){
+      return;
+    }
+    var globalObj={};
+    nodeObj["global"]=globalObj;
+    this.handleMapping(domainObject,globalObj,this.mapping);
+  }
+});
+export {WorkflowXmlMapper};

+ 29 - 0
contrib/views/wfmanager/src/main/resources/ui/app/helpers/date-helper.js

@@ -0,0 +1,29 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+/*global moment:false */
+import Ember from 'ember';
+
+export function dateHelper(dateStr) {
+  var date = dateStr && new Date(dateStr);
+  if (!date || isNaN(date.getTime())) {
+    return "";
+  }
+  return [moment(date).format("MM/DD/YYYY hh:mm A")].join("");
+}
+
+export default Ember.Helper.helper(dateHelper);

+ 77 - 0
contrib/views/wfmanager/src/main/resources/ui/app/index.html

@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+
+<!--
+  ~    Licensed to the Apache Software Foundation (ASF) under one or more
+  ~    contributor license agreements.  See the NOTICE file distributed with
+  ~    this work for additional information regarding copyright ownership.
+  ~    The ASF 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.
+  -->
+<html>
+
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <title>Workflow Designer </title>
+    <link rel="shortcut icon" type="image/icon" href="assets/favicon.ico" />
+    <meta name="description" content="">
+    <meta name="viewport" content="width=device-width, initial-scale=1"> {{content-for "head"}}
+    <link rel="stylesheet" href="assets/vendor.css">
+    <link rel="stylesheet" href="assets/oozie-designer.css"> {{content-for "head-footer"}}
+	<style>
+		body{background:#fff;}
+	</style>
+</head>
+
+<body>
+    {{content-for "body"}}
+    <script src="assets/vendor.js"></script>
+    <script src="assets/oozie-designer.js"></script>
+</body>
+<script type="text/javascript">
+var AmbariViewHelper = {
+    isAmbariView: function() {
+        return window.location.href.indexOf("/views/") > 0;
+    },
+    getViewInfo: function() {
+        var tokens = window.location.href.substring(window.location.href.indexOf("/views/") + 7).split("/");
+        var info = {
+            name: tokens[0],
+            version: tokens[1],
+            instance: tokens[2]
+        };
+        info.API_URL = ["/api/v1/views/", info.name, "/versions/", info.version, "/instances/" + info.instance].join("");
+        return info;
+    },
+    loadInstanceInfo: function() {
+        var viewInfo = this.getViewInfo();
+        $.ajax({
+            dataType: "json",
+            url: viewInfo.API_URL,
+            async: false,
+            success: function(result) {
+                Ember.ENV.instanceInfo = result;
+                // Update the API URL pointing to Ambari View Resource
+                Ember.ENV.API_URL = viewInfo.API_URL + "/resources/proxy";
+                Ember.ENV.FILE_API_URL = viewInfo.API_URL + "/resources/proxy/fileServices";
+            }
+        });
+    }
+};
+
+if (AmbariViewHelper.isAmbariView()) {
+    $('body').addClass("ambari-view");
+    AmbariViewHelper.loadInstanceInfo();
+}
+</script>
+
+</html>

+ 25 - 0
contrib/views/wfmanager/src/main/resources/ui/app/initializers/init.js

@@ -0,0 +1,25 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF 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.
+ */
+
+export function initialize( /* application */ ) {
+    //application.inject('route', 'oozie', 'service:oozie-api');
+}
+
+export default {
+    name: 'init',
+    initialize
+};

+ 20 - 0
contrib/views/wfmanager/src/main/resources/ui/app/resolver.js

@@ -0,0 +1,20 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF 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.
+ */
+
+import Resolver from 'ember-resolver';
+
+export default Resolver;

+ 33 - 0
contrib/views/wfmanager/src/main/resources/ui/app/router.js

@@ -0,0 +1,33 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+import config from './config/environment';
+
+const Router = Ember.Router.extend({
+  location: config.locationType
+});
+
+Router.map(function () {
+  this.route('dashboard');
+  this.route('design');
+  this.route('designtest');
+  this.route('job');
+  this.route('connection-error');
+});
+
+export default Router;

+ 35 - 0
contrib/views/wfmanager/src/main/resources/ui/app/routes/connection-error.js

@@ -0,0 +1,35 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF 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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Route.extend({
+  queryParams: {
+    current : { refreshModel : true}
+  },
+  previousLocation : '',
+  beforeModel (transition){
+    this.set('previousLocation',transition.queryParams.current);
+  },
+  model () {
+    var retryLocation = this.get('previousLocation');
+    if(!retryLocation){
+      retryLocation = '/#/dashboard';
+    }
+    return retryLocation;
+  }
+});

+ 146 - 0
contrib/views/wfmanager/src/main/resources/ui/app/routes/dashboard.js

@@ -0,0 +1,146 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Route.extend({
+  history: Ember.inject.service(),
+  errorMessage : "Error",
+  queryParams: {
+    jobType: { refreshModel: true },
+    start: { refreshModel: true },
+    filter: { refreshModel: true }
+  },
+  actions: {
+    onShowJobDetails : function(params){
+      this.transitionTo('job',{
+        queryParams : {
+          id : params.id,
+          jobType : params.type,
+          from: null,
+          fromType: null
+        }
+      });
+    },
+    loading: function ( /*transition, originRoute*/ ) {
+      Ember.$("#loading").css("display", "block");
+      var app = this.controllerFor('application');
+      if (app.get('currentRouteName') !== "dashboard") {
+        return true;
+      }
+      return false;
+    },
+    error: function() {
+
+    },
+    doSearch (params){
+      this.get('history').setSearchParams(params);
+      Ember.$("#loading").css("display", "block");
+      this.search(params);
+    }
+  },
+  fetchJobs (url){
+    var deferred = Ember.RSVP.defer();
+    Ember.$.get(url).done(function(res){
+      deferred.resolve(res);
+    }).fail(function(){
+      deferred.reject();
+    });
+    return deferred.promise;
+  },
+  search(params){
+    params = params || {};
+    var type = params.type || "wf",
+    start = Number(params.start || 1),
+    len = Number(params.len || Ember.ENV.PAGE_SIZE),
+    index = 0,
+    filter = params.filter || "",
+    API_URL = Ember.ENV.API_URL,
+    url = [API_URL,
+      "/v2/jobs?jobtype=", type,
+      "&offset=", start,
+      "&len=", len,
+      "&filter=", filter
+    ].join(""),
+    page = (start - 1) / len + 1;
+    return this.fetchJobs(url).catch(function(){
+      this.controllerFor('dashboard').set('model',{error : "Remote API Failed"});
+      Ember.$("#loading").css("display", "none");
+    }.bind(this)).then(function (res) {
+      if(!res){
+        return;
+      }
+      if (typeof res === "string") {
+        res = JSON.parse(res);
+      }
+      res.jobs = [];
+
+      if (res.workflows) {
+        res.areWfs = true;
+        res.type = "wf";
+        res.workflows.forEach(function (job) {
+          job.type = "wf";
+          res.jobs.push(job);
+        });
+      }
+      if (res.coordinatorjobs) {
+        res.areCoords = true;
+        res.type = "coords";
+        res.coordinatorjobs.forEach(function (job) {
+          job.type = "coordinatorjobs";
+          job.id = job.coordJobId;
+          job.appName = job.coordJobName;
+          res.jobs.push(job);
+        });
+      }
+      if (res.bundlejobs) {
+        res.areBundles = true;
+        res.type = "bundles";
+        res.bundlejobs.forEach(function (job) {
+          job.type = "bundlejobs";
+          job.id = job.bundleJobId;
+          job.appName = job.bundleJobName;
+          res.jobs.push(job);
+        });
+      }
+      res.pageSize = len;
+      res.pages = [];
+
+      while (index++ < (res.total / len)) {
+        res.pages.push({ index: index, active: page === index });
+      }
+
+      res.jobTypeValue = type;
+      res.filterValue = filter;
+      res.pageSize = len;
+      res.totalValue = res.total;
+      res.page = page;
+      res.start = start;
+      res.end = (start + res.jobs.length - 1);
+      res.time = new Date().getTime();
+      this.controllerFor('dashboard').set('model', res);
+      Ember.$("#loading").css("display", "none");
+      return res;
+    }.bind(this));
+  },
+  afterModel: function (model) {
+    Ember.$("#loading").css("display", "none");
+  },
+  model: function (params) {
+
+  }
+});

+ 25 - 0
contrib/views/wfmanager/src/main/resources/ui/app/routes/design.js

@@ -0,0 +1,25 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF 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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Route.extend({
+xmlAppPath : null,
+beforeModel: function(transition){
+      this.set("xmlAppPath", transition.queryParams.appPath);
+  }
+});

+ 65 - 0
contrib/views/wfmanager/src/main/resources/ui/app/routes/designtest.js

@@ -0,0 +1,65 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF 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.
+ */
+import Ember from 'ember';
+
+export default Ember.Route.extend({
+  actions:{
+    showChart() {
+      //       jsPlumb.ready(function() {
+      // var a = $("#a");
+      // var b = $("#b");
+      var connectionColor="#777";
+      var lineWidth=1;
+      var stateMachineConnector = {
+        connector:"Bezier",
+        paintStyle:{lineWidth:3,strokeStyle:"#056"},
+        hoverPaintStyle:{strokeStyle:"#dbe300"},
+        endpoint:"Blank",
+        anchor:"Continuous",
+        overlays:[ ["PlainArrow", {location:1, width:15, length:12} ]]
+      };
+
+      var connection=jsPlumb.connect({
+          source:"a",
+          target:"b",
+          //connector:["Flowchart", { stub:1,alwaysRespectStubs :true,cornerRadius: 5 }],
+        //  connector:["Straight"],
+        //  connector:["StateMachine",{curviness:0}],
+          //connector: ["Bezier"],
+          paintStyle:{lineWidth:lineWidth,strokeStyle:connectionColor},
+          endpointStyle:{fillStyle:'rgb(243,229,0)'},
+          endpoint: ["Dot", {
+            radius: 1
+          }],
+          alwaysRespectStubs:true,
+          anchors: [["Bottom"],["Top"]]
+        //  anchors: [["Continuous"],["Continuous"]]
+      },stateMachineConnector);
+
+      connection.addOverlay([ "Label", {label:"<span>hello</span>", location:0.75, id:"myLabel" } ]);
+      // jsPlumb.connect({
+      //   source:"b",
+      //   target:"a"
+      // }, stateMachineConnector);
+
+      jsPlumb.repaintEverything();
+
+
+
+  }
+}
+});

+ 24 - 0
contrib/views/wfmanager/src/main/resources/ui/app/routes/index.js

@@ -0,0 +1,24 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF 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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Route.extend({
+    beforeModel() {
+        this.transitionTo('dashboard');
+    }
+});

+ 84 - 0
contrib/views/wfmanager/src/main/resources/ui/app/routes/job.js

@@ -0,0 +1,84 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF 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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Route.extend({
+  history: Ember.inject.service(),
+  queryParams: {
+      jobType: { refreshModel: true },
+      id: { refreshModel: true },
+      from : {refreshModel: true},
+      fromType :{refreshModel : true}
+  },
+  from : null,
+  fromType : null,
+  getJobInfo (url){
+    var deferred = Ember.RSVP.defer();
+    Ember.$.get(url).done(function(res){
+      deferred.resolve(res);
+    }).fail(function(){
+      deferred.reject();
+    });
+    return deferred.promise;
+  },
+  model : function(params){
+    return this.getJobInfo(Ember.ENV.API_URL+'/v2/job/'+params.id+'?show=info&timezone=GMT&offset=1&len='+Ember.ENV.PAGE_SIZE).catch(function(){
+        return {error : "Remote API Failed"};
+      }).then(function(response){
+      if (typeof response === "string") {
+          response = JSON.parse(response);
+      }
+      response.jobType = params.jobType;
+      return response;
+    });
+  },
+  afterModel : function (model, transition){
+    if(transition.queryParams.from){
+      this.set('from', transition.queryParams.from);
+      this.set('fromType',transition.queryParams.fromType);
+    }else{
+      this.set('from', null);
+      this.set('fromType', null);
+    }
+  },
+  actions : {
+    didTransition (){
+      this.controller.set('from', this.get('from'));
+      this.controller.set('fromType',this.get('fromType'));
+    },
+    onTabChange : function(tab){
+      this.set('currentTab', tab);
+      this.controller.set('currentTab',tab);
+    },
+    backToSearch : function(){
+      var params = this.get('history').getSearchParams();
+      if(null != params){
+        this.transitionTo('dashboard', {
+            queryParams: {
+                jobType: params.type,
+                start: params.start,
+                end: params.end,
+                filter: params.filter
+            }
+        });
+      }else{
+        this.transitionTo('dashboard');
+      }
+    }
+  }
+});

+ 27 - 0
contrib/views/wfmanager/src/main/resources/ui/app/services/file-browser.js

@@ -0,0 +1,27 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Service.extend(Ember.Evented,{
+  setContext : function(context){
+    this.set('currentContext', context);
+  },
+  getContext : function(){
+    return this.get('currentContext');
+  }
+});

+ 31 - 0
contrib/views/wfmanager/src/main/resources/ui/app/services/history.js

@@ -0,0 +1,31 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Service.extend({
+
+  lastSearch: null,
+
+  getSearchParams: function() {
+    return this.get('lastSearch');
+  },
+
+  setSearchParams : function(routeParams) {
+    this.set('lastSearch', routeParams);
+  }
+});

+ 61 - 0
contrib/views/wfmanager/src/main/resources/ui/app/services/property-extractor.js

@@ -0,0 +1,61 @@
+/*
+*    Licensed to the Apache Software Foundation (ASF) under one or more
+*    contributor license agreements.  See the NOTICE file distributed with
+*    this work for additional information regarding copyright ownership.
+*    The ASF 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.
+*/
+
+import Ember from 'ember';
+
+export default Ember.Service.extend({
+  simpleProperty : /^[A-Za-z_][0-9A-Za-z_]+$/,
+  dynamicProperty :  /^\${[A-Za-z_][0-9A-Za-z_]*}$/,
+  dynamicPropertyWithElMethod : /^\${[A-Za-z_][0-9A-Za-z_]*\((([A-Za-z_][0-9A-Za-z_]*)(,([A-Za-z_][0-9A-Za-z_]*))*)*\)}$/,
+  dynamicPropertyWithWfMethod : /^\${wf:[A-Za-z_][0-9A-Za-z_]*\((([A-Za-z_][0-9A-Za-z_]*)(,([A-Za-z_][0-9A-Za-z_]*))*)*\)}$/i,
+  hadoopEL : /^\${hadoop:.*}$/,
+  extractor : /\${.+?\}/g,
+  extract : function(property) {
+    var matches = property.match(this.get('extractor'));
+    var dynamicProperties = [];
+    if(null == matches) {
+      return dynamicProperties;
+    }
+    matches.forEach((match)=>{
+      if(this.get('dynamicProperty').test(match)){
+        dynamicProperties.push(match);
+      }
+    }.bind(this));
+    return dynamicProperties;
+  },
+  getProperties : function(currentValue, dynamicProperties) {
+    if( typeof currentValue === "object" ) {
+      Object.keys(currentValue).forEach((value, key)=> {
+        this.getProperties(currentValue[value], dynamicProperties);
+      }.bind(this));
+    } else {
+      if (typeof currentValue==="string"){
+        var extractedProperties = this.extract(currentValue);
+        extractedProperties.forEach((value)=>{
+          dynamicProperties.set(value,value);
+        });
+      }
+    }
+    return dynamicProperties;
+  },
+  getDynamicProperties : function(xml){
+    var x2js = new X2JS();
+    var workflowJSON = x2js.xml_str2json(xml);
+    var props= this.getProperties(workflowJSON, new Map());
+    return props;
+  }
+});

Разница между файлами не показана из-за своего большого размера
+ 1281 - 0
contrib/views/wfmanager/src/main/resources/ui/app/styles/app.css


Некоторые файлы не были показаны из-за большого количества измененных файлов