azkaban-memoizeit

Changes

.classpath 2(+2 -0)

.gitignore 1(+1 -0)

build.xml 68(+65 -3)

eclipse-styles.xml 283(+283 -0)

README.md 4(+2 -2)

src/java/azkaban/executor/ExecutorMailer.java 167(+0 -167)

src/java/azkaban/executor/RemoteExecutorConnector.java 5(+0 -5)

src/java/azkaban/jmx/JmxScheduler.java 31(+0 -31)

src/java/azkaban/jmx/JmxSchedulerMBean.java 15(+0 -15)

src/java/azkaban/jmx/JmxSLAManager.java 45(+0 -45)

src/java/azkaban/jmx/JmxSLAManagerMBean.java 20(+0 -20)

src/java/azkaban/scheduler/JdbcScheduleLoader.java 348(+0 -348)

src/java/azkaban/sla/JdbcSLALoader.java 283(+0 -283)

src/java/azkaban/sla/SLA.java 251(+0 -251)

src/java/azkaban/sla/SLALoader.java 14(+0 -14)

src/java/azkaban/sla/SlaMailer.java 63(+0 -63)

src/java/azkaban/sla/SLAManager.java 486(+0 -486)

src/java/azkaban/sla/SLAManagerException.java 13(+0 -13)

src/java/azkaban/sla/SlaOptions.java 51(+0 -51)

src/java/azkaban/webapp/servlet/velocity/joblogpage.vm 93(+0 -93)

src/less/base.less 101(+101 -0)

src/less/flow.less 170(+170 -0)

src/less/log.less 41(+41 -0)

src/less/login.less 32(+32 -0)

src/less/navbar.less 121(+121 -0)

src/less/tables.less 107(+107 -0)

src/tl/flowsummary.tl 112(+112 -0)

src/web/css/archive.css 44(+0 -44)

src/web/css/azkaban.css 3030(+0 -3030)

src/web/css/bootstrap.css 7098(+7098 -0)

src/web/js/azkaban.joblog.view.js 100(+0 -100)

src/web/js/azkaban.nav.js 40(+0 -40)

src/web/js/azkaban.staging.flow.view.js 1033(+0 -1033)

src/web/js/backbone-0.5.3-min.js 33(+0 -33)

src/web/js/bootstrap.js 2002(+2002 -0)

src/web/js/d3.v2.min.js 4(+0 -4)

src/web/js/jquery.simplemodal.js 25(+0 -25)

src/web/js/jquery.simplemodal-1.4.4.js 716(+0 -716)

src/web/js/underscore-1.2.1-min.js 30(+0 -30)

unit/java/azkaban/scheduler/JdbcScheduleLoaderTest.java 325(+0 -325)

Details

.classpath 2(+2 -0)

diff --git a/.classpath b/.classpath
index d032477..f3617fb 100644
--- a/.classpath
+++ b/.classpath
@@ -17,6 +17,7 @@
 	<classpathentry kind="lib" path="lib/slf4j-api-1.6.1.jar"/>
 	<classpathentry kind="lib" path="lib/slf4j-log4j12-1.6.4.jar"/>
 	<classpathentry kind="lib" path="lib/velocity-1.7.jar"/>
+	<classpathentry kind="lib" path="lib/velocity-tools-2.0.jar"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
 	<classpathentry kind="lib" path="lib/httpclient-4.2.1.jar"/>
 	<classpathentry kind="lib" path="lib/commons-io-2.4.jar"/>
@@ -31,5 +32,6 @@
 	<classpathentry kind="lib" path="extlib/mysql-connector-java-5.1.16-bin.jar"/>
 	<classpathentry kind="lib" path="lib/commons-pool-1.6.jar"/>
 	<classpathentry kind="lib" path="lib/h2-1.3.170.jar"/>
+	<classpathentry kind="lib" path="lib/commons-jexl-2.1.1.jar"/>
 	<classpathentry kind="output" path="dist/classes"/>
 </classpath>

.gitignore 1(+1 -0)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..849ddff
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+dist/

build.xml 68(+65 -3)

diff --git a/build.xml b/build.xml
index 86db40b..c695082 100644
--- a/build.xml
+++ b/build.xml
@@ -4,6 +4,8 @@
 	<property file="build.properties" />
 	<property name="base.dir" value="${basedir}" />
 	<property name="dist.jar.dir" value="${basedir}/dist/jars" />
+	<property name="dist.dust.dir" value="${basedir}/dist/dust" />
+	<property name="dist.less.dir" value="${basedir}/dist/less" />
 	<property name="dist.classes.dir" value="${basedir}/dist/classes" />
 	<property name="dist.packages.dir" value="${basedir}/dist/packages" />
 	<property name="dist.web.package.dir" value="${dist.packages.dir}/azkaban-web-server" />
@@ -19,6 +21,8 @@
 	<property name="lib.dir" value="${basedir}/lib" />
 	<property name="bin.dir" value="${basedir}/bin" />
 	<property name="java.src.dir" value="${basedir}/src/java" />
+	<property name="dust.src.dir" value="${basedir}/src/tl" />
+	<property name="less.src.dir" value="${basedir}/src/less" />
 	<property name="web.src.dir" value="${basedir}/src/web" />
 	<property name="sql.src.dir" value="${basedir}/src/sql" />
 	
@@ -48,6 +52,10 @@
 	<target name="build" description="Compile main source tree java files">
 		<delete dir="${dist.classes.dir}" />
 		<mkdir dir="${dist.classes.dir}" />
+		<delete dir="${dist.dust.dir}" />
+		<mkdir dir="${dist.dust.dir}" />
+		<delete dir="${dist.less.dir}" />
+		<mkdir dir="${dist.less.dir}" />
 		
 		<!-- copy non-java files to classes dir to load from classpath -->
 		<copy todir="${dist.classes.dir}">
@@ -61,6 +69,33 @@
 			<src path="${java.src.dir}" />
 			<classpath refid="main.classpath" />
 		</javac>
+
+		<!-- Compile dustjs templates -->
+		<!-- Note: Because apply does not support multiple srcfile and targetfile
+				 elements, and for and foreach requires ant-contrib, we use targetfile 
+				 for the template name parameter and then redirect the output of dustc
+				 to the final output file -->
+		<echo message="Compiling Dust templates." />
+		<apply dir="${dust.src.dir}" executable="dustc" relative="true">
+			<mapper type="glob" from="*.tl" to="*" />
+			<targetfile prefix="--name=" />
+			<srcfile />
+			<fileset dir="${dust.src.dir}" includes="*.tl" />
+			<redirector>
+				<outputmapper id="out" type="glob" from="*.tl" to="${dist.dust.dir}/*.js" />
+			</redirector>
+		</apply>
+
+		<!-- Compile LESS to CSS -->
+		<echo message="Compiling LESS style sheets." />
+		<apply dir="${less.src.dir}" executable="lessc" relative="true">
+			<mapper type="glob" from="*.less" to="*.css" />
+			<srcfile />
+			<fileset dir="${less.src.dir}" includes="*.less" />
+			<redirector>
+				<outputmapper id="out" type="glob" from="*.less" to="${dist.less.dir}/*.css" />
+			</redirector>
+		</apply>
 	</target>
 	
 	<target name="jars" depends="build" description="Create azkaban jar">
@@ -147,17 +182,27 @@
 		</copy>
 		
 		<!-- Copy bin files for web server only-->
-		<copy todir="${dist.web.package.dir}/bin" >
+		<copy todir="${dist.web.package.dir}/bin">
 			<fileset dir="${web.package.dir}/bin"/>
 		</copy>
 		
 		<!-- Copy web files -->
-		<copy todir="${dist.web.package.dir}/web" >
+		<copy todir="${dist.web.package.dir}/web">
 			<fileset dir="${web.src.dir}" />
 		</copy>
+
+		<!-- Copy compiled dust templates -->
+		<copy todir="${dist.web.package.dir}/web/js">
+			<fileset dir="${dist.dust.dir}" />
+		</copy>
+
+		<!-- Copy compiled less CSS -->
+		<copy todir="${dist.web.package.dir}/web/css">
+			<fileset dir="${dist.less.dir}" />
+		</copy>
 		
 		<!-- Copy conf create table scripts -->
-		<copy todir="${dist.web.package.dir}/conf" >
+		<copy todir="${dist.web.package.dir}/conf">
 			<fileset dir="${web.package.dir}/conf" />
 		</copy>
 		
@@ -241,6 +286,19 @@
 			<fileset dir="${web.src.dir}" />
 		</copy>
 		
+		<!-- Copy compiled dust templates -->
+		<copy todir="${dist.solo.package.dir}/web/js">
+			<fileset dir="${dist.dust.dir}" />
+		</copy>
+		
+		<!-- Copy compiled less CSS -->
+		<copy todir="${dist.solo.package.dir}/web/css">
+      <fileset dir="${dist.less.dir}">
+        <include name="azkaban.css" />
+        <include name="azkaban-svg.css" />
+      </fileset>
+		</copy>
+		
 		<!-- Copy sql files -->
 		<copy todir="${dist.solo.package.dir}/sql" >
 			<fileset dir="${sql.src.dir}" />
@@ -259,4 +317,8 @@
 	
 	<target name="package-all" depends="package-exec-server, package-web-server, package-solo-server, package-sql-scripts" description="Create all packages">
 	</target>
+
+	<target name="package" depends="package-all" description="Create all packages">
+	</target>
+
 </project>

eclipse-styles.xml 283(+283 -0)

diff --git a/eclipse-styles.xml b/eclipse-styles.xml
new file mode 100644
index 0000000..07e74f7
--- /dev/null
+++ b/eclipse-styles.xml
@@ -0,0 +1,283 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<profiles version="12">
+<profile kind="CodeFormatterProfile" name="Azkaban" version="12">
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
+<setting id="org.eclipse.jdt.core.compiler.source" value="1.5"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="2000"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.5"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
+<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="2000"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.5"/>
+<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+</profile>
+</profiles>
diff --git a/lib/commons-jexl-2.1.1.jar b/lib/commons-jexl-2.1.1.jar
new file mode 100644
index 0000000..ab288a8
Binary files /dev/null and b/lib/commons-jexl-2.1.1.jar differ
diff --git a/lib/velocity-tools-2.0.jar b/lib/velocity-tools-2.0.jar
new file mode 100644
index 0000000..beb7434
Binary files /dev/null and b/lib/velocity-tools-2.0.jar differ

README.md 4(+2 -2)

diff --git a/README.md b/README.md
index 7375cbe..d0a972f 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-# Azkaban2
+## Azkaban2
 
-For all Azkaban Plugins documentation, please go to
+For Azkaban documentation, please go to
 [Azkaban Project Site](http://azkaban.github.io/azkaban2/)
 There is a google groups: [Azkaban Group](https://groups.google.com/forum/?fromgroups#!forum/azkaban-dev)
diff --git a/scheduleTriggerMigration/file2Trigger/file2Trigger b/scheduleTriggerMigration/file2Trigger/file2Trigger
new file mode 100644
index 0000000..7645b61
--- /dev/null
+++ b/scheduleTriggerMigration/file2Trigger/file2Trigger
@@ -0,0 +1,5 @@
+CONF_FILE=
+
+java -cp "lib/*:extlib/*" azkaban.file2Trigger.File2ScheduleTrigger $CONF_FILE ../schedule2File/schedules
+
+
diff --git a/scheduleTriggerMigration/file2Trigger/lib/azkaban-2.2.jar b/scheduleTriggerMigration/file2Trigger/lib/azkaban-2.2.jar
new file mode 100644
index 0000000..97a835e
Binary files /dev/null and b/scheduleTriggerMigration/file2Trigger/lib/azkaban-2.2.jar differ
diff --git a/scheduleTriggerMigration/file2Trigger/lib/commons-dbcp-1.4.jar b/scheduleTriggerMigration/file2Trigger/lib/commons-dbcp-1.4.jar
new file mode 100644
index 0000000..c4c1c4f
Binary files /dev/null and b/scheduleTriggerMigration/file2Trigger/lib/commons-dbcp-1.4.jar differ
diff --git a/scheduleTriggerMigration/file2Trigger/lib/commons-dbutils-1.5.jar b/scheduleTriggerMigration/file2Trigger/lib/commons-dbutils-1.5.jar
new file mode 100644
index 0000000..b0c0e12
Binary files /dev/null and b/scheduleTriggerMigration/file2Trigger/lib/commons-dbutils-1.5.jar differ
diff --git a/scheduleTriggerMigration/file2Trigger/lib/commons-jexl-2.1.1.jar b/scheduleTriggerMigration/file2Trigger/lib/commons-jexl-2.1.1.jar
new file mode 100644
index 0000000..ab288a8
Binary files /dev/null and b/scheduleTriggerMigration/file2Trigger/lib/commons-jexl-2.1.1.jar differ
diff --git a/scheduleTriggerMigration/file2Trigger/lib/commons-logging-1.1.1.jar b/scheduleTriggerMigration/file2Trigger/lib/commons-logging-1.1.1.jar
new file mode 100644
index 0000000..1deef14
Binary files /dev/null and b/scheduleTriggerMigration/file2Trigger/lib/commons-logging-1.1.1.jar differ
diff --git a/scheduleTriggerMigration/file2Trigger/lib/commons-pool-1.6.jar b/scheduleTriggerMigration/file2Trigger/lib/commons-pool-1.6.jar
new file mode 100644
index 0000000..72ca75a
Binary files /dev/null and b/scheduleTriggerMigration/file2Trigger/lib/commons-pool-1.6.jar differ
diff --git a/scheduleTriggerMigration/file2Trigger/lib/file2trigger.jar b/scheduleTriggerMigration/file2Trigger/lib/file2trigger.jar
new file mode 100644
index 0000000..3d27e94
Binary files /dev/null and b/scheduleTriggerMigration/file2Trigger/lib/file2trigger.jar differ
diff --git a/scheduleTriggerMigration/file2Trigger/lib/jackson-core-asl-1.9.5.jar b/scheduleTriggerMigration/file2Trigger/lib/jackson-core-asl-1.9.5.jar
new file mode 100644
index 0000000..6862bdd
Binary files /dev/null and b/scheduleTriggerMigration/file2Trigger/lib/jackson-core-asl-1.9.5.jar differ
diff --git a/scheduleTriggerMigration/file2Trigger/lib/jackson-mapper-asl-1.9.5.jar b/scheduleTriggerMigration/file2Trigger/lib/jackson-mapper-asl-1.9.5.jar
new file mode 100644
index 0000000..147ab38
Binary files /dev/null and b/scheduleTriggerMigration/file2Trigger/lib/jackson-mapper-asl-1.9.5.jar differ
diff --git a/scheduleTriggerMigration/file2Trigger/lib/joda-time-2.0.jar b/scheduleTriggerMigration/file2Trigger/lib/joda-time-2.0.jar
new file mode 100644
index 0000000..169a7a4
Binary files /dev/null and b/scheduleTriggerMigration/file2Trigger/lib/joda-time-2.0.jar differ
diff --git a/scheduleTriggerMigration/file2Trigger/lib/log4j-1.2.16.jar b/scheduleTriggerMigration/file2Trigger/lib/log4j-1.2.16.jar
new file mode 100644
index 0000000..3f9d847
Binary files /dev/null and b/scheduleTriggerMigration/file2Trigger/lib/log4j-1.2.16.jar differ
diff --git a/scheduleTriggerMigration/schedule2File/lib/azkaban-2.1.jar b/scheduleTriggerMigration/schedule2File/lib/azkaban-2.1.jar
new file mode 100644
index 0000000..63053f6
Binary files /dev/null and b/scheduleTriggerMigration/schedule2File/lib/azkaban-2.1.jar differ
diff --git a/scheduleTriggerMigration/schedule2File/lib/commons-dbcp-1.4.jar b/scheduleTriggerMigration/schedule2File/lib/commons-dbcp-1.4.jar
new file mode 100644
index 0000000..c4c1c4f
Binary files /dev/null and b/scheduleTriggerMigration/schedule2File/lib/commons-dbcp-1.4.jar differ
diff --git a/scheduleTriggerMigration/schedule2File/lib/commons-dbutils-1.5.jar b/scheduleTriggerMigration/schedule2File/lib/commons-dbutils-1.5.jar
new file mode 100644
index 0000000..b0c0e12
Binary files /dev/null and b/scheduleTriggerMigration/schedule2File/lib/commons-dbutils-1.5.jar differ
diff --git a/scheduleTriggerMigration/schedule2File/lib/commons-io-2.4.jar b/scheduleTriggerMigration/schedule2File/lib/commons-io-2.4.jar
new file mode 100644
index 0000000..90035a4
Binary files /dev/null and b/scheduleTriggerMigration/schedule2File/lib/commons-io-2.4.jar differ
diff --git a/scheduleTriggerMigration/schedule2File/lib/commons-pool-1.6.jar b/scheduleTriggerMigration/schedule2File/lib/commons-pool-1.6.jar
new file mode 100644
index 0000000..72ca75a
Binary files /dev/null and b/scheduleTriggerMigration/schedule2File/lib/commons-pool-1.6.jar differ
diff --git a/scheduleTriggerMigration/schedule2File/lib/jackson-core-asl-1.9.5.jar b/scheduleTriggerMigration/schedule2File/lib/jackson-core-asl-1.9.5.jar
new file mode 100644
index 0000000..6862bdd
Binary files /dev/null and b/scheduleTriggerMigration/schedule2File/lib/jackson-core-asl-1.9.5.jar differ
diff --git a/scheduleTriggerMigration/schedule2File/lib/jackson-mapper-asl-1.9.5.jar b/scheduleTriggerMigration/schedule2File/lib/jackson-mapper-asl-1.9.5.jar
new file mode 100644
index 0000000..147ab38
Binary files /dev/null and b/scheduleTriggerMigration/schedule2File/lib/jackson-mapper-asl-1.9.5.jar differ
diff --git a/scheduleTriggerMigration/schedule2File/lib/joda-time-2.0.jar b/scheduleTriggerMigration/schedule2File/lib/joda-time-2.0.jar
new file mode 100644
index 0000000..169a7a4
Binary files /dev/null and b/scheduleTriggerMigration/schedule2File/lib/joda-time-2.0.jar differ
diff --git a/scheduleTriggerMigration/schedule2File/lib/log4j-1.2.16.jar b/scheduleTriggerMigration/schedule2File/lib/log4j-1.2.16.jar
new file mode 100644
index 0000000..3f9d847
Binary files /dev/null and b/scheduleTriggerMigration/schedule2File/lib/log4j-1.2.16.jar differ
diff --git a/scheduleTriggerMigration/schedule2File/lib/schedule2file.jar b/scheduleTriggerMigration/schedule2File/lib/schedule2file.jar
new file mode 100644
index 0000000..d457612
Binary files /dev/null and b/scheduleTriggerMigration/schedule2File/lib/schedule2file.jar differ
diff --git a/scheduleTriggerMigration/schedule2File/schedule2file b/scheduleTriggerMigration/schedule2File/schedule2file
new file mode 100644
index 0000000..191b4f0
--- /dev/null
+++ b/scheduleTriggerMigration/schedule2File/schedule2file
@@ -0,0 +1,7 @@
+
+CONF_FILE=
+
+java -cp "lib/*:extlib/*" azkaban.schedule2File.Schedule2File $CONF_FILE schedules
+
+
+
diff --git a/src/java/azkaban/alert/Alerter.java b/src/java/azkaban/alert/Alerter.java
new file mode 100644
index 0000000..1ba02a8
--- /dev/null
+++ b/src/java/azkaban/alert/Alerter.java
@@ -0,0 +1,11 @@
+package azkaban.alert;
+
+import azkaban.executor.ExecutableFlow;
+import azkaban.sla.SlaOption;
+
+public interface Alerter {
+	void alertOnSuccess(ExecutableFlow exflow) throws Exception;
+	void alertOnError(ExecutableFlow exflow, String ... extraReasons) throws Exception;
+	void alertOnFirstError(ExecutableFlow exflow) throws Exception;
+	void alertOnSla(SlaOption slaOption, String slaMessage) throws Exception;
+}
diff --git a/src/java/azkaban/database/AbstractJdbcLoader.java b/src/java/azkaban/database/AbstractJdbcLoader.java
index c1c92b8..faac467 100644
--- a/src/java/azkaban/database/AbstractJdbcLoader.java
+++ b/src/java/azkaban/database/AbstractJdbcLoader.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.database;
 
 import java.io.IOException;
@@ -113,4 +129,4 @@ public abstract class AbstractJdbcLoader {
 			return results;
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/database/AzkabanDatabaseSetup.java b/src/java/azkaban/database/AzkabanDatabaseSetup.java
index d1687ef..bd292d9 100644
--- a/src/java/azkaban/database/AzkabanDatabaseSetup.java
+++ b/src/java/azkaban/database/AzkabanDatabaseSetup.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.database;
 
 import java.io.BufferedInputStream;
diff --git a/src/java/azkaban/database/AzkabanDatabaseUpdater.java b/src/java/azkaban/database/AzkabanDatabaseUpdater.java
index 1a37170..4c620bf 100644
--- a/src/java/azkaban/database/AzkabanDatabaseUpdater.java
+++ b/src/java/azkaban/database/AzkabanDatabaseUpdater.java
@@ -1,5 +1,20 @@
-package azkaban.database;
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 
+package azkaban.database;
 
 import java.io.IOException;
 import java.sql.SQLException;
diff --git a/src/java/azkaban/database/AzkabanDataSource.java b/src/java/azkaban/database/AzkabanDataSource.java
index 077e6cd..501c801 100644
--- a/src/java/azkaban/database/AzkabanDataSource.java
+++ b/src/java/azkaban/database/AzkabanDataSource.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.database;
 
 import org.apache.commons.dbcp.BasicDataSource;
@@ -6,4 +22,4 @@ public abstract class AzkabanDataSource extends BasicDataSource {
 	public abstract boolean allowsOnDuplicateKey();
 	
 	public abstract String getDBType();
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/database/DataSourceUtils.java b/src/java/azkaban/database/DataSourceUtils.java
index 9c4b74a..ff40296 100644
--- a/src/java/azkaban/database/DataSourceUtils.java
+++ b/src/java/azkaban/database/DataSourceUtils.java
@@ -1,16 +1,34 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 
 package azkaban.database;
 
 import java.sql.SQLException;
-
 import javax.sql.DataSource;
-
+import org.apache.commons.dbutils.DbUtils;
 import org.apache.commons.dbutils.QueryRunner;
-
+import org.apache.log4j.Logger;
+import java.sql.PreparedStatement;
+import java.sql.Connection;
 import azkaban.utils.Props;
 
 public class DataSourceUtils {
 	
+	private static Logger logger = Logger.getLogger(DataSourceUtils.class);
+	
 	/**
 	 * Property types
 	 */
@@ -100,6 +118,9 @@ public class DataSourceUtils {
 	 *
 	 */
 	public static class MySQLBasicDataSource extends AzkabanDataSource {
+		
+		private static MonitorThread monitorThread = null;
+		
 		private MySQLBasicDataSource(String host, int port, String dbName, String user, String password, int numConnections) {
 			super();
 			
@@ -111,6 +132,11 @@ public class DataSourceUtils {
 			setMaxActive(numConnections);
 			setValidationQuery("/* ping */ select 1");
 			setTestOnBorrow(true);
+			
+			if(monitorThread == null) {
+				monitorThread = new MonitorThread(this);
+				monitorThread.start();
+			}
 		}
 
 		@Override
@@ -122,6 +148,52 @@ public class DataSourceUtils {
 		public String getDBType() {
 			return "mysql";
 		}
+		
+		private class MonitorThread extends Thread {
+			private static final long MONITOR_THREAD_WAIT_INTERVAL_MS = 30*1000;
+			private boolean shutdown = false;
+			MySQLBasicDataSource dataSource;
+			
+			public MonitorThread(MySQLBasicDataSource mysqlSource) {
+				this.setName("MySQL-DB-Monitor-Thread");
+				dataSource = mysqlSource;
+			}
+			
+			@SuppressWarnings("unused")
+			public void shutdown() {
+				shutdown = true;
+				this.interrupt();
+			}
+			
+			public void run() {
+				while (!shutdown) {
+					synchronized (this) {
+						try {
+							pingDB();
+							wait(MONITOR_THREAD_WAIT_INTERVAL_MS);
+						} catch (InterruptedException e) {
+							logger.info("Interrupted. Probably to shut down.");
+						}
+					}
+				}
+			}
+
+			private void pingDB() {
+				Connection connection = null;
+				try {
+					connection = dataSource.getConnection();
+					PreparedStatement query = connection.prepareStatement("SELECT 1");
+					query.execute();
+				} catch (SQLException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+					logger.error("MySQL connection test failed. Please check MySQL connection health!");
+				} finally {
+					DbUtils.closeQuietly(connection);
+				}
+			}
+		}
+
 	}
 	
 	/**
diff --git a/src/java/azkaban/execapp/AzkabanExecutorServer.java b/src/java/azkaban/execapp/AzkabanExecutorServer.java
index fb67970..41681c0 100644
--- a/src/java/azkaban/execapp/AzkabanExecutorServer.java
+++ b/src/java/azkaban/execapp/AzkabanExecutorServer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -174,6 +174,7 @@ public class AzkabanExecutorServer {
 		// Setup time zone
 		if (azkabanSettings.containsKey(DEFAULT_TIMEZONE_ID)) {
 			String timezone = azkabanSettings.getString(DEFAULT_TIMEZONE_ID);
+			System.setProperty("user.timezone", timezone);
 			TimeZone.setDefault(TimeZone.getTimeZone(timezone));
 			DateTimeZone.setDefault(DateTimeZone.forID(timezone));
 
@@ -315,4 +316,4 @@ public class AzkabanExecutorServer {
 			return null;
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/execapp/event/BlockingStatus.java b/src/java/azkaban/execapp/event/BlockingStatus.java
index 3a262b4..4bf638d 100644
--- a/src/java/azkaban/execapp/event/BlockingStatus.java
+++ b/src/java/azkaban/execapp/event/BlockingStatus.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.execapp.event;
 
 import azkaban.executor.Status;
diff --git a/src/java/azkaban/execapp/event/Event.java b/src/java/azkaban/execapp/event/Event.java
index 764b527..16fbb0a 100644
--- a/src/java/azkaban/execapp/event/Event.java
+++ b/src/java/azkaban/execapp/event/Event.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/execapp/event/EventHandler.java b/src/java/azkaban/execapp/event/EventHandler.java
index a71de50..9aead0a 100644
--- a/src/java/azkaban/execapp/event/EventHandler.java
+++ b/src/java/azkaban/execapp/event/EventHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/execapp/event/EventListener.java b/src/java/azkaban/execapp/event/EventListener.java
index 0c5a713..490ec36 100644
--- a/src/java/azkaban/execapp/event/EventListener.java
+++ b/src/java/azkaban/execapp/event/EventListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/execapp/event/FlowWatcher.java b/src/java/azkaban/execapp/event/FlowWatcher.java
index 8ca895e..dabb9f0 100644
--- a/src/java/azkaban/execapp/event/FlowWatcher.java
+++ b/src/java/azkaban/execapp/event/FlowWatcher.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.execapp.event;
 
 import java.util.Map;
diff --git a/src/java/azkaban/execapp/event/LocalFlowWatcher.java b/src/java/azkaban/execapp/event/LocalFlowWatcher.java
index afe9248..1ae2cc9 100644
--- a/src/java/azkaban/execapp/event/LocalFlowWatcher.java
+++ b/src/java/azkaban/execapp/event/LocalFlowWatcher.java
@@ -1,5 +1,20 @@
-package azkaban.execapp.event;
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 
+package azkaban.execapp.event;
 
 import azkaban.execapp.FlowRunner;
 import azkaban.execapp.JobRunner;
diff --git a/src/java/azkaban/execapp/event/RemoteFlowWatcher.java b/src/java/azkaban/execapp/event/RemoteFlowWatcher.java
index ec60025..6d66c7e 100644
--- a/src/java/azkaban/execapp/event/RemoteFlowWatcher.java
+++ b/src/java/azkaban/execapp/event/RemoteFlowWatcher.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.execapp.event;
 
 import azkaban.executor.ExecutableFlow;
diff --git a/src/java/azkaban/execapp/ExecutorServlet.java b/src/java/azkaban/execapp/ExecutorServlet.java
index d342f0f..b85c922 100644
--- a/src/java/azkaban/execapp/ExecutorServlet.java
+++ b/src/java/azkaban/execapp/ExecutorServlet.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.execapp;
 
 import java.io.IOException;
diff --git a/src/java/azkaban/execapp/FlowRunner.java b/src/java/azkaban/execapp/FlowRunner.java
index d38aa47..e194687 100644
--- a/src/java/azkaban/execapp/FlowRunner.java
+++ b/src/java/azkaban/execapp/FlowRunner.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.execapp;
 
 import java.io.File;
@@ -836,4 +852,4 @@ public class FlowRunner extends EventHandler implements Runnable {
 	public int getNumRunningJobs() {
 		return activeJobRunners.size();
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/execapp/FlowRunnerManager.java b/src/java/azkaban/execapp/FlowRunnerManager.java
index d707c1f..8c98086 100644
--- a/src/java/azkaban/execapp/FlowRunnerManager.java
+++ b/src/java/azkaban/execapp/FlowRunnerManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -389,12 +389,24 @@ public class FlowRunnerManager implements EventListener {
 			}
 		}
 
+		int numJobThreads = numJobThreadPerFlow;
+		if(options.getFlowParameters().containsKey("flow.num.job.threads")) {
+			try{
+				int numJobs = Integer.valueOf(options.getFlowParameters().get("flow.num.job.threads"));
+				if(numJobs > 0 && numJobs <= numJobThreads) {
+					numJobThreads = numJobs;
+				}
+			} catch (Exception e) {
+				throw new ExecutorManagerException("Failed to set the number of job threads " + options.getFlowParameters().get("flow.num.job.threads") + " for flow " + execId, e);
+			}
+		}
+		
 		FlowRunner runner = new FlowRunner(flow, executorLoader, projectLoader, jobtypeManager);
 		runner.setFlowWatcher(watcher)
 			.setJobLogSettings(jobLogChunkSize, jobLogNumFiles)
 			.setValidateProxyUser(validateProxyUser)
 			.setGlobalProps(globalProps)
-			.setNumJobThreads(numJobThreadPerFlow)
+			.setNumJobThreads(numJobThreads)
 			.addListener(this);
 		
 		// Check again.
@@ -634,6 +646,12 @@ public class FlowRunnerManager implements EventListener {
 	public int getNumExecutingFlows() {
 		return runningFlows.size();
 	}
+	
+	public String getRunningFlowIds() {
+		List<Integer> ids = new ArrayList<Integer>(runningFlows.keySet());
+		Collections.sort(ids);
+		return ids.toString();
+	}
 
 	public int getNumExecutingJobs() {
 		int jobCount = 0;
@@ -643,5 +661,7 @@ public class FlowRunnerManager implements EventListener {
 		
 		return jobCount;
 	}
+
+	
 	
 }
diff --git a/src/java/azkaban/execapp/JMXHttpServlet.java b/src/java/azkaban/execapp/JMXHttpServlet.java
index 0ee2ebc..20f9aef 100644
--- a/src/java/azkaban/execapp/JMXHttpServlet.java
+++ b/src/java/azkaban/execapp/JMXHttpServlet.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.execapp;
 
 import java.io.IOException;
diff --git a/src/java/azkaban/execapp/JobRunner.java b/src/java/azkaban/execapp/JobRunner.java
index a363284..de9bfe3 100644
--- a/src/java/azkaban/execapp/JobRunner.java
+++ b/src/java/azkaban/execapp/JobRunner.java
@@ -1,6 +1,5 @@
-package azkaban.execapp;
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,6 +14,8 @@ package azkaban.execapp;
  * the License.
  */
 
+package azkaban.execapp;
+
 import java.io.File;
 import java.io.FilenameFilter;
 import java.io.IOException;
@@ -27,9 +28,9 @@ import java.util.Arrays;
 import java.util.Collections;
 
 import org.apache.log4j.Appender;
+import org.apache.log4j.EnhancedPatternLayout;
 import org.apache.log4j.Layout;
 import org.apache.log4j.Logger;
-import org.apache.log4j.PatternLayout;
 import org.apache.log4j.RollingFileAppender;
 
 import azkaban.execapp.event.BlockingStatus;
@@ -50,7 +51,7 @@ import azkaban.jobtype.JobTypeManagerException;
 import azkaban.utils.Props;
 
 public class JobRunner extends EventHandler implements Runnable {
-	private static final Layout DEFAULT_LAYOUT = new PatternLayout("%d{dd-MM-yyyy HH:mm:ss z} %c{1} %p - %m\n");
+	private final Layout DEFAULT_LAYOUT = new EnhancedPatternLayout("%d{dd-MM-yyyy HH:mm:ss z} %c{1} %p - %m\n");
 	
 	private ExecutorLoader loader;
 	private Props props;
@@ -157,6 +158,7 @@ public class JobRunner extends EventHandler implements Runnable {
 				fileAppender.setMaxFileSize(jobLogChunkSize);
 				jobAppender = fileAppender;
 				logger.addAppender(jobAppender);
+				logger.setAdditivity(false);
 			} catch (IOException e) {
 				flowLogger.error("Could not open log file in " + workingDir + " for job " + node.getJobId(), e);
 			}
diff --git a/src/java/azkaban/execapp/ProjectVersion.java b/src/java/azkaban/execapp/ProjectVersion.java
index 18e6e58..2e9faae 100644
--- a/src/java/azkaban/execapp/ProjectVersion.java
+++ b/src/java/azkaban/execapp/ProjectVersion.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.execapp;
 
 import java.io.File;
@@ -97,4 +113,4 @@ public class ProjectVersion implements Comparable<ProjectVersion> {
 		
 		return projectId - o.projectId;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/executor/ConnectorParams.java b/src/java/azkaban/executor/ConnectorParams.java
index 15a8bf8..4a24556 100644
--- a/src/java/azkaban/executor/ConnectorParams.java
+++ b/src/java/azkaban/executor/ConnectorParams.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/executor/ExecutableFlow.java b/src/java/azkaban/executor/ExecutableFlow.java
index 152a546..eaa27a6 100644
--- a/src/java/azkaban/executor/ExecutableFlow.java
+++ b/src/java/azkaban/executor/ExecutableFlow.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -39,7 +39,7 @@ public class ExecutableFlow {
 	private int version;
 
 	private String executionPath;
-	
+
 	private HashMap<String, FlowProps> flowProps = new HashMap<String, FlowProps>();
 	private HashMap<String, ExecutableNode> executableNodes = new HashMap<String, ExecutableNode>();
 	private ArrayList<String> startNodes;
@@ -52,10 +52,10 @@ public class ExecutableFlow {
 
 	private Status flowStatus = Status.READY;
 	private String submitUser;
-	
+
 	private HashSet<String> proxyUsers = new HashSet<String>();
 	private ExecutionOptions executionOptions;
-	
+
 	public ExecutableFlow(Flow flow) {
 		this.projectId = flow.getProjectId();
 		this.scheduleId = -1;
@@ -63,79 +63,81 @@ public class ExecutableFlow {
 		this.version = flow.getVersion();
 		this.setFlow(flow);
 	}
-	
+
 	public ExecutableFlow(int executionId, Flow flow) {
 		this.projectId = flow.getProjectId();
 		this.scheduleId = -1;
 		this.flowId = flow.getId();
 		this.version = flow.getVersion();
 		this.executionId = executionId;
-		
+
 		this.setFlow(flow);
 	}
-	
+
 	public ExecutableFlow() {
 	}
-	
+
 	public long getUpdateTime() {
 		return updateTime;
 	}
-	
+
 	public void setUpdateTime(long updateTime) {
 		this.updateTime = updateTime;
 	}
-	
+
 	public List<ExecutableNode> getExecutableNodes() {
 		return new ArrayList<ExecutableNode>(executableNodes.values());
 	}
-	
+
 	public ExecutableNode getExecutableNode(String id) {
 		return executableNodes.get(id);
 	}
-	
+
 	public Collection<FlowProps> getFlowProps() {
 		return flowProps.values();
 	}
-	
+
 	public void addAllProxyUsers(Collection<String> proxyUsers) {
 		this.proxyUsers.addAll(proxyUsers);
 	}
-	
+
 	public Set<String> getProxyUsers() {
 		return new HashSet<String>(this.proxyUsers);
 	}
-	
+
 	public void setExecutionOptions(ExecutionOptions options) {
 		executionOptions = options;
 	}
-	
+
 	public ExecutionOptions getExecutionOptions() {
 		return executionOptions;
 	}
-	
+
 	private void setFlow(Flow flow) {
 		executionOptions = new ExecutionOptions();
-		
+
 		for (Node node: flow.getNodes()) {
 			String id = node.getId();
 			ExecutableNode exNode = new ExecutableNode(node, this);
 			executableNodes.put(id, exNode);
 		}
-		
+
 		for (Edge edge: flow.getEdges()) {
 			ExecutableNode sourceNode = executableNodes.get(edge.getSourceId());
 			ExecutableNode targetNode = executableNodes.get(edge.getTargetId());
-			
+
 			sourceNode.addOutNode(edge.getTargetId());
 			targetNode.addInNode(edge.getSourceId());
 		}
-		
+
 		if (flow.getSuccessEmails() != null) {
 			executionOptions.setSuccessEmails(flow.getSuccessEmails());
 		}
 		if (flow.getFailureEmails() != null) {
 			executionOptions.setFailureEmails(flow.getFailureEmails());
 		}
+		executionOptions.setMailCreator(flow.getMailCreator());
+
 		flowProps.putAll(flow.getAllFlowProps());
 	}
 
@@ -148,10 +150,10 @@ public class ExecutableFlow {
 				}
 			}
 		}
-		
+
 		return startNodes;
 	}
-	
+
 	public List<String> getEndNodes() {
 		if (endNodes == null) {
 			endNodes = new ArrayList<String>();
@@ -161,10 +163,10 @@ public class ExecutableFlow {
 				}
 			}
 		}
-		
+
 		return endNodes;
 	}
-	
+
 	public boolean setNodeStatus(String nodeId, Status status) {
 		ExecutableNode exNode = executableNodes.get(nodeId);
 		if (exNode == null) {
@@ -179,18 +181,18 @@ public class ExecutableFlow {
 		if (exNode == null) {
 			return;
 		}
-		
+
 		exNode.setExternalExecutionId(externalExecutionId);
 	}
-	
+
 	public int getExecutionId() {
 		return executionId;
 	}
 
 	public void setExecutionId(int executionId) {
 		this.executionId = executionId;
-		
-		for(ExecutableNode node: executableNodes.values()) {
+
+		for (ExecutableNode node: executableNodes.values()) {
 			node.setExecutionId(executionId);
 		}
 	}
@@ -226,31 +228,31 @@ public class ExecutableFlow {
 	public void setExecutionPath(String executionPath) {
 		this.executionPath = executionPath;
 	}
-	
+
 	public long getStartTime() {
 		return startTime;
 	}
-	
+
 	public void setStartTime(long time) {
 		this.startTime = time;
 	}
-	
+
 	public long getEndTime() {
 		return endTime;
 	}
-	
+
 	public void setEndTime(long time) {
 		this.endTime = time;
 	}
-	
+
 	public long getSubmitTime() {
 		return submitTime;
 	}
-	
+
 	public void setSubmitTime(long time) {
 		this.submitTime = time;
 	}
-	
+
 	public Status getStatus() {
 		return flowStatus;
 	}
@@ -258,16 +260,16 @@ public class ExecutableFlow {
 	public void setStatus(Status flowStatus) {
 		this.flowStatus = flowStatus;
 	}
-	
-	public Map<String,Object> toObject() {
+
+	public Map<String, Object> toObject() {
 		HashMap<String, Object> flowObj = new HashMap<String, Object>();
 		flowObj.put("type", "executableflow");
 		flowObj.put("executionId", executionId);
 		flowObj.put("executionPath", executionPath);
 		flowObj.put("flowId", flowId);
 		flowObj.put("projectId", projectId);
-		
-		if(scheduleId >= 0) {
+
+		if (scheduleId >= 0) {
 			flowObj.put("scheduleId", scheduleId);
 		}
 		flowObj.put("submitTime", submitTime);
@@ -276,16 +278,16 @@ public class ExecutableFlow {
 		flowObj.put("status", flowStatus.toString());
 		flowObj.put("submitUser", submitUser);
 		flowObj.put("version", version);
-		
+
 		flowObj.put("executionOptions", this.executionOptions.toObject());
 		flowObj.put("version", version);
-		
+
 		ArrayList<Object> props = new ArrayList<Object>();
 		for (FlowProps fprop: flowProps.values()) {
 			HashMap<String, Object> propObj = new HashMap<String, Object>();
 			String source = fprop.getSource();
 			String inheritedSource = fprop.getInheritedSource();
-			
+
 			propObj.put("source", source);
 			if (inheritedSource != null) {
 				propObj.put("inherited", inheritedSource);
@@ -293,13 +295,13 @@ public class ExecutableFlow {
 			props.add(propObj);
 		}
 		flowObj.put("properties", props);
-		
+
 		ArrayList<Object> nodes = new ArrayList<Object>();
 		for (ExecutableNode node: executableNodes.values()) {
 			nodes.add(node.toObject());
 		}
 		flowObj.put("nodes", nodes);
-		
+
 		ArrayList<String> proxyUserList = new ArrayList<String>(proxyUsers);
 		flowObj.put("proxyUsers", proxyUserList);
 
@@ -307,57 +309,57 @@ public class ExecutableFlow {
 	}
 
 	public Object toUpdateObject(long lastUpdateTime) {
-		Map<String, Object> updateData = new HashMap<String,Object>();
+		Map<String, Object> updateData = new HashMap<String, Object>();
 		updateData.put("execId", this.executionId);
 		updateData.put("status", this.flowStatus.getNumVal());
 		updateData.put("startTime", this.startTime);
 		updateData.put("endTime", this.endTime);
 		updateData.put("updateTime", this.updateTime);
-		
-		List<Map<String,Object>> updatedNodes = new ArrayList<Map<String,Object>>();
+
+		List<Map<String, Object>> updatedNodes = new ArrayList<Map<String, Object>>();
 		for (ExecutableNode node: executableNodes.values()) {
-			
+
 			if (node.getUpdateTime() > lastUpdateTime) {
-				Map<String, Object> updatedNodeMap = new HashMap<String,Object>();
+				Map<String, Object> updatedNodeMap = new HashMap<String, Object>();
 				updatedNodeMap.put("jobId", node.getJobId());
 				updatedNodeMap.put("status", node.getStatus().getNumVal());
 				updatedNodeMap.put("startTime", node.getStartTime());
 				updatedNodeMap.put("endTime", node.getEndTime());
 				updatedNodeMap.put("updateTime", node.getUpdateTime());
 				updatedNodeMap.put("attempt", node.getAttempt());
-				
+
 				if (node.getAttempt() > 0) {
-					ArrayList<Map<String,Object>> pastAttempts = new ArrayList<Map<String,Object>>();
+					ArrayList<Map<String, Object>> pastAttempts = new ArrayList<Map<String, Object>>();
 					for (Attempt attempt: node.getPastAttemptList()) {
 						pastAttempts.add(attempt.toObject());
 					}
 					updatedNodeMap.put("pastAttempts", pastAttempts);
 				}
-				
+
 				updatedNodes.add(updatedNodeMap);
 			}
 		}
-		
+
 		updateData.put("nodes", updatedNodes);
 		return updateData;
 	}
-	
+
 	@SuppressWarnings("unchecked")
 	public void applyUpdateObject(Map<String, Object> updateData) {
-		List<Map<String,Object>> updatedNodes = (List<Map<String,Object>>)updateData.get("nodes");
-		for (Map<String,Object> node: updatedNodes) {
+		List<Map<String, Object>> updatedNodes = (List<Map<String, Object>>)updateData.get("nodes");
+		for (Map<String, Object> node: updatedNodes) {
 			String jobId = (String)node.get("jobId");
 			Status status = Status.fromInteger((Integer)node.get("status"));
 			long startTime = JSONUtils.getLongFromObject(node.get("startTime"));
 			long endTime = JSONUtils.getLongFromObject(node.get("endTime"));
 			long updateTime = JSONUtils.getLongFromObject(node.get("updateTime"));
-			
+
 			ExecutableNode exNode = executableNodes.get(jobId);
 			exNode.setEndTime(endTime);
 			exNode.setStartTime(startTime);
 			exNode.setUpdateTime(updateTime);
 			exNode.setStatus(status);
-			
+
 			int attempt = 0;
 			if (node.containsKey("attempt")) {
 				attempt = (Integer)node.get("attempt");
@@ -365,22 +367,22 @@ public class ExecutableFlow {
 					exNode.updatePastAttempts((List<Object>)node.get("pastAttempts"));
 				}
 			}
-			
+
 			exNode.setAttempt(attempt);
 		}
-		
+
 		this.flowStatus = Status.fromInteger((Integer)updateData.get("status"));
-		
+
 		this.startTime = JSONUtils.getLongFromObject(updateData.get("startTime"));
 		this.endTime = JSONUtils.getLongFromObject(updateData.get("endTime"));
 		this.updateTime = JSONUtils.getLongFromObject(updateData.get("updateTime"));
 	}
-	
+
 	@SuppressWarnings("unchecked")
 	public static ExecutableFlow createExecutableFlowFromObject(Object obj) {
 		ExecutableFlow exFlow = new ExecutableFlow();
-		
-		HashMap<String, Object> flowObj = (HashMap<String,Object>)obj;
+
+		HashMap<String, Object> flowObj = (HashMap<String, Object>)obj;
 		exFlow.executionId = (Integer)flowObj.get("executionId");
 		exFlow.executionPath = (String)flowObj.get("executionPath");
 		exFlow.flowId = (String)flowObj.get("flowId");
@@ -394,7 +396,7 @@ public class ExecutableFlow {
 		exFlow.flowStatus = Status.valueOf((String)flowObj.get("status"));
 		exFlow.submitUser = (String)flowObj.get("submitUser");
 		exFlow.version = (Integer)flowObj.get("version");
-		
+
 		if (flowObj.containsKey("executionOptions")) {
 			exFlow.executionOptions = ExecutionOptions.createFromObject(flowObj.get("executionOptions"));
 		}
@@ -402,7 +404,7 @@ public class ExecutableFlow {
 			// for backawards compatibility should remove in a few versions.
 			exFlow.executionOptions = ExecutionOptions.createFromObject(flowObj);
 		}
-		
+
 		// Copy nodes
 		List<Object> nodes = (List<Object>)flowObj.get("nodes");
 		for (Object nodeObj: nodes) {
@@ -411,57 +413,57 @@ public class ExecutableFlow {
 		}
 
 		List<Object> properties = (List<Object>)flowObj.get("properties");
-		for (Object propNode : properties) {
+		for (Object propNode: properties) {
 			HashMap<String, Object> fprop = (HashMap<String, Object>)propNode;
 			String source = (String)fprop.get("source");
 			String inheritedSource = (String)fprop.get("inherited");
-			
+
 			FlowProps flowProps = new FlowProps(inheritedSource, source);
 			exFlow.flowProps.put(source, flowProps);
 		}
-		
-		if(flowObj.containsKey("proxyUsers")) {
-			ArrayList<String> proxyUserList = (ArrayList<String>) flowObj.get("proxyUsers");
+
+		if (flowObj.containsKey("proxyUsers")) {
+			ArrayList<String> proxyUserList = (ArrayList<String>)flowObj.get("proxyUsers");
 			exFlow.addAllProxyUsers(proxyUserList);
 		}
-		
+
 		return exFlow;
 	}
-	
+
 	@SuppressWarnings("unchecked")
 	public void updateExecutableFlowFromObject(Object obj) {
-		HashMap<String, Object> flowObj = (HashMap<String,Object>)obj;
+		HashMap<String, Object> flowObj = (HashMap<String, Object>)obj;
 
 		submitTime = JSONUtils.getLongFromObject(flowObj.get("submitTime"));
 		startTime = JSONUtils.getLongFromObject(flowObj.get("startTime"));
 		endTime = JSONUtils.getLongFromObject(flowObj.get("endTime"));
 		flowStatus = Status.valueOf((String)flowObj.get("status"));
-		
+
 		List<Object> nodes = (List<Object>)flowObj.get("nodes");
 		for (Object nodeObj: nodes) {
-			HashMap<String, Object> nodeHash= (HashMap<String, Object>)nodeObj;
+			HashMap<String, Object> nodeHash = (HashMap<String, Object>)nodeObj;
 			String nodeId = (String)nodeHash.get("id");
 			ExecutableNode node = executableNodes.get(nodeId);
 			if (nodeId == null) {
 				throw new RuntimeException("Node " + nodeId + " doesn't exist in flow.");
 			}
-			
+
 			node.updateNodeFromObject(nodeObj);
 		}
 	}
-	
+
 	public Set<String> getSources() {
 		HashSet<String> set = new HashSet<String>();
 		for (ExecutableNode exNode: executableNodes.values()) {
 			set.add(exNode.getJobPropsSource());
 		}
-		
+
 		for (FlowProps props: flowProps.values()) {
 			set.add(props.getSource());
 		}
 		return set;
 	}
-	
+
 	public String getSubmitUser() {
 		return submitUser;
 	}
@@ -469,7 +471,7 @@ public class ExecutableFlow {
 	public void setSubmitUser(String submitUser) {
 		this.submitUser = submitUser;
 	}
-	
+
 	public int getVersion() {
 		return version;
 	}
@@ -477,4 +479,15 @@ public class ExecutableFlow {
 	public void setVersion(int version) {
 		this.version = version;
 	}
+	
+	public static boolean isFinished(ExecutableFlow flow) {
+		switch(flow.getStatus()) {
+		case SUCCEEDED:
+		case FAILED:
+		case KILLED:
+			return true;
+		default:
+			return false;
+		}
+	}
 }
diff --git a/src/java/azkaban/executor/ExecutableJobInfo.java b/src/java/azkaban/executor/ExecutableJobInfo.java
index b096505..53eca5c 100644
--- a/src/java/azkaban/executor/ExecutableJobInfo.java
+++ b/src/java/azkaban/executor/ExecutableJobInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.executor;
 
 import java.util.HashMap;
@@ -75,4 +91,4 @@ public class ExecutableJobInfo {
 		
 		return map;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/executor/ExecutableNode.java b/src/java/azkaban/executor/ExecutableNode.java
index 32eac5d..1bea9b7 100644
--- a/src/java/azkaban/executor/ExecutableNode.java
+++ b/src/java/azkaban/executor/ExecutableNode.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -373,4 +373,4 @@ public class ExecutableNode {
 			return attempts;
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/executor/ExecutionOptions.java b/src/java/azkaban/executor/ExecutionOptions.java
index a91a6d0..1461dcc 100644
--- a/src/java/azkaban/executor/ExecutionOptions.java
+++ b/src/java/azkaban/executor/ExecutionOptions.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.executor;
 
 import java.util.ArrayList;
@@ -8,6 +24,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import azkaban.executor.mail.DefaultMailCreator;
+
 /**
  * Execution options for submitted flows and scheduled flows
  */
@@ -27,6 +45,7 @@ public class ExecutionOptions {
 	private Integer pipelineExecId = null;
 	private Integer queueLevel = 0;
 	private String concurrentOption = CONCURRENT_OPTION_IGNORE;
+	private String mailCreator = DefaultMailCreator.DEFAULT_MAIL_CREATOR;
 	private Map<String, String> flowParameters = new HashMap<String, String>();
 	
 	public enum FailureAction {
@@ -40,7 +59,7 @@ public class ExecutionOptions {
 	private Set<String> initiallyDisabledJobs = new HashSet<String>();
 	
 	public void setFlowParameters(Map<String,String> flowParam) {
-		flowParameters.get(flowParam);
+		flowParameters.putAll(flowParam);
 	}
 	
 	public Map<String,String> getFlowParameters() {
@@ -107,10 +126,18 @@ public class ExecutionOptions {
 		this.concurrentOption = concurrentOption;
 	}
 	
+	public void setMailCreator(String mailCreator) {
+		this.mailCreator = mailCreator;
+	}
+	
 	public String getConcurrentOption() {
 		return concurrentOption;
 	}
 	
+	public String getMailCreator() {
+		return mailCreator;
+	}
+	
 	public Integer getPipelineLevel() {
 		return pipelineLevel;
 	}
@@ -152,6 +179,7 @@ public class ExecutionOptions {
 		flowOptionObj.put("pipelineExecId", pipelineExecId);
 		flowOptionObj.put("queueLevel", queueLevel);
 		flowOptionObj.put("concurrentOption", concurrentOption);
+		flowOptionObj.put("mailCreator", mailCreator);
 		flowOptionObj.put("disabled", initiallyDisabledJobs);
 		flowOptionObj.put("failureEmailsOverride", failureEmailsOverride);
 		flowOptionObj.put("successEmailsOverride", successEmailsOverride);
@@ -180,6 +208,9 @@ public class ExecutionOptions {
 		if (optionsMap.containsKey("concurrentOption")) {
 			options.concurrentOption = (String)optionsMap.get("concurrentOption");
 		}
+		if (optionsMap.containsKey("mailCreator")) {
+			options.mailCreator = (String)optionsMap.get("mailCreator");
+		}
 		if (optionsMap.containsKey("disabled")) {
 			options.initiallyDisabledJobs = new HashSet<String>((List<String>)optionsMap.get("disabled"));
 		}
diff --git a/src/java/azkaban/executor/ExecutionReference.java b/src/java/azkaban/executor/ExecutionReference.java
index 930d11d..61eadbf 100644
--- a/src/java/azkaban/executor/ExecutionReference.java
+++ b/src/java/azkaban/executor/ExecutionReference.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.executor;
 
 public class ExecutionReference {
@@ -49,4 +65,4 @@ public class ExecutionReference {
 	public void setNumErrors(int numErrors) {
 		this.numErrors = numErrors;
 	}
- }
\ No newline at end of file
+ }
diff --git a/src/java/azkaban/executor/ExecutorLoader.java b/src/java/azkaban/executor/ExecutorLoader.java
index 97581e9..04e0e44 100644
--- a/src/java/azkaban/executor/ExecutorLoader.java
+++ b/src/java/azkaban/executor/ExecutorLoader.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/executor/ExecutorManager.java b/src/java/azkaban/executor/ExecutorManager.java
index 527956d..bf87720 100644
--- a/src/java/azkaban/executor/ExecutorManager.java
+++ b/src/java/azkaban/executor/ExecutorManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -16,6 +16,7 @@
 
 package azkaban.executor;
 
+import java.io.File;
 import java.io.IOException;
 import java.lang.Thread.State;
 import java.net.URI;
@@ -39,6 +40,10 @@ import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.log4j.Logger;
 import org.joda.time.DateTime;
 
+import azkaban.alert.Alerter;
+import azkaban.execapp.event.Event;
+import azkaban.execapp.event.Event.Type;
+import azkaban.execapp.event.EventHandler;
 import azkaban.project.Project;
 import azkaban.scheduler.ScheduleStatisticManager;
 import azkaban.utils.FileIOUtils.JobMetaData;
@@ -51,7 +56,7 @@ import azkaban.utils.Props;
  * Executor manager used to manage the client side job.
  *
  */
-public class ExecutorManager {
+public class ExecutorManager extends EventHandler implements ExecutorManagerAdapter {
 	private static Logger logger = Logger.getLogger(ExecutorManager.class);
 	private ExecutorLoader executorLoader;
 	private String executorHost;
@@ -62,46 +67,54 @@ public class ExecutorManager {
 	private ConcurrentHashMap<Integer, Pair<ExecutionReference, ExecutableFlow>> runningFlows = new ConcurrentHashMap<Integer, Pair<ExecutionReference, ExecutableFlow>>();
 	private ConcurrentHashMap<Integer, ExecutableFlow> recentlyFinished = new ConcurrentHashMap<Integer, ExecutableFlow>();
 
-	private ExecutorMailer mailer;
 	private ExecutingManagerUpdaterThread executingManager;
 	
 	private static final long DEFAULT_EXECUTION_LOGS_RETENTION_MS = 3*4*7*24*60*60*1000l;
 	private long lastCleanerThreadCheckTime = -1;
 	
 	private long lastThreadCheckTime = -1;
+	private String updaterStage = "not started";
+
+	private Map<String, Alerter> alerters;
+	
+	File cacheDir;
 	
-	public ExecutorManager(Props props, ExecutorLoader loader) throws ExecutorManagerException {
+	public ExecutorManager(Props props, ExecutorLoader loader, Map<String, Alerter> alters) throws ExecutorManagerException {
+		
 		this.executorLoader = loader;
 		this.loadRunningFlows();
-		
 		executorHost = props.getString("executor.host", "localhost");
 		executorPort = props.getInt("executor.port");
-		mailer = new ExecutorMailer(props);
+		
+		alerters = alters;
+		
+		cacheDir = new File(props.getString("cache.directory", "cache"));
+
 		executingManager = new ExecutingManagerUpdaterThread();
 		executingManager.start();
-
+		
 		long executionLogsRetentionMs = props.getLong("execution.logs.retention.ms", DEFAULT_EXECUTION_LOGS_RETENTION_MS);
 		cleanerThread = new CleanerThread(executionLogsRetentionMs);
 		cleanerThread.start();
+		
 	}
 	
-	public String getExecutorHost() {
-		return executorHost;
-	}
-	
-	public int getExecutorPort() {
-		return executorPort;
+	@Override
+	public State getExecutorManagerThreadState() {
+		return executingManager.getState();
 	}
 	
-	public State getExecutorThreadState() {
-		return executingManager.getState();
+	public String getExecutorThreadStage() {
+		return updaterStage;
 	}
 	
-	public boolean isThreadActive() {
+	@Override
+	public boolean isExecutorManagerThreadActive() {
 		return executingManager.isAlive();
 	}
 	
-	public long getLastThreadCheckTime() {
+	@Override
+	public long getLastExecutorManagerThreadCheckTime() {
 		return lastThreadCheckTime;
 	}
 	
@@ -109,6 +122,7 @@ public class ExecutorManager {
 		return this.lastCleanerThreadCheckTime;
 	}
 	
+	@Override
 	public Set<String> getPrimaryServerHosts() {
 		// Only one for now. More probably later.
 		HashSet<String> ports = new HashSet<String>();
@@ -116,6 +130,7 @@ public class ExecutorManager {
 		return ports;
 	}
 	
+	@Override
 	public Set<String> getAllActiveExecutorServerHosts() {
 		// Includes non primary server/hosts
 		HashSet<String> ports = new HashSet<String>();
@@ -132,38 +147,37 @@ public class ExecutorManager {
 		runningFlows.putAll(executorLoader.fetchActiveFlows());
 	}
 	
+	@Override
 	public List<Integer> getRunningFlows(int projectId, String flowId) {
 		ArrayList<Integer> executionIds = new ArrayList<Integer>();
 		for (Pair<ExecutionReference, ExecutableFlow> ref : runningFlows.values()) {
-			if (ref.getSecond().getFlowId().equals(flowId)) {
+			if (ref.getSecond().getFlowId().equals(flowId) && ref.getSecond().getProjectId() == projectId) {
 				executionIds.add(ref.getFirst().getExecId());
 			}
 		}
-		
 		return executionIds;
 	}
 	
+	@Override
 	public boolean isFlowRunning(int projectId, String flowId) {
 		for (Pair<ExecutionReference, ExecutableFlow> ref : runningFlows.values()) {
-
 			if (ref.getSecond().getProjectId() == projectId && ref.getSecond().getFlowId().equals(flowId)) {
 				return true;
 			}
 		}
-		
 		return false;
 	}
 	
+	@Override
 	public ExecutableFlow getExecutableFlow(int execId) throws ExecutorManagerException {
 		Pair<ExecutionReference, ExecutableFlow> active = runningFlows.get(execId);
-		
 		if (active == null) {
 			return executorLoader.fetchExecutableFlow(execId);
 		}
-
 		return active.getSecond();
 	}
 	
+	@Override
 	public List<ExecutableFlow> getRunningFlows() {
 		ArrayList<ExecutableFlow> flows = new ArrayList<ExecutableFlow>();
 		for (Pair<ExecutionReference, ExecutableFlow> ref : runningFlows.values()) {
@@ -172,43 +186,60 @@ public class ExecutorManager {
 		return flows;
 	}
 	
+	public String getRunningFlowIds() {
+		List<Integer> allIds = new ArrayList<Integer>();
+		for (Pair<ExecutionReference, ExecutableFlow> ref : runningFlows.values()) {
+			allIds.add(ref.getSecond().getExecutionId());
+		}
+		Collections.sort(allIds);
+		return allIds.toString();
+	}
+	
 	public List<ExecutableFlow> getRecentlyFinishedFlows() {
 		return new ArrayList<ExecutableFlow>(recentlyFinished.values());
 	}
 	
+	@Override
 	public List<ExecutableFlow> getExecutableFlows(Project project, String flowId, int skip, int size) throws ExecutorManagerException {
 		List<ExecutableFlow> flows = executorLoader.fetchFlowHistory(project.getId(), flowId, skip, size);
 		return flows;
 	}
 	
+	@Override
 	public List<ExecutableFlow> getExecutableFlows(int skip, int size) throws ExecutorManagerException {
 		List<ExecutableFlow> flows = executorLoader.fetchFlowHistory(skip, size);
 		return flows;
 	}
 	
+	@Override
 	public List<ExecutableFlow> getExecutableFlows(String flowIdContains, int skip, int size) throws ExecutorManagerException {
 		List<ExecutableFlow> flows = executorLoader.fetchFlowHistory(null, '%'+flowIdContains+'%', null, 0, -1, -1 , skip, size);
 		return flows;
 	}
 
+	@Override
 	public List<ExecutableFlow> getExecutableFlows(String projContain, String flowContain, String userContain, int status, long begin, long end, int skip, int size) throws ExecutorManagerException {
 		List<ExecutableFlow> flows = executorLoader.fetchFlowHistory(projContain, flowContain, userContain, status, begin, end , skip, size);
 		return flows;
 	}
 	
+	@Override
 	public List<ExecutableJobInfo> getExecutableJobs(Project project, String jobId, int skip, int size) throws ExecutorManagerException {
 		List<ExecutableJobInfo> nodes = executorLoader.fetchJobHistory(project.getId(), jobId, skip, size);
 		return nodes;
 	}
 	
+	@Override
 	public int getNumberOfJobExecutions(Project project, String jobId) throws ExecutorManagerException{
 		return executorLoader.fetchNumExecutableNodes(project.getId(), jobId);
 	}
 	
+	@Override
 	public int getNumberOfExecutions(Project project, String flowId) throws ExecutorManagerException{
 		return executorLoader.fetchNumExecutableFlows(project.getId(), flowId);
 	}
 	
+	@Override
 	public LogData getExecutableFlowLog(ExecutableFlow exFlow, int offset, int length) throws ExecutorManagerException {
 		Pair<ExecutionReference, ExecutableFlow> pair = runningFlows.get(exFlow.getExecutionId());
 		if (pair != null) {
@@ -226,10 +257,10 @@ public class ExecutorManager {
 		}
 	}
 	
+	@Override
 	public LogData getExecutionJobLog(ExecutableFlow exFlow, String jobId, int offset, int length, int attempt) throws ExecutorManagerException {
 		Pair<ExecutionReference, ExecutableFlow> pair = runningFlows.get(exFlow.getExecutionId());
 		if (pair != null) {
-
 			Pair<String,String> typeParam = new Pair<String,String>("type", "job");
 			Pair<String,String> jobIdParam = new Pair<String,String>("jobId", jobId);
 			Pair<String,String> offsetParam = new Pair<String,String>("offset", String.valueOf(offset));
@@ -246,6 +277,7 @@ public class ExecutorManager {
 		}
 	}
 	
+	@Override
 	public JobMetaData getExecutionJobMetaData(ExecutableFlow exFlow, String jobId, int offset, int length, int attempt) throws ExecutorManagerException {
 		Pair<ExecutionReference, ExecutableFlow> pair = runningFlows.get(exFlow.getExecutionId());
 		if (pair != null) {
@@ -265,6 +297,7 @@ public class ExecutorManager {
 		}
 	}
 	
+	@Override
 	public void cancelFlow(ExecutableFlow exFlow, String userId) throws ExecutorManagerException {
 		synchronized(exFlow) {
 			Pair<ExecutionReference, ExecutableFlow> pair = runningFlows.get(exFlow.getExecutionId());
@@ -275,6 +308,7 @@ public class ExecutorManager {
 		}
 	}
 	
+	@Override
 	public void resumeFlow(ExecutableFlow exFlow, String userId) throws ExecutorManagerException {
 		synchronized(exFlow) {
 			Pair<ExecutionReference, ExecutableFlow> pair = runningFlows.get(exFlow.getExecutionId());
@@ -285,6 +319,7 @@ public class ExecutorManager {
 		}
 	}
 	
+	@Override
 	public void pauseFlow(ExecutableFlow exFlow, String userId) throws ExecutorManagerException {
 		synchronized(exFlow) {
 			Pair<ExecutionReference, ExecutableFlow> pair = runningFlows.get(exFlow.getExecutionId());
@@ -295,30 +330,37 @@ public class ExecutorManager {
 		}
 	}
 	
+	@Override
 	public void pauseExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException {
 		modifyExecutingJobs(exFlow, ConnectorParams.MODIFY_PAUSE_JOBS, userId, jobIds);
 	}
 	
+	@Override
 	public void resumeExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException {
 		modifyExecutingJobs(exFlow, ConnectorParams.MODIFY_RESUME_JOBS, userId, jobIds);
 	}
 	
+	@Override
 	public void retryFailures(ExecutableFlow exFlow, String userId) throws ExecutorManagerException {
 		modifyExecutingJobs(exFlow, ConnectorParams.MODIFY_RETRY_FAILURES, userId);
 	}
 	
+	@Override
 	public void retryExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException {
 		modifyExecutingJobs(exFlow, ConnectorParams.MODIFY_RETRY_JOBS, userId, jobIds);
 	}
 	
+	@Override
 	public void disableExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException {
 		modifyExecutingJobs(exFlow, ConnectorParams.MODIFY_DISABLE_JOBS, userId, jobIds);
 	}
 	
+	@Override
 	public void enableExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException {
 		modifyExecutingJobs(exFlow, ConnectorParams.MODIFY_ENABLE_JOBS, userId, jobIds);
 	}
 	
+	@Override
 	public void cancelExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException {
 		modifyExecutingJobs(exFlow, ConnectorParams.MODIFY_CANCEL_JOBS, userId, jobIds);
 	}
@@ -361,9 +403,10 @@ public class ExecutorManager {
 		}
 	}
 	
-	public String submitExecutableFlow(ExecutableFlow exflow) throws ExecutorManagerException {
+	@Override
+	public String submitExecutableFlow(ExecutableFlow exflow, String userId) throws ExecutorManagerException {
 		synchronized(exflow) {
-			logger.info("Submitting execution flow " + exflow.getFlowId());
+			logger.info("Submitting execution flow " + exflow.getFlowId() + " by " + userId);
 
 			int projectId = exflow.getProjectId();
 			String flowId = exflow.getFlowId();
@@ -423,7 +466,7 @@ public class ExecutorManager {
 	}
 	
 	
-	public void cleanOldExecutionLogs(long millis) {
+	private void cleanOldExecutionLogs(long millis) {
 		try {
 			int count = executorLoader.removeExecutionLogsByTime(millis);
 			logger.info("Cleaned up " + count + " log entries.");
@@ -519,6 +562,7 @@ public class ExecutorManager {
 		return jsonResponse;
 	}
 	
+	@Override
 	public Map<String, Object> callExecutorJMX(String hostPort, String action, String mBean) throws IOException {
 		URIBuilder builder = new URIBuilder();
 		
@@ -563,6 +607,7 @@ public class ExecutorManager {
 		return jsonResponse;
 	}
 	
+	@Override
 	public void shutdown() {
 		executingManager.shutdown();
 	}
@@ -592,6 +637,10 @@ public class ExecutorManager {
 			while(!shutdown) {
 				try {
 					lastThreadCheckTime = System.currentTimeMillis();
+
+//					loadRunningFlows();
+
+					updaterStage = "Starting update all flows.";
 					
 					Map<ConnectionInfo, List<ExecutableFlow>> exFlowMap = getFlowToExecutorMap();
 					ArrayList<ExecutableFlow> finishedFlows = new ArrayList<ExecutableFlow>();
@@ -602,6 +651,10 @@ public class ExecutorManager {
 							List<Long> updateTimesList = new ArrayList<Long>();
 							List<Integer> executionIdsList = new ArrayList<Integer>();
 						
+							ConnectionInfo connection = entry.getKey();
+							
+							updaterStage = "Starting update flows on " + connection.getHost() + ":" + connection.getPort();
+							
 							// We pack the parameters of the same host together before we query.
 							fillUpdateTimeAndExecId(entry.getValue(), executionIdsList, updateTimesList);
 							
@@ -612,7 +665,7 @@ public class ExecutorManager {
 									ConnectorParams.EXEC_ID_LIST_PARAM, 
 									JSONUtils.toJSON(executionIdsList));
 							
-							ConnectionInfo connection = entry.getKey();
+							
 							Map<String, Object> results = null;
 							try {
 								results = callExecutorServer(connection.getHost(), connection.getPort(), ConnectorParams.UPDATE_ACTION, null, null, executionIds, updateTimes);
@@ -620,6 +673,9 @@ public class ExecutorManager {
 								logger.error(e);
 								for (ExecutableFlow flow: entry.getValue()) {
 									Pair<ExecutionReference, ExecutableFlow> pair = runningFlows.get(flow.getExecutionId());
+									
+									updaterStage = "Failed to get update. Doing some clean up for flow " + pair.getSecond().getExecutionId();
+									
 									if (pair != null) {
 										ExecutionReference ref = pair.getFirst();
 										int numErrors = ref.getNumErrors();
@@ -642,6 +698,9 @@ public class ExecutorManager {
 								for (Map<String,Object> updateMap: executionUpdates) {
 									try {
 										ExecutableFlow flow = updateExecution(updateMap);
+										
+										updaterStage = "Updated flow " + flow.getExecutionId();
+										
 										if (isFinished(flow)) {
 											finishedFlows.add(flow);
 											finalizeFlows.add(flow);
@@ -659,21 +718,28 @@ public class ExecutorManager {
 							}
 						}
 	
+						updaterStage = "Evicting old recently finished flows.";
+						
 						evictOldRecentlyFinished(recentlyFinishedLifetimeMs);
 						// Add new finished
 						for (ExecutableFlow flow: finishedFlows) {
 							if(flow.getScheduleId() >= 0 && flow.getStatus() == Status.SUCCEEDED){
-								ScheduleStatisticManager.invalidateCache(flow.getScheduleId());
+								ScheduleStatisticManager.invalidateCache(flow.getScheduleId(), cacheDir);
 							}
+							fireEventListeners(Event.create(flow, Type.FLOW_FINISHED));
 							recentlyFinished.put(flow.getExecutionId(), flow);
 						}
 						
+						updaterStage = "Finalizing " + finalizeFlows.size() + " error flows.";
+						
 						// Kill error flows
 						for (ExecutableFlow flow: finalizeFlows) {
 							finalizeFlows(flow);
 						}
 					}
 					
+					updaterStage = "Updated all active flows. Waiting for next round.";
+					
 					synchronized(this) {
 						try {
 							if (runningFlows.size() > 0) {
@@ -688,14 +754,16 @@ public class ExecutorManager {
 				}
 				catch (Exception e) {
 					logger.error(e);
-				}
+				} 
 			}
 		}
 	}
 	
 	private void finalizeFlows(ExecutableFlow flow) {
+
 		int execId = flow.getExecutionId();
 		
+		updaterStage = "finalizing flow " + execId;
 		// First we check if the execution in the datastore is complete
 		try {
 			ExecutableFlow dsFlow;
@@ -703,15 +771,19 @@ public class ExecutorManager {
 				dsFlow = flow;
 			}
 			else {
+				updaterStage = "finalizing flow " + execId + " loading from db";
 				dsFlow = executorLoader.fetchExecutableFlow(execId);
 			
 				// If it's marked finished, we're good. If not, we fail everything and then mark it finished.
 				if (!isFinished(dsFlow)) {
+					updaterStage = "finalizing flow " + execId + " failing the flow";
 					failEverything(dsFlow);
 					executorLoader.updateExecutableFlow(dsFlow);
 				}
 			}
 
+			updaterStage = "finalizing flow " + execId + " deleting active reference";
+			
 			// Delete the executing reference.
 			if (flow.getEndTime() == -1) {
 				flow.setEndTime(System.currentTimeMillis());
@@ -719,8 +791,11 @@ public class ExecutorManager {
 			}
 			executorLoader.removeActiveExecutableReference(execId);
 			
+			updaterStage = "finalizing flow " + execId + " cleaning from memory";
 			runningFlows.remove(execId);
+			fireEventListeners(Event.create(dsFlow, Type.FLOW_FINISHED));
 			recentlyFinished.put(execId, dsFlow);
+
 		} catch (ExecutorManagerException e) {
 			logger.error(e);
 		}
@@ -728,29 +803,64 @@ public class ExecutorManager {
 		// TODO append to the flow log that we forced killed this flow because the target no longer had
 		// the reference.
 		
+		updaterStage = "finalizing flow " + execId + " alerting and emailing";
 		ExecutionOptions options = flow.getExecutionOptions();
 		// But we can definitely email them.
+		Alerter mailAlerter = alerters.get("email");
 		if(flow.getStatus() == Status.FAILED || flow.getStatus() == Status.KILLED)
 		{
 			if(options.getFailureEmails() != null && !options.getFailureEmails().isEmpty())
 			{
 				try {
-					mailer.sendErrorEmail(flow, "Executor no longer seems to be running this execution. Most likely due to executor bounce.");
+					mailAlerter.alertOnError(flow, "Executor no longer seems to be running this execution. Most likely due to executor bounce.");
 				} catch (Exception e) {
 					logger.error(e);
 				}
 			}
+			if(options.getFlowParameters().containsKey("alert.type")) {
+				String alertType = options.getFlowParameters().get("alert.type");
+				Alerter alerter = alerters.get(alertType);
+				if(alerter != null) {
+					try {
+						alerter.alertOnError(flow, "Executor no longer seems to be running this execution. Most likely due to executor bounce.");
+					} catch (Exception e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+						logger.error("Failed to alert by " + alertType);
+					}
+				}
+				else {
+					logger.error("Alerter type " + alertType + " doesn't exist. Failed to alert.");
+				}
+			}
 		}
 		else
 		{
 			if(options.getSuccessEmails() != null && !options.getSuccessEmails().isEmpty())
 			{
 				try {
-					mailer.sendSuccessEmail(flow);
+					
+					mailAlerter.alertOnSuccess(flow);
 				} catch (Exception e) {
 					logger.error(e);
 				}
 			}
+			if(options.getFlowParameters().containsKey("alert.type")) {
+				String alertType = options.getFlowParameters().get("alert.type");
+				Alerter alerter = alerters.get(alertType);
+				if(alerter != null) {
+					try {
+						alerter.alertOnSuccess(flow);
+					} catch (Exception e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+						logger.error("Failed to alert by " + alertType);
+					}
+				}
+				else {
+					logger.error("Alerter type " + alertType + " doesn't exist. Failed to alert.");
+				}
+			}
 		}
 		
 	}
@@ -803,6 +913,7 @@ public class ExecutorManager {
 	}
 	
 	private ExecutableFlow updateExecution(Map<String,Object> updateData) throws ExecutorManagerException {
+		
 		Integer execId = (Integer)updateData.get(ConnectorParams.UPDATE_MAP_EXEC_ID);
 		if (execId == null) {
 			throw new ExecutorManagerException("Response is malformed. Need exec id to update.");
@@ -831,7 +942,29 @@ public class ExecutorManager {
 		if (oldStatus != newStatus && newStatus.equals(Status.FAILED_FINISHING)) {
 			// We want to see if we should give an email status on first failure.
 			if (options.getNotifyOnFirstFailure()) {
-				mailer.sendFirstErrorMessage(flow);
+				Alerter mailAlerter = alerters.get("email");
+				try {
+					mailAlerter.alertOnFirstError(flow);
+				} catch (Exception e) {
+					e.printStackTrace();
+					logger.error("Failed to send first error email." + e.getMessage());
+				}
+			}
+			if(options.getFlowParameters().containsKey("alert.type")) {
+				String alertType = options.getFlowParameters().get("alert.type");
+				Alerter alerter = alerters.get(alertType);
+				if(alerter != null) {
+					try {
+						alerter.alertOnFirstError(flow);
+					} catch (Exception e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+						logger.error("Failed to alert by " + alertType);
+					}
+				}
+				else {
+					logger.error("Alerter type " + alertType + " doesn't exist. Failed to alert.");
+				}
 			}
 		}
 		
@@ -941,12 +1074,14 @@ public class ExecutorManager {
 		}
 	}
 	
+	@Override
 	public int getExecutableFlows(int projectId, String flowId, int from, int length, List<ExecutableFlow> outputList) throws ExecutorManagerException {
 		List<ExecutableFlow> flows = executorLoader.fetchFlowHistory(projectId, flowId, from, length);
 		outputList.addAll(flows);
 		return executorLoader.fetchNumExecutableFlows(projectId, flowId);
 	}
 
+	@Override
 	public List<ExecutableFlow> getExecutableFlows(int projectId, String flowId, int from, int length, Status status) throws ExecutorManagerException {
 		return executorLoader.fetchFlowHistory(projectId, flowId, from, length, status);
 	}
@@ -1006,4 +1141,8 @@ public class ExecutorManager {
 			cleanOldExecutionLogs(DateTime.now().getMillis() - executionLogsRetentionMs);
 		}
 	}
+
+	
+
+	
 }
diff --git a/src/java/azkaban/executor/ExecutorManagerAdapter.java b/src/java/azkaban/executor/ExecutorManagerAdapter.java
new file mode 100644
index 0000000..cc81589
--- /dev/null
+++ b/src/java/azkaban/executor/ExecutorManagerAdapter.java
@@ -0,0 +1,132 @@
+package azkaban.executor;
+
+import java.io.IOException;
+import java.lang.Thread.State;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import azkaban.project.Project;
+import azkaban.utils.FileIOUtils.JobMetaData;
+import azkaban.utils.FileIOUtils.LogData;
+
+public interface ExecutorManagerAdapter{
+	
+	public static final String LOCAL_MODE = "local";
+	public static final String REMOTE_MODE = "remote";
+	
+	public static final String REMOTE_EXECUTOR_MANAGER_HOST = "remote.executor.manager.host";
+	public static final String REMOTE_EXECUTOR_MANAGER_PORT = "remote.executor.manager.port";
+	public static final String REMOTE_EXECUTOR_MANAGER_URL = "/executormanager";
+	
+	public static final String ACTION_GET_FLOW_LOG = "getFlowLog";
+	public static final String ACTION_GET_JOB_LOG = "getJobLog";
+	public static final String ACTION_CANCEL_FLOW = "cancelFlow";
+	public static final String ACTION_SUBMIT_FLOW = "submitFlow";
+	public static final String ACTION_RESUME_FLOW = "resumeFlow";
+	public static final String ACTION_PAUSE_FLOW = "pauseFlow";
+	public static final String ACTION_MODIFY_EXECUTION = "modifyExecution"; 
+	public static final String ACTION_UPDATE = "update";
+	public static final String ACTION_GET_JMX = "getJMX";
+	
+	public static final String COMMAND_MODIFY_PAUSE_JOBS = "modifyPauseJobs";
+	public static final String COMMAND_MODIFY_RESUME_JOBS = "modifyResumeJobs";
+	public static final String COMMAND_MODIFY_RETRY_FAILURES = "modifyRetryFailures";
+	public static final String COMMAND_MODIFY_RETRY_JOBS = "modifyRetryJobs";
+	public static final String COMMAND_MODIFY_DISABLE_JOBS = "modifyDisableJobs";
+	public static final String COMMAND_MODIFY_ENABLE_JOBS = "modifyEnableJobs";
+	public static final String COMMAND_MODIFY_CANCEL_JOBS = "modifyCancelJobs";
+	
+	public static final String INFO_JMX_TYPE = "jmxType";
+	public static final String INFO_JMX_DATA = "jmxData";
+	public static final String INFO_ACTION = "action";
+	public static final String INFO_TYPE = "type";
+	public static final String INFO_EXEC_ID = "execId";
+	public static final String INFO_EXEC_FLOW_JSON = "execFlowJson";
+	public static final String INFO_PROJECT_ID = "projectId";
+	public static final String INFO_FLOW_NAME = "flowName";
+	public static final String INFO_JOB_NAME = "jobName";
+	public static final String INFO_OFFSET = "offset";
+	public static final String INFO_LENGTH = "length";
+	public static final String INFO_ATTEMPT = "attempt";
+	public static final String INFO_MODIFY_JOB_IDS = "modifyJobIds";
+	public static final String INFO_MODIFY_COMMAND = "modifyCommand";
+	public static final String INFO_MESSAGE = "message";
+	public static final String INFO_ERROR = "error";
+	public static final String INFO_UPDATE_TIME_LIST = "updateTimeList";
+	public static final String INFO_EXEC_ID_LIST = "execIdList";
+	public static final String INFO_UPDATES = "updates";
+	public static final String INFO_USER_ID = "userId";
+	public static final String INFO_LOG = "logData";
+	
+	public boolean isFlowRunning(int projectId, String flowId);
+	
+	public ExecutableFlow getExecutableFlow(int execId) throws ExecutorManagerException;
+	
+	public List<Integer> getRunningFlows(int projectId, String flowId);
+	
+	public List<ExecutableFlow> getRunningFlows() throws IOException;
+	
+	public List<ExecutableFlow> getRecentlyFinishedFlows();
+	
+	public List<ExecutableFlow> getExecutableFlows(Project project, String flowId, int skip, int size) throws ExecutorManagerException;
+	
+	public List<ExecutableFlow> getExecutableFlows(int skip, int size) throws ExecutorManagerException;
+	
+	public List<ExecutableFlow> getExecutableFlows(String flowIdContains, int skip, int size) throws ExecutorManagerException;
+	
+	public List<ExecutableFlow> getExecutableFlows(String projContain, String flowContain, String userContain, int status, long begin, long end, int skip, int size) throws ExecutorManagerException;
+
+	public int getExecutableFlows(int projectId, String flowId, int from, int length, List<ExecutableFlow> outputList) throws ExecutorManagerException;
+
+	public List<ExecutableFlow> getExecutableFlows(int projectId, String flowId, int from, int length, Status status) throws ExecutorManagerException;
+
+	public List<ExecutableJobInfo> getExecutableJobs(Project project, String jobId, int skip, int size) throws ExecutorManagerException;
+	
+	public int getNumberOfJobExecutions(Project project, String jobId) throws ExecutorManagerException;
+	
+	public int getNumberOfExecutions(Project project, String flowId) throws ExecutorManagerException;
+	
+	public LogData getExecutableFlowLog(ExecutableFlow exFlow, int offset, int length) throws ExecutorManagerException;
+	
+	public LogData getExecutionJobLog(ExecutableFlow exFlow, String jobId, int offset, int length, int attempt) throws ExecutorManagerException;
+	
+	public JobMetaData getExecutionJobMetaData(ExecutableFlow exFlow, String jobId, int offset, int length, int attempt) throws ExecutorManagerException;
+	
+	public void cancelFlow(ExecutableFlow exFlow, String userId) throws ExecutorManagerException;
+	
+	public void resumeFlow(ExecutableFlow exFlow, String userId) throws ExecutorManagerException;
+	
+	public void pauseFlow(ExecutableFlow exFlow, String userId) throws ExecutorManagerException;
+	
+	public void pauseExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException;
+	
+	public void resumeExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException;
+	
+	public void retryFailures(ExecutableFlow exFlow, String userId) throws ExecutorManagerException;
+	
+	public void retryExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException;
+	
+	public void disableExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException;
+	
+	public void enableExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException;
+	
+	public void cancelExecutingJobs(ExecutableFlow exFlow, String userId, String ... jobIds) throws ExecutorManagerException;
+
+	public String submitExecutableFlow(ExecutableFlow exflow, String userId) throws ExecutorManagerException;
+	
+	public Map<String, Object> callExecutorJMX(String hostPort, String action, String mBean) throws IOException;
+
+	public void shutdown();
+
+	public Set<String> getAllActiveExecutorServerHosts();
+
+	public State getExecutorManagerThreadState();
+
+	public boolean isExecutorManagerThreadActive();
+
+	public long getLastExecutorManagerThreadCheckTime();
+
+	public Set<? extends String> getPrimaryServerHosts();
+	
+}
diff --git a/src/java/azkaban/executor/ExecutorManagerException.java b/src/java/azkaban/executor/ExecutorManagerException.java
index a5c264e..09b8953 100644
--- a/src/java/azkaban/executor/ExecutorManagerException.java
+++ b/src/java/azkaban/executor/ExecutorManagerException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/executor/ExecutorManagerServlet.java b/src/java/azkaban/executor/ExecutorManagerServlet.java
new file mode 100644
index 0000000..ab8d5b2
--- /dev/null
+++ b/src/java/azkaban/executor/ExecutorManagerServlet.java
@@ -0,0 +1,225 @@
+package azkaban.executor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+
+import azkaban.utils.FileIOUtils.LogData;
+import azkaban.utils.JSONUtils;
+import azkaban.webapp.servlet.AbstractServiceServlet;
+
+
+public class ExecutorManagerServlet extends AbstractServiceServlet {
+	private final ExecutorManagerAdapter executorManager;
+	
+	public static final String URL = "executorManager";
+	private static final long serialVersionUID = 1L;
+	private static final Logger logger = Logger.getLogger(ExecutorManagerServlet.class);
+	
+	public ExecutorManagerServlet(ExecutorManagerAdapter executorManager) {
+		this.executorManager = executorManager;
+	}
+	
+	@Override
+	public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+		HashMap<String,Object> respMap= new HashMap<String,Object>();
+		//logger.info("ExecutorServer called by " + req.getRemoteAddr());
+		try {
+			if (!hasParam(req, ExecutorManagerAdapter.INFO_ACTION)) {
+				logger.error("Parameter action not set");
+				respMap.put("error", "Parameter action not set");
+			}
+			else {
+				String action = getParam(req, ExecutorManagerAdapter.INFO_ACTION);
+				if (action.equals(ExecutorManagerAdapter.ACTION_UPDATE)) {
+					//logger.info("Updated called");
+					handleAjaxUpdateRequest(req, respMap);
+				}
+				else {
+					int execid = Integer.parseInt(getParam(req, ExecutorManagerAdapter.INFO_EXEC_ID));
+					String user = getParam(req, ExecutorManagerAdapter.INFO_USER_ID, null);
+					
+					logger.info("User " + user + " has called action " + action + " on " + execid);
+					if (action.equals(ExecutorManagerAdapter.ACTION_GET_FLOW_LOG)) { 
+						handleFetchFlowLogEvent(execid, req, resp, respMap);
+					} else if (action.equals(ExecutorManagerAdapter.ACTION_GET_JOB_LOG)) {
+						handleFetchJobLogEvent(execid, req, resp, respMap);
+					}
+					else if (action.equals(ExecutorManagerAdapter.ACTION_SUBMIT_FLOW)) {
+						handleAjaxSubmitFlow(req, respMap, execid);
+					}
+					else if (action.equals(ExecutorManagerAdapter.ACTION_CANCEL_FLOW)) {
+						logger.info("Cancel called.");
+						handleAjaxCancelFlow(respMap, execid, user);
+					}
+					else if (action.equals(ExecutorManagerAdapter.ACTION_PAUSE_FLOW)) {
+						logger.info("Paused called.");
+						handleAjaxPauseFlow(respMap, execid, user);
+					}
+					else if (action.equals(ExecutorManagerAdapter.ACTION_RESUME_FLOW)) {
+						logger.info("Resume called.");
+						handleAjaxResumeFlow(respMap, execid, user);
+					}
+					else if (action.equals(ExecutorManagerAdapter.ACTION_MODIFY_EXECUTION)) {
+						logger.info("Modify Execution Action");
+						handleModifyExecution(respMap, execid, user, req);
+					}
+					else {
+						logger.error("action: '" + action + "' not supported.");
+						respMap.put("error", "action: '" + action + "' not supported.");
+					}
+				}
+			}
+		} catch (Exception e) {
+			logger.error(e);
+			respMap.put(ExecutorManagerAdapter.INFO_ERROR, e.getMessage());
+		}
+		writeJSON(resp, respMap);
+		resp.flushBuffer();
+	}
+
+	private void handleModifyExecution(HashMap<String, Object> respMap,
+			int execid, String user, HttpServletRequest req) {
+		if (!hasParam(req, ExecutorManagerAdapter.INFO_MODIFY_COMMAND)) {
+			respMap.put(ExecutorManagerAdapter.INFO_ERROR, "Modification command not set.");
+			return;
+		}
+
+		try {
+			String modificationType = getParam(req, ExecutorManagerAdapter.INFO_MODIFY_COMMAND);
+			ExecutableFlow exflow = executorManager.getExecutableFlow(execid);
+			if (ExecutorManagerAdapter.COMMAND_MODIFY_RETRY_FAILURES.equals(modificationType)) {
+				executorManager.retryFailures(exflow, user);
+			}
+			else {
+//				String modifiedJobList = getParam(req, MODIFY_JOBS_LIST);
+//				String[] jobIds = modifiedJobList.split("\\s*,\\s*");
+//				
+//				if (MODIFY_RETRY_JOBS.equals(modificationType)) {
+//				}
+//				else if (MODIFY_CANCEL_JOBS.equals(modificationType)) {
+//				}
+//				else if (MODIFY_DISABLE_JOBS.equals(modificationType)) {
+//				}
+//				else if (MODIFY_ENABLE_JOBS.equals(modificationType)) {
+//				}
+//				else if (MODIFY_PAUSE_JOBS.equals(modificationType)) {
+//				}
+//				else if (MODIFY_RESUME_JOBS.equals(modificationType)) {
+//				}
+			}
+		} catch (Exception e) {
+			respMap.put(ExecutorManagerAdapter.INFO_ERROR, e);
+		}
+	}
+
+	private void handleAjaxResumeFlow(HashMap<String, Object> respMap, int execid, String user) {
+		try {
+			ExecutableFlow exFlow = executorManager.getExecutableFlow(execid);
+			executorManager.resumeFlow(exFlow, user);
+		} catch (Exception e) {
+			respMap.put(ExecutorManagerAdapter.INFO_ERROR, e);
+		}
+		
+	}
+
+	private void handleAjaxPauseFlow(HashMap<String, Object> respMap, int execid, String user) {
+		try {
+			ExecutableFlow exFlow = executorManager.getExecutableFlow(execid);
+			executorManager.pauseFlow(exFlow, user);
+		} catch (Exception e) {
+			respMap.put(ExecutorManagerAdapter.INFO_ERROR, e);
+		}
+	}
+
+	private void handleAjaxCancelFlow(HashMap<String, Object> respMap, int execid, String user) {
+		try {
+			ExecutableFlow exFlow = executorManager.getExecutableFlow(execid);
+			executorManager.cancelFlow(exFlow, user);
+		} catch (Exception e) {
+			respMap.put(ExecutorManagerAdapter.INFO_ERROR, e);
+		}
+	}
+
+	private void handleAjaxSubmitFlow(HttpServletRequest req, HashMap<String, Object> respMap, int execid) {
+		try{
+			String execFlowJson = getParam(req, ExecutorManagerAdapter.INFO_EXEC_FLOW_JSON);
+			ExecutableFlow exflow = ExecutableFlow.createExecutableFlowFromObject(JSONUtils.parseJSONFromString(execFlowJson));
+			String user = getParam(req, ExecutorManagerAdapter.INFO_USER_ID);
+			executorManager.submitExecutableFlow(exflow, user);
+			respMap.put(ExecutorManagerAdapter.INFO_EXEC_ID, exflow.getExecutionId());
+		} catch (Exception e) {
+			e.printStackTrace();
+			respMap.put(ExecutorManagerAdapter.INFO_ERROR, e);
+		}
+	}
+
+	private void handleFetchJobLogEvent(int execid, HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> respMap) {
+		try{
+			ExecutableFlow exFlow = executorManager.getExecutableFlow(execid);
+			String jobId = getParam(req, ExecutorManagerAdapter.INFO_JOB_NAME);
+			int offset = getIntParam(req, ExecutorManagerAdapter.INFO_OFFSET);
+			int length = getIntParam(req, ExecutorManagerAdapter.INFO_LENGTH);
+			int attempt = getIntParam(req, ExecutorManagerAdapter.INFO_ATTEMPT);
+			LogData log = executorManager.getExecutionJobLog(exFlow, jobId, offset, length, attempt);
+			respMap.put(ExecutorManagerAdapter.INFO_LOG, JSONUtils.toJSON(log.toObject()));
+		}  catch (Exception e) {
+			e.printStackTrace();
+			respMap.put(ExecutorManagerAdapter.INFO_ERROR, e);
+		}
+	}
+
+	private void handleFetchFlowLogEvent(int execid, HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> respMap) {
+		try{
+			ExecutableFlow exFlow = executorManager.getExecutableFlow(execid);
+			int offset = getIntParam(req, ExecutorManagerAdapter.INFO_OFFSET);
+			int length = getIntParam(req, ExecutorManagerAdapter.INFO_LENGTH);
+			LogData log = executorManager.getExecutableFlowLog(exFlow, offset, length);
+			respMap.put(ExecutorManagerAdapter.INFO_LOG, JSONUtils.toJSON(log.toObject()));
+		}  catch (Exception e) {
+			e.printStackTrace();
+			respMap.put(ExecutorManagerAdapter.INFO_ERROR, e);
+		}
+		
+	}
+
+	@SuppressWarnings("unchecked")
+	private void handleAjaxUpdateRequest(HttpServletRequest req, HashMap<String, Object> respMap) {
+		try {
+			ArrayList<Object> updateTimesList = (ArrayList<Object>)JSONUtils.parseJSONFromString(getParam(req, ExecutorManagerAdapter.INFO_UPDATE_TIME_LIST));
+			ArrayList<Object> execIDList = (ArrayList<Object>)JSONUtils.parseJSONFromString(getParam(req, ExecutorManagerAdapter.INFO_EXEC_ID_LIST));
+			
+			ArrayList<Object> updateList = new ArrayList<Object>();
+			for (int i = 0; i < execIDList.size(); ++i) {
+				long updateTime = JSONUtils.getLongFromObject(updateTimesList.get(i));
+				int execId = (Integer)execIDList.get(i);
+				
+				ExecutableFlow flow = executorManager.getExecutableFlow(execId);
+				if (flow == null) {
+					Map<String, Object> errorResponse = new HashMap<String,Object>();
+					errorResponse.put(ExecutorManagerAdapter.INFO_ERROR, "Flow does not exist");
+					errorResponse.put(ExecutorManagerAdapter.INFO_EXEC_ID, execId);
+					updateList.add(errorResponse);
+					continue;
+				}
+				
+				if (flow.getUpdateTime() > updateTime) {
+					updateList.add(flow.toUpdateObject(updateTime));
+				}
+			}
+			
+			respMap.put(ExecutorManagerAdapter.INFO_UPDATES, updateList);
+		}  catch (Exception e) {
+			e.printStackTrace();
+			respMap.put(ExecutorManagerAdapter.INFO_ERROR, e);
+		}		
+	}
+	
+}
diff --git a/src/java/azkaban/executor/JdbcExecutorLoader.java b/src/java/azkaban/executor/JdbcExecutorLoader.java
index 3c7332d..5373b7d 100644
--- a/src/java/azkaban/executor/JdbcExecutorLoader.java
+++ b/src/java/azkaban/executor/JdbcExecutorLoader.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.executor;
 
 import java.io.BufferedInputStream;
@@ -901,4 +917,4 @@ public class JdbcExecutorLoader extends AbstractJdbcLoader implements ExecutorLo
 		
 		return updateNum;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/executor/mail/DefaultMailCreator.java b/src/java/azkaban/executor/mail/DefaultMailCreator.java
new file mode 100644
index 0000000..0802cae
--- /dev/null
+++ b/src/java/azkaban/executor/mail/DefaultMailCreator.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.executor.mail;
+
+import java.util.HashMap;
+import java.util.List;
+
+import azkaban.executor.ExecutableFlow;
+import azkaban.executor.ExecutionOptions;
+import azkaban.executor.ExecutionOptions.FailureAction;
+import azkaban.utils.EmailMessage;
+import azkaban.utils.Emailer;
+import azkaban.utils.Utils;
+
+public class DefaultMailCreator implements MailCreator {
+	public static final String DEFAULT_MAIL_CREATOR = "default";
+	private static HashMap<String, MailCreator> registeredCreators = new HashMap<String, MailCreator>();
+	private static MailCreator defaultCreator;
+
+	public static void registerCreator(String name, MailCreator creator) {
+		registeredCreators.put(name, creator);
+	}
+
+	public static MailCreator getCreator(String name) {
+		MailCreator creator = registeredCreators.get(name);
+		if (creator == null) {
+			creator = defaultCreator;
+		}
+		return creator;
+	}
+
+	static {
+		defaultCreator = new DefaultMailCreator();
+		registerCreator(DEFAULT_MAIL_CREATOR, defaultCreator);
+	}
+
+	@Override
+	public boolean createFirstErrorMessage(ExecutableFlow flow, EmailMessage message, String azkabanName, String clientHostname, String clientPortNumber, String... vars) {
+
+		ExecutionOptions option = flow.getExecutionOptions();
+		List<String> emailList = option.getDisabledJobs();
+		int execId = flow.getExecutionId();
+
+		if (emailList != null && !emailList.isEmpty()) {
+			message.addAllToAddress(emailList);
+			message.setMimeType("text/html");
+			message.setSubject("Flow '" + flow.getFlowId() + "' has failed on " + azkabanName);
+
+			message.println("<h2 style=\"color:#FF0000\"> Execution '" + flow.getExecutionId() + "' of flow '" + flow.getFlowId() + "' has encountered a failure on " + azkabanName + "</h2>");
+
+			if (option.getFailureAction() == FailureAction.CANCEL_ALL) {
+				message.println("This flow is set to cancel all currently running jobs.");
+			}
+			else if (option.getFailureAction() == FailureAction.FINISH_ALL_POSSIBLE) {
+				message.println("This flow is set to complete all jobs that aren't blocked by the failure.");
+			}
+			else {
+				message.println("This flow is set to complete all currently running jobs before stopping.");
+			}
+
+			message.println("<table>");
+			message.println("<tr><td>Start Time</td><td>" + flow.getStartTime() + "</td></tr>");
+			message.println("<tr><td>End Time</td><td>" + flow.getEndTime() + "</td></tr>");
+			message.println("<tr><td>Duration</td><td>" + Utils.formatDuration(flow.getStartTime(), flow.getEndTime()) + "</td></tr>");
+			message.println("</table>");
+			message.println("");
+			String executionUrl = "https://" + clientHostname + ":" + clientPortNumber + "/" + "executor?" + "execid=" + execId;
+			message.println("<a href='\"" + executionUrl + "\">" + flow.getFlowId() + " Execution Link</a>");
+
+			message.println("");
+			message.println("<h3>Reason</h3>");
+			List<String> failedJobs = Emailer.findFailedJobs(flow);
+			message.println("<ul>");
+			for (String jobId : failedJobs) {
+				message.println("<li><a href=\"" + executionUrl + "&job=" + jobId + "\">Failed job '" + jobId + "' Link</a></li>");
+			}
+
+			message.println("</ul>");
+			return true;
+		}
+
+		return false;
+	}
+
+	@Override
+	public boolean createErrorEmail(ExecutableFlow flow, EmailMessage message, String azkabanName, String clientHostname, String clientPortNumber, String... vars) {
+
+		ExecutionOptions option = flow.getExecutionOptions();
+
+		List<String> emailList = option.getFailureEmails();
+		int execId = flow.getExecutionId();
+
+		if (emailList != null && !emailList.isEmpty()) {
+			message.addAllToAddress(emailList);
+			message.setMimeType("text/html");
+			message.setSubject("Flow '" + flow.getFlowId() + "' has failed on " + azkabanName);
+
+			message.println("<h2 style=\"color:#FF0000\"> Execution '" + execId + "' of flow '" + flow.getFlowId() + "' has failed on " + azkabanName + "</h2>");
+			message.println("<table>");
+			message.println("<tr><td>Start Time</td><td>" + flow.getStartTime() + "</td></tr>");
+			message.println("<tr><td>End Time</td><td>" + flow.getEndTime() + "</td></tr>");
+			message.println("<tr><td>Duration</td><td>" + Utils.formatDuration(flow.getStartTime(), flow.getEndTime()) + "</td></tr>");
+			message.println("</table>");
+			message.println("");
+			String executionUrl = "https://" + clientHostname + ":" + clientPortNumber + "/" + "executor?" + "execid=" + execId;
+			message.println("<a href='\"" + executionUrl + "\">" + flow.getFlowId() + " Execution Link</a>");
+
+			message.println("");
+			message.println("<h3>Reason</h3>");
+			List<String> failedJobs = Emailer.findFailedJobs(flow);
+			message.println("<ul>");
+			for (String jobId : failedJobs) {
+				message.println("<li><a href=\"" + executionUrl + "&job=" + jobId + "\">Failed job '" + jobId + "' Link</a></li>");
+			}
+			for (String reasons : vars) {
+				message.println("<li>" + reasons + "</li>");
+			}
+
+			message.println("</ul>");
+			return true;
+		}
+		return false;
+	}
+
+	@Override
+	public boolean createSuccessEmail(ExecutableFlow flow, EmailMessage message, String azkabanName, String clientHostname, String clientPortNumber, String... vars) {
+
+		ExecutionOptions option = flow.getExecutionOptions();
+		List<String> emailList = option.getSuccessEmails();
+
+		int execId = flow.getExecutionId();
+
+		if (emailList != null && !emailList.isEmpty()) {
+			message.addAllToAddress(emailList);
+			message.setMimeType("text/html");
+			message.setSubject("Flow '" + flow.getFlowId() + "' has succeeded on " + azkabanName);
+
+			message.println("<h2> Execution '" + flow.getExecutionId() + "' of flow '" + flow.getFlowId() + "' has succeeded on " + azkabanName + "</h2>");
+			message.println("<table>");
+			message.println("<tr><td>Start Time</td><td>" + flow.getStartTime() + "</td></tr>");
+			message.println("<tr><td>End Time</td><td>" + flow.getEndTime() + "</td></tr>");
+			message.println("<tr><td>Duration</td><td>" + Utils.formatDuration(flow.getStartTime(), flow.getEndTime()) + "</td></tr>");
+			message.println("</table>");
+			message.println("");
+			String executionUrl = "https://" + clientHostname + ":" + clientPortNumber + "/" + "executor?" + "execid=" + execId;
+			message.println("<a href=\"" + executionUrl + "\">" + flow.getFlowId() + " Execution Link</a>");
+			return true;
+		}
+		return false;
+	}
+}
diff --git a/src/java/azkaban/executor/mail/MailCreator.java b/src/java/azkaban/executor/mail/MailCreator.java
new file mode 100644
index 0000000..19bed3b
--- /dev/null
+++ b/src/java/azkaban/executor/mail/MailCreator.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.executor.mail;
+
+import azkaban.executor.ExecutableFlow;
+import azkaban.utils.EmailMessage;
+
+public interface MailCreator {
+	public boolean createFirstErrorMessage(ExecutableFlow flow, EmailMessage message, String azkabanName, String clientHostname, String clientPortNumber, String... vars);
+	public boolean createErrorEmail(ExecutableFlow flow, EmailMessage message, String azkabanName, String clientHostname, String clientPortNumber, String... vars);
+	public boolean createSuccessEmail(ExecutableFlow flow, EmailMessage message, String azkabanName, String clientHostname, String clientPortNumber, String... vars);
+}
diff --git a/src/java/azkaban/executor/Status.java b/src/java/azkaban/executor/Status.java
index d8215df..c62287d 100644
--- a/src/java/azkaban/executor/Status.java
+++ b/src/java/azkaban/executor/Status.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.executor;
 
 public enum Status {
@@ -56,4 +72,4 @@ public enum Status {
 			return false;
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/flow/CommonJobProperties.java b/src/java/azkaban/flow/CommonJobProperties.java
index 7a8ffc4..ef59fbd 100644
--- a/src/java/azkaban/flow/CommonJobProperties.java
+++ b/src/java/azkaban/flow/CommonJobProperties.java
@@ -1,5 +1,20 @@
-package azkaban.flow;
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 
+package azkaban.flow;
 
 public class CommonJobProperties {
 	/*
diff --git a/src/java/azkaban/flow/Edge.java b/src/java/azkaban/flow/Edge.java
index b7b7ba8..5820a33 100644
--- a/src/java/azkaban/flow/Edge.java
+++ b/src/java/azkaban/flow/Edge.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/flow/Flow.java b/src/java/azkaban/flow/Flow.java
index 67375cf..d28a159 100644
--- a/src/java/azkaban/flow/Flow.java
+++ b/src/java/azkaban/flow/Flow.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -24,6 +24,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import azkaban.executor.mail.DefaultMailCreator;
+
 public class Flow {
 	private final String id;
 	private int projectId;
@@ -40,8 +42,10 @@ public class Flow {
 	
 	private List<String> failureEmail = new ArrayList<String>();
 	private List<String> successEmail = new ArrayList<String>();
+	private String mailCreator = DefaultMailCreator.DEFAULT_MAIL_CREATOR;
 	private ArrayList<String> errors;
 	private int version = -1;
+	private Map<String, Object> metadata = new HashMap<String, Object>();
 	
 	private boolean isLayedOut = false;
 	
@@ -106,10 +110,18 @@ public class Flow {
 		return successEmail;
 	}
 	
+	public String getMailCreator() {
+		return mailCreator;
+	}
+	
 	public List<String> getFailureEmails() {
 		return failureEmail;
 	}
 	
+	public void setMailCreator(String mailCreator) {
+		this.mailCreator = mailCreator;
+	}
+	
 	public void addSuccessEmails(Collection<String> emails) {
 		successEmail.addAll(emails);
 	}
@@ -226,10 +238,15 @@ public class Flow {
 		flowObj.put("edges", objectizeEdges());
 		flowObj.put("failure.email", failureEmail);
 		flowObj.put("success.email", successEmail);
+		flowObj.put("mailCreator", mailCreator);
 		flowObj.put("layedout", isLayedOut);
 		if (errors != null) {
 			flowObj.put("errors", errors);
 		}
+
+		if (metadata != null) {
+			flowObj.put("metadata", metadata);
+		}
 		
 		return flowObj;
 	}
@@ -294,9 +311,18 @@ public class Flow {
 		List<Object> edgeList = (List<Object>)flowObject.get("edges");
 		List<Edge> edges = loadEdgeFromObjects(edgeList, nodes);
 		flow.addAllEdges(edges);
+
+		Map<String, Object> metadata = (Map<String, Object>)flowObject.get("metadata");
+		
+		if (metadata != null) {
+			flow.setMetadata(metadata);
+		}
 		
 		flow.failureEmail = (List<String>)flowObject.get("failure.email");
 		flow.successEmail = (List<String>)flowObject.get("success.email");
+		if (flowObject.containsKey("mailCreator")) {
+			flow.mailCreator = flowObject.get("mailCreator").toString();
+		}
 		return flow;
 	}
 
@@ -337,6 +363,17 @@ public class Flow {
 		return isLayedOut;
 	}
 
+	public Map<String, Object> getMetadata() {
+		if(metadata == null){
+			metadata = new HashMap<String, Object>();
+		}
+		return metadata;
+	}
+
+	public void setMetadata(Map<String, Object> metadata) {
+		this.metadata = metadata;
+	}
+
 	public void setLayedOut(boolean layedOut) {
 		this.isLayedOut = layedOut;
 	}
@@ -368,4 +405,4 @@ public class Flow {
 	public void setProjectId(int projectId) {
 		this.projectId = projectId;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/flow/FlowProps.java b/src/java/azkaban/flow/FlowProps.java
index 15a6001..f4cf7fe 100644
--- a/src/java/azkaban/flow/FlowProps.java
+++ b/src/java/azkaban/flow/FlowProps.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/flow/Node.java b/src/java/azkaban/flow/Node.java
index fbe5a39..7903fe1 100644
--- a/src/java/azkaban/flow/Node.java
+++ b/src/java/azkaban/flow/Node.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -165,4 +165,4 @@ public class Node {
 		
 		return objMap;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/jmx/JmxExecutorManager.java b/src/java/azkaban/jmx/JmxExecutorManager.java
index 123340d..e3acefb 100644
--- a/src/java/azkaban/jmx/JmxExecutorManager.java
+++ b/src/java/azkaban/jmx/JmxExecutorManager.java
@@ -19,21 +19,33 @@ public class JmxExecutorManager implements JmxExecutorManagerMBean {
 
 	@Override
 	public String getExecutorThreadState() {
-		return manager.getExecutorThreadState().toString();
+		return manager.getExecutorManagerThreadState().toString();
+	}
+	
+	@Override
+	public String getExecutorThreadStage() {
+		return manager.getExecutorThreadStage();
 	}
 
 	@Override
 	public boolean isThreadActive() {
-		return manager.isThreadActive();
+		return manager.isExecutorManagerThreadActive();
 	}
 
 	@Override
 	public Long getLastThreadCheckTime() {
-		return manager.getLastThreadCheckTime();
+		return manager.getLastExecutorManagerThreadCheckTime();
 	}
 	
 	@Override 
 	public List<String> getPrimaryExecutorHostPorts() {
 		return new ArrayList<String>(manager.getPrimaryServerHosts());
 	}
+
+	@Override
+	public String getRunningFlows() {
+		return manager.getRunningFlowIds();
+	}
+
+	
 }
diff --git a/src/java/azkaban/jmx/JmxExecutorManagerAdapter.java b/src/java/azkaban/jmx/JmxExecutorManagerAdapter.java
new file mode 100644
index 0000000..fc0f8cf
--- /dev/null
+++ b/src/java/azkaban/jmx/JmxExecutorManagerAdapter.java
@@ -0,0 +1,47 @@
+package azkaban.jmx;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import azkaban.executor.ExecutorManagerAdapter;
+
+public class JmxExecutorManagerAdapter implements JmxExecutorManagerAdapterMBean {
+	private ExecutorManagerAdapter manager;
+
+	public JmxExecutorManagerAdapter(ExecutorManagerAdapter manager) {
+		this.manager = manager;
+	}
+
+	@Override
+	public int getNumRunningFlows() {
+		try {
+			return this.manager.getRunningFlows().size();
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			return 0;
+		}
+	}
+
+	@Override
+	public String getExecutorManagerThreadState() {
+		return manager.getExecutorManagerThreadState().toString();
+	}
+
+	@Override
+	public boolean isExecutorManagerThreadActive() {
+		return manager.isExecutorManagerThreadActive();
+	}
+
+	@Override
+	public Long getLastExecutorManagerThreadCheckTime() {
+		return manager.getLastExecutorManagerThreadCheckTime();
+	}
+	
+	@Override 
+	public List<String> getPrimaryExecutorHostPorts() {
+		return new ArrayList<String>(manager.getPrimaryServerHosts());
+	}
+
+}
diff --git a/src/java/azkaban/jmx/JmxExecutorManagerAdapterMBean.java b/src/java/azkaban/jmx/JmxExecutorManagerAdapterMBean.java
new file mode 100644
index 0000000..197e721
--- /dev/null
+++ b/src/java/azkaban/jmx/JmxExecutorManagerAdapterMBean.java
@@ -0,0 +1,26 @@
+package azkaban.jmx;
+
+import java.util.List;
+
+public interface JmxExecutorManagerAdapterMBean {
+	@DisplayName("OPERATION: getNumRunningFlows")
+	public int getNumRunningFlows();
+	
+	@DisplayName("OPERATION: getExecutorThreadState")
+	public String getExecutorManagerThreadState();
+
+	@DisplayName("OPERATION: isThreadActive")
+	public boolean isExecutorManagerThreadActive();
+
+	@DisplayName("OPERATION: getLastThreadCheckTime")
+	public Long getLastExecutorManagerThreadCheckTime();
+
+	@DisplayName("OPERATION: getPrimaryExecutorHostPorts")
+	public List<String> getPrimaryExecutorHostPorts();
+	
+//	@DisplayName("OPERATION: getExecutorThreadStage")
+//	public String getExecutorThreadStage();
+//	
+//	@DisplayName("OPERATION: getRunningFlows")
+//	public String getRunningFlows();
+}
diff --git a/src/java/azkaban/jmx/JmxExecutorManagerMBean.java b/src/java/azkaban/jmx/JmxExecutorManagerMBean.java
index b4a3888..b29d00a 100644
--- a/src/java/azkaban/jmx/JmxExecutorManagerMBean.java
+++ b/src/java/azkaban/jmx/JmxExecutorManagerMBean.java
@@ -6,8 +6,14 @@ public interface JmxExecutorManagerMBean {
 	@DisplayName("OPERATION: getNumRunningFlows")
 	public int getNumRunningFlows();
 	
+	@DisplayName("OPERATION: getRunningFlows")
+	public String getRunningFlows();
+	
 	@DisplayName("OPERATION: getExecutorThreadState")
 	public String getExecutorThreadState();
+	
+	@DisplayName("OPERATION: getExecutorThreadStage")
+	public String getExecutorThreadStage();
 
 	@DisplayName("OPERATION: isThreadActive")
 	public boolean isThreadActive();
diff --git a/src/java/azkaban/jmx/JmxFlowRunnerManager.java b/src/java/azkaban/jmx/JmxFlowRunnerManager.java
index f4f59d3..3541140 100644
--- a/src/java/azkaban/jmx/JmxFlowRunnerManager.java
+++ b/src/java/azkaban/jmx/JmxFlowRunnerManager.java
@@ -54,4 +54,9 @@ public class JmxFlowRunnerManager implements JmxFlowRunnerManagerMBean {
 		return manager.getNumExecutingJobs();
 	}
 
+	@Override
+	public String getRunningFlows() {
+		return manager.getRunningFlowIds();
+	}
+
 }
diff --git a/src/java/azkaban/jmx/JmxFlowRunnerManagerMBean.java b/src/java/azkaban/jmx/JmxFlowRunnerManagerMBean.java
index 47c6a02..ed509ef 100644
--- a/src/java/azkaban/jmx/JmxFlowRunnerManagerMBean.java
+++ b/src/java/azkaban/jmx/JmxFlowRunnerManagerMBean.java
@@ -25,6 +25,9 @@ public interface JmxFlowRunnerManagerMBean {
 	@DisplayName("OPERATION: getNumExecutingFlows")
 	public int getNumExecutingFlows();
 	
+	@DisplayName("OPERATION: getRunningFlows")
+	public String getRunningFlows();
+	
 	@DisplayName("OPERATION: getTotalNumRunningJobs")
 	public int countTotalNumRunningJobs();
 }
diff --git a/src/java/azkaban/jmx/JmxTriggerManager.java b/src/java/azkaban/jmx/JmxTriggerManager.java
new file mode 100644
index 0000000..7537b0b
--- /dev/null
+++ b/src/java/azkaban/jmx/JmxTriggerManager.java
@@ -0,0 +1,63 @@
+package azkaban.jmx;
+
+import org.joda.time.DateTime;
+
+import azkaban.trigger.TriggerManagerAdapter;
+import azkaban.trigger.TriggerManagerAdapter.TriggerJMX;
+
+public class JmxTriggerManager implements JmxTriggerManagerMBean {
+	private TriggerJMX jmxStats;
+
+	public JmxTriggerManager(TriggerManagerAdapter manager) {
+		this.jmxStats = manager.getJMX();
+	}
+
+	@Override
+	public String getLastRunnerThreadCheckTime() {
+		return new DateTime(jmxStats.getLastRunnerThreadCheckTime()).toString();
+	}
+
+	@Override
+	public boolean isRunnerThreadActive() {
+		return jmxStats.isRunnerThreadActive();
+	}
+
+	@Override
+	public String getPrimaryTriggerHostPort() {
+		return jmxStats.getPrimaryServerHost();
+	}
+
+//	@Override
+//	public List<String> getAllTriggerHostPorts() {
+//		return new ArrayList<String>(manager.getAllActiveTriggerServerHosts());
+//	}
+
+	@Override
+	public int getNumTriggers() {
+		return jmxStats.getNumTriggers();
+	}
+
+	@Override
+	public String getTriggerSources() {
+		return jmxStats.getTriggerSources();
+	}
+
+	@Override
+	public String getTriggerIds() {
+		return jmxStats.getTriggerIds();
+	}
+
+	@Override
+	public long getScannerIdleTime() {
+		return jmxStats.getScannerIdleTime();
+	}
+
+	@Override
+	public String getScannerThreadStage() {
+		// TODO Auto-generated method stub
+		return jmxStats.getScannerThreadStage();
+	}
+	
+	
+	
+}
diff --git a/src/java/azkaban/jmx/JmxTriggerManagerMBean.java b/src/java/azkaban/jmx/JmxTriggerManagerMBean.java
new file mode 100644
index 0000000..c87fbd0
--- /dev/null
+++ b/src/java/azkaban/jmx/JmxTriggerManagerMBean.java
@@ -0,0 +1,31 @@
+package azkaban.jmx;
+
+public interface JmxTriggerManagerMBean {	
+	
+	@DisplayName("OPERATION: getLastThreadCheckTime")
+	public String getLastRunnerThreadCheckTime();
+
+	@DisplayName("OPERATION: isThreadActive")
+	public boolean isRunnerThreadActive();
+
+	@DisplayName("OPERATION: getPrimaryTriggerHostPort")
+	public String getPrimaryTriggerHostPort();
+	
+//	@DisplayName("OPERATION: getAllTriggerHostPorts")
+//	public List<String> getAllTriggerHostPorts();
+	
+	@DisplayName("OPERATION: getNumTriggers")
+	public int getNumTriggers();
+	
+	@DisplayName("OPERATION: getTriggerSources")
+	public String getTriggerSources();
+	
+	@DisplayName("OPERATION: getTriggerIds")
+	public String getTriggerIds();
+	
+	@DisplayName("OPERATION: getScannerIdleTime")
+	public long getScannerIdleTime();
+	
+	@DisplayName("OPERATION: getScannerThreadStage")
+	public String getScannerThreadStage();
+}
diff --git a/src/java/azkaban/jmx/JmxTriggerRunnerManagerMBean.java b/src/java/azkaban/jmx/JmxTriggerRunnerManagerMBean.java
new file mode 100644
index 0000000..ca0f45b
--- /dev/null
+++ b/src/java/azkaban/jmx/JmxTriggerRunnerManagerMBean.java
@@ -0,0 +1,23 @@
+package azkaban.jmx;
+
+public interface JmxTriggerRunnerManagerMBean {
+
+	@DisplayName("OPERATION: getLastRunnerThreadCheckTime")
+	public long getLastRunnerThreadCheckTime();
+
+	@DisplayName("OPERATION: getNumTriggers")
+	public int getNumTriggers();
+	
+	@DisplayName("OPERATION: isRunnerThreadActive")
+	public boolean isRunnerThreadActive();
+	
+	@DisplayName("OPERATION: getTriggerSources")
+	public String getTriggerSources();
+	
+	@DisplayName("OPERATION: getTriggerIds")
+	public String getTriggerIds();
+	
+	@DisplayName("OPERATION: getScannerIdleTime")
+	public long getScannerIdleTime();
+	
+}
diff --git a/src/java/azkaban/jobExecutor/AbstractJob.java b/src/java/azkaban/jobExecutor/AbstractJob.java
index 4e5f573..4274727 100644
--- a/src/java/azkaban/jobExecutor/AbstractJob.java
+++ b/src/java/azkaban/jobExecutor/AbstractJob.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/jobExecutor/AbstractProcessJob.java b/src/java/azkaban/jobExecutor/AbstractProcessJob.java
index 694965d..df9e1d1 100644
--- a/src/java/azkaban/jobExecutor/AbstractProcessJob.java
+++ b/src/java/azkaban/jobExecutor/AbstractProcessJob.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/jobExecutor/JavaProcessJob.java b/src/java/azkaban/jobExecutor/JavaProcessJob.java
index bf0b086..c6be913 100644
--- a/src/java/azkaban/jobExecutor/JavaProcessJob.java
+++ b/src/java/azkaban/jobExecutor/JavaProcessJob.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/jobExecutor/Job.java b/src/java/azkaban/jobExecutor/Job.java
index 3a1308a..8f51d2e 100644
--- a/src/java/azkaban/jobExecutor/Job.java
+++ b/src/java/azkaban/jobExecutor/Job.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/jobExecutor/LongArgJob.java b/src/java/azkaban/jobExecutor/LongArgJob.java
index f646866..adeea74 100644
--- a/src/java/azkaban/jobExecutor/LongArgJob.java
+++ b/src/java/azkaban/jobExecutor/LongArgJob.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package azkaban.jobExecutor;
 
 import java.io.File;
@@ -29,7 +30,6 @@ import azkaban.jobExecutor.utils.process.AzkabanProcessBuilder;
 /**
  * A job that passes all the job properties as command line arguments in "long"
  * format, e.g. --key1 value1 --key2 value2 ...
- * 
  */
 public abstract class LongArgJob extends AbstractProcessJob {
 
diff --git a/src/java/azkaban/jobExecutor/NoopJob.java b/src/java/azkaban/jobExecutor/NoopJob.java
index 510c71b..c16844d 100644
--- a/src/java/azkaban/jobExecutor/NoopJob.java
+++ b/src/java/azkaban/jobExecutor/NoopJob.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package azkaban.jobExecutor;
 
 import org.apache.log4j.Logger;
@@ -25,7 +26,7 @@ import azkaban.utils.Props;
 public class NoopJob implements Job {
 	private String jobId;
 
-	public NoopJob(String jobid, Props props, Logger log) {
+	public NoopJob(String jobid, Props props, Props jobProps, Logger log) {
 		this.jobId = jobid;
 	}
 
diff --git a/src/java/azkaban/jobExecutor/ProcessJob.java b/src/java/azkaban/jobExecutor/ProcessJob.java
index 4510bbe..13272d4 100644
--- a/src/java/azkaban/jobExecutor/ProcessJob.java
+++ b/src/java/azkaban/jobExecutor/ProcessJob.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/jobExecutor/PythonJob.java b/src/java/azkaban/jobExecutor/PythonJob.java
index 8989ff5..60e03b3 100644
--- a/src/java/azkaban/jobExecutor/PythonJob.java
+++ b/src/java/azkaban/jobExecutor/PythonJob.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package azkaban.jobExecutor;
 
 import org.apache.log4j.Logger;
diff --git a/src/java/azkaban/jobExecutor/RubyJob.java b/src/java/azkaban/jobExecutor/RubyJob.java
index c913e97..3635d9e 100644
--- a/src/java/azkaban/jobExecutor/RubyJob.java
+++ b/src/java/azkaban/jobExecutor/RubyJob.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package azkaban.jobExecutor;
 
 import org.apache.log4j.Logger;
diff --git a/src/java/azkaban/jobExecutor/ScriptJob.java b/src/java/azkaban/jobExecutor/ScriptJob.java
index 52623e7..4ab712b 100644
--- a/src/java/azkaban/jobExecutor/ScriptJob.java
+++ b/src/java/azkaban/jobExecutor/ScriptJob.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package azkaban.jobExecutor;
 
 import org.apache.log4j.Logger;
diff --git a/src/java/azkaban/jobExecutor/utils/InitErrorJob.java b/src/java/azkaban/jobExecutor/utils/InitErrorJob.java
index 09e1c15..43c8194 100644
--- a/src/java/azkaban/jobExecutor/utils/InitErrorJob.java
+++ b/src/java/azkaban/jobExecutor/utils/InitErrorJob.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/jobExecutor/utils/JobExecutionException.java b/src/java/azkaban/jobExecutor/utils/JobExecutionException.java
index 549e844..8dbb1b0 100644
--- a/src/java/azkaban/jobExecutor/utils/JobExecutionException.java
+++ b/src/java/azkaban/jobExecutor/utils/JobExecutionException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -32,4 +32,4 @@ public class JobExecutionException extends RuntimeException {
         super(message, cause);
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/jobExecutor/utils/process/AzkabanProcess.java b/src/java/azkaban/jobExecutor/utils/process/AzkabanProcess.java
index f1d49ee..54179fd 100644
--- a/src/java/azkaban/jobExecutor/utils/process/AzkabanProcess.java
+++ b/src/java/azkaban/jobExecutor/utils/process/AzkabanProcess.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package azkaban.jobExecutor.utils.process;
 
 import java.io.File;
diff --git a/src/java/azkaban/jobExecutor/utils/process/AzkabanProcessBuilder.java b/src/java/azkaban/jobExecutor/utils/process/AzkabanProcessBuilder.java
index f0f2fc1..b2e0213 100644
--- a/src/java/azkaban/jobExecutor/utils/process/AzkabanProcessBuilder.java
+++ b/src/java/azkaban/jobExecutor/utils/process/AzkabanProcessBuilder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package azkaban.jobExecutor.utils.process;
 
 import java.io.File;
diff --git a/src/java/azkaban/jobExecutor/utils/process/ProcessFailureException.java b/src/java/azkaban/jobExecutor/utils/process/ProcessFailureException.java
index 26f0d5f..01c4656 100644
--- a/src/java/azkaban/jobExecutor/utils/process/ProcessFailureException.java
+++ b/src/java/azkaban/jobExecutor/utils/process/ProcessFailureException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package azkaban.jobExecutor.utils.process;
 
 public class ProcessFailureException extends RuntimeException {
diff --git a/src/java/azkaban/jobtype/JobTypeManager.java b/src/java/azkaban/jobtype/JobTypeManager.java
index 1a0b25d..c2ad137 100644
--- a/src/java/azkaban/jobtype/JobTypeManager.java
+++ b/src/java/azkaban/jobtype/JobTypeManager.java
@@ -1,7 +1,5 @@
-package azkaban.jobtype;
-
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,6 +13,9 @@ package azkaban.jobtype;
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
+package azkaban.jobtype;
+
 import azkaban.jobExecutor.JavaProcessJob;
 import azkaban.jobExecutor.Job;
 import azkaban.jobExecutor.NoopJob;
@@ -90,7 +91,7 @@ public class JobTypeManager
 		jobToClass.put("script", ScriptJob.class);	
 	}
 
-	// load Job Typs from dir
+	// load Job Types from jobtype plugin dir
 	private void loadPluginJobTypes() throws JobTypeManagerException
 	{
 		File jobPluginsDir = new File(jobtypePluginDir);
@@ -130,19 +131,33 @@ public class JobTypeManager
 			throw new JobTypeManagerException("Failed to get global jobtype properties" + e.getCause());
 		}
 		
-		for(File dir : jobPluginsDir.listFiles()) {
-			if(dir.isDirectory() && dir.canRead()) {
-				// get its conf file
-				try {
-					loadJob(dir, globalConf, globalSysConf);
-				}
-				catch (Exception e) {
-					logger.error("Failed to load jobtype " + dir.getName() + e.getMessage());
-					throw new JobTypeManagerException(e);
+		
+		synchronized (this) {
+			ClassLoader prevCl = Thread.currentThread().getContextClassLoader();
+			try{
+				for(File dir : jobPluginsDir.listFiles()) {
+					if(dir.isDirectory() && dir.canRead()) {
+						// get its conf file
+						try {
+							loadJob(dir, globalConf, globalSysConf);
+							Thread.currentThread().setContextClassLoader(prevCl);
+						}
+						catch (Exception e) {
+							logger.error("Failed to load jobtype " + dir.getName() + e.getMessage());
+							throw new JobTypeManagerException(e);
+						}
+					}
 				}
+			} catch(Exception e) {
+				e.printStackTrace();
+				throw new JobTypeManagerException(e);
+			} catch(Throwable t) {
+				t.printStackTrace();
+				throw new JobTypeManagerException(t);
+			} finally {
+				Thread.currentThread().setContextClassLoader(prevCl);
 			}
-		}
-
+ 		}
 	}
 	
 	public static File findFilefromDir(File dir, String fn){
@@ -213,7 +228,6 @@ public class JobTypeManager
 		try {
 			if(confFile != null) {
 				conf = new Props(commonConf, confFile);
-//				conf = PropsUtils.resolveProps(conf);
 			}
 			else {
 				conf = new Props(commonConf);
@@ -236,18 +250,56 @@ public class JobTypeManager
 		logger.info("Loading jobtype " + jobtypeName );
 
 		// sysconf says what jars/confs to load
-		//List<String> jobtypeClasspath = sysConf.getStringList("jobtype.classpath", null, ",");
 		List<URL> resources = new ArrayList<URL>();		
-		for(File f : dir.listFiles()) {
-			try {
+		
+		try {
+			//first global classpath
+			logger.info("Adding global resources.");
+			List<String> typeGlobalClassPath = sysConf.getStringList("jobtype.global.classpath", null, ",");
+			if(typeGlobalClassPath != null) {
+				for(String jar : typeGlobalClassPath) {
+					URL cpItem = new File(jar).toURI().toURL();
+					if(!resources.contains(cpItem)) {
+						logger.info("adding to classpath " + cpItem);
+						resources.add(cpItem);
+					}
+				}
+			}
+			
+			//type specific classpath
+			logger.info("Adding type resources.");
+			List<String> typeClassPath = sysConf.getStringList("jobtype.classpath", null, ",");
+			if(typeClassPath != null) {
+				for(String jar : typeClassPath) {
+					URL cpItem = new File(jar).toURI().toURL();
+					if(!resources.contains(cpItem)) {
+						logger.info("adding to classpath " + cpItem);
+						resources.add(cpItem);
+					}
+				}
+			}			
+			List<String> jobtypeLibDirs = sysConf.getStringList("jobtype.lib.dir", null, ",");
+			if(jobtypeLibDirs != null) {
+				for(String libDir : jobtypeLibDirs) {
+					for(File f : new File(libDir).listFiles()) {
+						if(f.getName().endsWith(".jar")) {
+								resources.add(f.toURI().toURL());
+								logger.info("adding to classpath " + f.toURI().toURL());
+						}
+					}
+				}
+			}
+			
+			logger.info("Adding type override resources.");
+			for(File f : dir.listFiles()) {
 				if(f.getName().endsWith(".jar")) {
-					resources.add(f.toURI().toURL());
-					logger.info("adding to classpath " + f.toURI().toURL());
+						resources.add(f.toURI().toURL());
+						logger.info("adding to classpath " + f.toURI().toURL());
 				}
-			} catch (MalformedURLException e) {
-				// TODO Auto-generated catch block
-				throw new JobTypeManagerException(e);
 			}
+			
+		} catch (MalformedURLException e) {
+			throw new JobTypeManagerException(e);
 		}
 		
 		// each job type can have a different class loader
@@ -265,7 +317,7 @@ public class JobTypeManager
 		logger.info("Doing simple testing...");
 		try {
 			Props fakeSysProps = new Props(sysConf);
-			fakeSysProps.put("type", jobtypeName);
+//			fakeSysProps.put("type", jobtypeName);
 			Props fakeJobProps = new Props(conf);
 			@SuppressWarnings("unused")
 			Job job = (Job)Utils.callConstructor(clazz, "dummy", fakeSysProps, fakeJobProps, logger);
diff --git a/src/java/azkaban/jobtype/JobTypeManagerException.java b/src/java/azkaban/jobtype/JobTypeManagerException.java
index 987db2f..a5750d4 100644
--- a/src/java/azkaban/jobtype/JobTypeManagerException.java
+++ b/src/java/azkaban/jobtype/JobTypeManagerException.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.jobtype;
 
 public class JobTypeManagerException extends RuntimeException {
diff --git a/src/java/azkaban/project/JdbcProjectLoader.java b/src/java/azkaban/project/JdbcProjectLoader.java
index 7825694..3d6362a 100644
--- a/src/java/azkaban/project/JdbcProjectLoader.java
+++ b/src/java/azkaban/project/JdbcProjectLoader.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.project;
 
 import java.io.BufferedInputStream;
@@ -253,7 +269,6 @@ public class JdbcProjectLoader extends AbstractJdbcLoader implements ProjectLoad
 		}
 	}
 
-	@SuppressWarnings("resource")
 	private void uploadProjectFile(Connection connection, Project project, int version, String filetype, String filename, File localFile, String uploader) throws ProjectManagerException {
 		QueryRunner runner = new QueryRunner();
 		long updateTime = System.currentTimeMillis();
@@ -343,7 +358,6 @@ public class JdbcProjectLoader extends AbstractJdbcLoader implements ProjectLoad
 		return handler;
 	}
 	
-	@SuppressWarnings("resource")
 	private ProjectFileHandler getUploadedFile(Connection connection, int projectId, int version) throws ProjectManagerException {
 		QueryRunner runner = new QueryRunner();
 		ProjectVersionResultHandler pfHandler = new ProjectVersionResultHandler();
@@ -681,6 +695,41 @@ public class JdbcProjectLoader extends AbstractJdbcLoader implements ProjectLoad
 		}
 	}
 
+	@Override
+	public void updateFlow(Project project, int version, Flow flow) throws ProjectManagerException {
+		logger.info("Uploading flows");
+		Connection connection = getConnection();
+
+		try {
+			QueryRunner runner = new QueryRunner();
+			String json = JSONUtils.toJSON(flow.toObject());
+			byte[] stringData = json.getBytes("UTF-8");
+			byte[] data = stringData;
+
+			logger.info("UTF-8 size:" + data.length);
+			if (defaultEncodingType == EncodingType.GZIP) {
+				data = GZIPUtils.gzipBytes(stringData);
+			}
+
+			logger.info("Flow upload " + flow.getId() + " is byte size " + data.length);
+			final String UPDATE_FLOW = "UPDATE project_flows SET encoding_type=?,json=? WHERE project_id=? AND version=? AND flow_id=?";
+			try {
+				runner.update(connection, UPDATE_FLOW, defaultEncodingType.getNumVal(), data, project.getId(), version, flow.getId());
+			} catch (SQLException e) {
+				e.printStackTrace();
+				throw new ProjectManagerException("Error inserting flow " + flow.getId(), e);
+			}
+			connection.commit();
+		} catch (IOException e) {
+			throw new ProjectManagerException("Flow Upload failed.", e);
+		} catch (SQLException e) {
+			throw new ProjectManagerException("Flow Upload failed commit.", e);
+		}
+		finally {
+			DbUtils.closeQuietly(connection);
+		}
+	}
+
 	public EncodingType getDefaultEncodingType() {
 		return defaultEncodingType;
 	}
@@ -1259,4 +1308,4 @@ public class JdbcProjectLoader extends AbstractJdbcLoader implements ProjectLoad
 		return connection;
 	}
 }
-	
\ No newline at end of file
+	
diff --git a/src/java/azkaban/project/Project.java b/src/java/azkaban/project/Project.java
index d2e1f52..76bf207 100644
--- a/src/java/azkaban/project/Project.java
+++ b/src/java/azkaban/project/Project.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -45,6 +45,7 @@ public class Project {
 	private LinkedHashMap<String, Permission> groupPermissionMap = new LinkedHashMap<String, Permission>();
 	private Map<String, Flow> flows = null;
 	private HashSet<String> proxyUsers = new HashSet<String>();
+	private Map<String, Object> metadata = new HashMap<String, Object>();
 	
 	public Project(int id, String name) {
 		this.id = id;
@@ -67,6 +68,10 @@ public class Project {
 		return flows.get(flowId);
 	}
 	
+	public Map<String, Flow> getFlowMap() {
+		return flows;
+	}
+	
 	public List<Flow> getFlows() {
 		List<Flow> retFlow = null;
 		if (flows != null) {
@@ -218,6 +223,10 @@ public class Project {
 		userPermissionMap.remove(userId);
 	}
 	
+	public void clearUserPermission() {
+		userPermissionMap.clear();
+	}
+	
 	public long getCreateTimestamp() {
 		return createTimestamp;
 	}
@@ -251,6 +260,10 @@ public class Project {
 		if (source != null) {
 			projectObject.put("source", source);
 		}
+		
+		if (metadata != null) {
+			projectObject.put("metadata", metadata);
+		}
 
 		ArrayList<Map<String, Object>> users = new ArrayList<Map<String, Object>>();
 		for (Map.Entry<String, Permission> entry : userPermissionMap.entrySet()) {
@@ -282,6 +295,7 @@ public class Project {
 		Boolean active = (Boolean)projectObject.get("active");
 		active = active == null ? true : active;
 		int version = (Integer)projectObject.get("version");
+		Map<String, Object> metadata = (Map<String, Object>)projectObject.get("metadata");
 		
 		Project project = new Project(id, name);
 		project.setVersion(version);
@@ -294,6 +308,9 @@ public class Project {
 		if (source != null) {
 			project.setSource(source);
 		}
+		if (metadata != null) {
+			project.setMetadata(metadata);
+		}
 		
 		List<Map<String, Object>> users = (List<Map<String, Object>>) projectObject
 				.get("users");
@@ -403,6 +420,17 @@ public class Project {
 		this.source = source;
 	}
 
+	public Map<String, Object> getMetadata() {
+		if(metadata == null){
+			metadata = new HashMap<String, Object>();
+		}
+		return metadata;
+	}
+
+	protected void setMetadata(Map<String, Object> metadata) {
+		this.metadata = metadata;
+	}
+
 	public int getId() {
 		return id;
 	}
diff --git a/src/java/azkaban/project/ProjectFileHandler.java b/src/java/azkaban/project/ProjectFileHandler.java
index 9457efe..648c6f0 100644
--- a/src/java/azkaban/project/ProjectFileHandler.java
+++ b/src/java/azkaban/project/ProjectFileHandler.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.project;
 
 import java.io.File;
@@ -103,4 +119,4 @@ public class ProjectFileHandler {
 		this.numChunks = numChunks;
 	}
 
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/project/ProjectLoader.java b/src/java/azkaban/project/ProjectLoader.java
index 7e99298..986ba55 100644
--- a/src/java/azkaban/project/ProjectLoader.java
+++ b/src/java/azkaban/project/ProjectLoader.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.project;
 
 import java.io.File;
@@ -122,6 +138,9 @@ public interface ProjectLoader {
 	 * @throws ProjectManagerException
 	 */
 	public void changeProjectVersion(Project project, int version, String user) throws ProjectManagerException;
+
+
+	public void updateFlow(Project project, int version, Flow flow) throws ProjectManagerException;
 	
 	/**
 	 * Uploads all computed flows
@@ -224,4 +243,4 @@ public interface ProjectLoader {
 
 	void updateProjectSettings(Project project) throws ProjectManagerException;
 	
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/project/ProjectLogEvent.java b/src/java/azkaban/project/ProjectLogEvent.java
index af38a65..e6c3399 100644
--- a/src/java/azkaban/project/ProjectLogEvent.java
+++ b/src/java/azkaban/project/ProjectLogEvent.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.project;
 
 public class ProjectLogEvent {
@@ -90,4 +106,4 @@ public class ProjectLogEvent {
 		return message;
 	}
 
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/project/ProjectManager.java b/src/java/azkaban/project/ProjectManager.java
index 0972276..a5be083 100644
--- a/src/java/azkaban/project/ProjectManager.java
+++ b/src/java/azkaban/project/ProjectManager.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.project;
 
 import java.io.File;
@@ -49,7 +65,7 @@ public class ProjectManager {
 		
 		loadAllProjects();
 	}
-	
+
 	private void loadAllProjects() {
 		List<Project> projects;
 		try {
@@ -338,6 +354,22 @@ public class ProjectManager {
 			logger.info("Uploading Props properties");
 			projectLoader.uploadProjectProperties(project, propProps);
 		}
+	
+		//TODO: find something else to load triggers
+//		if(loadTriggerFromFile) {
+//			logger.info("Loading triggers.");
+//			Props triggerProps = new Props();
+//			triggerProps.put("projectId", project.getId());
+//			triggerProps.put("projectName", project.getName());
+//			triggerProps.put("submitUser", uploader.getUserId());
+//			try {
+//				triggerManager.loadTriggerFromDir(file, triggerProps);
+//			} catch (Exception e) {
+//				// TODO Auto-generated catch block
+//				e.printStackTrace();
+//				logger.error("Failed to load triggers.", e);
+//			}
+//		}
 		
 		logger.info("Uploaded project files. Cleaning up temp files.");
 		projectLoader.postEvent(project, EventType.UPLOADED, uploader.getUserId(), "Uploaded project files zip " + archive.getName());
@@ -352,6 +384,10 @@ public class ProjectManager {
 		projectLoader.cleanOlderProjectVersion(project.getId(), project.getVersion() - projectVersionRetention);
 	}
 	
+	public void updateFlow(Project project, Flow flow) throws ProjectManagerException {
+		projectLoader.updateFlow(project, flow.getVersion(), flow);
+	}
+	
 	private File unzipFile(File archiveFile) throws IOException {
 		ZipFile zipfile = new ZipFile(archiveFile);
 		File unzipped = Utils.createTempDir(tempDir);
diff --git a/src/java/azkaban/project/ProjectManagerException.java b/src/java/azkaban/project/ProjectManagerException.java
index 7da970d..e6f279d 100644
--- a/src/java/azkaban/project/ProjectManagerException.java
+++ b/src/java/azkaban/project/ProjectManagerException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/scheduler/Schedule.java b/src/java/azkaban/scheduler/Schedule.java
index f11c0fe..32eb916 100644
--- a/src/java/azkaban/scheduler/Schedule.java
+++ b/src/java/azkaban/scheduler/Schedule.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -16,7 +16,9 @@
 
 package azkaban.scheduler;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.joda.time.DateTime;
@@ -31,7 +33,7 @@ import org.joda.time.Seconds;
 import org.joda.time.Weeks;
 
 import azkaban.executor.ExecutionOptions;
-import azkaban.sla.SlaOptions;
+import azkaban.sla.SlaOption;
 import azkaban.utils.Pair;
 
 public class Schedule{
@@ -54,8 +56,10 @@ public class Schedule{
 	private String status;
 	private long submitTime;
 	
+	private boolean skipPastOccurrences = true;
+	
 	private ExecutionOptions executionOptions;
-	private SlaOptions slaOptions;
+	private List<SlaOption> slaOptions;
 	
 	public Schedule(
 						int scheduleId,
@@ -103,7 +107,7 @@ public class Schedule{
 						long submitTime,
 						String submitUser,
 						ExecutionOptions executionOptions,
-						SlaOptions slaOptions
+						List<SlaOption> slaOptions
 			) {
 		this(scheduleId, projectId, 
 				projectName, 
@@ -135,7 +139,7 @@ public class Schedule{
 						long submitTime,
 						String submitUser,
 						ExecutionOptions executionOptions,
-						SlaOptions slaOptions
+						List<SlaOption> slaOptions
 						) {
 		this.scheduleId = scheduleId;
 		this.projectId = projectId;
@@ -156,16 +160,16 @@ public class Schedule{
 	public ExecutionOptions getExecutionOptions() {
 		return executionOptions;
 	}
+	
+	public List<SlaOption> getSlaOptions() {
+		return slaOptions;
+	}
 
 	public void setFlowOptions(ExecutionOptions executionOptions) {
 		this.executionOptions = executionOptions;
 	}
-
-	public SlaOptions getSlaOptions() {
-		return slaOptions;
-	}
-
-	public void setSlaOptions(SlaOptions slaOptions) {
+	
+	public void setSlaOptions(List<SlaOption> slaOptions) {
 		this.slaOptions = slaOptions;
 	}
 
@@ -249,6 +253,10 @@ public class Schedule{
 		return false;
 	}
 	
+	public void setNextExecTime(long nextExecTime) {
+		this.nextExecTime = nextExecTime;
+	}
+	
 	private DateTime getNextRuntime(long scheduleTime, DateTimeZone timezone, ReadablePeriod period) {
 		DateTime now = new DateTime();
 		DateTime date = new DateTime(scheduleTime).withZone(timezone);
@@ -337,16 +345,21 @@ public class Schedule{
 		return periodStr;
 	}
 	
-	
 	public Map<String,Object> optionsToObject() {
-		if(executionOptions != null || slaOptions != null) {
+		if(executionOptions != null ) {
 			HashMap<String, Object> schedObj = new HashMap<String, Object>();
 			
 			if(executionOptions != null) {
 				schedObj.put("executionOptions", executionOptions.toObject());
 			}
+			
 			if(slaOptions != null) {
-				schedObj.put("slaOptions", slaOptions.toObject());
+				List<Object> slaOptionsObject = new ArrayList<Object>();
+//				schedObj.put("slaOptions", slaOptions.toObject());
+				for(SlaOption sla : slaOptions) {
+					slaOptionsObject.add(sla.toObject());
+				}
+				schedObj.put("slaOptions", slaOptionsObject);
 			}
 	
 			return schedObj;
@@ -354,8 +367,8 @@ public class Schedule{
 		return null;
 	}
 	
+	@SuppressWarnings("unchecked")
 	public void createAndSetScheduleOptions(Object obj) {
-		@SuppressWarnings("unchecked")
 		HashMap<String, Object> schedObj = (HashMap<String, Object>)obj;
 		if (schedObj.containsKey("executionOptions")) {
 			ExecutionOptions execOptions = ExecutionOptions.createFromObject(schedObj.get("executionOptions"));
@@ -370,10 +383,25 @@ public class Schedule{
 			this.executionOptions = new ExecutionOptions();
 			this.executionOptions.setConcurrentOption(ExecutionOptions.CONCURRENT_OPTION_SKIP);
 		}
-
-		if (schedObj.containsKey("slaOptions")) {
-			SlaOptions slaOptions = SlaOptions.fromObject(schedObj.get("slaOptions"));
+		
+		if(schedObj.containsKey("slaOptions")) {
+			List<Object> slaOptionsObject = (List<Object>) schedObj.get("slaOptions");
+			List<SlaOption> slaOptions = new ArrayList<SlaOption>();
+			for(Object slaObj : slaOptionsObject) {
+				slaOptions.add(SlaOption.fromObject(slaObj));
+			}
 			this.slaOptions = slaOptions;
 		}
+		
+		
 	}
-}
\ No newline at end of file
+
+	public boolean isRecurring() {
+		return period == null ? false : true;
+	}
+
+	public boolean skipPastOccurrences() {
+		return skipPastOccurrences;
+	}
+	
+}
diff --git a/src/java/azkaban/scheduler/ScheduleLoader.java b/src/java/azkaban/scheduler/ScheduleLoader.java
index 276b10e..e834cea 100644
--- a/src/java/azkaban/scheduler/ScheduleLoader.java
+++ b/src/java/azkaban/scheduler/ScheduleLoader.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -30,4 +30,5 @@ public interface ScheduleLoader {
 
 	public void updateNextExecTime(Schedule s) throws ScheduleManagerException;
 
-}
\ No newline at end of file
+	public List<Schedule> loadUpdatedSchedules() throws ScheduleManagerException;
+}
diff --git a/src/java/azkaban/scheduler/ScheduleManager.java b/src/java/azkaban/scheduler/ScheduleManager.java
index c60f143..f0325af 100644
--- a/src/java/azkaban/scheduler/ScheduleManager.java
+++ b/src/java/azkaban/scheduler/ScheduleManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -16,37 +16,23 @@
 
 package azkaban.scheduler;
 
-import java.lang.Thread.State;
 import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.PriorityBlockingQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.log4j.Logger;
-import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.ReadablePeriod;
 import org.joda.time.format.DateTimeFormat;
 import org.joda.time.format.DateTimeFormatter;
 
-import azkaban.executor.ExecutableFlow;
 import azkaban.executor.ExecutionOptions;
-import azkaban.executor.ExecutorManager;
-import azkaban.executor.ExecutorManagerException;
-import azkaban.flow.Flow;
-import azkaban.project.Project;
-import azkaban.project.ProjectManager;
-import azkaban.sla.SLA.SlaAction;
-import azkaban.sla.SLA.SlaRule;
-import azkaban.sla.SLA.SlaSetting;
-import azkaban.sla.SLAManager;
-import azkaban.sla.SlaOptions;
+import azkaban.sla.SlaOption;
+import azkaban.trigger.TriggerAgent;
+import azkaban.trigger.TriggerStatus;
 import azkaban.utils.Pair;
+import azkaban.utils.Props;
 
 /**
  * The ScheduleManager stores and executes the schedule. It uses a single thread
@@ -54,22 +40,29 @@ import azkaban.utils.Pair;
  * the flow from the schedule when it is run, which can potentially allow the
  * flow to and overlap each other.
  */
-public class ScheduleManager {
+public class ScheduleManager implements TriggerAgent {
 	private static Logger logger = Logger.getLogger(ScheduleManager.class);
 
+	public static final String triggerSource = "SimpleTimeTrigger";
 	private final DateTimeFormatter _dateFormat = DateTimeFormat.forPattern("MM-dd-yyyy HH:mm:ss:SSS");
 	private ScheduleLoader loader;
 
-	private Map<Pair<Integer, String>, Set<Schedule>> scheduleIdentityPairMap = new LinkedHashMap<Pair<Integer, String>, Set<Schedule>>();
+//	private Map<Pair<Integer, String>, Set<Schedule>> scheduleIdentityPairMap = new LinkedHashMap<Pair<Integer, String>, Set<Schedule>>();
 	private Map<Integer, Schedule> scheduleIDMap = new LinkedHashMap<Integer, Schedule>();
-	private final ScheduleRunner runner;
-	private final ExecutorManager executorManager;
-	private final ProjectManager projectManager;
-	private final SLAManager slaManager;
+	private Map<Pair<Integer, String>, Schedule> scheduleIdentityPairMap = new LinkedHashMap<Pair<Integer, String>, Schedule>();
 	
+//	private final ExecutorManagerAdapter executorManager;
+//	
+//	private ProjectManager projectManager = null;
+//	
 	// Used for mbeans to query Scheduler status
-	private long lastCheckTime = -1;
-	private long nextWakupTime = -1;
+//<<<<<<< HEAD
+//	
+//=======
+//	private long lastCheckTime = -1;
+//	private long nextWakupTime = -1;
+//	private String runnerStage = "not started";
+//>>>>>>> 10830aeb8ac819473873cac3bb4e07b4aeda67e8
 
 	/**
 	 * Give the schedule manager a loader class that will properly load the
@@ -77,17 +70,19 @@ public class ScheduleManager {
 	 * 
 	 * @param loader
 	 */
-	public ScheduleManager(ExecutorManager executorManager,
-							ProjectManager projectManager, 
-							SLAManager slaManager,
-							ScheduleLoader loader) 
+	public ScheduleManager (ScheduleLoader loader) 
 	{
-		this.executorManager = executorManager;
-		this.projectManager = projectManager;
-		this.slaManager = slaManager;
+//		this.executorManager = executorManager;
 		this.loader = loader;
-		this.runner = new ScheduleRunner();
-
+		
+	}
+	
+//	public void setProjectManager(ProjectManager projectManager) {
+//		this.projectManager = projectManager;
+//	}
+	
+	@Override
+	public void start() throws ScheduleManagerException {
 		List<Schedule> scheduleList = null;
 		try {
 			scheduleList = loader.loadSchedules();
@@ -98,10 +93,29 @@ public class ScheduleManager {
 		}
 
 		for (Schedule sched : scheduleList) {
-			internalSchedule(sched);
+			if(sched.getStatus().equals(TriggerStatus.EXPIRED.toString())) {
+				onScheduleExpire(sched);
+			} else {
+				internalSchedule(sched);
+			}
 		}
 
-		this.runner.start();
+	}
+	
+	// only do this when using external runner
+	public synchronized void updateLocal() throws ScheduleManagerException {
+		List<Schedule> updates = loader.loadUpdatedSchedules();
+		for(Schedule s : updates) {
+			if(s.getStatus().equals(TriggerStatus.EXPIRED.toString())) {
+				onScheduleExpire(s);
+			} else {
+				internalSchedule(s);
+			}
+		}
+	}
+	
+	private void onScheduleExpire(Schedule s) {
+		removeSchedule(s);
 	}
 
 	/**
@@ -109,16 +123,31 @@ public class ScheduleManager {
 	 * it again.
 	 */
 	public void shutdown() {
-		this.runner.shutdown();
+
 	}
 
 	/**
 	 * Retrieves a copy of the list of schedules.
 	 * 
 	 * @return
+	 * @throws ScheduleManagerException 
 	 */
-	public synchronized List<Schedule> getSchedules() {
-		return runner.getRunnerSchedules();
+	public synchronized List<Schedule> getSchedules() throws ScheduleManagerException {
+//		if(useExternalRunner) {
+//			for(Schedule s : scheduleIDMap.values()) {
+//				try {
+//					loader.updateNextExecTime(s);
+//				} catch (ScheduleManagerException e) {
+//					// TODO Auto-generated catch block
+//					e.printStackTrace();
+//					logger.error("Failed to update schedule from external runner for schedule " + s.getScheduleId());
+//				}
+//			}
+//		}
+		
+		//return runner.getRunnerSchedules();
+		updateLocal();
+		return new ArrayList<Schedule>(scheduleIDMap.values());
 	}
 
 	/**
@@ -126,8 +155,14 @@ public class ScheduleManager {
 	 * 
 	 * @param id
 	 * @return
-	*/
-	public Set<Schedule> getSchedules(int projectId, String flowId) {
+	 * @throws ScheduleManagerException 
+	 */
+//	public Set<Schedule> getSchedules(int projectId, String flowId) throws ScheduleManagerException {
+//		updateLocal();
+//		return scheduleIdentityPairMap.get(new Pair<Integer,String>(projectId, flowId));
+//	}
+	public Schedule getSchedule(int projectId, String flowId) throws ScheduleManagerException {
+		updateLocal();
 		return scheduleIdentityPairMap.get(new Pair<Integer,String>(projectId, flowId));
 	}
 
@@ -136,8 +171,10 @@ public class ScheduleManager {
 	 * 
 	 * @param id
 	 * @return
+	 * @throws ScheduleManagerException 
 	*/
-	public Schedule getSchedule(int scheduleId) {
+	public Schedule getSchedule(int scheduleId) throws ScheduleManagerException {
+		updateLocal();
 		return scheduleIDMap.get(scheduleId);
 	}
 
@@ -146,10 +183,19 @@ public class ScheduleManager {
 	 * Removes the flow from the schedule if it exists.
 	 * 
 	 * @param id
+	 * @throws ScheduleManagerException 
 	 */
-	public synchronized void removeSchedules(int projectId, String flowId) {
-		Set<Schedule> schedules = getSchedules(projectId, flowId);
-		for(Schedule sched : schedules) {
+//	public synchronized void removeSchedules(int projectId, String flowId) throws ScheduleManagerException {
+//		Set<Schedule> schedules = getSchedules(projectId, flowId);
+//		if(schedules != null) {
+//			for(Schedule sched : schedules) {
+//				removeSchedule(sched);
+//			}
+//		}
+//	}
+	public synchronized void removeSchedule(int projectId, String flowId) throws ScheduleManagerException {
+		Schedule sched = getSchedule(projectId, flowId);
+		if(sched != null) {
 			removeSchedule(sched);
 		}
 	}
@@ -159,24 +205,29 @@ public class ScheduleManager {
 	 * @param id
 	 */
 	public synchronized void removeSchedule(Schedule sched) {
-
 		Pair<Integer,String> identityPairMap = sched.getScheduleIdentityPair();
-		Set<Schedule> schedules = scheduleIdentityPairMap.get(identityPairMap);
-		if(schedules != null) {
-			schedules.remove(sched);
-			if(schedules.size() == 0) {
-				scheduleIdentityPairMap.remove(identityPairMap);
-			}
+//		Set<Schedule> schedules = scheduleIdentityPairMap.get(identityPairMap);
+//		if(schedules != null) {
+//			schedules.remove(sched);
+//			if(schedules.size() == 0) {
+//				scheduleIdentityPairMap.remove(identityPairMap);
+//			}
+//		}
+		Schedule schedule = scheduleIdentityPairMap.get(identityPairMap);
+		if(schedule != null) {
+			scheduleIdentityPairMap.remove(identityPairMap);
 		}
+
 		scheduleIDMap.remove(sched.getScheduleId());
 		
-		runner.removeRunnerSchedule(sched);
 		try {
 			loader.removeSchedule(sched);
 		} catch (ScheduleManagerException e) {
 			// TODO Auto-generated catch block
 			e.printStackTrace();
 		}
+
+		
 	}
 
 	// public synchronized void pauseScheduledFlow(String scheduleId){
@@ -232,7 +283,7 @@ public class ScheduleManager {
 			final long submitTime,
 			final String submitUser,
 			ExecutionOptions execOptions,
-			SlaOptions slaOptions
+			List<SlaOption> slaOptions
 			) {
 		Schedule sched = new Schedule(scheduleId, projectId, projectName, flowName, status, firstSchedTime, timezone, period, lastModifyTime, nextExecTime, submitTime, submitUser, execOptions, slaOptions);
 		logger.info("Scheduling flow '" + sched.getScheduleName() + "' for "
@@ -249,19 +300,20 @@ public class ScheduleManager {
 	 * @param flow
 	 */
 	private synchronized void internalSchedule(Schedule s) {
-		Schedule existing = scheduleIDMap.get(s.getScheduleId());
-		if (existing != null) {
-			this.runner.removeRunnerSchedule(existing);
-		}
-		s.updateTime();
-		this.runner.addRunnerSchedule(s);
+		//Schedule existing = scheduleIDMap.get(s.getScheduleId());
+//		Schedule existing = null;
+//		if(scheduleIdentityPairMap.get(s.getScheduleIdentityPair()) != null) {
+//			existing = scheduleIdentityPairMap.get(s.getScheduleIdentityPair());
+//		}
+
 		scheduleIDMap.put(s.getScheduleId(), s);
-		Set<Schedule> schedules = scheduleIdentityPairMap.get(s.getScheduleIdentityPair());
-		if(schedules == null) {
-			schedules = new HashSet<Schedule>();
-			scheduleIdentityPairMap.put(s.getScheduleIdentityPair(), schedules);
-		}
-		schedules.add(s);
+//		Set<Schedule> schedules = scheduleIdentityPairMap.get(s.getScheduleIdentityPair());
+//		if(schedules == null) {
+//			schedules = new HashSet<Schedule>();
+//			scheduleIdentityPairMap.put(s.getScheduleIdentityPair(), schedules);
+//		}
+//		schedules.add(s);
+		scheduleIdentityPairMap.put(s.getScheduleIdentityPair(), s);
 	}
 
 	/**
@@ -270,14 +322,16 @@ public class ScheduleManager {
 	 * @param flow
 	 */
 	public synchronized void insertSchedule(Schedule s) {
-		boolean exist = s.getScheduleId() != -1;
+		//boolean exist = s.getScheduleId() != -1;
+		Schedule exist = scheduleIdentityPairMap.get(s.getScheduleIdentityPair());
 		if(s.updateTime()) {
 			try {
-				if(!exist) {
+				if(exist == null) {
 					loader.insertSchedule(s);
 					internalSchedule(s);
 				}
 				else{
+					s.setScheduleId(exist.getScheduleId());
 					loader.updateSchedule(s);
 					internalSchedule(s);
 				}
@@ -290,14 +344,13 @@ public class ScheduleManager {
 			logger.error("The provided schedule is non-recurring and the scheduled time already passed. " + s.getScheduleName());
 		}
 	}
-
-//	/**
-//	 * Save the schedule
-//	 */
-//	private void saveSchedule() {
-//		loader.saveSchedule(getSchedule());
-//	}
 	
+
+	@Override
+	public void loadTriggerFromProps(Props props) throws ScheduleManagerException {
+		throw new ScheduleManagerException("create " + getTriggerSource() + " from json not supported yet" );
+	}	
+
 	/**
 	 * Thread that simply invokes the running of flows when the schedule is
 	 * ready.
@@ -305,241 +358,260 @@ public class ScheduleManager {
 	 * @author Richard Park
 	 * 
 	 */
-	public class ScheduleRunner extends Thread {
-		private final PriorityBlockingQueue<Schedule> schedules;
-		private AtomicBoolean stillAlive = new AtomicBoolean(true);
-
-		// Five minute minimum intervals
-		private static final int TIMEOUT_MS = 300000;
-
-		public ScheduleRunner() {
-			schedules = new PriorityBlockingQueue<Schedule>(1,new ScheduleComparator());
-		}
-
-		public void shutdown() {
-			logger.error("Shutting down scheduler thread");
-			stillAlive.set(false);
-			this.interrupt();
-		}
-
-		/**
-		 * Return a list of scheduled flow
-		 * 
-		 * @return
-		 */
-		public synchronized List<Schedule> getRunnerSchedules() {
-			return new ArrayList<Schedule>(schedules);
-		}
-
-		/**
-		 * Adds the flow to the schedule and then interrupts so it will update
-		 * its wait time.
-		 * 
-		 * @param flow
-		 */
-		public synchronized void addRunnerSchedule(Schedule s) {
-			logger.info("Adding " + s + " to schedule runner.");
-			schedules.add(s);
-			// MonitorImpl.getInternalMonitorInterface().workflowEvent(null,
-			// System.currentTimeMillis(),
-			// WorkflowAction.SCHEDULE_WORKFLOW,
-			// WorkflowState.NOP,
-			// flow.getId());
-
-			this.interrupt();
-		}
-
-		/**
-		 * Remove scheduled flows. Does not interrupt.
-		 * 
-		 * @param flow
-		 */
-		public synchronized void removeRunnerSchedule(Schedule s) {
-			logger.info("Removing " + s + " from the schedule runner.");
-			schedules.remove(s);
-			// MonitorImpl.getInternalMonitorInterface().workflowEvent(null,
-			// System.currentTimeMillis(),
-			// WorkflowAction.UNSCHEDULE_WORKFLOW,
-			// WorkflowState.NOP,
-			// flow.getId());
-			// Don't need to interrupt, because if this is originally on the top
-			// of the queue,
-			// it'll just skip it.
-		}
-
-		public void run() {
-			while (stillAlive.get()) {
-				synchronized (this) {
-					try {
-						lastCheckTime = System.currentTimeMillis();
-						// TODO clear up the exception handling
-						Schedule s = schedules.peek();
-
-						if (s == null) {
-							// If null, wake up every minute or so to see if
-							// there's something to do. Most likely there will not be.
-							try {
-								logger.info("Nothing scheduled to run. Checking again soon.");
-								nextWakupTime = System.currentTimeMillis() + TIMEOUT_MS;
-								this.wait(TIMEOUT_MS);
-							} catch (InterruptedException e) {
-								// interruption should occur when items are added or removed from the queue.
-							}
-						} else {
-							// We've passed the flow execution time, so we will run.
-							if (!(new DateTime(s.getNextExecTime())).isAfterNow()) {
-								// Run flow. The invocation of flows should be quick.
-								Schedule runningSched = schedules.poll();
-
-								logger.info("Scheduler ready to run " + runningSched.toString());
-								// Execute the flow here
-								try {
-									Project project = projectManager.getProject(runningSched.getProjectId());
-									if (project == null) {
-										logger.error("Scheduled Project " + runningSched.getProjectId() + " does not exist!");
-										throw new RuntimeException("Error finding the scheduled project. "+ runningSched.getProjectId());
-									}	
-									//TODO It is possible that the project is there, but the flow doesn't exist because upload a version that changes flow structure
-
-									Flow flow = project.getFlow(runningSched.getFlowName());
-									if (flow == null) {
-										logger.error("Flow " + runningSched.getScheduleName() + " cannot be found in project " + project.getName());
-										throw new RuntimeException("Error finding the scheduled flow. " + runningSched.getScheduleName());
-									}
-
-									// Create ExecutableFlow
-									ExecutableFlow exflow = new ExecutableFlow(flow);
-									System.out.println("ScheduleManager: creating schedule: " +runningSched.getScheduleId());
-									exflow.setScheduleId(runningSched.getScheduleId());
-									exflow.setSubmitUser(runningSched.getSubmitUser());
-									exflow.addAllProxyUsers(project.getProxyUsers());
-									
-									ExecutionOptions flowOptions = runningSched.getExecutionOptions();
-									if(flowOptions == null) {
-										flowOptions = new ExecutionOptions();
-										flowOptions.setConcurrentOption(ExecutionOptions.CONCURRENT_OPTION_SKIP);
-									}
-									exflow.setExecutionOptions(flowOptions);
-									
-									if (!flowOptions.isFailureEmailsOverridden()) {
-										flowOptions.setFailureEmails(flow.getFailureEmails());
-									}
-									if (!flowOptions.isSuccessEmailsOverridden()) {
-										flowOptions.setSuccessEmails(flow.getSuccessEmails());
-									}
-									
-									try {
-										executorManager.submitExecutableFlow(exflow);
-										logger.info("Scheduler has invoked " + exflow.getExecutionId());
-									} 
-									catch (ExecutorManagerException e) {
-										throw e;
-									}
-									catch (Exception e) {	
-										e.printStackTrace();
-										throw new ScheduleManagerException("Scheduler invoked flow " + exflow.getExecutionId() + " has failed.", e);
-									}
-									
-									SlaOptions slaOptions = runningSched.getSlaOptions();
-									if(slaOptions != null) {
-										logger.info("Submitting SLA checkings for " + runningSched.getFlowName());
-										// submit flow slas
-										List<SlaSetting> jobsettings = new ArrayList<SlaSetting>();
-										for(SlaSetting set : slaOptions.getSettings()) {
-											if(set.getId().equals("")) {
-												DateTime checkTime = new DateTime(runningSched.getNextExecTime()).plus(set.getDuration());
-												slaManager.submitSla(exflow.getExecutionId(), "", checkTime, slaOptions.getSlaEmails(), set.getActions(), null, set.getRule());
-											}
-											else {
-												jobsettings.add(set);
-											}
-										}
-										if(jobsettings.size() > 0) {
-											slaManager.submitSla(exflow.getExecutionId(), "", DateTime.now(), slaOptions.getSlaEmails(), new ArrayList<SlaAction>(), jobsettings, SlaRule.WAITANDCHECKJOB);
-										}
-									}
-									
-								} 
-								catch (ExecutorManagerException e) {
-									if (e.getReason() != null && e.getReason() == ExecutorManagerException.Reason.SkippedExecution) {
-										logger.info(e.getMessage());
-									}
-									else {
-										e.printStackTrace();
-									}
-								}
-								catch (Exception e) {
-									logger.info("Scheduler failed to run job. " + e.getMessage() + e.getCause());
-								}
-
-								removeRunnerSchedule(runningSched);
-
-								// Immediately reschedule if it's possible. Let
-								// the execution manager
-								// handle any duplicate runs.
-								if (runningSched.updateTime()) {
-									addRunnerSchedule(runningSched);
-									loader.updateSchedule(runningSched);
-								}
-								else {
-									removeSchedule(runningSched);
-								}								
-							} else {
-								// wait until flow run
-								long millisWait = Math.max(0, s.getNextExecTime() - (new DateTime()).getMillis());
-								try {
-									nextWakupTime = System.currentTimeMillis() + millisWait;
-									this.wait(Math.min(millisWait, TIMEOUT_MS));
-								} catch (InterruptedException e) {
-									// interruption should occur when items are
-									// added or removed from the queue.
-								}
-							}
-						}
-					} catch (Exception e) {
-						logger.error("Unexpected exception has been thrown in scheduler", e);
-					} catch (Throwable e) {
-						logger.error("Unexpected throwable has been thrown in scheduler", e);
-					}
-				}
-			}
-		}
-
-		/**
-		 * Class to sort the schedule based on time.
-		 * 
-		 * @author Richard Park
-		 */
-		private class ScheduleComparator implements Comparator<Schedule> {
-			@Override
-			public int compare(Schedule arg0, Schedule arg1) {
-				long first = arg1.getNextExecTime();
-				long second = arg0.getNextExecTime();
-
-				if (first == second) {
-					return 0;
-				} else if (first < second) {
-					return 1;
-				}
-
-				return -1;
-			}
-		}
-	}
-	
-	public long getLastCheckTime() {
-		return lastCheckTime;
-	}
-	
-	public long getNextUpdateTime() {
-		return nextWakupTime;
-	}
+//	public class ScheduleRunner extends Thread {
+//		private final PriorityBlockingQueue<Schedule> schedules;
+//		private AtomicBoolean stillAlive = new AtomicBoolean(true);
+//
+//		// Five minute minimum intervals
+//		private static final int TIMEOUT_MS = 300000;
+//
+//		public ScheduleRunner() {
+//			schedules = new PriorityBlockingQueue<Schedule>(1,new ScheduleComparator());
+//		}
+//
+//		public void shutdown() {
+//			logger.error("Shutting down scheduler thread");
+//			stillAlive.set(false);
+//			this.interrupt();
+//		}
+//
+//		/**
+//		 * Return a list of scheduled flow
+//		 * 
+//		 * @return
+//		 */
+//		public synchronized List<Schedule> getRunnerSchedules() {
+//			return new ArrayList<Schedule>(schedules);
+//		}
+//
+//		/**
+//		 * Adds the flow to the schedule and then interrupts so it will update
+//		 * its wait time.
+//		 * 
+//		 * @param flow
+//		 */
+//		public synchronized void addRunnerSchedule(Schedule s) {
+//			logger.info("Adding " + s + " to schedule runner.");
+//			schedules.add(s);
+//			// MonitorImpl.getInternalMonitorInterface().workflowEvent(null,
+//			// System.currentTimeMillis(),
+//			// WorkflowAction.SCHEDULE_WORKFLOW,
+//			// WorkflowState.NOP,
+//			// flow.getId());
+//
+//			this.interrupt();
+//		}
+//
+//		/**
+//		 * Remove scheduled flows. Does not interrupt.
+//		 * 
+//		 * @param flow
+//		 */
+//		public synchronized void removeRunnerSchedule(Schedule s) {
+//			logger.info("Removing " + s + " from the schedule runner.");
+//			schedules.remove(s);
+//			// MonitorImpl.getInternalMonitorInterface().workflowEvent(null,
+//			// System.currentTimeMillis(),
+//			// WorkflowAction.UNSCHEDULE_WORKFLOW,
+//			// WorkflowState.NOP,
+//			// flow.getId());
+//			// Don't need to interrupt, because if this is originally on the top
+//			// of the queue,
+//			// it'll just skip it.
+//		}
+//
+//		public void run() {
+//			while (stillAlive.get()) {
+//				synchronized (this) {
+//					try {
+//						lastCheckTime = System.currentTimeMillis();
+//						
+//						runnerStage = "Starting schedule scan.";
+//						// TODO clear up the exception handling
+//						Schedule s = schedules.peek();
+//
+//						if (s == null) {
+//							// If null, wake up every minute or so to see if
+//							// there's something to do. Most likely there will not be.
+//							try {
+//								logger.info("Nothing scheduled to run. Checking again soon.");
+//								runnerStage = "Waiting for next round scan.";
+//								nextWakupTime = System.currentTimeMillis() + TIMEOUT_MS;
+//								this.wait(TIMEOUT_MS);
+//							} catch (InterruptedException e) {
+//								// interruption should occur when items are added or removed from the queue.
+//							}
+//						} else {
+//							// We've passed the flow execution time, so we will run.
+//							if (!(new DateTime(s.getNextExecTime())).isAfterNow()) {
+//								// Run flow. The invocation of flows should be quick.
+//								Schedule runningSched = schedules.poll();
+//
+//								runnerStage = "Ready to run schedule " + runningSched.toString();
+//								
+//								logger.info("Scheduler ready to run " + runningSched.toString());
+//								// Execute the flow here
+//								try {
+//									Project project = projectManager.getProject(runningSched.getProjectId());
+//									if (project == null) {
+//										logger.error("Scheduled Project " + runningSched.getProjectId() + " does not exist!");
+//										throw new RuntimeException("Error finding the scheduled project. "+ runningSched.getProjectId());
+//									}	
+//									//TODO It is possible that the project is there, but the flow doesn't exist because upload a version that changes flow structure
+//
+//									Flow flow = project.getFlow(runningSched.getFlowName());
+//									if (flow == null) {
+//										logger.error("Flow " + runningSched.getScheduleName() + " cannot be found in project " + project.getName());
+//										throw new RuntimeException("Error finding the scheduled flow. " + runningSched.getScheduleName());
+//									}
+//									
+//									// Create ExecutableFlow
+//									ExecutableFlow exflow = new ExecutableFlow(flow);
+//									System.out.println("ScheduleManager: creating schedule: " +runningSched.getScheduleId());
+//									exflow.setScheduleId(runningSched.getScheduleId());
+//									exflow.setSubmitUser(runningSched.getSubmitUser());
+//									exflow.addAllProxyUsers(project.getProxyUsers());
+//									
+//									ExecutionOptions flowOptions = runningSched.getExecutionOptions();
+//									if(flowOptions == null) {
+//										flowOptions = new ExecutionOptions();
+//										flowOptions.setConcurrentOption(ExecutionOptions.CONCURRENT_OPTION_SKIP);
+//									}
+//									exflow.setExecutionOptions(flowOptions);
+//									
+//									if (!flowOptions.isFailureEmailsOverridden()) {
+//										flowOptions.setFailureEmails(flow.getFailureEmails());
+//									}
+//									if (!flowOptions.isSuccessEmailsOverridden()) {
+//										flowOptions.setSuccessEmails(flow.getSuccessEmails());
+//									}
+//									
+//									runnerStage = "Submitting flow " + exflow.getFlowId();
+//									flowOptions.setMailCreator(flow.getMailCreator());
+//									
+//									try {
+//										executorManager.submitExecutableFlow(exflow);
+//										logger.info("Scheduler has invoked " + exflow.getExecutionId());
+//									} 
+//									catch (ExecutorManagerException e) {
+//										throw e;
+//									}
+//									catch (Exception e) {	
+//										e.printStackTrace();
+//										throw new ScheduleManagerException("Scheduler invoked flow " + exflow.getExecutionId() + " has failed.", e);
+//									}
+//									
+//									SlaOptions slaOptions = runningSched.getSlaOptions();
+//									if(slaOptions != null) {
+//										logger.info("Submitting SLA checkings for " + runningSched.getFlowName());
+//										runnerStage = "Submitting SLA checkings for " + runningSched.getFlowName();
+//										// submit flow slas
+//										List<SlaSetting> jobsettings = new ArrayList<SlaSetting>();
+//										for(SlaSetting set : slaOptions.getSettings()) {
+//											if(set.getId().equals("")) {
+//												DateTime checkTime = new DateTime(runningSched.getNextExecTime()).plus(set.getDuration());
+//												slaManager.submitSla(exflow.getExecutionId(), "", checkTime, slaOptions.getSlaEmails(), set.getActions(), null, set.getRule());
+//											}
+//											else {
+//												jobsettings.add(set);
+//											}
+//										}
+//										if(jobsettings.size() > 0) {
+//											slaManager.submitSla(exflow.getExecutionId(), "", DateTime.now(), slaOptions.getSlaEmails(), new ArrayList<SlaAction>(), jobsettings, SlaRule.WAITANDCHECKJOB);
+//										}
+//									}
+//									
+//								} 
+//								catch (ExecutorManagerException e) {
+//									if (e.getReason() != null && e.getReason() == ExecutorManagerException.Reason.SkippedExecution) {
+//										logger.info(e.getMessage());
+//									}
+//									else {
+//										e.printStackTrace();
+//									}
+//								}
+//								catch (Exception e) {
+//									logger.info("Scheduler failed to run job. " + e.getMessage() + e.getCause());
+//								}
+//
+//								runnerStage = "Done running schedule for " + runningSched.toString();
+//								removeRunnerSchedule(runningSched);
+//
+//								// Immediately reschedule if it's possible. Let
+//								// the execution manager
+//								// handle any duplicate runs.
+//								if (runningSched.updateTime()) {
+//									addRunnerSchedule(runningSched);
+//									loader.updateSchedule(runningSched);
+//								}
+//								else {
+//									removeSchedule(runningSched);
+//								}								
+//							} else {
+//								runnerStage = "Waiting for next round scan.";
+//								// wait until flow run
+//								long millisWait = Math.max(0, s.getNextExecTime() - (new DateTime()).getMillis());
+//								try {
+//									nextWakupTime = System.currentTimeMillis() + millisWait;
+//									this.wait(Math.min(millisWait, TIMEOUT_MS));
+//								} catch (InterruptedException e) {
+//									// interruption should occur when items are
+//									// added or removed from the queue.
+//								}
+//							}
+//						}
+//					} catch (Exception e) {
+//						logger.error("Unexpected exception has been thrown in scheduler", e);
+//					} catch (Throwable e) {
+//						logger.error("Unexpected throwable has been thrown in scheduler", e);
+//					}
+//				}
+//			}
+//		}
+//
+//		/**
+//		 * Class to sort the schedule based on time.
+//		 * 
+//		 * @author Richard Park
+//		 */
+//		private class ScheduleComparator implements Comparator<Schedule> {
+//			@Override
+//			public int compare(Schedule arg0, Schedule arg1) {
+//				long first = arg1.getNextExecTime();
+//				long second = arg0.getNextExecTime();
+//
+//				if (first == second) {
+//					return 0;
+//				} else if (first < second) {
+//					return 1;
+//				}
+//
+//				return -1;
+//			}
+//		}
+//	}
 	
-	public State getThreadState() {
-		return runner.getState();
+//	public long getLastCheckTime() {
+//		return lastCheckTime;
+//	}
+//	
+//	public long getNextUpdateTime() {
+//		return nextWakupTime;
+//	}
+//	
+//	public State getThreadState() {
+//		return runner.getState();
+//	}
+//	
+//	public boolean isThreadActive() {
+//		return runner.isAlive();
+//>>>>>>> df6eb48ad044ae68afffae2254991289792f33a0
+//	}
+
+	@Override
+	public String getTriggerSource() {
+		return triggerSource;
 	}
 	
-	public boolean isThreadActive() {
-		return runner.isAlive();
-	}
+
 }
diff --git a/src/java/azkaban/scheduler/ScheduleManagerException.java b/src/java/azkaban/scheduler/ScheduleManagerException.java
index a977e2a..3ffb1b6 100644
--- a/src/java/azkaban/scheduler/ScheduleManagerException.java
+++ b/src/java/azkaban/scheduler/ScheduleManagerException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -26,4 +26,8 @@ public class ScheduleManagerException extends Exception{
 	public ScheduleManagerException(String message, Throwable cause) {
 		super(message, cause);
 	}
+
+	public ScheduleManagerException(Exception e) {
+		super(e);
+	}
 }
diff --git a/src/java/azkaban/scheduler/ScheduleStatisticManager.java b/src/java/azkaban/scheduler/ScheduleStatisticManager.java
index 28fc11c..d5de1e1 100644
--- a/src/java/azkaban/scheduler/ScheduleStatisticManager.java
+++ b/src/java/azkaban/scheduler/ScheduleStatisticManager.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.scheduler;
 
 import java.io.File;
@@ -8,7 +24,7 @@ import java.util.List;
 import java.util.Map;
 
 import azkaban.executor.ExecutableFlow;
-import azkaban.executor.ExecutorManager;
+import azkaban.executor.ExecutorManagerAdapter;
 import azkaban.executor.ExecutorManagerException;
 import azkaban.executor.Status;
 import azkaban.utils.JSONUtils;
@@ -16,10 +32,13 @@ import azkaban.webapp.AzkabanWebServer;
 
 public class ScheduleStatisticManager {
 	private static HashMap<Integer, Object> cacheLock = new HashMap<Integer, Object>();
-	private static File cacheDirectory = new File("cache/schedule-statistics");
+	private static File cacheDirectory;
 	private static final int STAT_NUMBERS = 10;
 
-	public static Map<String, Object> getStatistics(int scheduleId, AzkabanWebServer server) {
+	public static Map<String, Object> getStatistics(int scheduleId, AzkabanWebServer server) throws ScheduleManagerException {
+		if (cacheDirectory == null) {
+			setCacheFolder(new File(server.getServerProps().getString("cache.directory", "cache")));
+		}
 		Map<String, Object> data = loadCache(scheduleId);
 		if (data != null) {
 			return data;
@@ -33,9 +52,9 @@ public class ScheduleStatisticManager {
 		return data;
 	}
 
-	private static Map<String, Object> calculateStats(int scheduleId, AzkabanWebServer server) {
+	private static Map<String, Object> calculateStats(int scheduleId, AzkabanWebServer server) throws ScheduleManagerException {
 		Map<String, Object> data = new HashMap<String, Object>();
-		ExecutorManager executorManager = server.getExecutorManager();
+		ExecutorManagerAdapter executorManager = server.getExecutorManager();
 		ScheduleManager scheduleManager = server.getScheduleManager();
 		Schedule schedule = scheduleManager.getSchedule(scheduleId);
 
@@ -74,7 +93,8 @@ public class ScheduleStatisticManager {
 		return data;
 	}
 
-	public static void invalidateCache(int scheduleId) {
+	public static void invalidateCache(int scheduleId, File cacheDir) {
+		setCacheFolder(cacheDir);
 		// This should be silent and not fail
 		try {
 			Object lock = getLock(scheduleId);
@@ -147,4 +167,10 @@ public class ScheduleStatisticManager {
 			cacheLock.remove(scheduleId);
 		}
 	}
+
+	private static void setCacheFolder(File cacheDir) {
+		if (cacheDirectory == null) {
+			cacheDirectory = new File(cacheDir, "schedule-statistics");
+		}
+	}
 }
diff --git a/src/java/azkaban/scheduler/TriggerBasedScheduleLoader.java b/src/java/azkaban/scheduler/TriggerBasedScheduleLoader.java
new file mode 100644
index 0000000..66bc178
--- /dev/null
+++ b/src/java/azkaban/scheduler/TriggerBasedScheduleLoader.java
@@ -0,0 +1,206 @@
+package azkaban.scheduler;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import azkaban.trigger.Condition;
+import azkaban.trigger.ConditionChecker;
+import azkaban.trigger.Trigger;
+import azkaban.trigger.TriggerAction;
+import azkaban.trigger.TriggerManager;
+import azkaban.trigger.TriggerManagerAdapter;
+import azkaban.trigger.TriggerManagerException;
+import azkaban.trigger.builtin.BasicTimeChecker;
+import azkaban.trigger.builtin.ExecuteFlowAction;
+
+public class TriggerBasedScheduleLoader implements ScheduleLoader {
+	
+	private static Logger logger = Logger.getLogger(TriggerBasedScheduleLoader.class);
+	
+	private TriggerManagerAdapter triggerManager;
+	
+	private String triggerSource;
+	
+	private long lastUpdateTime = -1;
+	
+	public TriggerBasedScheduleLoader(TriggerManager triggerManager, String triggerSource) {
+		this.triggerManager = triggerManager;
+		this.triggerSource = triggerSource;
+//		// need to init the action types and condition checker types 
+//		ExecuteFlowAction.setExecutorManager(executorManager);
+//		ExecuteFlowAction.setProjectManager(projectManager);
+	}
+	
+	private Trigger scheduleToTrigger(Schedule s) {
+		Condition triggerCondition = createTriggerCondition(s);
+		Condition expireCondition = createExpireCondition(s);
+		List<TriggerAction> actions = createActions(s);
+		Trigger t = new Trigger(s.getScheduleId(), s.getLastModifyTime(), s.getSubmitTime(), s.getSubmitUser(), triggerSource, triggerCondition, expireCondition, actions);
+		if(s.isRecurring()) {
+			t.setResetOnTrigger(true);
+		} else {
+			t.setResetOnTrigger(false);
+		}
+		return t;
+	}
+	
+	private List<TriggerAction> createActions (Schedule s) {
+		List<TriggerAction> actions = new ArrayList<TriggerAction>();
+		ExecuteFlowAction executeAct = new ExecuteFlowAction("executeFlowAction", s.getProjectId(), s.getProjectName(), s.getFlowName(), s.getSubmitUser(), s.getExecutionOptions(), s.getSlaOptions());
+		actions.add(executeAct);
+//		List<SlaOption> slaOptions = s.getSlaOptions();
+//		if(slaOptions != null && slaOptions.size() > 0) {
+//			// insert a trigger to keep watching that execution
+//			for(SlaOption sla : slaOptions) {
+//				// need to create triggers for each sla
+//				SlaChecker slaChecker = new SlaChecker("slaChecker", sla, executeAct.getId());
+//				
+//			}
+//		}
+		
+		return actions;
+	}
+	
+	private Condition createTriggerCondition (Schedule s) {
+		Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
+		ConditionChecker checker = new BasicTimeChecker("BasicTimeChecker_1", s.getFirstSchedTime(), s.getTimezone(), s.isRecurring(), s.skipPastOccurrences(), s.getPeriod());
+		checkers.put(checker.getId(), checker);
+		String expr = checker.getId() + ".eval()";
+		Condition cond = new Condition(checkers, expr);
+		return cond;
+	}
+	
+	// if failed to trigger, auto expire?
+	private Condition createExpireCondition (Schedule s) {
+		Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
+		ConditionChecker checker = new BasicTimeChecker("BasicTimeChecker_2", s.getFirstSchedTime(), s.getTimezone(), s.isRecurring(), s.skipPastOccurrences(), s.getPeriod());
+		checkers.put(checker.getId(), checker);
+		String expr = checker.getId() + ".eval()";
+		Condition cond = new Condition(checkers, expr);
+		return cond;
+	}
+
+	@Override
+	public void insertSchedule(Schedule s) throws ScheduleManagerException {
+		Trigger t = scheduleToTrigger(s);
+		try {
+			triggerManager.insertTrigger(t, t.getSubmitUser());
+			s.setScheduleId(t.getTriggerId());
+		} catch (TriggerManagerException e) {
+			throw new ScheduleManagerException("Failed to insert new schedule!", e);
+		}
+	}
+
+	@Override
+	public void updateSchedule(Schedule s) throws ScheduleManagerException {
+		Trigger t = scheduleToTrigger(s);
+		try {
+			triggerManager.updateTrigger(t, t.getSubmitUser());
+		} catch (TriggerManagerException e) {
+			throw new ScheduleManagerException("Failed to update schedule!", e);
+		}
+	}
+
+	//TODO
+	// may need to add logic to filter out skip runs
+	@Override
+	public synchronized List<Schedule> loadSchedules() throws ScheduleManagerException {
+		List<Trigger> triggers = triggerManager.getTriggers(triggerSource);
+		List<Schedule> schedules = new ArrayList<Schedule>();
+//		triggersLocalCopy = new HashMap<Integer, Trigger>();
+		for(Trigger t : triggers) {
+			lastUpdateTime = Math.max(lastUpdateTime, t.getLastModifyTime());
+			Schedule s = triggerToSchedule(t);
+			schedules.add(s);
+			System.out.println("loaded schedule for " + s.getProjectId() + s.getProjectName());
+		}
+		return schedules;
+		
+	}
+	
+	private Schedule triggerToSchedule(Trigger t) throws ScheduleManagerException {
+		Condition triggerCond = t.getTriggerCondition();
+		Map<String, ConditionChecker> checkers = triggerCond.getCheckers();
+		BasicTimeChecker ck = null;
+		for(ConditionChecker checker : checkers.values()) {
+			if(checker.getType().equals(BasicTimeChecker.type)) {
+				ck = (BasicTimeChecker) checker;
+				break;
+			}
+		}
+		List<TriggerAction> actions = t.getActions();
+		ExecuteFlowAction act = null;
+		for(TriggerAction action : actions) {
+			if(action.getType().equals(ExecuteFlowAction.type)) {
+				act = (ExecuteFlowAction) action;
+				break;
+			}
+		}
+		if(ck != null && act != null) {
+			Schedule s = new Schedule(
+					t.getTriggerId(), 
+					act.getProjectId(), 
+					act.getProjectName(), 
+					act.getFlowName(), 
+					t.getStatus().toString(), 
+					ck.getFirstCheckTime(), 
+					ck.getTimeZone(), 
+					ck.getPeriod(),
+					t.getLastModifyTime(),
+					ck.getNextCheckTime(),
+					t.getSubmitTime(),
+					t.getSubmitUser(),
+					act.getExecutionOptions(),
+					act.getSlaOptions());
+			return s;
+		} else {
+			logger.error("Failed to parse schedule from trigger!");
+			throw new ScheduleManagerException("Failed to parse schedule from trigger!");
+		}
+	}
+
+	@Override
+	public void removeSchedule(Schedule s) throws ScheduleManagerException {
+		try {
+			triggerManager.removeTrigger(s.getScheduleId(), s.getSubmitUser());
+//			triggersLocalCopy.remove(s.getScheduleId());
+		} catch (TriggerManagerException e) {
+			// TODO Auto-generated catch block
+			throw new ScheduleManagerException(e.getMessage());
+		}
+		
+	}
+
+	@Override
+	public void updateNextExecTime(Schedule s)
+			throws ScheduleManagerException {
+//		Trigger t = triggersLocalCopy.get(s.getScheduleId());
+//		BasicTimeChecker ck = (BasicTimeChecker) t.getTriggerCondition().getCheckers().values().toArray()[0];
+//		s.setNextExecTime(ck.getNextCheckTime().getMillis());
+	}
+
+	@Override
+	public synchronized List<Schedule> loadUpdatedSchedules() throws ScheduleManagerException {
+		List<Trigger> triggers;
+		try {
+			triggers = triggerManager.getTriggerUpdates(triggerSource, lastUpdateTime);
+		} catch (TriggerManagerException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			throw new ScheduleManagerException(e);
+		}
+		List<Schedule> schedules = new ArrayList<Schedule>();
+		for(Trigger t : triggers) {
+			lastUpdateTime = Math.max(lastUpdateTime, t.getLastModifyTime());
+			Schedule s = triggerToSchedule(t);
+			schedules.add(s);
+			System.out.println("loaded schedule for " + s.getProjectId() + s.getProjectName());
+		}
+		return schedules;
+	}
+
+}
diff --git a/src/java/azkaban/sla/SlaOption.java b/src/java/azkaban/sla/SlaOption.java
new file mode 100644
index 0000000..e8f7a04
--- /dev/null
+++ b/src/java/azkaban/sla/SlaOption.java
@@ -0,0 +1,164 @@
+package azkaban.sla;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import azkaban.executor.ExecutableFlow;
+
+public class SlaOption {
+	
+	public static final String TYPE_FLOW_FINISH = "FlowFinish";
+	public static final String TYPE_FLOW_SUCCEED = "FlowSucceed";
+	public static final String TYPE_FLOW_PROGRESS = "FlowProgress";
+	
+	public static final String TYPE_JOB_FINISH = "JobFinish";
+	public static final String TYPE_JOB_SUCCEED = "JobSucceed";
+	public static final String TYPE_JOB_PROGRESS = "JobProgress";
+	
+	public static final String INFO_DURATION = "Duration";
+	public static final String INFO_FLOW_NAME = "FlowName";
+	public static final String INFO_JOB_NAME = "JobName";
+	public static final String INFO_PROGRESS_PERCENT = "ProgressPercent";
+	public static final String INFO_EMAIL_LIST = "EmailList";
+	
+	// always alert
+	public static final String ALERT_TYPE = "SlaAlertType";
+	public static final String ACTION_CANCEL_FLOW = "SlaCancelFlow";
+	public static final String ACTION_ALERT = "SlaAlert";
+	
+	private String type;
+	private Map<String, Object> info;
+	private List<String> actions;
+	
+	private static DateTimeFormatter fmt = DateTimeFormat.forPattern("MM/dd, YYYY HH:mm");
+	
+	public SlaOption(
+			String type,
+			List<String> actions,
+			Map<String, Object> info
+	) {
+		this.type = type;
+		this.info = info;
+		this.actions = actions;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		this.type = type;
+	}
+
+	public Map<String, Object> getInfo() {
+		return info;
+	}
+
+	public void setInfo(Map<String, Object> info) {
+		this.info = info;
+	}
+
+	public List<String> getActions() {
+		return actions;
+	}
+
+	public void setActions(List<String> actions) {
+		this.actions = actions;
+	}
+
+	public Map<String,Object> toObject() {
+		HashMap<String, Object> slaObj = new HashMap<String, Object>();
+
+		slaObj.put("type", type);
+		slaObj.put("info", info);
+		slaObj.put("actions", actions);
+
+		return slaObj;
+	}
+
+	@SuppressWarnings("unchecked")
+	public static SlaOption fromObject(Object object) {
+
+		HashMap<String, Object> slaObj = (HashMap<String,Object>)object;
+
+		String type = (String) slaObj.get("type");
+		List<String> actions = (List<String>) slaObj.get("actions");
+		Map<String, Object> info = (Map<String, Object>) slaObj.get("info");
+
+		return new SlaOption(type, actions, info);
+	}
+
+	public Object toWebObject() {
+		HashMap<String, Object> slaObj = new HashMap<String, Object>();
+
+//		slaObj.put("type", type);
+//		slaObj.put("info", info);
+//		slaObj.put("actions", actions);
+		if(type.equals(TYPE_FLOW_FINISH) || type.equals(TYPE_FLOW_SUCCEED)) {
+			slaObj.put("id", "");
+		} else {
+			slaObj.put("id", info.get(INFO_JOB_NAME));
+		}
+		slaObj.put("duration", info.get(INFO_DURATION));
+		if(type.equals(TYPE_FLOW_FINISH) || type.equals(TYPE_JOB_FINISH)) {
+			slaObj.put("rule", "FINISH");
+		} else {
+			slaObj.put("rule", "SUCCESS");
+		} 
+		List<String> actionsObj = new ArrayList<String>();
+		for(String act : actions) {
+			if(act.equals(ACTION_ALERT)) {
+				actionsObj.add("EMAIL");
+			}
+			else {
+				actionsObj.add("KILL");
+			}
+		}
+		slaObj.put("actions", actionsObj);
+		
+		return slaObj;
+	}
+	
+	@Override
+	public String toString() {
+		return "Sla of " + getType() +  getInfo() + getActions();
+	}
+	
+	public static String createSlaMessage(SlaOption slaOption, ExecutableFlow flow) {
+		String type = slaOption.getType();
+		int execId = flow.getExecutionId();
+		if(type.equals(SlaOption.TYPE_FLOW_FINISH)) {
+			String flowName = (String) slaOption.getInfo().get(SlaOption.INFO_FLOW_NAME);
+			String duration = (String) slaOption.getInfo().get(SlaOption.INFO_DURATION);
+			String basicinfo =  "SLA Alert: Your flow " + flowName + " failed to FINISH within " + duration + "</br>";
+			String expected = "Here is details : </br>" + "Flow " + flowName + " in execution " + execId + " is expected to FINISH within " + duration + " from " + fmt.print(new DateTime(flow.getStartTime())) + "</br>"; 
+			String actual = "Actual flow status is " + flow.getStatus();
+			return basicinfo + expected + actual;
+		} else if(type.equals(SlaOption.TYPE_FLOW_SUCCEED)) {
+			String flowName = (String) slaOption.getInfo().get(SlaOption.INFO_FLOW_NAME);
+			String duration = (String) slaOption.getInfo().get(SlaOption.INFO_DURATION);
+			String basicinfo =  "SLA Alert: Your flow " + flowName + " failed to SUCCEED within " + duration + "</br>";
+			String expected = "Here is details : </br>" + "Flow " + flowName + " in execution " + execId + " expected to FINISH within " + duration + " from " + fmt.print(new DateTime(flow.getStartTime())) + "</br>"; 
+			String actual = "Actual flow status is " + flow.getStatus();
+			return basicinfo + expected + actual;
+		} else if(type.equals(SlaOption.TYPE_JOB_FINISH)) {
+			String jobName = (String) slaOption.getInfo().get(SlaOption.INFO_JOB_NAME);
+			String duration = (String) slaOption.getInfo().get(SlaOption.INFO_DURATION);
+			return "SLA Alert: Your job " + jobName + " failed to FINISH within " + duration + " in execution " + execId;
+		} else if(type.equals(SlaOption.TYPE_JOB_SUCCEED)) {
+			String jobName = (String) slaOption.getInfo().get(SlaOption.INFO_JOB_NAME);
+			String duration = (String) slaOption.getInfo().get(SlaOption.INFO_DURATION);
+			return "SLA Alert: Your job " + jobName + " failed to SUCCEED within " + duration + " in execution " + execId;
+		} else {
+			return "Unrecognized SLA type " + type;
+		}
+	}
+
+
+}
diff --git a/src/java/azkaban/trigger/ActionTypeLoader.java b/src/java/azkaban/trigger/ActionTypeLoader.java
new file mode 100644
index 0000000..2efdf99
--- /dev/null
+++ b/src/java/azkaban/trigger/ActionTypeLoader.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+
+import azkaban.utils.Props;
+import azkaban.utils.Utils;
+
+public class ActionTypeLoader {
+	
+	private static Logger logger = Logger.getLogger(ActionTypeLoader.class);
+	
+	public static final String DEFAULT_TRIGGER_ACTION_PLUGIN_DIR = "plugins/triggeractions";
+
+	protected static Map<String, Class<? extends TriggerAction>> actionToClass = new HashMap<String, Class<? extends TriggerAction>>();
+	
+	public void init(Props props) throws TriggerException {
+		// load built-in actions
+		
+//
+//		loadBuiltinActions();
+//		
+//		loadPluginActions(props);
+
+	}
+	
+	public synchronized void registerActionType(String type, Class<? extends TriggerAction> actionClass) {
+		logger.info("Registering action " + type);
+		if(!actionToClass.containsKey(type)) {
+			actionToClass.put(type, actionClass);
+		}
+	}
+	
+//	private void loadPluginActions(Props props) throws TriggerException {
+//		String checkerDir = props.getString("azkaban.trigger.action.plugin.dir", DEFAULT_TRIGGER_ACTION_PLUGIN_DIR);
+//		File pluginDir = new File(checkerDir);
+//		if(!pluginDir.exists() || !pluginDir.isDirectory() || !pluginDir.canRead()) {
+//			logger.info("No trigger action plugins to load.");
+//			return;
+//		}
+//		
+//		logger.info("Loading plugin trigger actions from " + pluginDir);
+//		ClassLoader parentCl = this.getClass().getClassLoader();
+//		
+//		Props globalActionConf = null;
+//		File confFile = Utils.findFilefromDir(pluginDir, COMMONCONFFILE);
+//		try {
+//			if(confFile != null) {
+//				globalActionConf = new Props(null, confFile);
+//			} else {
+//				globalActionConf = new Props();
+//			}
+//		} catch (IOException e) {
+//			throw new TriggerException("Failed to get global properties." + e);
+//		}
+//		
+//		for(File dir : pluginDir.listFiles()) {
+//			if(dir.isDirectory() && dir.canRead()) {
+//				try {
+//					loadPluginTypes(globalActionConf, pluginDir, parentCl);
+//				} catch (Exception e) {
+//					logger.info("Plugin actions failed to load. " + e.getCause());
+//					throw new TriggerException("Failed to load all trigger actions!", e);
+//				}
+//			}
+//		}
+//	}
+//	
+//	@SuppressWarnings("unchecked")
+//	private void loadPluginTypes(Props globalConf, File dir, ClassLoader parentCl) throws TriggerException {
+//		Props actionConf = null;
+//		File confFile = Utils.findFilefromDir(dir, ACTIONTYPECONFFILE);
+//		if(confFile == null) {
+//			logger.info("No action type found in " + dir.getAbsolutePath());
+//			return;
+//		}
+//		try {
+//			actionConf = new Props(globalConf, confFile);
+//		} catch (IOException e) {
+//			throw new TriggerException("Failed to load config for the action type", e);
+//		}
+//		
+//		String actionName = dir.getName();
+//		String actionClass = actionConf.getString("action.class");
+//		
+//		List<URL> resources = new ArrayList<URL>();		
+//		for(File f : dir.listFiles()) {
+//			try {
+//				if(f.getName().endsWith(".jar")) {
+//					resources.add(f.toURI().toURL());
+//					logger.info("adding to classpath " + f.toURI().toURL());
+//				}
+//			} catch (MalformedURLException e) {
+//				// TODO Auto-generated catch block
+//				throw new TriggerException(e);
+//			}
+//		}
+//		
+//		// each job type can have a different class loader
+//		ClassLoader actionCl = new URLClassLoader(resources.toArray(new URL[resources.size()]), parentCl);
+//		
+//		Class<? extends TriggerAction> clazz = null;
+//		try {
+//			clazz = (Class<? extends TriggerAction>)actionCl.loadClass(actionClass);
+//			actionToClass.put(actionName, clazz);
+//		}
+//		catch (ClassNotFoundException e) {
+//			throw new TriggerException(e);
+//		}
+//		
+//		if(actionConf.getBoolean("need.init")) {
+//			try {
+//				Utils.invokeStaticMethod(actionCl, actionClass, "init", actionConf);
+//			} catch (Exception e) {
+//				e.printStackTrace();
+//				logger.error("Failed to init the action type " + actionName);
+//				throw new TriggerException(e);
+//			}
+//		}
+//		
+//		logger.info("Loaded action type " + actionName + " " + actionClass);
+//	}
+//	
+//	private void loadBuiltinActions() {
+//		actionToClass.put(ExecuteFlowAction.type, ExecuteFlowAction.class);		
+//		logger.info("Loaded ExecuteFlowAction type.");
+//	}
+	
+	public static void registerBuiltinActions(Map<String, Class<? extends TriggerAction>> builtinActions) {
+		actionToClass.putAll(builtinActions);
+		for(String type : builtinActions.keySet()) {
+			logger.info("Loaded " + type + " action.");
+		}
+	}
+	
+	public TriggerAction createActionFromJson(String type, Object obj) throws Exception {
+		TriggerAction action = null;
+		Class<? extends TriggerAction> actionClass = actionToClass.get(type);		
+		if(actionClass == null) {
+			throw new Exception("Action Type " + type + " not supported!");
+		}
+		action = (TriggerAction) Utils.invokeStaticMethod(actionClass.getClassLoader(), actionClass.getName(), "createFromJson", obj);
+		
+		return action;
+	}
+	
+	public TriggerAction createAction(String type, Object ... args) {
+		TriggerAction action = null;
+		Class<? extends TriggerAction> actionClass = actionToClass.get(type);		
+		action = (TriggerAction) Utils.callConstructor(actionClass, args);
+		
+		return action;
+	}
+	
+	public Set<String> getSupportedActions() {
+		return actionToClass.keySet();
+	}
+}
diff --git a/src/java/azkaban/trigger/builtin/BasicTimeChecker.java b/src/java/azkaban/trigger/builtin/BasicTimeChecker.java
new file mode 100644
index 0000000..9f80ce6
--- /dev/null
+++ b/src/java/azkaban/trigger/builtin/BasicTimeChecker.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger.builtin;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.ReadablePeriod;
+
+import azkaban.trigger.ConditionChecker;
+import azkaban.utils.Utils;
+
+public class BasicTimeChecker implements ConditionChecker {
+
+	public static final String type = "BasicTimeChecker";
+	
+	private long firstCheckTime;
+	private long nextCheckTime;
+	private DateTimeZone timezone;
+	private boolean isRecurring = true;
+	private boolean skipPastChecks = true;
+	private ReadablePeriod period;
+	
+	private final String id; 
+	
+	public BasicTimeChecker(
+			String id,
+			long firstCheckTime,
+			DateTimeZone timezone,
+			boolean isRecurring, 
+			boolean skipPastChecks,
+			ReadablePeriod period) {
+		this.id = id;
+		this.firstCheckTime = firstCheckTime;
+		this.timezone = timezone;
+		this.isRecurring = isRecurring;
+		this.skipPastChecks = skipPastChecks;
+		this.period = period;
+		this.nextCheckTime = firstCheckTime;
+		this.nextCheckTime = calculateNextCheckTime();
+	}
+	
+	public long getFirstCheckTime() {
+		return firstCheckTime;
+	}
+	
+	public DateTimeZone getTimeZone() {
+		return timezone;
+	}
+
+	public boolean isRecurring() {
+		return isRecurring;
+	}
+
+	public boolean isSkipPastChecks() {
+		return skipPastChecks;
+	}
+
+	public ReadablePeriod getPeriod() {
+		return period;
+	}
+
+	public long getNextCheckTime() {
+		return nextCheckTime;
+	}
+	
+	public BasicTimeChecker(
+			String id,
+			long firstCheckTime,
+			DateTimeZone timezone,
+			long nextCheckTime,
+			boolean isRecurring, 
+			boolean skipPastChecks,
+			ReadablePeriod period) {
+		this.id = id;
+		this.firstCheckTime = firstCheckTime;
+		this.timezone = timezone;
+		this.nextCheckTime = nextCheckTime;
+		this.isRecurring = isRecurring;
+		this.skipPastChecks = skipPastChecks;
+		this.period = period;
+	}
+	
+	@Override
+	public Boolean eval() {
+		return nextCheckTime < System.currentTimeMillis();
+	}
+
+	@Override
+	public void reset() {
+		this.nextCheckTime = calculateNextCheckTime();
+	}
+	
+	@Override
+	public String getId() {
+		return id;
+	}
+
+	@Override
+	public String getType() {
+		return type;
+	}
+
+	@SuppressWarnings("unchecked")
+	public static BasicTimeChecker createFromJson(Object obj) throws Exception {
+		return createFromJson((HashMap<String, Object>)obj);
+	}
+	
+	public static BasicTimeChecker createFromJson(HashMap<String, Object> obj) throws Exception {
+		Map<String, Object> jsonObj = (HashMap<String, Object>) obj;
+		if(!jsonObj.get("type").equals(type)) {
+			throw new Exception("Cannot create checker of " + type + " from " + jsonObj.get("type"));
+		}
+		Long firstCheckTime = Long.valueOf((String) jsonObj.get("firstCheckTime"));
+		String timezoneId = (String) jsonObj.get("timezone");
+		long nextCheckTime = Long.valueOf((String) jsonObj.get("nextCheckTime"));
+		DateTimeZone timezone = DateTimeZone.forID(timezoneId);
+		boolean isRecurring = Boolean.valueOf((String)jsonObj.get("isRecurring"));
+		boolean skipPastChecks = Boolean.valueOf((String)jsonObj.get("skipPastChecks"));
+		ReadablePeriod period = Utils.parsePeriodString((String)jsonObj.get("period"));
+		String id = (String) jsonObj.get("id");
+
+		BasicTimeChecker checker = new BasicTimeChecker(id, firstCheckTime, timezone, nextCheckTime, isRecurring, skipPastChecks, period);
+		if(skipPastChecks) {
+			checker.updateNextCheckTime();
+		}
+		return checker;
+	}
+	
+	@Override
+	public BasicTimeChecker fromJson(Object obj) throws Exception{
+		return createFromJson(obj);
+	}
+	
+	private void updateNextCheckTime(){
+		nextCheckTime = calculateNextCheckTime();
+	}
+	
+	private long calculateNextCheckTime(){
+		DateTime date = new DateTime(nextCheckTime).withZone(timezone);
+		int count = 0;
+		while(!date.isAfterNow()) {
+			if(count > 100000) {
+				throw new IllegalStateException("100000 increments of period did not get to present time.");
+			}
+			if(period == null) {
+				break;
+			}else {
+				date = date.plus(period);
+			}
+			count += 1;
+			if(!skipPastChecks) {
+				continue;
+			}
+		}
+		return date.getMillis();
+	}
+	
+	@Override
+	public Object getNum() {
+		return null;
+	}
+
+	@Override
+	public Object toJson() {
+		Map<String, Object> jsonObj = new HashMap<String, Object>();
+		jsonObj.put("type", type);
+		jsonObj.put("firstCheckTime", String.valueOf(firstCheckTime));
+		jsonObj.put("timezone", timezone.getID());
+		jsonObj.put("nextCheckTime", String.valueOf(nextCheckTime));
+		jsonObj.put("isRecurring", String.valueOf(isRecurring));
+		jsonObj.put("skipPastChecks", String.valueOf(skipPastChecks));
+		jsonObj.put("period", Utils.createPeriodString(period));
+		jsonObj.put("id", id);
+		
+		return jsonObj;
+	}
+
+	@Override
+	public void stopChecker() {
+		return;
+	}
+
+	@Override
+	public void setContext(Map<String, Object> context) {
+	}
+
+}
diff --git a/src/java/azkaban/trigger/builtin/CreateTriggerAction.java b/src/java/azkaban/trigger/builtin/CreateTriggerAction.java
new file mode 100644
index 0000000..ad86c3f
--- /dev/null
+++ b/src/java/azkaban/trigger/builtin/CreateTriggerAction.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger.builtin;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import azkaban.trigger.Trigger;
+import azkaban.trigger.TriggerAction;
+import azkaban.trigger.TriggerManager;
+
+public class CreateTriggerAction implements TriggerAction {
+	
+	public static final String type = "CreateTriggerAction";
+	private static TriggerManager triggerManager;
+	private Trigger trigger;
+	@SuppressWarnings("unused")
+	private Map<String, Object> context;
+	private String actionId;
+	
+	public CreateTriggerAction(String actionId, Trigger trigger) {
+		this.actionId = actionId;
+		this.trigger = trigger;
+	}
+	
+	@Override
+	public String getType() {
+		return type;
+	}
+	
+	public static void setTriggerManager(TriggerManager trm) {
+		triggerManager = trm;
+	}
+
+	@SuppressWarnings("unchecked")
+	public static CreateTriggerAction createFromJson(Object obj) throws Exception {
+		Map<String, Object> jsonObj = (HashMap<String, Object>) obj;
+		if(!jsonObj.get("type").equals(type)) {
+			throw new Exception("Cannot create action of " + type + " from " + jsonObj.get("type"));
+		}
+		String actionId = (String) jsonObj.get("actionId");
+		Trigger trigger = Trigger.fromJson(jsonObj.get("trigger"));
+		return new CreateTriggerAction(actionId, trigger);
+	}
+	
+	@Override
+	public CreateTriggerAction fromJson(Object obj) throws Exception {
+		// TODO Auto-generated method stub
+		return createFromJson(obj);
+	}
+
+	@Override
+	public Object toJson() {
+		Map<String, Object> jsonObj = new HashMap<String, Object>();
+		jsonObj.put("actionId", actionId);
+		jsonObj.put("type", type);
+		jsonObj.put("trigger", trigger.toJson());
+
+		return jsonObj;
+	}
+
+	@Override
+	public void doAction() throws Exception {
+		triggerManager.insertTrigger(trigger);
+	}
+
+	@Override
+	public String getDescription() {
+		return "create another: " + trigger.getDescription();
+	}
+
+	@Override
+	public String getId() {
+		return actionId;
+	}
+
+	@Override
+	public void setContext(Map<String, Object> context) {
+		this.context = context;
+	}
+
+}
diff --git a/src/java/azkaban/trigger/builtin/ExecuteFlowAction.java b/src/java/azkaban/trigger/builtin/ExecuteFlowAction.java
new file mode 100644
index 0000000..ab1586a
--- /dev/null
+++ b/src/java/azkaban/trigger/builtin/ExecuteFlowAction.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger.builtin;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import azkaban.executor.ExecutableFlow;
+import azkaban.executor.ExecutionOptions;
+import azkaban.executor.ExecutorManagerAdapter;
+import azkaban.executor.ExecutorManagerException;
+import azkaban.flow.Flow;
+import azkaban.project.Project;
+import azkaban.project.ProjectManager;
+import azkaban.sla.SlaOption;
+import azkaban.trigger.Condition;
+import azkaban.trigger.ConditionChecker;
+import azkaban.trigger.Trigger;
+import azkaban.trigger.TriggerAction;
+import azkaban.trigger.TriggerManager;
+
+public class ExecuteFlowAction implements TriggerAction {
+
+	public static final String type = "ExecuteFlowAction";
+
+	public static final String EXEC_ID = "ExecuteFlowAction.execid";
+	
+	private static ExecutorManagerAdapter executorManager;
+	private static TriggerManager triggerManager;
+	private String actionId;
+	private int projectId;
+	private String projectName;
+	private String flowName;
+	private String submitUser;
+	private static ProjectManager projectManager;
+	private ExecutionOptions executionOptions = new ExecutionOptions();
+	private List<SlaOption> slaOptions;
+	
+	private static Logger logger = Logger.getLogger(ExecuteFlowAction.class);
+	
+	public ExecuteFlowAction(String actionId, int projectId, String projectName, String flowName, String submitUser, ExecutionOptions executionOptions, List<SlaOption> slaOptions) {
+		this.actionId = actionId;
+		this.projectId = projectId;
+		this.projectName = projectName;
+		this.flowName = flowName;
+		this.submitUser = submitUser;
+		this.executionOptions = executionOptions;
+		this.slaOptions = slaOptions;
+	}
+	
+	public static void setLogger(Logger logger) {
+		ExecuteFlowAction.logger = logger;
+	}
+	
+	public String getProjectName() {
+		return projectName;
+	}
+
+	public int getProjectId() {
+		return projectId;
+	}
+
+	protected void setProjectId(int projectId) {
+		this.projectId = projectId;
+	}
+
+	public String getFlowName() {
+		return flowName;
+	}
+
+	protected void setFlowName(String flowName) {
+		this.flowName = flowName;
+	}
+
+	public String getSubmitUser() {
+		return submitUser;
+	}
+
+	protected void setSubmitUser(String submitUser) {
+		this.submitUser = submitUser;
+	}
+
+	public ExecutionOptions getExecutionOptions() {
+		return executionOptions;
+	}
+
+	protected void setExecutionOptions(ExecutionOptions executionOptions) {
+		this.executionOptions = executionOptions;
+	}
+	
+	public List<SlaOption> getSlaOptions() {
+		return slaOptions;
+	}
+
+	protected void setSlaOptions(List<SlaOption> slaOptions) {
+		this.slaOptions = slaOptions;
+	}
+
+	public static ExecutorManagerAdapter getExecutorManager() {
+		return executorManager;
+	}
+ 	
+	public static void setExecutorManager(ExecutorManagerAdapter executorManager) {
+		ExecuteFlowAction.executorManager = executorManager;
+	}
+	
+	public static TriggerManager getTriggerManager() {
+		return triggerManager;
+	}
+ 	
+	public static void setTriggerManager(TriggerManager triggerManager) {
+		ExecuteFlowAction.triggerManager = triggerManager;
+	}
+
+	public static ProjectManager getProjectManager() {
+		return projectManager;
+	}
+	
+	public static void setProjectManager(ProjectManager projectManager) {
+		ExecuteFlowAction.projectManager = projectManager;
+	}
+
+	@Override
+	public String getType() {
+		return type;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public TriggerAction fromJson(Object obj) {
+		return createFromJson((HashMap<String, Object>) obj);
+	}
+
+	@SuppressWarnings("unchecked")
+	public static TriggerAction createFromJson(HashMap<String, Object> obj) {
+		Map<String, Object> jsonObj = (HashMap<String, Object>) obj;
+		String objType = (String) jsonObj.get("type");
+		if(! objType.equals(type)) {
+			throw new RuntimeException("Cannot create action of " + type + " from " + objType);
+		}
+		String actionId = (String) jsonObj.get("actionId");
+		int projectId = Integer.valueOf((String)jsonObj.get("projectId"));
+		String projectName = (String) jsonObj.get("projectName");
+		String flowName = (String) jsonObj.get("flowName");
+		String submitUser = (String) jsonObj.get("submitUser");
+		ExecutionOptions executionOptions = null;
+		if(jsonObj.containsKey("executionOptions")) {
+			executionOptions = ExecutionOptions.createFromObject(jsonObj.get("executionOptions"));
+		}
+		List<SlaOption> slaOptions = null;
+		if(jsonObj.containsKey("slaOptions")) {
+			slaOptions = new ArrayList<SlaOption>();
+			List<Object> slaOptionsObj = (List<Object>) jsonObj.get("slaOptions");
+			for(Object slaObj : slaOptionsObj) {
+				slaOptions.add(SlaOption.fromObject(slaObj));
+			}
+		}
+		return new ExecuteFlowAction(actionId, projectId, projectName, flowName, submitUser, executionOptions, slaOptions);
+	}
+	
+	@Override
+	public Object toJson() {
+		Map<String, Object> jsonObj = new HashMap<String, Object>();
+		jsonObj.put("actionId", actionId);
+		jsonObj.put("type", type);
+		jsonObj.put("projectId", String.valueOf(projectId));
+		jsonObj.put("projectName", projectName);
+		jsonObj.put("flowName", flowName);
+		jsonObj.put("submitUser", submitUser);
+		if(executionOptions != null) {
+			jsonObj.put("executionOptions", executionOptions.toObject());
+		}
+		if(slaOptions != null) {
+			List<Object> slaOptionsObj = new ArrayList<Object>();
+			for(SlaOption sla : slaOptions) {
+				slaOptionsObj.add(sla.toObject());
+			}
+			jsonObj.put("slaOptions", slaOptionsObj);
+		}
+		return jsonObj;
+	}
+
+	@Override
+	public void doAction() throws Exception {
+		if(projectManager == null || executorManager == null) {
+			throw new Exception("ExecuteFlowAction not properly initialized!");
+		}
+		
+		Project project = projectManager.getProject(projectId);
+		if(project == null) {
+			logger.error("Project to execute " + projectId + " does not exist!");
+			throw new RuntimeException("Error finding the project to execute " + projectId);
+		}
+		
+		Flow flow = project.getFlow(flowName);
+		if(flow == null) {
+			logger.error("Flow " + flowName + " cannot be found in project " + project.getName());
+			throw new RuntimeException("Error finding the flow to execute " + flowName);
+		}
+		
+		ExecutableFlow exflow = new ExecutableFlow(flow);
+		exflow.setSubmitUser(submitUser);
+		exflow.addAllProxyUsers(project.getProxyUsers());
+		
+		if(executionOptions == null) {
+			executionOptions = new ExecutionOptions();
+		}
+		if(!executionOptions.isFailureEmailsOverridden()) {
+			executionOptions.setFailureEmails(flow.getFailureEmails());
+		}
+		if(!executionOptions.isSuccessEmailsOverridden()) {
+			executionOptions.setSuccessEmails(flow.getSuccessEmails());
+		}
+		exflow.setExecutionOptions(executionOptions);
+		
+		try{
+			executorManager.submitExecutableFlow(exflow, submitUser);
+//			Map<String, Object> outputProps = new HashMap<String, Object>();
+//			outputProps.put(EXEC_ID, exflow.getExecutionId());
+//			context.put(actionId, outputProps);
+			logger.info("Invoked flow " + project.getName() + "." + flowName);
+		} catch (ExecutorManagerException e) {
+			throw new RuntimeException(e);
+		}
+		
+		// deal with sla
+		if(slaOptions != null && slaOptions.size() > 0) {
+			int execId = exflow.getExecutionId();
+			for(SlaOption sla : slaOptions) {
+				logger.info("Adding sla trigger " + sla.toString() + " to execution " + execId);
+				SlaChecker slaFailChecker = new SlaChecker("slaFailChecker", sla, execId);
+				Map<String, ConditionChecker> slaCheckers = new HashMap<String, ConditionChecker>();
+				slaCheckers.put(slaFailChecker.getId(), slaFailChecker);
+				Condition triggerCond = new Condition(slaCheckers, slaFailChecker.getId() + ".isSlaFailed()");
+				// if whole flow finish before violate sla, just expire
+				SlaChecker slaPassChecker = new SlaChecker("slaPassChecker", sla, execId);
+				Map<String, ConditionChecker> expireCheckers = new HashMap<String, ConditionChecker>();
+				expireCheckers.put(slaPassChecker.getId(), slaPassChecker);
+				Condition expireCond = new Condition(expireCheckers, slaPassChecker.getId() + ".isSlaPassed()");
+				List<TriggerAction> actions = new ArrayList<TriggerAction>();
+				List<String> slaActions = sla.getActions();
+				for(String act : slaActions) {
+					if(act.equals(SlaOption.ACTION_ALERT)) {
+						SlaAlertAction slaAlert = new SlaAlertAction("slaAlert", sla, execId);
+						actions.add(slaAlert);
+					} else if(act.equals(SlaOption.ACTION_CANCEL_FLOW)) {
+						KillExecutionAction killAct = new KillExecutionAction("killExecution", execId);
+						actions.add(killAct);
+					}
+				}
+				Trigger slaTrigger = new Trigger("azkaban_sla", "azkaban", triggerCond, expireCond, actions);
+				slaTrigger.getInfo().put("monitored.finished.execution", String.valueOf(execId));
+				slaTrigger.setResetOnTrigger(false);
+				slaTrigger.setResetOnExpire(false);
+				logger.info("Ready to put in the sla trigger");
+				triggerManager.insertTrigger(slaTrigger);
+				logger.info("Sla inserted.");
+			}
+		}
+		
+	}
+
+	@Override
+	public String getDescription() {
+		return "Execute flow " + getFlowName() + 
+				" from project " + getProjectName();
+	}
+
+	@Override
+	public void setContext(Map<String, Object> context) {
+	}
+
+	@Override
+	public String getId() {
+		return actionId;
+	}
+
+}
diff --git a/src/java/azkaban/trigger/builtin/ExecutionChecker.java b/src/java/azkaban/trigger/builtin/ExecutionChecker.java
new file mode 100644
index 0000000..01fa43a
--- /dev/null
+++ b/src/java/azkaban/trigger/builtin/ExecutionChecker.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger.builtin;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import azkaban.executor.ExecutableFlow;
+import azkaban.executor.ExecutableNode;
+import azkaban.executor.ExecutorManagerAdapter;
+import azkaban.executor.ExecutorManagerException;
+import azkaban.executor.Status;
+import azkaban.trigger.ConditionChecker;
+
+public class ExecutionChecker implements ConditionChecker{
+
+	public static final String type = "ExecutionChecker";
+	public static ExecutorManagerAdapter executorManager;
+	
+	private String checkerId;
+	private int execId;
+	private String jobName;
+	private Status wantedStatus;
+	
+	public ExecutionChecker(String checkerId, int execId, String jobName, Status wantedStatus) {
+		this.checkerId = checkerId;
+		this.execId = execId;
+		this.jobName = jobName;
+		this.wantedStatus = wantedStatus;
+	}
+	
+	public static void setExecutorManager(ExecutorManagerAdapter em) {
+		executorManager = em;
+	}
+	
+	@Override
+	public Object eval() {
+		ExecutableFlow exflow;
+		try {
+			exflow = executorManager.getExecutableFlow(execId);
+		} catch (ExecutorManagerException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			return Boolean.FALSE;
+		}
+		if(jobName != null) {
+			ExecutableNode job = exflow.getExecutableNode(jobName);
+			if(job != null) {
+				return job.getStatus().equals(wantedStatus);
+			} else {
+				return Boolean.FALSE;
+			}
+		} else {
+			return exflow.getStatus().equals(wantedStatus);
+		}
+		
+	}
+
+	@Override
+	public Object getNum() {
+		return null;
+	}
+
+	@Override
+	public void reset() {
+	}
+
+	@Override
+	public String getId() {
+		return checkerId;
+	}
+
+	@Override
+	public String getType() {
+		return type;
+	}
+
+	public static ExecutionChecker createFromJson(HashMap<String, Object> jsonObj) throws Exception {
+		if(!jsonObj.get("type").equals(type)) {
+			throw new Exception("Cannot create checker of " + type + " from " + jsonObj.get("type"));
+		}
+		int execId = Integer.valueOf((String) jsonObj.get("execId"));
+		String jobName = null;
+		if(jsonObj.containsKey("jobName")) {
+			jobName = (String) jsonObj.get("jobName");
+		}
+		String checkerId = (String) jsonObj.get("checkerId");
+		Status wantedStatus = Status.valueOf((String)jsonObj.get("wantedStatus"));
+
+		return new ExecutionChecker(checkerId, execId, jobName, wantedStatus);
+	}
+	
+	@SuppressWarnings("unchecked")
+	@Override
+	public ConditionChecker fromJson(Object obj) throws Exception {
+		return createFromJson((HashMap<String, Object>) obj);
+	}
+
+	@Override
+	public Object toJson() {
+		Map<String, Object> jsonObj = new HashMap<String, Object>();
+		jsonObj.put("type", type);
+		jsonObj.put("execId", String.valueOf(execId));
+		if(jobName != null) {
+			jsonObj.put("jobName", jobName);
+		}
+		jsonObj.put("wantedStatus", wantedStatus.toString());
+		jsonObj.put("checkerId", checkerId);
+		return jsonObj;
+	}
+
+	@Override
+	public void stopChecker() {
+	}
+
+	@Override
+	public void setContext(Map<String, Object> context) {
+	}
+
+	@Override
+	public long getNextCheckTime() {
+		return -1;
+	}
+
+}
diff --git a/src/java/azkaban/trigger/builtin/KillExecutionAction.java b/src/java/azkaban/trigger/builtin/KillExecutionAction.java
new file mode 100644
index 0000000..dd53efe
--- /dev/null
+++ b/src/java/azkaban/trigger/builtin/KillExecutionAction.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger.builtin;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import azkaban.executor.ExecutableFlow;
+import azkaban.executor.ExecutorManagerAdapter;
+import azkaban.trigger.TriggerAction;
+
+public class KillExecutionAction implements TriggerAction{
+
+	public static final String type = "KillExecutionAction";
+	
+	private static final Logger logger = Logger.getLogger(KillExecutionAction.class);
+	
+	private String actionId;
+	private int execId;
+	private static ExecutorManagerAdapter executorManager;
+	
+	public KillExecutionAction(String actionId, int execId) {
+		this.execId = execId;
+		this.actionId = actionId;
+	}
+	
+	public static void setExecutorManager(ExecutorManagerAdapter em) {
+		executorManager = em;
+	}
+	
+	@Override
+	public String getId() {
+		return actionId;
+	}
+
+	@Override
+	public String getType() {
+		return type;
+	}
+
+	@SuppressWarnings("unchecked")
+	public static KillExecutionAction createFromJson(Object obj) {
+		return createFromJson((HashMap<String, Object>)obj);
+	}
+	
+	public static KillExecutionAction createFromJson(HashMap<String, Object> obj) {
+		Map<String, Object> jsonObj = (HashMap<String, Object>) obj;
+		String objType = (String) jsonObj.get("type");
+		if(! objType.equals(type)) {
+			throw new RuntimeException("Cannot create action of " + type + " from " + objType);
+		}
+		String actionId = (String) jsonObj.get("actionId");
+		int execId = Integer.valueOf((String) jsonObj.get("execId"));
+		return new KillExecutionAction(actionId, execId);
+	}
+	
+	@SuppressWarnings("unchecked")
+	@Override
+	public KillExecutionAction fromJson(Object obj) throws Exception {
+		return createFromJson((HashMap<String, Object>)obj);
+	}
+
+	@Override
+	public Object toJson() {
+		Map<String, Object> jsonObj = new HashMap<String, Object>();
+		jsonObj.put("actionId", actionId);
+		jsonObj.put("type", type);
+		jsonObj.put("execId", String.valueOf(execId));
+		return jsonObj;
+	}
+
+	@Override
+	public void doAction() throws Exception {
+		ExecutableFlow exFlow = executorManager.getExecutableFlow(execId);
+		logger.info("ready to kill execution " + execId);
+		if(!ExecutableFlow.isFinished(exFlow)) {
+			logger.info("Killing execution " + execId);
+			executorManager.cancelFlow(exFlow, "azkaban_sla");
+		}
+	}
+
+	@Override
+	public void setContext(Map<String, Object> context) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	@Override
+	public String getDescription() {
+		return type + " for " + execId;
+	}
+
+}
diff --git a/src/java/azkaban/trigger/builtin/SendEmailAction.java b/src/java/azkaban/trigger/builtin/SendEmailAction.java
new file mode 100644
index 0000000..dd5997b
--- /dev/null
+++ b/src/java/azkaban/trigger/builtin/SendEmailAction.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger.builtin;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import azkaban.trigger.TriggerAction;
+import azkaban.utils.AbstractMailer;
+import azkaban.utils.EmailMessage;
+import azkaban.utils.Props;
+
+public class SendEmailAction implements TriggerAction {
+	
+	private String actionId;
+	private static AbstractMailer mailer;
+	private String message;
+	public static final String type = "SendEmailAction";
+	private String mimetype = "text/html";
+	private List<String> emailList;
+	private String subject;
+	
+	public static void init(Props props) {
+		mailer = new AbstractMailer(props);
+	}
+	
+	public SendEmailAction(String actionId, String subject, String message, List<String> emailList) {
+		this.actionId = actionId;
+		this.message = message;
+		this.subject = subject;
+		this.emailList = emailList;
+	}
+	
+	@Override
+	public String getId() {
+		return actionId;
+	}
+
+	@Override
+	public String getType() {
+		return type;
+	}
+
+	@SuppressWarnings("unchecked")
+	public static SendEmailAction createFromJson(Object obj) throws Exception {
+		Map<String, Object> jsonObj = (HashMap<String, Object>) obj;
+		if(!jsonObj.get("type").equals(type)) {
+			throw new Exception("Cannot create action of " + type + " from " + jsonObj.get("type"));
+		}
+		String actionId = (String) jsonObj.get("actionId");
+		String subject = (String) jsonObj.get("subject");
+		String message = (String) jsonObj.get("message");
+		List<String> emailList = (List<String>) jsonObj.get("emailList");
+		return new SendEmailAction(actionId, subject, message, emailList);
+	}
+	
+	@Override
+	public TriggerAction fromJson(Object obj) throws Exception {
+		return createFromJson(obj);
+	}
+
+	@Override
+	public Object toJson() {
+		Map<String, Object> jsonObj = new HashMap<String, Object>();
+		jsonObj.put("actionId", actionId);
+		jsonObj.put("type", type);
+		jsonObj.put("subject", subject);
+		jsonObj.put("message", message);
+		jsonObj.put("emailList", emailList);
+
+		return jsonObj;
+	}
+
+	@Override
+	public void doAction() throws Exception {
+		EmailMessage email = mailer.prepareEmailMessage(subject, mimetype, emailList);
+		email.setBody(message);
+		email.sendEmail();
+	}
+
+	@Override
+	public void setContext(Map<String, Object> context) {
+		
+	}
+
+	@Override
+	public String getDescription() {
+		return type;
+	}
+
+	
+}
diff --git a/src/java/azkaban/trigger/builtin/SlaAlertAction.java b/src/java/azkaban/trigger/builtin/SlaAlertAction.java
new file mode 100644
index 0000000..1f6eb00
--- /dev/null
+++ b/src/java/azkaban/trigger/builtin/SlaAlertAction.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger.builtin;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import azkaban.alert.Alerter;
+import azkaban.executor.ExecutableFlow;
+import azkaban.executor.ExecutorManagerAdapter;
+import azkaban.sla.SlaOption;
+import azkaban.trigger.TriggerAction;
+
+public class SlaAlertAction implements TriggerAction{
+
+	public static final String type = "AlertAction";
+	
+	private static final Logger logger = Logger.getLogger(SlaAlertAction.class);
+	
+	private String actionId;
+	private SlaOption slaOption;
+	private int execId;
+//	private List<Map<String, Object>> alerts;
+	private static Map<String, azkaban.alert.Alerter> alerters;
+	private static ExecutorManagerAdapter executorManager;
+
+	public SlaAlertAction(String id, SlaOption slaOption, int execId) {
+		this.actionId = id;
+		this.slaOption = slaOption;
+		this.execId = execId;
+//		this.alerts = alerts;
+	}
+	
+	public static void setAlerters(Map<String, Alerter> alts) {
+		alerters = alts;
+	}
+	
+	public static void setExecutorManager(ExecutorManagerAdapter em) {
+		executorManager = em;
+	}
+	
+	@Override
+	public String getId() {
+		return actionId;
+	}
+
+	@Override
+	public String getType() {
+		return type;
+	}
+
+	@SuppressWarnings("unchecked")
+	public static SlaAlertAction createFromJson(Object obj) throws Exception {
+		return createFromJson((HashMap<String, Object>) obj);
+	}
+	
+	public static SlaAlertAction createFromJson(HashMap<String, Object> obj) throws Exception {
+		Map<String, Object> jsonObj = (HashMap<String, Object>) obj;
+		if(!jsonObj.get("type").equals(type)) {
+			throw new Exception("Cannot create action of " + type + " from " + jsonObj.get("type"));
+		}
+		String actionId = (String) jsonObj.get("actionId");
+		SlaOption slaOption = SlaOption.fromObject(jsonObj.get("slaOption"));
+		int execId = Integer.valueOf((String) jsonObj.get("execId"));
+//		List<Map<String, Object>> alerts = (List<Map<String, Object>>) jsonObj.get("alerts");
+		return new SlaAlertAction(actionId, slaOption, execId);
+	}
+	
+	@Override
+	public TriggerAction fromJson(Object obj) throws Exception {
+		return createFromJson(obj);
+	}
+
+	@Override
+	public Object toJson() {
+		Map<String, Object> jsonObj = new HashMap<String, Object>();
+		jsonObj.put("actionId", actionId);
+		jsonObj.put("type", type);
+		jsonObj.put("slaOption", slaOption.toObject());
+		jsonObj.put("execId", String.valueOf(execId));
+//		jsonObj.put("alerts", alerts);
+
+		return jsonObj;
+	}
+
+	@Override
+	public void doAction() throws Exception {
+//		for(Map<String, Object> alert : alerts) {
+		logger.info("Alerting on sla failure.");
+		Map<String, Object> alert = slaOption.getInfo();
+			if(alert.containsKey(SlaOption.ALERT_TYPE)) {
+				String alertType = (String) alert.get(SlaOption.ALERT_TYPE);
+				Alerter alerter = alerters.get(alertType);
+				if(alerter != null) {
+					try {
+						ExecutableFlow flow = executorManager.getExecutableFlow(execId);
+						alerter.alertOnSla(slaOption, SlaOption.createSlaMessage(slaOption, flow));
+					} catch (Exception e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+						logger.error("Failed to alert by " + alertType);
+					}
+				}
+				else {
+					logger.error("Alerter type " + alertType + " doesn't exist. Failed to alert.");
+				}
+			}
+//		}
+	}
+
+//	private String createSlaMessage() {
+//		ExecutableFlow flow = null;
+//		try {
+//			flow = executorManager.getExecutableFlow(execId);
+//		} catch (ExecutorManagerException e) {
+//			e.printStackTrace();
+//			logger.error("Failed to get executable flow.");
+//		}
+//		String type = slaOption.getType();
+//		if(type.equals(SlaOption.TYPE_FLOW_FINISH)) {
+//			String flowName = (String) slaOption.getInfo().get(SlaOption.INFO_FLOW_NAME);
+//			String duration = (String) slaOption.getInfo().get(SlaOption.INFO_DURATION);
+//			String basicinfo =  "SLA Alert: Your flow " + flowName + " failed to FINISH within " + duration + "</br>";
+//			String expected = "Here is details : </br>" + "Flow " + flowName + " in execution " + execId + " is expected to FINISH within " + duration + " from " + flow.getStartTime() + "</br>"; 
+//			String actual = "Actual flow status is " + flow.getStatus();
+//			return basicinfo + expected + actual;
+//		} else if(type.equals(SlaOption.TYPE_FLOW_SUCCEED)) {
+//			String flowName = (String) slaOption.getInfo().get(SlaOption.INFO_FLOW_NAME);
+//			String duration = (String) slaOption.getInfo().get(SlaOption.INFO_DURATION);
+//			String basicinfo =  "SLA Alert: Your flow " + flowName + " failed to SUCCEED within " + duration + "</br>";
+//			String expected = "Here is details : </br>" + "Flow " + flowName + " in execution " + execId + " expected to FINISH within " + duration + " from " + flow.getStartTime() + "</br>"; 
+//			String actual = "Actual flow status is " + flow.getStatus();
+//			return basicinfo + expected + actual;
+//		} else if(type.equals(SlaOption.TYPE_JOB_FINISH)) {
+//			String jobName = (String) slaOption.getInfo().get(SlaOption.INFO_JOB_NAME);
+//			String duration = (String) slaOption.getInfo().get(SlaOption.INFO_DURATION);
+//			return "SLA Alert: Your job " + jobName + " failed to FINISH within " + duration + " in execution " + execId;
+//		} else if(type.equals(SlaOption.TYPE_JOB_SUCCEED)) {
+//			String jobName = (String) slaOption.getInfo().get(SlaOption.INFO_JOB_NAME);
+//			String duration = (String) slaOption.getInfo().get(SlaOption.INFO_DURATION);
+//			return "SLA Alert: Your job " + jobName + " failed to SUCCEED within " + duration + " in execution " + execId;
+//		} else {
+//			return "Unrecognized SLA type " + type;
+//		}
+//	}
+
+	@Override
+	public void setContext(Map<String, Object> context) {
+	}
+
+	@Override
+	public String getDescription() {
+		return type + " with " + slaOption.toString();
+	}
+
+}
diff --git a/src/java/azkaban/trigger/builtin/SlaChecker.java b/src/java/azkaban/trigger/builtin/SlaChecker.java
new file mode 100644
index 0000000..f8897a5
--- /dev/null
+++ b/src/java/azkaban/trigger/builtin/SlaChecker.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger.builtin;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+import org.joda.time.ReadablePeriod;
+
+import azkaban.executor.ExecutableFlow;
+import azkaban.executor.ExecutableNode;
+import azkaban.executor.ExecutorManagerAdapter;
+import azkaban.executor.ExecutorManagerException;
+import azkaban.executor.Status;
+import azkaban.sla.SlaOption;
+import azkaban.trigger.ConditionChecker;
+import azkaban.utils.Utils;
+
+public class SlaChecker implements ConditionChecker{
+
+	private static final Logger logger = Logger.getLogger(SlaChecker.class);
+	public static final String type = "SlaChecker";
+	
+	private String id;
+	private SlaOption slaOption;
+	private int execId;
+	private long checkTime = -1;
+	
+	private static ExecutorManagerAdapter executorManager;
+	
+	public SlaChecker(String id, SlaOption slaOption, int execId) {
+		this.id = id;
+		this.slaOption = slaOption;
+		this.execId = execId;
+	}
+
+	public static void setExecutorManager(ExecutorManagerAdapter em) {
+		executorManager = em;
+	}
+	
+	private Boolean isSlaMissed(ExecutableFlow flow) {
+		String type = slaOption.getType();
+		logger.info("flow is " + flow.getStatus());
+		if(flow.getStartTime() < 0) {
+			return Boolean.FALSE;
+		}
+		Status status;
+		if(type.equals(SlaOption.TYPE_FLOW_FINISH)) {
+			if(checkTime < flow.getStartTime()) {
+				ReadablePeriod duration = Utils.parsePeriodString((String) slaOption.getInfo().get(SlaOption.INFO_DURATION));
+				DateTime startTime = new DateTime(flow.getStartTime());
+				DateTime nextCheckTime = startTime.plus(duration);
+				this.checkTime = nextCheckTime.getMillis();
+			}
+			status = flow.getStatus();
+			if(checkTime < DateTime.now().getMillis()) {
+				return !isFlowFinished(status);
+			}
+		} else if(type.equals(SlaOption.TYPE_FLOW_SUCCEED)) {
+			if(checkTime < flow.getStartTime()) {
+				ReadablePeriod duration = Utils.parsePeriodString((String) slaOption.getInfo().get(SlaOption.INFO_DURATION));
+				DateTime startTime = new DateTime(flow.getStartTime());
+				DateTime nextCheckTime = startTime.plus(duration);
+				this.checkTime = nextCheckTime.getMillis();
+			}
+			status = flow.getStatus();
+			if(checkTime < DateTime.now().getMillis()) {
+				return !isFlowSucceeded(status);
+			} else {
+				return status.equals(Status.FAILED) || status.equals(Status.KILLED);
+			}
+		} else if(type.equals(SlaOption.TYPE_JOB_FINISH)) {
+			String jobName = (String) slaOption.getInfo().get(SlaOption.INFO_JOB_NAME); 
+			ExecutableNode node = flow.getExecutableNode(jobName);
+			if(node.getStartTime() < 0) {
+				return Boolean.FALSE;
+			}
+			if(checkTime < node.getStartTime()) {
+				ReadablePeriod duration = Utils.parsePeriodString((String) slaOption.getInfo().get(SlaOption.INFO_DURATION));
+				DateTime startTime = new DateTime(node.getStartTime());
+				DateTime nextCheckTime = startTime.plus(duration);
+				this.checkTime = nextCheckTime.getMillis();
+			}
+			status = node.getStatus();
+			if(checkTime < DateTime.now().getMillis()) {
+				return !isJobFinished(status);
+			}
+		} else if(type.equals(SlaOption.TYPE_JOB_SUCCEED)) {
+			String jobName = (String) slaOption.getInfo().get(SlaOption.INFO_JOB_NAME); 
+			ExecutableNode node = flow.getExecutableNode(jobName);
+			if(node.getStartTime() < 0) {
+				return Boolean.FALSE;
+			}
+			if(checkTime < node.getStartTime()) {
+				ReadablePeriod duration = Utils.parsePeriodString((String) slaOption.getInfo().get(SlaOption.INFO_DURATION));
+				DateTime startTime = new DateTime(node.getStartTime());
+				DateTime nextCheckTime = startTime.plus(duration);
+				this.checkTime = nextCheckTime.getMillis();
+			}
+			status = node.getStatus();
+			if(checkTime < DateTime.now().getMillis()) {
+				return !isJobFinished(status);
+			} else {
+				return status.equals(Status.FAILED) || status.equals(Status.KILLED);
+			}
+		} 
+		return Boolean.FALSE;
+	}
+	
+	private Boolean isSlaGood(ExecutableFlow flow) {
+		String type = slaOption.getType();
+		logger.info("flow is " + flow.getStatus());
+		if(flow.getStartTime() < 0) {
+			return Boolean.FALSE;
+		}
+		Status status;
+		if(type.equals(SlaOption.TYPE_FLOW_FINISH)) {
+			if(checkTime < flow.getStartTime()) {
+				ReadablePeriod duration = Utils.parsePeriodString((String) slaOption.getInfo().get(SlaOption.INFO_DURATION));
+				DateTime startTime = new DateTime(flow.getStartTime());
+				DateTime nextCheckTime = startTime.plus(duration);
+				this.checkTime = nextCheckTime.getMillis();
+			}
+			status = flow.getStatus();
+			return isFlowFinished(status);
+		} else if(type.equals(SlaOption.TYPE_FLOW_SUCCEED)) {
+			if(checkTime < flow.getStartTime()) {
+				ReadablePeriod duration = Utils.parsePeriodString((String) slaOption.getInfo().get(SlaOption.INFO_DURATION));
+				DateTime startTime = new DateTime(flow.getStartTime());
+				DateTime nextCheckTime = startTime.plus(duration);
+				this.checkTime = nextCheckTime.getMillis();
+			}
+			status = flow.getStatus();
+			return isFlowSucceeded(status);
+		} else if(type.equals(SlaOption.TYPE_JOB_FINISH)) {
+			String jobName = (String) slaOption.getInfo().get(SlaOption.INFO_JOB_NAME); 
+			ExecutableNode node = flow.getExecutableNode(jobName);
+			if(node.getStartTime() < 0) {
+				return Boolean.FALSE;
+			}
+			if(checkTime < node.getStartTime()) {
+				ReadablePeriod duration = Utils.parsePeriodString((String) slaOption.getInfo().get(SlaOption.INFO_DURATION));
+				DateTime startTime = new DateTime(node.getStartTime());
+				DateTime nextCheckTime = startTime.plus(duration);
+				this.checkTime = nextCheckTime.getMillis();
+			}
+			status = node.getStatus();
+			return isJobFinished(status);
+		} else if(type.equals(SlaOption.TYPE_JOB_SUCCEED)) {
+			String jobName = (String) slaOption.getInfo().get(SlaOption.INFO_JOB_NAME); 
+			ExecutableNode node = flow.getExecutableNode(jobName);
+			if(node.getStartTime() < 0) {
+				return Boolean.FALSE;
+			}
+			if(checkTime < node.getStartTime()) {
+				ReadablePeriod duration = Utils.parsePeriodString((String) slaOption.getInfo().get(SlaOption.INFO_DURATION));
+				DateTime startTime = new DateTime(node.getStartTime());
+				DateTime nextCheckTime = startTime.plus(duration);
+				this.checkTime = nextCheckTime.getMillis();
+			}
+			status = node.getStatus();
+			return isJobSucceeded(status);
+		} 
+		return Boolean.FALSE;
+	}
+	
+	// return true to trigger sla action
+	@Override
+	public Object eval() {
+		logger.info("Checking sla for execution " + execId);
+		ExecutableFlow flow;
+		try {
+			flow = executorManager.getExecutableFlow(execId);
+		} catch (ExecutorManagerException e) {
+			logger.error("Can't get executable flow.", e);
+			e.printStackTrace();
+			// something wrong, send out alerts
+			return Boolean.TRUE;
+		}
+		return isSlaMissed(flow);
+	}
+	
+	public Object isSlaFailed() {
+		logger.info("Testing if sla failed for execution " + execId);
+		ExecutableFlow flow;
+		try {
+			flow = executorManager.getExecutableFlow(execId);
+		} catch (ExecutorManagerException e) {
+			logger.error("Can't get executable flow.", e);
+			e.printStackTrace();
+			// something wrong, send out alerts
+			return Boolean.TRUE;
+		}
+		return isSlaMissed(flow);
+	}
+	
+	public Object isSlaPassed() {
+		logger.info("Testing if sla is good for execution " + execId);
+		ExecutableFlow flow;
+		try {
+			flow = executorManager.getExecutableFlow(execId);
+		} catch (ExecutorManagerException e) {
+			logger.error("Can't get executable flow.", e);
+			e.printStackTrace();
+			// something wrong, send out alerts
+			return Boolean.TRUE;
+		}
+		return isSlaGood(flow);
+	}
+
+	@Override
+	public Object getNum() {
+		return null;
+	}
+
+	@Override
+	public void reset() {
+	}
+
+	@Override
+	public String getId() {
+		return id;
+	}
+
+	@Override
+	public String getType() {
+		return type;
+	}
+
+	@Override
+	public ConditionChecker fromJson(Object obj) throws Exception {
+		return createFromJson(obj);
+	}
+
+	@SuppressWarnings("unchecked")
+	public static SlaChecker createFromJson(Object obj) throws Exception {
+		return createFromJson((HashMap<String, Object>)obj);
+	}
+	
+	public static SlaChecker createFromJson(HashMap<String, Object> obj) throws Exception {
+		Map<String, Object> jsonObj = (HashMap<String, Object>) obj;
+		if(!jsonObj.get("type").equals(type)) {
+			throw new Exception("Cannot create checker of " + type + " from " + jsonObj.get("type"));
+		}
+		String id = (String) jsonObj.get("id");
+		SlaOption slaOption = SlaOption.fromObject(jsonObj.get("slaOption"));
+		int execId = Integer.valueOf((String) jsonObj.get("execId"));
+		return new SlaChecker(id, slaOption, execId);
+	}
+	
+	@Override
+	public Object toJson() {
+		Map<String, Object> jsonObj = new HashMap<String, Object>();
+		jsonObj.put("type", type);
+		jsonObj.put("id", id);
+		jsonObj.put("slaOption", slaOption.toObject());
+		jsonObj.put("execId", String.valueOf(execId));
+	
+		return jsonObj;
+	}
+
+	@Override
+	public void stopChecker() {
+		
+	}
+
+	@Override
+	public void setContext(Map<String, Object> context) {
+	}
+
+	@Override
+	public long getNextCheckTime() {
+		return checkTime;
+	}
+	
+	private boolean isFlowFinished(Status status) {
+		if(status.equals(Status.FAILED) || status.equals(Status.KILLED) || status.equals(Status.SUCCEEDED)) {
+			return Boolean.TRUE;
+		} else {
+			return Boolean.FALSE;
+		}
+	}
+	
+	private boolean isFlowSucceeded(Status status) {
+		return status.equals(Status.SUCCEEDED);
+	}
+	
+	private boolean isJobFinished(Status status) {
+		if(status.equals(Status.FAILED) || status.equals(Status.KILLED) || status.equals(Status.SUCCEEDED)) {
+			return Boolean.TRUE;
+		} else {
+			return Boolean.FALSE;
+		}
+	}
+	
+	private boolean isJobSucceeded(Status status) {
+		return status.equals(Status.SUCCEEDED);
+	}
+}
diff --git a/src/java/azkaban/trigger/CheckerTypeLoader.java b/src/java/azkaban/trigger/CheckerTypeLoader.java
new file mode 100644
index 0000000..7c02bed
--- /dev/null
+++ b/src/java/azkaban/trigger/CheckerTypeLoader.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import azkaban.utils.Props;
+import azkaban.utils.Utils;
+
+
+public class CheckerTypeLoader {
+	
+	private static Logger logger = Logger.getLogger(CheckerTypeLoader.class);
+	
+	public static final String DEFAULT_CONDITION_CHECKER_PLUGIN_DIR = "plugins/conditioncheckers";
+	
+	protected static Map<String, Class<? extends ConditionChecker>> checkerToClass = new HashMap<String, Class<? extends ConditionChecker>>();
+	
+	public void init(Props props) throws TriggerException {
+		
+		
+		// load built-in checkers
+//		
+//		loadBuiltinCheckers();
+//		
+//		loadPluginCheckers(props);
+
+	}
+	
+	public synchronized void registerCheckerType(String type, Class<? extends ConditionChecker> checkerClass) {
+		logger.info("Registering checker " + type);
+		if(!checkerToClass.containsKey(type)) {
+			checkerToClass.put(type, checkerClass);
+		}
+	}
+	
+//	private void loadPluginCheckers(Props props) throws TriggerException {
+//		
+//		String checkerDir = props.getString("azkaban.condition.checker.plugin.dir", DEFAULT_CONDITION_CHECKER_PLUGIN_DIR);
+//		File pluginDir = new File(checkerDir);
+//		if(!pluginDir.exists() || !pluginDir.isDirectory() || !pluginDir.canRead()) {
+//			logger.info("No conditon checker plugins to load.");
+//			return;
+//		}
+//		
+//		logger.info("Loading plugin condition checkers from " + pluginDir);
+//		ClassLoader parentCl = this.getClass().getClassLoader();
+//		
+//		Props globalCheckerConf = null;
+//		File confFile = Utils.findFilefromDir(pluginDir, COMMONCONFFILE);
+//		try {
+//			if(confFile != null) {
+//				globalCheckerConf = new Props(null, confFile);
+//			} else {
+//				globalCheckerConf = new Props();
+//			}
+//		} catch (IOException e) {
+//			throw new TriggerException("Failed to get global properties." + e);
+//		}
+//		
+//		for(File dir : pluginDir.listFiles()) {
+//			if(dir.isDirectory() && dir.canRead()) {
+//				try {
+//					loadPluginTypes(globalCheckerConf, pluginDir, parentCl);
+//				} catch (Exception e) {
+//					logger.info("Plugin checkers failed to load. " + e.getCause());
+//					throw new TriggerException("Failed to load all condition checkers!", e);
+//				}
+//			}
+//		}
+//	}
+//	
+//	@SuppressWarnings("unchecked")
+//	private void loadPluginTypes(Props globalConf, File dir, ClassLoader parentCl) throws TriggerException {
+//		Props checkerConf = null;
+//		File confFile = Utils.findFilefromDir(dir, CHECKERTYPECONFFILE);
+//		if(confFile == null) {
+//			logger.info("No checker type found in " + dir.getAbsolutePath());
+//			return;
+//		}
+//		try {
+//			checkerConf = new Props(globalConf, confFile);
+//		} catch (IOException e) {
+//			throw new TriggerException("Failed to load config for the checker type", e);
+//		}
+//		
+//		String checkerName = dir.getName();
+//		String checkerClass = checkerConf.getString("checker.class");
+//		
+//		List<URL> resources = new ArrayList<URL>();		
+//		for(File f : dir.listFiles()) {
+//			try {
+//				if(f.getName().endsWith(".jar")) {
+//					resources.add(f.toURI().toURL());
+//					logger.info("adding to classpath " + f.toURI().toURL());
+//				}
+//			} catch (MalformedURLException e) {
+//				// TODO Auto-generated catch block
+//				throw new TriggerException(e);
+//			}
+//		}
+//		
+//		// each job type can have a different class loader
+//		ClassLoader checkerCl = new URLClassLoader(resources.toArray(new URL[resources.size()]), parentCl);
+//		
+//		Class<? extends ConditionChecker> clazz = null;
+//		try {
+//			clazz = (Class<? extends ConditionChecker>)checkerCl.loadClass(checkerClass);
+//			checkerToClass.put(checkerName, clazz);
+//		}
+//		catch (ClassNotFoundException e) {
+//			throw new TriggerException(e);
+//		}
+//		
+//		if(checkerConf.getBoolean("need.init")) {
+//			try {
+//				Utils.invokeStaticMethod(checkerCl, checkerClass, "init", checkerConf);
+//			} catch (Exception e) {
+//				e.printStackTrace();
+//				logger.error("Failed to init the checker type " + checkerName);
+//				throw new TriggerException(e);
+//			}
+//		}
+//		
+//		logger.info("Loaded checker type " + checkerName + " " + checkerClass);
+//	}
+	
+	public static void registerBuiltinCheckers(Map<String, Class<? extends ConditionChecker>> builtinCheckers) {
+		checkerToClass.putAll(checkerToClass);
+		for(String type : builtinCheckers.keySet()) {
+			logger.info("Loaded " + type + " checker.");
+		}
+	}
+	
+//	private void loadBuiltinCheckers() {
+//		checkerToClass.put("BasicTimeChecker", BasicTimeChecker.class);
+//		logger.info("Loaded BasicTimeChecker type.");
+//	}
+	
+	public ConditionChecker createCheckerFromJson(String type, Object obj) throws Exception {
+		ConditionChecker checker = null;
+		Class<? extends ConditionChecker> checkerClass = checkerToClass.get(type);	
+		if(checkerClass == null) {
+			throw new Exception("Checker type " + type + " not supported!");
+		}
+		checker = (ConditionChecker) Utils.invokeStaticMethod(checkerClass.getClassLoader(), checkerClass.getName(), "createFromJson", obj);
+		
+		return checker;
+	}
+	
+	public ConditionChecker createChecker(String type, Object ... args) {
+		ConditionChecker checker = null;
+		Class<? extends ConditionChecker> checkerClass = checkerToClass.get(type);		
+		checker = (ConditionChecker) Utils.callConstructor(checkerClass, args);
+		
+		return checker;
+	}
+	
+	public Map<String, Class<? extends ConditionChecker>> getSupportedCheckers() {
+		return checkerToClass;
+	}
+	
+}
diff --git a/src/java/azkaban/trigger/Condition.java b/src/java/azkaban/trigger/Condition.java
new file mode 100644
index 0000000..1bead36
--- /dev/null
+++ b/src/java/azkaban/trigger/Condition.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.jexl2.Expression;
+import org.apache.commons.jexl2.JexlEngine;
+import org.apache.commons.jexl2.MapContext;
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+
+public class Condition {
+	
+	private static Logger logger = Logger.getLogger(Condition.class);
+	
+	private static JexlEngine jexl = new JexlEngine();
+	private static CheckerTypeLoader checkerLoader = null;
+	private Expression expression;
+	private Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
+	private MapContext context = new MapContext();
+	private Long nextCheckTime = -1L;	
+	
+	public Condition(Map<String, ConditionChecker> checkers, String expr) {
+		setCheckers(checkers);
+		this.expression = jexl.createExpression(expr);
+		updateNextCheckTime();
+	}
+	
+	public Condition(Map<String, ConditionChecker> checkers, String expr, long nextCheckTime) {
+		this.nextCheckTime = nextCheckTime;
+		setCheckers(checkers);
+//		for(ConditionChecker ck : checkers.values()) {
+//			ck.setCondition(this);
+//		}
+		this.expression = jexl.createExpression(expr);
+	}
+	
+	public synchronized static void setJexlEngine(JexlEngine jexl) {
+		Condition.jexl = jexl;
+	}
+	
+	public synchronized static void setCheckerLoader(CheckerTypeLoader loader) {
+		Condition.checkerLoader = loader;
+	}
+	
+	protected static CheckerTypeLoader getCheckerLoader() {
+		return checkerLoader;
+	}
+	
+	protected void registerChecker(ConditionChecker checker) {
+		checkers.put(checker.getId(), checker);
+		context.set(checker.getId(), checker);
+		updateNextCheckTime();
+	}
+	
+	public long getNextCheckTime() {
+		return nextCheckTime;
+	}
+	
+	public Map<String, ConditionChecker> getCheckers() {
+		return this.checkers;
+	}
+	
+	public void setCheckers(Map<String, ConditionChecker> checkers){
+		this.checkers = checkers;
+		for(ConditionChecker checker : checkers.values()) {
+			this.context.set(checker.getId(), checker);
+//			checker.setCondition(this);
+		}
+		updateNextCheckTime();
+	}
+	
+	public void updateCheckTime(Long ct) {
+		if(nextCheckTime < ct) {
+			nextCheckTime = ct;
+		}
+	}
+	
+	private void updateNextCheckTime() {
+		long time = Long.MAX_VALUE;
+		for(ConditionChecker checker : checkers.values()) {
+			time = Math.min(time, checker.getNextCheckTime());
+		}
+		this.nextCheckTime = time;
+	}
+	
+	public void resetCheckers() {
+		for(ConditionChecker checker : checkers.values()) {
+			checker.reset();
+		}
+		updateNextCheckTime();
+		logger.info("Done resetting checkers. The next check time will be " + new DateTime(nextCheckTime));
+	}
+	
+	public String getExpression() {
+		return this.expression.getExpression();
+	}
+	
+	public void setExpression(String expr) {
+		this.expression = jexl.createExpression(expr);
+	}
+	
+	public boolean isMet() {
+		logger.info("Testing condition " + expression);
+		return expression.evaluate(context).equals(Boolean.TRUE);
+	}
+	
+	public Object toJson() {
+		Map<String, Object> jsonObj = new HashMap<String, Object>();
+		jsonObj.put("expression", expression.getExpression());
+		
+		List<Object> checkersJson = new ArrayList<Object>();
+		for(ConditionChecker checker : checkers.values()) {
+			Map<String, Object> oneChecker = new HashMap<String, Object>();
+			oneChecker.put("type", checker.getType());
+			oneChecker.put("checkerJson", checker.toJson());
+			checkersJson.add(oneChecker);
+		}
+		jsonObj.put("checkers", checkersJson);
+		jsonObj.put("nextCheckTime", String.valueOf(nextCheckTime));
+		
+		return jsonObj;
+	}
+
+	@SuppressWarnings("unchecked")
+	public static Condition fromJson(Object obj) throws Exception {
+		if(checkerLoader == null) {
+			throw new Exception("Condition Checker loader not initialized!");
+		}
+		
+		Map<String, Object> jsonObj = (HashMap<String, Object>) obj;
+		Condition cond = null;
+		
+		try {
+			Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
+			List<Object> checkersJson = (List<Object>) jsonObj.get("checkers");			
+			for(Object oneCheckerJson : checkersJson) {
+				Map<String, Object> oneChecker = (HashMap<String, Object>) oneCheckerJson;
+				String type = (String) oneChecker.get("type");
+				ConditionChecker ck = checkerLoader.createCheckerFromJson(type, oneChecker.get("checkerJson"));
+				checkers.put(ck.getId(), ck);
+			}
+			String expr = (String) jsonObj.get("expression");
+			Long nextCheckTime = Long.valueOf((String) jsonObj.get("nextCheckTime"));
+				
+			cond = new Condition(checkers, expr, nextCheckTime);
+			
+		} catch(Exception e) {
+			e.printStackTrace();
+			logger.error("Failed to recreate condition from json.", e);
+			throw new Exception("Failed to recreate condition from json.", e);
+		}
+		
+		return cond;
+	}
+	
+
+}
diff --git a/src/java/azkaban/trigger/ConditionChecker.java b/src/java/azkaban/trigger/ConditionChecker.java
new file mode 100644
index 0000000..16869b4
--- /dev/null
+++ b/src/java/azkaban/trigger/ConditionChecker.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+import java.util.Map;
+
+public interface ConditionChecker {
+	
+	Object eval();
+	
+	Object getNum();
+	
+	void reset();
+
+	String getId();
+	
+	String getType();
+	
+	ConditionChecker fromJson(Object obj) throws Exception;
+	
+	Object toJson();
+
+	void stopChecker();
+	
+	void setContext(Map<String, Object> context);
+	
+	long getNextCheckTime();
+	
+//	void setCondition(Condition c);
+//	
+//	String getDescription();
+}
diff --git a/src/java/azkaban/trigger/JdbcTriggerLoader.java b/src/java/azkaban/trigger/JdbcTriggerLoader.java
new file mode 100644
index 0000000..b290c6e
--- /dev/null
+++ b/src/java/azkaban/trigger/JdbcTriggerLoader.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.dbutils.DbUtils;
+import org.apache.commons.dbutils.QueryRunner;
+import org.apache.commons.dbutils.ResultSetHandler;
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+
+import azkaban.database.AbstractJdbcLoader;
+import azkaban.utils.GZIPUtils;
+import azkaban.utils.JSONUtils;
+import azkaban.utils.Props;
+
+public class JdbcTriggerLoader extends AbstractJdbcLoader implements TriggerLoader {
+	private static Logger logger = Logger.getLogger(JdbcTriggerLoader.class);
+
+	private EncodingType defaultEncodingType = EncodingType.GZIP;
+	
+	private static final String triggerTblName = "triggers";
+
+	private static final String GET_UPDATED_TRIGGERS = 
+			"SELECT trigger_id, trigger_source, modify_time, enc_type, data FROM " + triggerTblName + " WHERE modify_time>=?";
+
+	private static String GET_ALL_TRIGGERS =
+			"SELECT trigger_id, trigger_source, modify_time, enc_type, data FROM " + triggerTblName;
+	
+	private static String GET_TRIGGER = 
+			"SELECT trigger_id, trigger_source, modify_time, enc_type, data FROM " + triggerTblName + " WHERE trigger_id=?";
+	
+	private static String ADD_TRIGGER = 
+			"INSERT INTO " + triggerTblName + " ( modify_time) values (?)";
+	
+	private static String REMOVE_TRIGGER = 
+			"DELETE FROM " + triggerTblName + " WHERE trigger_id=?";
+	
+	private static String UPDATE_TRIGGER = 
+			"UPDATE " + triggerTblName + " SET trigger_source=?, modify_time=?, enc_type=?, data=? WHERE trigger_id=?";
+	
+	public EncodingType getDefaultEncodingType() {
+		return defaultEncodingType;
+	}
+
+	public void setDefaultEncodingType(EncodingType defaultEncodingType) {
+		this.defaultEncodingType = defaultEncodingType;
+	}
+	
+	public JdbcTriggerLoader(Props props) {
+		super(props);
+	}
+
+	@Override
+	public List<Trigger> getUpdatedTriggers(long lastUpdateTime) throws TriggerLoaderException {
+		logger.info("Loading triggers changed since " + new DateTime(lastUpdateTime).toString());
+		Connection connection = getConnection();
+
+		QueryRunner runner = new QueryRunner();
+		ResultSetHandler<List<Trigger>> handler = new TriggerResultHandler();
+	
+		List<Trigger> triggers;
+		
+		try {
+			triggers = runner.query(connection, GET_UPDATED_TRIGGERS, handler, lastUpdateTime);
+		} catch (SQLException e) {
+			logger.error(GET_ALL_TRIGGERS + " failed.");
+
+			throw new TriggerLoaderException("Loading triggers from db failed. ", e);
+		} finally {
+			DbUtils.closeQuietly(connection);
+		}
+		
+		logger.info("Loaded " + triggers.size() + " triggers.");
+		
+		return triggers;
+	}
+	
+	@Override
+	public List<Trigger> loadTriggers() throws TriggerLoaderException {
+		logger.info("Loading all triggers from db.");
+		Connection connection = getConnection();
+
+		QueryRunner runner = new QueryRunner();
+		ResultSetHandler<List<Trigger>> handler = new TriggerResultHandler();
+	
+		List<Trigger> triggers;
+		
+		try {
+			triggers = runner.query(connection, GET_ALL_TRIGGERS, handler);
+		} catch (SQLException e) {
+			logger.error(GET_ALL_TRIGGERS + " failed.");
+
+			throw new TriggerLoaderException("Loading triggers from db failed. ", e);
+		} finally {
+			DbUtils.closeQuietly(connection);
+		}
+		
+		logger.info("Loaded " + triggers.size() + " triggers.");
+		
+		return triggers;
+	}
+
+	@Override
+	public void removeTrigger(Trigger t) throws TriggerLoaderException {		
+		logger.info("Removing trigger " + t.toString() + " from db.");
+
+		QueryRunner runner = createQueryRunner();
+		try {
+			int removes =  runner.update(REMOVE_TRIGGER, t.getTriggerId());
+			if (removes == 0) {
+				throw new TriggerLoaderException("No trigger has been removed.");
+			}
+		} catch (SQLException e) {
+			logger.error(REMOVE_TRIGGER + " failed.");
+			throw new TriggerLoaderException("Remove trigger " + t.toString() + " from db failed. ", e);
+		}
+	}
+	
+	@Override
+	public void addTrigger(Trigger t) throws TriggerLoaderException {
+		logger.info("Inserting trigger " + t.toString() + " into db.");
+		t.setLastModifyTime(System.currentTimeMillis());
+		Connection connection = getConnection();
+		try {
+			addTrigger(connection, t, defaultEncodingType);
+		}
+		catch (Exception e) {
+			throw new TriggerLoaderException("Error uploading trigger", e);
+		}
+		finally {
+			DbUtils.closeQuietly(connection);
+		}
+	}
+
+	private synchronized void addTrigger(Connection connection, Trigger t, EncodingType encType) throws TriggerLoaderException {
+		
+		QueryRunner runner = new QueryRunner();
+		
+		long id;
+		
+		try {
+			runner.update(connection, ADD_TRIGGER, DateTime.now().getMillis());
+			connection.commit();
+			id = runner.query(connection, LastInsertID.LAST_INSERT_ID, new LastInsertID());
+
+			if (id == -1l) {
+				logger.error("trigger id is not properly created.");
+				throw new TriggerLoaderException("trigger id is not properly created.");
+			}
+			
+			t.setTriggerId((int)id);
+			updateTrigger(t);
+			logger.info("uploaded trigger " + t.getDescription());
+		} catch (SQLException e) {
+			throw new TriggerLoaderException("Error creating trigger.", e);
+		}
+		
+	}
+	
+	@Override
+	public void updateTrigger(Trigger t) throws TriggerLoaderException {
+		logger.info("Updating trigger " + t.getTriggerId() + " into db.");
+		t.setLastModifyTime(System.currentTimeMillis());
+		Connection connection = getConnection();
+		try{
+			updateTrigger(connection, t, defaultEncodingType);
+		}
+		catch(Exception e) {
+			e.printStackTrace();
+			throw new TriggerLoaderException("Failed to update trigger " + t.toString() + " into db!");
+		}
+		finally {
+			DbUtils.closeQuietly(connection);
+		}
+	}
+		
+	private void updateTrigger(Connection connection, Trigger t, EncodingType encType) throws TriggerLoaderException {
+
+		String json = JSONUtils.toJSON(t.toJson());
+		byte[] data = null;
+		try {
+			byte[] stringData = json.getBytes("UTF-8");
+			data = stringData;
+	
+			if (encType == EncodingType.GZIP) {
+				data = GZIPUtils.gzipBytes(stringData);
+			}
+			logger.debug("NumChars: " + json.length() + " UTF-8:" + stringData.length + " Gzip:"+ data.length);
+		}
+		catch (IOException e) {
+			throw new TriggerLoaderException("Error encoding the trigger " + t.toString());
+		}
+		
+		QueryRunner runner = new QueryRunner();
+	
+		try {
+			int updates =  runner.update( connection, 
+					UPDATE_TRIGGER, 
+					t.getSource(),
+					t.getLastModifyTime(),
+					encType.getNumVal(),
+					data,
+					t.getTriggerId());
+			connection.commit();
+			if (updates == 0) {
+				throw new TriggerLoaderException("No trigger has been updated.");
+				//logger.error("No trigger is updated!");
+			} else {
+				logger.info("Updated " + updates + " records.");
+			}
+		} catch (SQLException e) {
+			logger.error(UPDATE_TRIGGER + " failed.");
+			throw new TriggerLoaderException("Update trigger " + t.toString() + " into db failed. ", e);
+		}
+	}
+	
+	private static class LastInsertID implements ResultSetHandler<Long> {
+		private static String LAST_INSERT_ID = "SELECT LAST_INSERT_ID()";
+		
+		@Override
+		public Long handle(ResultSet rs) throws SQLException {
+			if (!rs.next()) {
+				return -1l;
+			}
+
+			long id = rs.getLong(1);
+			return id;
+		}
+		
+	}
+	
+	public class TriggerResultHandler implements ResultSetHandler<List<Trigger>> {
+		
+		@Override
+		public List<Trigger> handle(ResultSet rs) throws SQLException {
+			if (!rs.next()) {
+				return Collections.<Trigger>emptyList();
+			}
+			
+			ArrayList<Trigger> triggers = new ArrayList<Trigger>();
+			do {
+				int triggerId = rs.getInt(1);
+//				String triggerSource = rs.getString(2);
+//				long modifyTime = rs.getLong(3);
+				int encodingType = rs.getInt(4);
+				byte[] data = rs.getBytes(5);
+				
+				Object jsonObj = null;
+				if (data != null) {
+					EncodingType encType = EncodingType.fromInteger(encodingType);
+
+					try {
+						// Convoluted way to inflate strings. Should find common package or helper function.
+						if (encType == EncodingType.GZIP) {
+							// Decompress the sucker.
+							String jsonString = GZIPUtils.unGzipString(data, "UTF-8");
+							jsonObj = JSONUtils.parseJSONFromString(jsonString);
+						}
+						else {
+							String jsonString = new String(data, "UTF-8");
+							jsonObj = JSONUtils.parseJSONFromString(jsonString);
+						}	
+					} catch (IOException e) {
+						throw new SQLException("Error reconstructing trigger data " );
+					}
+				}
+				
+				Trigger t = null;
+				try {
+					t = Trigger.fromJson(jsonObj);
+					triggers.add(t);
+				} catch (Exception e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+					logger.error("Failed to load trigger " + triggerId);
+				}
+			} while (rs.next());
+			
+			return triggers;
+		}
+		
+	}
+	
+	private Connection getConnection() throws TriggerLoaderException {
+		Connection connection = null;
+		try {
+			connection = super.getDBConnection(false);
+		} catch (Exception e) {
+			DbUtils.closeQuietly(connection);
+			throw new TriggerLoaderException("Error getting DB connection.", e);
+		}
+		
+		return connection;
+	}
+
+	@Override
+	public Trigger loadTrigger(int triggerId) throws TriggerLoaderException {
+		logger.info("Loading trigger " + triggerId + " from db.");
+		Connection connection = getConnection();
+
+		QueryRunner runner = new QueryRunner();
+		ResultSetHandler<List<Trigger>> handler = new TriggerResultHandler();
+	
+		List<Trigger> triggers;
+		
+		try {
+			triggers = runner.query(connection, GET_TRIGGER, handler, triggerId);
+		} catch (SQLException e) {
+			logger.error(GET_TRIGGER + " failed.");
+			throw new TriggerLoaderException("Loading trigger from db failed. ", e);
+		} finally {
+			DbUtils.closeQuietly(connection);
+		}
+		
+		if(triggers.size() == 0) {
+			logger.error("Loaded 0 triggers. Failed to load trigger " + triggerId);
+			throw new TriggerLoaderException("Loaded 0 triggers. Failed to load trigger " + triggerId);
+		}
+		
+		return triggers.get(0);
+	}
+
+	
+
+}
diff --git a/src/java/azkaban/trigger/Trigger.java b/src/java/azkaban/trigger/Trigger.java
new file mode 100644
index 0000000..740ce3d
--- /dev/null
+++ b/src/java/azkaban/trigger/Trigger.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+
+import azkaban.utils.JSONUtils;
+
+public class Trigger {
+	
+	private static Logger logger = Logger.getLogger(Trigger.class);
+	
+	private int triggerId = -1;
+	private long lastModifyTime;
+	private long submitTime;
+	private String submitUser;
+	private String source;
+	private TriggerStatus status = TriggerStatus.READY;
+	
+	private Condition triggerCondition;
+	private Condition expireCondition;
+	private List<TriggerAction> actions;
+	private List<TriggerAction> expireActions;
+	
+	private Map<String, Object> info = new HashMap<String, Object>();
+	private Map<String, Object> context = new HashMap<String, Object>();
+	
+	private static ActionTypeLoader actionTypeLoader;
+	
+	private boolean resetOnTrigger = true;
+	private boolean resetOnExpire = true;
+	
+	private long nextCheckTime = -1;
+	
+	@SuppressWarnings("unused")
+	private Trigger() throws TriggerManagerException {	
+		throw new TriggerManagerException("Triggers should always be specified");
+	}
+	
+	public void updateNextCheckTime() {
+		this.nextCheckTime = Math.min(triggerCondition.getNextCheckTime(), expireCondition.getNextCheckTime());
+	}
+	
+	public long getNextCheckTime() {
+		return nextCheckTime;
+	}
+
+	public void setNextCheckTime(long nct) {
+		this.nextCheckTime = nct;
+	}
+	
+	public long getSubmitTime() {
+		return submitTime;
+	}
+
+	public String getSubmitUser() {
+		return submitUser;
+	}
+
+	public TriggerStatus getStatus() {
+		return status;
+	}
+
+	public void setStatus(TriggerStatus status) {
+		this.status = status;
+	}
+
+	public Condition getTriggerCondition() {
+		return triggerCondition;
+	}
+
+	public Condition getExpireCondition() {
+		return expireCondition;
+	}
+
+	public List<TriggerAction> getActions() {
+		return actions;
+	}
+
+	public List<TriggerAction> getExpireActions() {
+		return expireActions;
+	}
+	
+	public Map<String, Object> getInfo() {
+		return info;
+	}
+
+	public void setInfo(Map<String, Object> info) {
+		this.info = info;
+	}
+	
+	public Map<String, Object> getContext() {
+		return context;
+	}
+
+	public void setContext(Map<String, Object> context) {
+		this.context = context;
+	}
+	
+	public Trigger(
+			long lastModifyTime, 
+			long submitTime, 
+			String submitUser, 
+			String source,
+			Condition triggerCondition,
+			Condition expireCondition,
+			List<TriggerAction> actions, 
+			List<TriggerAction> expireActions,
+			Map<String, Object> info,
+			Map<String, Object> context) {
+		this.lastModifyTime = lastModifyTime;
+		this.submitTime = submitTime;
+		this.submitUser = submitUser;
+		this.source = source;
+		this.triggerCondition = triggerCondition;
+		this.expireCondition = expireCondition;
+		this.actions = actions;
+		this.expireActions = expireActions;
+		this.info = info;
+		this.context = context;
+	}
+	
+	public Trigger(
+			long lastModifyTime, 
+			long submitTime, 
+			String submitUser, 
+			String source,
+			Condition triggerCondition,
+			Condition expireCondition,
+			List<TriggerAction> actions, 
+			List<TriggerAction> expireActions) {
+		this.lastModifyTime = lastModifyTime;
+		this.submitTime = submitTime;
+		this.submitUser = submitUser;
+		this.source = source;
+		this.triggerCondition = triggerCondition;
+		this.expireCondition = expireCondition;
+		this.actions = actions;
+		this.expireActions = expireActions;
+	}
+	
+	public Trigger(
+			String submitUser, 
+			String source,
+			Condition triggerCondition,
+			Condition expireCondition,
+			List<TriggerAction> actions, 
+			List<TriggerAction> expireActions) {
+		this.lastModifyTime = DateTime.now().getMillis();
+		this.submitTime = DateTime.now().getMillis();
+		this.submitUser = submitUser;
+		this.source = source;
+		this.triggerCondition = triggerCondition;
+		this.expireCondition = expireCondition;
+		this.actions = actions;
+		this.expireActions = expireActions;
+	}
+	
+	public Trigger(
+			String submitUser, 
+			String source,
+			Condition triggerCondition,
+			Condition expireCondition,
+			List<TriggerAction> actions) {
+		this.lastModifyTime = DateTime.now().getMillis();
+		this.submitTime = DateTime.now().getMillis();
+		this.submitUser = submitUser;
+		this.source = source;
+		this.triggerCondition = triggerCondition;
+		this.expireCondition = expireCondition;
+		this.actions = actions;
+		this.expireActions = new ArrayList<TriggerAction>();
+	}
+	
+	public Trigger(
+			long lastModifyTime, 
+			long submitTime, 
+			String submitUser, 
+			String source,
+			Condition triggerCondition,
+			Condition expireCondition,
+			List<TriggerAction> actions) {
+		this.lastModifyTime = lastModifyTime;
+		this.submitTime = submitTime;
+		this.submitUser = submitUser;
+		this.source = source;
+		this.triggerCondition = triggerCondition;
+		this.expireCondition = expireCondition;
+		this.actions = actions;
+		this.expireActions = new ArrayList<TriggerAction>();
+	}
+	
+	public Trigger(
+			int triggerId,
+			long lastModifyTime, 
+			long submitTime,
+			String submitUser, 
+			String source,
+			Condition triggerCondition,
+			Condition expireCondition,
+			List<TriggerAction> actions,
+			List<TriggerAction> expireActions,
+			Map<String, Object> info,
+			Map<String, Object> context) {
+		this.triggerId = triggerId;
+		this.lastModifyTime = lastModifyTime;
+		this.submitTime = submitTime;
+		this.submitUser = submitUser;
+		this.source = source;
+		this.triggerCondition = triggerCondition;
+		this.expireCondition = expireCondition;
+		this.actions = actions;
+		this.expireActions = expireActions;
+		this.info = info;
+		this.context = context;
+	}
+	
+	public Trigger(
+			int triggerId,
+			long lastModifyTime, 
+			long submitTime, 
+			String submitUser, 
+			String source,
+			Condition triggerCondition,
+			Condition expireCondition,
+			List<TriggerAction> actions,
+			List<TriggerAction> expireActions) {
+		this.triggerId = triggerId;
+		this.lastModifyTime = lastModifyTime;
+		this.submitTime = submitTime;
+		this.submitUser = submitUser;
+		this.source = source;
+		this.triggerCondition = triggerCondition;
+		this.expireCondition = expireCondition;
+		this.actions = actions;
+		this.expireActions = expireActions;
+	}
+	
+	public Trigger(
+			int triggerId,
+			long lastModifyTime, 
+			long submitTime,
+			String submitUser, 
+			String source,
+			Condition triggerCondition,
+			Condition expireCondition,
+			List<TriggerAction> actions) {
+		this.triggerId = triggerId;
+		this.lastModifyTime = lastModifyTime;
+		this.submitTime = submitTime;
+		this.submitUser = submitUser;
+		this.source = source;
+		this.triggerCondition = triggerCondition;
+		this.expireCondition = expireCondition;
+		this.actions = actions;
+		this.expireActions = new ArrayList<TriggerAction>();
+	}
+	
+	public static synchronized void setActionTypeLoader(ActionTypeLoader loader) {
+		Trigger.actionTypeLoader = loader;
+	}
+	
+	public static ActionTypeLoader getActionTypeLoader() {
+		return actionTypeLoader;
+	}
+	
+	public boolean isResetOnTrigger() {
+		return resetOnTrigger;
+	}
+	
+	public void setResetOnTrigger(boolean resetOnTrigger) {
+		this.resetOnTrigger = resetOnTrigger;
+	}
+	
+	public boolean isResetOnExpire() {
+		return resetOnExpire;
+	}
+	
+	public void setResetOnExpire(boolean resetOnExpire) {
+		this.resetOnExpire = resetOnExpire;
+	}
+
+	public long getLastModifyTime() {
+		return lastModifyTime;
+	}
+	
+	public void setLastModifyTime(long lastModifyTime) {
+		this.lastModifyTime = lastModifyTime;
+	}
+
+	public void setTriggerId(int id) {
+		this.triggerId = id;
+	}
+	
+	public int getTriggerId() {
+		return triggerId;
+	}
+
+	public boolean triggerConditionMet(){
+		return triggerCondition.isMet();
+	}
+	
+	public boolean expireConditionMet(){
+		return expireCondition.isMet();
+	}
+	
+	public void resetTriggerConditions() {
+		triggerCondition.resetCheckers();
+		updateNextCheckTime();
+	}
+	
+	public void resetExpireCondition() {
+		expireCondition.resetCheckers();
+		updateNextCheckTime();
+	}
+	
+	public List<TriggerAction> getTriggerActions () {
+		return actions;
+	}
+	
+	public Map<String, Object> toJson() {
+		Map<String, Object> jsonObj = new HashMap<String, Object>();
+		jsonObj.put("triggerCondition", triggerCondition.toJson());
+		jsonObj.put("expireCondition", expireCondition.toJson());
+		List<Object> actionsJson = new ArrayList<Object>();
+		for(TriggerAction action : actions) {
+			Map<String, Object> oneActionJson = new HashMap<String, Object>();
+			oneActionJson.put("type", action.getType());
+			oneActionJson.put("actionJson", action.toJson());
+			actionsJson.add(oneActionJson);
+		}
+		jsonObj.put("actions", actionsJson);
+		List<Object> expireActionsJson = new ArrayList<Object>();
+		for(TriggerAction expireAction : expireActions) {
+			Map<String, Object> oneExpireActionJson = new HashMap<String, Object>();
+			oneExpireActionJson.put("type", expireAction.getType());
+			oneExpireActionJson.put("actionJson", expireAction.toJson());
+			expireActionsJson.add(oneExpireActionJson);
+		}
+		jsonObj.put("expireActions", expireActionsJson);
+		
+		jsonObj.put("resetOnTrigger", String.valueOf(resetOnTrigger));
+		jsonObj.put("resetOnExpire", String.valueOf(resetOnExpire));
+		jsonObj.put("submitUser", submitUser);
+		jsonObj.put("source", source);
+		jsonObj.put("submitTime", String.valueOf(submitTime));
+		jsonObj.put("lastModifyTime", String.valueOf(lastModifyTime));
+		jsonObj.put("triggerId", String.valueOf(triggerId));
+		jsonObj.put("status", status.toString());
+		jsonObj.put("info", info);
+		jsonObj.put("context", context);
+		return jsonObj;
+	}
+	
+	
+	public String getSource() {
+		return source;
+	}
+
+	@SuppressWarnings("unchecked")
+	public static Trigger fromJson(Object obj) throws Exception {
+		
+		if(actionTypeLoader == null) {
+			throw new Exception("Trigger Action Type loader not initialized.");
+		}
+ 		
+		Map<String, Object> jsonObj = (HashMap<String, Object>) obj;
+		
+		Trigger trigger = null;
+		try{
+			logger.info("Decoding for " + JSONUtils.toJSON(obj));
+			Condition triggerCond = Condition.fromJson(jsonObj.get("triggerCondition"));
+			Condition expireCond = Condition.fromJson(jsonObj.get("expireCondition"));
+			List<TriggerAction> actions = new ArrayList<TriggerAction>();
+			List<Object> actionsJson = (List<Object>) jsonObj.get("actions");
+			for(Object actObj : actionsJson) {
+				Map<String, Object> oneActionJson = (HashMap<String, Object>) actObj;
+				String type = (String) oneActionJson.get("type");
+				TriggerAction act = actionTypeLoader.createActionFromJson(type, oneActionJson.get("actionJson"));
+				actions.add(act);
+			}
+			List<TriggerAction> expireActions = new ArrayList<TriggerAction>();
+			List<Object> expireActionsJson = (List<Object>) jsonObj.get("expireActions");
+			for(Object expireActObj : expireActionsJson) {
+				Map<String, Object> oneExpireActionJson = (HashMap<String, Object>) expireActObj;
+				String type = (String) oneExpireActionJson.get("type");
+				TriggerAction expireAct = actionTypeLoader.createActionFromJson(type, oneExpireActionJson.get("actionJson"));
+				expireActions.add(expireAct);
+			}
+			boolean resetOnTrigger = Boolean.valueOf((String) jsonObj.get("resetOnTrigger"));
+			boolean resetOnExpire = Boolean.valueOf((String) jsonObj.get("resetOnExpire"));
+			String submitUser = (String) jsonObj.get("submitUser");
+			String source = (String) jsonObj.get("source");
+			long submitTime = Long.valueOf((String) jsonObj.get("submitTime"));
+			long lastModifyTime = Long.valueOf((String) jsonObj.get("lastModifyTime"));
+			int triggerId = Integer.valueOf((String) jsonObj.get("triggerId"));
+			TriggerStatus status = TriggerStatus.valueOf((String)jsonObj.get("status"));
+			Map<String, Object> info = (Map<String, Object>) jsonObj.get("info");
+			Map<String, Object> context = (Map<String, Object>) jsonObj.get("context");
+			if(context == null) {
+				context = new HashMap<String, Object>();
+			}
+			for(ConditionChecker checker : triggerCond.getCheckers().values()) {
+				checker.setContext(context);
+			}
+			for(ConditionChecker checker : expireCond.getCheckers().values()) {
+				checker.setContext(context);
+			}
+			for(TriggerAction action : actions) {
+				action.setContext(context);
+			}
+			for(TriggerAction action : expireActions) {
+				action.setContext(context);
+			}
+			
+			trigger = new Trigger(triggerId, lastModifyTime, submitTime, submitUser, source, triggerCond, expireCond, actions, expireActions, info, context);
+			trigger.setResetOnExpire(resetOnExpire);
+			trigger.setResetOnTrigger(resetOnTrigger);
+			trigger.setStatus(status);
+		}catch(Exception e) {
+			e.printStackTrace();
+			logger.error("Failed to decode the trigger.", e);
+			throw new Exception("Failed to decode the trigger.", e);
+		}
+		
+		return trigger;
+	}
+
+	public String getDescription() {
+		StringBuffer actionsString = new StringBuffer();
+		for(TriggerAction act : actions) {
+			actionsString.append(", ");
+			actionsString.append(act.getDescription());
+		}
+		return "Trigger from " + getSource() +
+				" with trigger condition of " + triggerCondition.getExpression() +
+				" and expire condition of " + expireCondition.getExpression() + 
+				actionsString;
+	}
+
+	public void stopCheckers() {
+		for(ConditionChecker checker : triggerCondition.getCheckers().values()) {
+			checker.stopChecker();
+		}
+		for(ConditionChecker checker : expireCondition.getCheckers().values()) {
+			checker.stopChecker();
+		}
+		
+	}
+
+	
+}
diff --git a/src/java/azkaban/trigger/TriggerAction.java b/src/java/azkaban/trigger/TriggerAction.java
new file mode 100644
index 0000000..e496080
--- /dev/null
+++ b/src/java/azkaban/trigger/TriggerAction.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+import java.util.Map;
+
+public interface TriggerAction {
+	
+	String getId();
+	
+	String getType();
+	
+	TriggerAction fromJson(Object obj) throws Exception;
+	
+	Object toJson();
+	
+	void doAction() throws Exception;
+	
+	void setContext(Map<String, Object> context);
+
+	String getDescription();
+	
+}
diff --git a/src/java/azkaban/trigger/TriggerAgent.java b/src/java/azkaban/trigger/TriggerAgent.java
new file mode 100644
index 0000000..91b27b3
--- /dev/null
+++ b/src/java/azkaban/trigger/TriggerAgent.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+import azkaban.utils.Props;
+
+public interface TriggerAgent {
+	public void loadTriggerFromProps(Props props) throws Exception;
+
+	public String getTriggerSource();
+	
+	public void start() throws Exception;
+	
+	public void shutdown();
+
+}
diff --git a/src/java/azkaban/trigger/TriggerException.java b/src/java/azkaban/trigger/TriggerException.java
new file mode 100644
index 0000000..dbe8283
--- /dev/null
+++ b/src/java/azkaban/trigger/TriggerException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+public class TriggerException extends Exception {
+	private static final long serialVersionUID = 1L;
+
+	public TriggerException(String message) {
+		super(message);
+	}
+	
+	public TriggerException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public TriggerException(Throwable e) {
+		super(e);
+	}
+}
+
diff --git a/src/java/azkaban/trigger/TriggerLoader.java b/src/java/azkaban/trigger/TriggerLoader.java
new file mode 100644
index 0000000..5af0954
--- /dev/null
+++ b/src/java/azkaban/trigger/TriggerLoader.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+import java.util.List;
+
+public interface TriggerLoader {
+
+	public void addTrigger(Trigger t) throws TriggerLoaderException;	
+
+	public void removeTrigger(Trigger s) throws TriggerLoaderException;
+	
+	public void updateTrigger(Trigger t) throws TriggerLoaderException;
+	
+	public List<Trigger> loadTriggers() throws TriggerLoaderException;
+
+	public Trigger loadTrigger(int triggerId) throws TriggerLoaderException;
+
+	public List<Trigger> getUpdatedTriggers(long lastUpdateTime) throws TriggerLoaderException;
+	
+}
diff --git a/src/java/azkaban/trigger/TriggerLoaderException.java b/src/java/azkaban/trigger/TriggerLoaderException.java
new file mode 100644
index 0000000..0265984
--- /dev/null
+++ b/src/java/azkaban/trigger/TriggerLoaderException.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+public class TriggerLoaderException extends Exception{
+	private static final long serialVersionUID = 1L;
+
+	public TriggerLoaderException(String message) {
+		super(message);
+	}
+	
+	public TriggerLoaderException(String message, Throwable cause) {
+		super(message, cause);
+	}
+	
+	public TriggerLoaderException(Throwable e) {
+		super(e);
+	}
+}
diff --git a/src/java/azkaban/trigger/TriggerManager.java b/src/java/azkaban/trigger/TriggerManager.java
new file mode 100644
index 0000000..6f403e7
--- /dev/null
+++ b/src/java/azkaban/trigger/TriggerManager.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.PriorityBlockingQueue;
+import org.apache.log4j.Logger;
+
+import azkaban.execapp.event.Event;
+import azkaban.execapp.event.EventHandler;
+import azkaban.execapp.event.EventListener;
+import azkaban.execapp.event.Event.Type;
+import azkaban.executor.ExecutableFlow;
+import azkaban.executor.ExecutorManager;
+import azkaban.utils.Props;
+
+public class TriggerManager extends EventHandler implements TriggerManagerAdapter{
+	private static Logger logger = Logger.getLogger(TriggerManager.class);
+	public static final long DEFAULT_SCANNER_INTERVAL_MS = 60000;
+
+	private static Map<Integer, Trigger> triggerIdMap = new ConcurrentHashMap<Integer, Trigger>();
+	
+	private CheckerTypeLoader checkerTypeLoader;
+	private ActionTypeLoader actionTypeLoader;
+	private TriggerLoader triggerLoader;
+
+	private final TriggerScannerThread runnerThread;
+	private long lastRunnerThreadCheckTime = -1;
+	private long runnerThreadIdleTime = -1;
+	private LocalTriggerJMX jmxStats = new LocalTriggerJMX();
+	
+	private ExecutorManagerEventListener listener = new ExecutorManagerEventListener();
+	
+	private String scannerStage = "";
+	
+	public TriggerManager(Props props, TriggerLoader triggerLoader, ExecutorManager executorManager) throws TriggerManagerException {
+
+		this.triggerLoader = triggerLoader;
+		
+		long scannerInterval = props.getLong("trigger.scan.interval", DEFAULT_SCANNER_INTERVAL_MS);
+		runnerThread = new TriggerScannerThread(scannerInterval);
+
+		checkerTypeLoader = new CheckerTypeLoader();
+		actionTypeLoader = new ActionTypeLoader();
+
+		try {
+			checkerTypeLoader.init(props);
+			actionTypeLoader.init(props);
+		} catch (Exception e) {
+			throw new TriggerManagerException(e);
+		}
+		
+		Condition.setCheckerLoader(checkerTypeLoader);
+		Trigger.setActionTypeLoader(actionTypeLoader);
+		
+		executorManager.addListener(listener);
+		
+		logger.info("TriggerManager loaded.");
+	}
+
+	@Override
+	public void start() throws TriggerManagerException{
+		
+		try{
+			// expect loader to return valid triggers
+			List<Trigger> triggers = triggerLoader.loadTriggers();
+			for(Trigger t : triggers) {
+				runnerThread.addTrigger(t);
+				triggerIdMap.put(t.getTriggerId(), t);
+			}
+		}catch(Exception e) {
+			e.printStackTrace();
+			throw new TriggerManagerException(e);
+		}
+		
+		runnerThread.start();
+	}
+	
+	protected CheckerTypeLoader getCheckerLoader() {
+		return checkerTypeLoader;
+	}
+
+	protected ActionTypeLoader getActionLoader() {
+		return actionTypeLoader;
+	}
+
+	public synchronized void insertTrigger(Trigger t) throws TriggerManagerException {
+		try {
+			triggerLoader.addTrigger(t);
+		} catch (TriggerLoaderException e) {
+			throw new TriggerManagerException(e);
+		}
+		runnerThread.addTrigger(t);
+		triggerIdMap.put(t.getTriggerId(), t);
+	}
+	
+	public synchronized void removeTrigger(int id) throws TriggerManagerException {
+		Trigger t = triggerIdMap.get(id);
+		if(t != null) {
+			removeTrigger(triggerIdMap.get(id));
+		}
+	}
+	
+	public synchronized void updateTrigger(int id) throws TriggerManagerException {
+		if(! triggerIdMap.containsKey(id)) {
+			throw new TriggerManagerException("The trigger to update " + id + " doesn't exist!");
+		}
+		
+		Trigger t;
+		try {
+			t = triggerLoader.loadTrigger(id);
+		} catch (TriggerLoaderException e) {
+			throw new TriggerManagerException(e);
+		}
+		updateTrigger(t);
+	}
+	
+	public synchronized void updateTrigger(Trigger t) throws TriggerManagerException {
+		runnerThread.deleteTrigger(triggerIdMap.get(t.getTriggerId()));
+		runnerThread.addTrigger(t);
+		triggerIdMap.put(t.getTriggerId(), t);
+	}
+
+	public synchronized void removeTrigger(Trigger t) throws TriggerManagerException {
+		runnerThread.deleteTrigger(t);
+		triggerIdMap.remove(t.getTriggerId());
+		try {
+			t.stopCheckers();
+			triggerLoader.removeTrigger(t);
+		} catch (TriggerLoaderException e) {
+			throw new TriggerManagerException(e);
+		}
+	}
+	
+	public List<Trigger> getTriggers() {
+		return new ArrayList<Trigger>(triggerIdMap.values());
+	}
+	
+	public Map<String, Class<? extends ConditionChecker>> getSupportedCheckers() {
+		return checkerTypeLoader.getSupportedCheckers();
+	}
+	
+	private class TriggerScannerThread extends Thread {
+		private BlockingQueue<Trigger> triggers;
+		private Map<Integer, ExecutableFlow> justFinishedFlows;
+		private boolean shutdown = false;
+		//private AtomicBoolean stillAlive = new AtomicBoolean(true);
+		private final long scannerInterval;
+		
+		public TriggerScannerThread(long scannerInterval) {
+			triggers = new PriorityBlockingQueue<Trigger>(1, new TriggerComparator());
+			justFinishedFlows = new ConcurrentHashMap<Integer, ExecutableFlow>();
+			this.setName("TriggerRunnerManager-Trigger-Scanner-Thread");
+			this.scannerInterval = scannerInterval;;
+		}
+
+		public void shutdown() {
+			logger.error("Shutting down trigger manager thread " + this.getName());
+			shutdown = true;
+			//stillAlive.set(false);
+			this.interrupt();
+		}
+		
+		public synchronized void addJustFinishedFlow(ExecutableFlow flow) {
+			justFinishedFlows.put(flow.getExecutionId(), flow);
+		}
+		
+		public synchronized void addTrigger(Trigger t) {
+			t.updateNextCheckTime();
+			triggers.add(t);
+		}
+		
+		public synchronized void deleteTrigger(Trigger t) {
+			triggers.remove(t);
+		}
+
+		public void run() {
+			//while(stillAlive.get()) {
+			while(!shutdown) {
+				synchronized (this) {
+					try{
+						lastRunnerThreadCheckTime = System.currentTimeMillis();
+						
+						scannerStage = "Ready to start a new scan cycle at " + lastRunnerThreadCheckTime;
+						
+						try{
+							checkAllTriggers();
+							justFinishedFlows.clear();
+						} catch(Exception e) {
+							e.printStackTrace();
+							logger.error(e.getMessage());
+						} catch(Throwable t) {
+							t.printStackTrace();
+							logger.error(t.getMessage());
+						}
+						
+						scannerStage = "Done flipping all triggers.";
+						
+						runnerThreadIdleTime = scannerInterval - (System.currentTimeMillis() - lastRunnerThreadCheckTime);
+
+						if(runnerThreadIdleTime < 0) {
+							logger.error("Trigger manager thread " + this.getName() + " is too busy!");
+						} else {
+							wait(runnerThreadIdleTime);
+						}
+					} catch(InterruptedException e) {
+						logger.info("Interrupted. Probably to shut down.");
+					}
+				}
+			}
+		}
+		
+		private void checkAllTriggers() throws TriggerManagerException {
+			long now = System.currentTimeMillis();
+			
+			// sweep through the rest of them
+			for(Trigger t : triggers) {
+				scannerStage = "Checking for trigger " + t.getTriggerId();
+				
+				boolean shouldSkip = true;
+				if(shouldSkip && t.getInfo() != null && t.getInfo().containsKey("monitored.finished.execution")) {
+					int execId = Integer.valueOf((String) t.getInfo().get("monitored.finished.execution"));
+					if(justFinishedFlows.containsKey(execId)) {
+						logger.info("Monitored execution has finished. Checking trigger earlier " + t.getTriggerId());
+						shouldSkip = false;
+					}
+				}
+				if(shouldSkip && t.getNextCheckTime() > now) {
+					shouldSkip = false;
+				}
+
+				if(shouldSkip) {
+					logger.info("Skipping trigger" + t.getTriggerId() + " until " + t.getNextCheckTime());
+				}
+				
+				logger.info("Checking trigger " + t.getTriggerId());
+				if(t.getStatus().equals(TriggerStatus.READY)) {
+					if(t.triggerConditionMet()) {
+						onTriggerTrigger(t);
+					} else if (t.expireConditionMet()) {
+						onTriggerExpire(t);
+					}
+				}
+				if(t.getStatus().equals(TriggerStatus.EXPIRED) && t.getSource().equals("azkaban")) {
+					removeTrigger(t);
+				} else {
+					t.updateNextCheckTime();
+				}
+			}
+		}
+		
+		private void onTriggerTrigger(Trigger t) throws TriggerManagerException {
+			List<TriggerAction> actions = t.getTriggerActions();
+			for(TriggerAction action : actions) {
+				try {
+					logger.info("Doing trigger actions");
+					action.doAction();
+				} catch (Exception e) {
+					// TODO Auto-generated catch block
+					//throw new TriggerManagerException("action failed to execute", e);
+					logger.error("Failed to do action " + action.getDescription(), e);
+				} catch (Throwable th) {
+					logger.error("Failed to do action " + action.getDescription(), th);
+				}
+			}
+			if(t.isResetOnTrigger()) {
+				t.resetTriggerConditions();
+				t.resetExpireCondition();
+			} else {
+				t.setStatus(TriggerStatus.EXPIRED);
+			}
+			try {
+				triggerLoader.updateTrigger(t);
+			}
+			catch (TriggerLoaderException e) {
+				throw new TriggerManagerException(e);
+			}
+		}
+		
+		private void onTriggerExpire(Trigger t) throws TriggerManagerException {
+			List<TriggerAction> expireActions = t.getExpireActions();
+			for(TriggerAction action : expireActions) {
+				try {
+					logger.info("Doing expire actions");
+					action.doAction();
+				} catch (Exception e) {
+					// TODO Auto-generated catch block
+					//throw new TriggerManagerException("action failed to execute", e);
+					logger.error("Failed to do expire action " + action.getDescription(), e);
+				} catch (Throwable th) {
+					logger.error("Failed to do expire action " + action.getDescription(), th);
+				}
+			}
+			if(t.isResetOnExpire()) {
+				t.resetTriggerConditions();
+				t.resetExpireCondition();
+//				updateTrigger(t);
+			} else {
+				t.setStatus(TriggerStatus.EXPIRED);
+			}
+			try {
+				triggerLoader.updateTrigger(t);
+			} catch (TriggerLoaderException e) {
+				throw new TriggerManagerException(e);
+			}
+		}
+		
+		private class TriggerComparator implements Comparator<Trigger> {
+			@Override
+			public int compare(Trigger arg0, Trigger arg1) {
+				long first = arg1.getNextCheckTime();
+				long second = arg0.getNextCheckTime();
+				
+				if(first == second) {
+					return 0;
+				} else if (first < second) {
+					return 1;
+				}
+				return -1;
+			}
+		}
+	}
+	
+	public synchronized Trigger getTrigger(int triggerId) {
+		return triggerIdMap.get(triggerId);
+	}
+
+	public void expireTrigger(int triggerId) {
+		Trigger t = getTrigger(triggerId);
+		t.setStatus(TriggerStatus.EXPIRED);
+//		updateAgent(t);
+	}
+
+	@Override
+	public List<Trigger> getTriggers(String triggerSource) {
+		List<Trigger> triggers = new ArrayList<Trigger>();
+		for(Trigger t : triggerIdMap.values()) {
+			if(t.getSource().equals(triggerSource)) {
+				triggers.add(t);
+			}
+		}
+		return triggers;
+	}
+
+	@Override
+	public List<Trigger> getTriggerUpdates(String triggerSource, long lastUpdateTime) throws TriggerManagerException{
+		List<Trigger> triggers = new ArrayList<Trigger>();
+		for(Trigger t : triggerIdMap.values()) {
+			if(t.getSource().equals(triggerSource) && t.getLastModifyTime() > lastUpdateTime) {
+				triggers.add(t);
+			}
+		}
+		return triggers;
+	}
+	
+	@Override
+	public List<Trigger> getAllTriggerUpdates(long lastUpdateTime) throws TriggerManagerException {
+		List<Trigger> triggers = new ArrayList<Trigger>();
+		for(Trigger t : triggerIdMap.values()) {
+			if(t.getLastModifyTime() > lastUpdateTime) {
+				triggers.add(t);
+			}
+		}
+		return triggers;
+	}
+
+//	public void loadTrigger(int triggerId) throws TriggerManagerException {
+//		Trigger t;
+//		try {
+//			t = triggerLoader.loadTrigger(triggerId);
+//		} catch (TriggerLoaderException e) {
+//			throw new TriggerManagerException(e);
+//		}
+//		if(t.getStatus().equals(TriggerStatus.PREPARING)) {
+//			triggerIdMap.put(t.getTriggerId(), t);
+//			runnerThread.addTrigger(t);
+//			t.setStatus(TriggerStatus.READY);
+//		}
+//	}
+
+	@Override
+	public void insertTrigger(Trigger t, String user) throws TriggerManagerException {
+		insertTrigger(t);
+	}
+
+	@Override
+	public void removeTrigger(int id, String user) throws TriggerManagerException {
+		removeTrigger(id);
+	}
+
+	@Override
+	public void updateTrigger(Trigger t, String user) throws TriggerManagerException {
+		updateTrigger(t);
+	}
+	
+//	@Override
+//	public void insertTrigger(int triggerId, String user) throws TriggerManagerException {
+//		Trigger t;
+//		try {
+//			t = triggerLoader.loadTrigger(triggerId);
+//		} catch (TriggerLoaderException e) {
+//			throw new TriggerManagerException(e);
+//		}
+//		if(t != null) {
+//			insertTrigger(t);
+//		}
+//	}
+	
+	@Override
+	public void shutdown() {
+		runnerThread.shutdown();
+	}
+
+	@Override
+	public TriggerJMX getJMX() {
+		return this.jmxStats;
+	}
+	
+	private class LocalTriggerJMX implements TriggerJMX {
+
+		@Override
+		public long getLastRunnerThreadCheckTime() {
+			// TODO Auto-generated method stub
+			return lastRunnerThreadCheckTime;
+		}
+
+		@Override
+		public boolean isRunnerThreadActive() {
+			// TODO Auto-generated method stub
+			return runnerThread.isAlive();
+		}
+
+		@Override
+		public String getPrimaryServerHost() {
+			return "local";
+		}
+
+		@Override
+		public int getNumTriggers() {
+			// TODO Auto-generated method stub
+			return triggerIdMap.size();
+		}
+
+		@Override
+		public String getTriggerSources() {
+			Set<String> sources = new HashSet<String>();
+			for(Trigger t : triggerIdMap.values()) {
+				sources.add(t.getSource());
+			}
+			return sources.toString();
+		}
+
+		@Override
+		public String getTriggerIds() {
+			return triggerIdMap.keySet().toString();
+		}
+
+		@Override
+		public long getScannerIdleTime() {
+			// TODO Auto-generated method stub
+			return runnerThreadIdleTime;
+		}
+
+		@Override
+		public Map<String, Object> getAllJMXMbeans() {
+			return new HashMap<String, Object>();
+		}
+
+		@Override
+		public String getScannerThreadStage() {
+			return scannerStage;
+		}
+		
+	}
+
+	@Override
+	public void registerCheckerType(String name, Class<? extends ConditionChecker> checker) {
+		checkerTypeLoader.registerCheckerType(name, checker);
+	}
+
+	@Override
+	public void registerActionType(String name, Class<? extends TriggerAction> action) {
+		actionTypeLoader.registerActionType(name, action);
+	}
+	
+	private class ExecutorManagerEventListener implements EventListener {
+		public ExecutorManagerEventListener() {
+		}
+		
+		@Override
+		public synchronized void handleEvent(Event event) {
+			
+			ExecutableFlow flow = (ExecutableFlow) event.getRunner();
+			if (event.getType() == Type.FLOW_FINISHED) {
+				logger.info("Flow finish event received. " + flow.getExecutionId() );
+				runnerThread.addJustFinishedFlow(flow);
+			}
+		}
+	}
+	
+}
diff --git a/src/java/azkaban/trigger/TriggerManagerAdapter.java b/src/java/azkaban/trigger/TriggerManagerAdapter.java
new file mode 100644
index 0000000..79d4626
--- /dev/null
+++ b/src/java/azkaban/trigger/TriggerManagerAdapter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+import java.util.List;
+import java.util.Map;
+
+public interface TriggerManagerAdapter {
+	
+	public void insertTrigger(Trigger t, String user) throws TriggerManagerException;
+	
+	public void removeTrigger(int id, String user) throws TriggerManagerException;
+	
+	public void updateTrigger(Trigger t, String user) throws TriggerManagerException;
+
+	public List<Trigger> getAllTriggerUpdates(long lastUpdateTime) throws TriggerManagerException;
+	
+	public List<Trigger> getTriggerUpdates(String triggerSource, long lastUpdateTime) throws TriggerManagerException;
+	
+	public List<Trigger> getTriggers(String trigegerSource);
+
+	public void start() throws TriggerManagerException;
+	
+	public void shutdown();
+
+	public void registerCheckerType(String name, Class<? extends ConditionChecker> checker);
+	
+	public void registerActionType(String name, Class<? extends TriggerAction> action);
+	
+	public TriggerJMX getJMX();
+	
+	public interface TriggerJMX {
+		public long getLastRunnerThreadCheckTime();
+		public boolean isRunnerThreadActive();
+		public String getPrimaryServerHost();
+		public int getNumTriggers();
+		public String getTriggerSources();
+		public String getTriggerIds();
+		public long getScannerIdleTime();
+		public Map<String, Object> getAllJMXMbeans();
+		public String getScannerThreadStage();
+	}
+	
+}
diff --git a/src/java/azkaban/trigger/TriggerManagerException.java b/src/java/azkaban/trigger/TriggerManagerException.java
new file mode 100644
index 0000000..bff0f81
--- /dev/null
+++ b/src/java/azkaban/trigger/TriggerManagerException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+public class TriggerManagerException extends Exception{
+	private static final long serialVersionUID = 1L;
+
+	public TriggerManagerException(String message) {
+		super(message);
+	}
+	
+	public TriggerManagerException(String message, Throwable cause) {
+		super(message, cause);
+	}
+	
+	public TriggerManagerException(Throwable e) {
+		super(e);
+	}
+}
+
diff --git a/src/java/azkaban/trigger/TriggerStatus.java b/src/java/azkaban/trigger/TriggerStatus.java
new file mode 100644
index 0000000..349c3ff
--- /dev/null
+++ b/src/java/azkaban/trigger/TriggerStatus.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.trigger;
+
+public enum TriggerStatus {
+	READY(10), PAUSED(20), EXPIRED(30);
+	
+	private int numVal;
+
+	TriggerStatus(int numVal) {
+		this.numVal = numVal;
+	}
+
+	public int getNumVal() {
+		return numVal;
+	}
+	
+	public static TriggerStatus fromInteger(int x) {
+		switch (x) {
+		case 10:
+			return READY;
+		case 20:
+			return PAUSED;
+		case 30:
+			return EXPIRED;
+		default:
+			return READY;
+		}
+	}
+
+}
diff --git a/src/java/azkaban/user/Permission.java b/src/java/azkaban/user/Permission.java
index 9f26055..42158f8 100644
--- a/src/java/azkaban/user/Permission.java
+++ b/src/java/azkaban/user/Permission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -29,6 +29,7 @@ public class Permission {
 		WRITE(0x0000002),
 		EXECUTE(0x0000004),
 		SCHEDULE(0x0000008),
+		METRICS(0x0000010),
 		CREATEPROJECTS(0x40000000), // Only used for roles
 		ADMIN(0x8000000);
 		
diff --git a/src/java/azkaban/user/Role.java b/src/java/azkaban/user/Role.java
index 08c7009..64123ff 100644
--- a/src/java/azkaban/user/Role.java
+++ b/src/java/azkaban/user/Role.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.user;
 
 public class Role {
diff --git a/src/java/azkaban/user/User.java b/src/java/azkaban/user/User.java
index d538e72..2262d92 100644
--- a/src/java/azkaban/user/User.java
+++ b/src/java/azkaban/user/User.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -23,17 +23,42 @@ import java.util.Set;
 
 public class User {
 	private final String userid;
+	private String email = "";
 	private Set<String> roles = new HashSet<String>();
 	private Set<String> groups = new HashSet<String>();
-	
+	private UserPermissions userPermissions;
+
 	public User(String userid) {
 		this.userid = userid;
 	}
-	
+
 	public String getUserId() {
 		return userid;
 	}
 
+	public void setEmail(String email) {
+		this.email = email;
+	}
+
+	public String getEmail() {
+		return email;
+	}
+
+	public void setPermissions(UserPermissions checker) {
+		this.userPermissions = checker;
+	}
+
+	public UserPermissions getPermissions() {
+		return userPermissions;
+	}
+
+	public boolean hasPermission(String permission) {
+		if (userPermissions == null) {
+			return false;
+		}
+		return this.userPermissions.hasPermission(permission);
+	}
+
 	public List<String> getGroups() {
 		return new ArrayList<String>(groups);
 	}
@@ -41,27 +66,27 @@ public class User {
 	public void clearGroup() {
 		groups.clear();
 	}
-	
+
 	public void addGroup(String name) {
 		groups.add(name);
 	}
-	
+
 	public boolean isInGroup(String group) {
 		return this.groups.contains(group);
 	}
-	
+
 	public List<String> getRoles() {
 		return new ArrayList<String>(roles);
 	}
-	
+
 	public void addRole(String role) {
 		this.roles.add(role);
 	}
-	
+
 	public boolean hasRole(String role) {
 		return roles.contains(role);
 	}
-	
+
 	public String toString() {
 		String groupStr = "[";
 		for (String group: groups) {
@@ -70,7 +95,7 @@ public class User {
 		groupStr += "]";
 		return userid + ": " + groupStr;
 	}
-	
+
 	@Override
 	public int hashCode() {
 		final int prime = 31;
@@ -87,12 +112,40 @@ public class User {
 			return false;
 		if (getClass() != obj.getClass())
 			return false;
-		User other = (User) obj;
+		User other = (User)obj;
 		if (userid == null) {
 			if (other.userid != null)
 				return false;
-		} else if (!userid.equals(other.userid))
+		}
+		else if (!userid.equals(other.userid))
 			return false;
 		return true;
 	}
+
+	public static interface UserPermissions {
+		public boolean hasPermission(String permission);
+		public void addPermission(String permission);
+	}
+
+	public static class DefaultUserPermission implements UserPermissions {
+		Set<String> permissions;
+
+		public DefaultUserPermission() {
+			this(new HashSet<String>());
+		}
+
+		public DefaultUserPermission(Set<String> permissions) {
+			this.permissions = permissions;
+		}
+
+		@Override
+		public boolean hasPermission(String permission) {
+			return permissions.contains(permission);
+		}
+
+		@Override
+		public void addPermission(String permission) {
+			permissions.add(permission);
+		}
+	}
 }
diff --git a/src/java/azkaban/user/UserManager.java b/src/java/azkaban/user/UserManager.java
index a5329c4..071b307 100644
--- a/src/java/azkaban/user/UserManager.java
+++ b/src/java/azkaban/user/UserManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/user/UserManagerException.java b/src/java/azkaban/user/UserManagerException.java
index 6a53df6..0943c16 100644
--- a/src/java/azkaban/user/UserManagerException.java
+++ b/src/java/azkaban/user/UserManagerException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/user/XmlUserManager.java b/src/java/azkaban/user/XmlUserManager.java
index 55ee224..7bcfe26 100644
--- a/src/java/azkaban/user/XmlUserManager.java
+++ b/src/java/azkaban/user/XmlUserManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -33,6 +33,7 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
 
+import azkaban.user.User.UserPermissions;
 import azkaban.utils.Props;
 
 /**
@@ -55,6 +56,7 @@ public class XmlUserManager implements UserManager {
 	public static final String ROLEPERMISSIONS_ATTR = "permissions";
 	public static final String USERNAME_ATTR = "username";
 	public static final String PASSWORD_ATTR = "password";
+	public static final String EMAIL_ATTR = "email";
 	public static final String ROLES_ATTR = "roles";
 	public static final String PROXY_ATTR = "proxy";
 	public static final String GROUPS_ATTR = "groups";
@@ -149,6 +151,7 @@ public class XmlUserManager implements UserManager {
 		if (userNameAttr == null) {
 			throw new RuntimeException("Error loading user. The '" + USERNAME_ATTR + "' attribute doesn't exist");
 		}
+		
 		Node passwordAttr = userAttrMap.getNamedItem(PASSWORD_ATTR);
 		if (passwordAttr == null) {
 			throw new RuntimeException("Error loading user. The '" + PASSWORD_ATTR + "' attribute doesn't exist");
@@ -195,6 +198,11 @@ public class XmlUserManager implements UserManager {
 				user.addGroup(group);
 			}
 		}
+		
+		Node emailAttr = userAttrMap.getNamedItem(EMAIL_ATTR);
+		if (emailAttr != null) {
+			user.setEmail(emailAttr.getNodeValue());
+		}
 	}
 
 	private void parseRoleTag(Node node, HashMap<String, Role> roles) {
@@ -207,7 +215,7 @@ public class XmlUserManager implements UserManager {
 		Node permissionAttr = roleAttrMap.getNamedItem(ROLEPERMISSIONS_ATTR);
 		if (permissionAttr == null) {
 			throw new RuntimeException(
-					"Error loading user. The password doesn't exist for "+ permissionAttr);
+					"Error loading role. The role 'permissions' attribute doesn't exist");
 		}
 
 		String roleName = roleNameAttr.getNodeValue();
@@ -262,6 +270,16 @@ public class XmlUserManager implements UserManager {
 
 		// Add all the roles the group has to the user
 		resolveGroupRoles(user);
+		user.setPermissions(new UserPermissions() {
+			@Override
+			public boolean hasPermission(String permission) {
+				return true;
+			}
+
+			@Override
+			public void addPermission(String permission) {
+			}
+		});
 		return user;
 	}
 
diff --git a/src/java/azkaban/utils/AbstractMailer.java b/src/java/azkaban/utils/AbstractMailer.java
index 85f589c..8d91456 100644
--- a/src/java/azkaban/utils/AbstractMailer.java
+++ b/src/java/azkaban/utils/AbstractMailer.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 import java.util.Collection;
@@ -48,6 +64,10 @@ public class AbstractMailer {
 		return message;
 	}
 	
+	public EmailMessage prepareEmailMessage(String subject, String mimetype, Collection<String> emailList) {
+		return createEmailMessage(subject, mimetype, emailList);
+	}
+	
 	public String getAzkabanName() {
 		return azkabanName;
 	}
diff --git a/src/java/azkaban/utils/cache/Cache.java b/src/java/azkaban/utils/cache/Cache.java
index 2125906..5ba5e99 100644
--- a/src/java/azkaban/utils/cache/Cache.java
+++ b/src/java/azkaban/utils/cache/Cache.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils.cache;
 
 import java.util.ArrayList;
@@ -172,4 +188,4 @@ public class Cache {
 
 		return false;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/utils/cache/CacheManager.java b/src/java/azkaban/utils/cache/CacheManager.java
index 9b38577..139af88 100644
--- a/src/java/azkaban/utils/cache/CacheManager.java
+++ b/src/java/azkaban/utils/cache/CacheManager.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils.cache;
 
 import java.util.HashSet;
@@ -113,4 +129,4 @@ public class CacheManager {
 			updaterThread.interrupt();
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/utils/cache/Element.java b/src/java/azkaban/utils/cache/Element.java
index 14c2ad9..e064ac7 100644
--- a/src/java/azkaban/utils/cache/Element.java
+++ b/src/java/azkaban/utils/cache/Element.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils.cache;
 
 public class Element<T> {
@@ -29,4 +45,4 @@ public class Element<T> {
 	public long getLastUpdateTime() {
 		return lastAccessTime;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/utils/CircularBuffer.java b/src/java/azkaban/utils/CircularBuffer.java
index b0f8f9c..c8e1934 100644
--- a/src/java/azkaban/utils/CircularBuffer.java
+++ b/src/java/azkaban/utils/CircularBuffer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 LinkedIn, Inc
+ * Copyright 2010 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -70,4 +70,4 @@ public class CircularBuffer<T> implements Iterable<T> {
 	    return this.lines.size();
 	}
 	
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/utils/DirectoryFlowLoader.java b/src/java/azkaban/utils/DirectoryFlowLoader.java
index bad673c..81d4c5b 100644
--- a/src/java/azkaban/utils/DirectoryFlowLoader.java
+++ b/src/java/azkaban/utils/DirectoryFlowLoader.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 import java.io.File;
diff --git a/src/java/azkaban/utils/Emailer.java b/src/java/azkaban/utils/Emailer.java
new file mode 100644
index 0000000..dcc749c
--- /dev/null
+++ b/src/java/azkaban/utils/Emailer.java
@@ -0,0 +1,192 @@
+
+package azkaban.utils;
+
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+
+import org.apache.log4j.Logger;
+
+import azkaban.alert.Alerter;
+import azkaban.executor.ExecutableFlow;
+import azkaban.executor.ExecutableNode;
+import azkaban.executor.ExecutionOptions;
+import azkaban.executor.Status;
+import azkaban.sla.SlaOption;
+import azkaban.utils.AbstractMailer;
+import azkaban.executor.mail.DefaultMailCreator;
+import azkaban.executor.mail.MailCreator;
+import azkaban.utils.EmailMessage;
+import azkaban.utils.Props;
+
+public class Emailer extends AbstractMailer implements Alerter {
+	private static Logger logger = Logger.getLogger(Emailer.class);
+	
+	private boolean testMode = false;
+
+	private String clientHostname;
+	private String clientPortNumber;
+
+	private String mailHost;
+	private String mailUser;
+	private String mailPassword;
+	private String mailSender;
+	private String azkabanName;
+
+	public Emailer(Props props) {
+		super(props);
+		this.azkabanName = props.getString("azkaban.name", "azkaban");
+		this.mailHost = props.getString("mail.host", "localhost");
+		this.mailUser = props.getString("mail.user", "");
+		this.mailPassword = props.getString("mail.password", "");
+		this.mailSender = props.getString("mail.sender", "");
+
+		int mailTimeout = props.getInt("mail.timeout.millis", 10000);
+		EmailMessage.setTimeout(mailTimeout);
+		int connectionTimeout = props.getInt("mail.connection.timeout.millis", 10000);
+		EmailMessage.setConnectionTimeout(connectionTimeout);
+		
+		this.clientHostname = props.getString("jetty.hostname", "localhost");
+		
+		if (props.getBoolean("jetty.use.ssl", true)) {
+			this.clientPortNumber = props.getString("jetty.ssl.port");
+		} else {
+			this.clientPortNumber = props.getString("jetty.port");
+		}
+		
+		testMode = props.getBoolean("test.mode", false);
+	}
+	
+	@SuppressWarnings("unchecked")
+	private void sendSlaAlertEmail(SlaOption slaOption, String slaMessage) {
+		String subject = "Sla Violation Alert on " + getAzkabanName();
+		String body = slaMessage;
+		List<String> emailList = (List<String>) slaOption.getInfo().get(SlaOption.INFO_EMAIL_LIST);
+		if (emailList != null && !emailList.isEmpty()) {
+			EmailMessage message = super.createEmailMessage(
+					subject, 
+					"text/html", 
+					emailList);
+			
+			message.setBody(body);
+			
+			if (!testMode) {
+				try {
+					message.sendEmail();
+				} catch (MessagingException e) {
+					logger.error("Email message send failed" , e);
+				}
+			}
+		}
+	}
+
+	public void sendFirstErrorMessage(ExecutableFlow flow) {
+		EmailMessage message = new EmailMessage(mailHost, mailUser, mailPassword);
+		message.setFromAddress(mailSender);
+
+		ExecutionOptions option = flow.getExecutionOptions();
+
+		MailCreator mailCreator = DefaultMailCreator.getCreator(option.getMailCreator());
+
+		logger.debug("ExecutorMailer using mail creator:" + mailCreator.getClass().getCanonicalName());
+
+		boolean mailCreated = mailCreator.createFirstErrorMessage(flow, message, azkabanName, clientHostname, clientPortNumber);
+
+		if (mailCreated && !testMode) {
+			try {
+				message.sendEmail();
+			} catch (MessagingException e) {
+				logger.error("Email message send failed", e);
+			}
+		}
+	}
+
+	public void sendErrorEmail(ExecutableFlow flow, String... extraReasons) {
+		EmailMessage message = new EmailMessage(mailHost, mailUser, mailPassword);
+		message.setFromAddress(mailSender);
+
+		ExecutionOptions option = flow.getExecutionOptions();
+
+		MailCreator mailCreator = DefaultMailCreator.getCreator(option.getMailCreator());
+		logger.debug("ExecutorMailer using mail creator:" + mailCreator.getClass().getCanonicalName());
+
+		boolean mailCreated = mailCreator.createErrorEmail(flow, message, azkabanName, clientHostname, clientPortNumber, extraReasons);
+
+		if (mailCreated && !testMode) {
+			try {
+				message.sendEmail();
+			} catch (MessagingException e) {
+				logger.error("Email message send failed", e);
+			}
+		}
+	}
+
+	public void sendSuccessEmail(ExecutableFlow flow) {
+		EmailMessage message = new EmailMessage(mailHost, mailUser, mailPassword);
+		message.setFromAddress(mailSender);
+
+		ExecutionOptions option = flow.getExecutionOptions();
+
+		MailCreator mailCreator = DefaultMailCreator.getCreator(option.getMailCreator());
+		logger.debug("ExecutorMailer using mail creator:" + mailCreator.getClass().getCanonicalName());
+
+		boolean mailCreated = mailCreator.createSuccessEmail(flow, message, azkabanName, clientHostname, clientPortNumber);
+
+		if (mailCreated && !testMode) {
+			try {
+				message.sendEmail();
+			} catch (MessagingException e) {
+				logger.error("Email message send failed", e);
+			}
+		}
+	}
+
+	public static List<String> findFailedJobs(ExecutableFlow flow) {
+		ArrayList<String> failedJobs = new ArrayList<String>();
+		for (ExecutableNode node : flow.getExecutableNodes()) {
+			if (node.getStatus() == Status.FAILED) {
+				failedJobs.add(node.getJobId());
+			}
+		}
+		return failedJobs;
+	}
+
+	@Override
+	public void alertOnSuccess(ExecutableFlow exflow) throws Exception {
+		sendSuccessEmail(exflow);
+	}
+	
+	@Override
+	public void alertOnError(ExecutableFlow exflow, String ... extraReasons) throws Exception {
+		sendErrorEmail(exflow, extraReasons);
+	}
+	
+	@Override
+	public void alertOnFirstError(ExecutableFlow exflow) throws Exception {
+		sendFirstErrorMessage(exflow);
+	}
+
+	@Override
+	public void alertOnSla(SlaOption slaOption, String slaMessage)
+			throws Exception {
+		sendSlaAlertEmail(slaOption, slaMessage);		
+	}
+}
diff --git a/src/java/azkaban/utils/EmailMessage.java b/src/java/azkaban/utils/EmailMessage.java
index 06ca9bc..bbcfdd1 100644
--- a/src/java/azkaban/utils/EmailMessage.java
+++ b/src/java/azkaban/utils/EmailMessage.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 import java.io.File;
@@ -32,7 +48,9 @@ public class EmailMessage {
 	private String _fromAddress;
 	private String _mimeType = "text/plain";
 	private StringBuffer _body = new StringBuffer();
-
+	private static int _mailTimeout = 10000;
+	private static int _connectionTimeout = 10000;
+	
 	private ArrayList<BodyPart> _attachments = new ArrayList<BodyPart>();
 
 	public EmailMessage() {
@@ -44,7 +62,15 @@ public class EmailMessage {
 		_mailHost = host;
 		_mailPassword = password;
 	}
-
+	
+	public static void setTimeout(int timeoutMillis) {
+		_mailTimeout = timeoutMillis;
+	}
+	
+	public static void setConnectionTimeout(int timeoutMillis) {
+		_connectionTimeout = timeoutMillis;
+	}
+	
 	public EmailMessage setMailHost(String host) {
 		_mailHost = host;
 		return this;
@@ -136,6 +162,8 @@ public class EmailMessage {
 		props.put("mail."+protocol+".auth", "true");
 		props.put("mail.user", _mailUser);
 		props.put("mail.password", _mailPassword);
+		props.put("mail."+protocol+".timeout", _mailTimeout);
+		props.put("mail."+protocol+".connectiontimeout", _connectionTimeout);
 
 		Session session = Session.getInstance(props, null);
 		Message message = new MimeMessage(session);
@@ -149,15 +177,16 @@ public class EmailMessage {
 
 		if (_attachments.size() > 0) {
 			MimeMultipart multipart = new MimeMultipart("related");
+			
+			BodyPart messageBodyPart = new MimeBodyPart();
+			messageBodyPart.setContent(_body.toString(), _mimeType);
+			multipart.addBodyPart(messageBodyPart);
+			
 			// Add attachments
 			for (BodyPart part : _attachments) {
 				multipart.addBodyPart(part);
 			}
 
-			BodyPart messageBodyPart = new MimeBodyPart();
-			messageBodyPart.setContent(_body.toString(), _mimeType);
-			multipart.addBodyPart(messageBodyPart);
-
 			message.setContent(multipart);
 		} else {
 			message.setContent(_body.toString(), _mimeType);
diff --git a/src/java/azkaban/utils/FileIOUtils.java b/src/java/azkaban/utils/FileIOUtils.java
index d1b275f..b9edc56 100644
--- a/src/java/azkaban/utils/FileIOUtils.java
+++ b/src/java/azkaban/utils/FileIOUtils.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 import java.io.BufferedInputStream;
@@ -417,4 +433,4 @@ public class FileIOUtils {
 			return "[offset=" + offset + ",length="+length + ",data=" + data + "]";
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/utils/GZIPUtils.java b/src/java/azkaban/utils/GZIPUtils.java
index b402067..2d42c4e 100644
--- a/src/java/azkaban/utils/GZIPUtils.java
+++ b/src/java/azkaban/utils/GZIPUtils.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 import java.io.ByteArrayInputStream;
@@ -45,4 +61,4 @@ public class GZIPUtils {
 		byte[] response = unGzipBytes(bytes);
 		return new String(response, encType);
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/utils/JSONUtils.java b/src/java/azkaban/utils/JSONUtils.java
index 063b129..e811b82 100644
--- a/src/java/azkaban/utils/JSONUtils.java
+++ b/src/java/azkaban/utils/JSONUtils.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 import java.io.BufferedOutputStream;
diff --git a/src/java/azkaban/utils/LogGobbler.java b/src/java/azkaban/utils/LogGobbler.java
index ec20256..e053b64 100644
--- a/src/java/azkaban/utils/LogGobbler.java
+++ b/src/java/azkaban/utils/LogGobbler.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 import java.io.BufferedReader;
diff --git a/src/java/azkaban/utils/LogSummary.java b/src/java/azkaban/utils/LogSummary.java
new file mode 100644
index 0000000..c9e5495
--- /dev/null
+++ b/src/java/azkaban/utils/LogSummary.java
@@ -0,0 +1,349 @@
+package azkaban.utils;
+
+import azkaban.utils.FileIOUtils.LogData;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class LogSummary {
+	private static final String HIVE_PARSING_START = "Parsing command: ";
+	private static final String HIVE_PARSING_END = "Parse Completed";
+	private static final String HIVE_NUM_MAP_REDUCE_JOBS_STRING = "Total MapReduce jobs = ";
+	private static final String HIVE_MAP_REDUCE_JOB_START = "Starting Job";
+	private static final String HIVE_MAP_REDUCE_JOBS_SUMMARY = "MapReduce Jobs Launched:";
+	
+	// Regex to search for URLs to job details pages.
+	private static final Pattern jobTrackerUrl = Pattern.compile(
+			"https?://" + // http(s)://
+			"[-\\w\\.]+" + // domain
+			"(?::\\d+)?" + // port
+			"/[\\w/\\.]*" + // path
+			// query string
+			"\\?\\S+" + 
+			"(job_\\d{12}_\\d{4,})" + // job id
+			"\\S*"
+	);
+	
+	private String jobType = null;
+	private List<Pair<String,String>> commandProperties = new ArrayList<Pair<String,String>>();
+	
+	private String[] pigStatTableHeaders = null;
+	private List<String[]> pigStatTableData = new ArrayList<String[]>();
+	
+	private String[] pigSummaryTableHeaders = null;
+	private List<String[]> pigSummaryTableData = new ArrayList<String[]>();
+	
+	private List<String> hiveQueries = new ArrayList<String>();
+	
+	// Each element in hiveQueryJobs contains a list of the jobs for a query.
+	// Each job contains a list of strings of the job summary values.
+	private List<List<List<String>>> hiveQueryJobs = new ArrayList<List<List<String>>>();
+	
+	public LogSummary(LogData log) {
+		if (log != null) {
+			parseLogData(log.getData());
+		}
+	}
+	
+	private void parseLogData(String data) {
+		// Filter out all the timestamps
+		data = data.replaceAll("(?m)^.*? - ", "");
+		String[] lines = data.split("\n");
+		
+		if (parseCommand(lines)) {
+			jobType = parseJobType(lines);
+			
+			if (jobType.contains("pig")) {
+				parsePigJobSummary(lines);
+				parsePigJobStats(lines);
+			} else if (jobType.contains("hive")) {
+				parseHiveQueries(lines);
+			}
+		}
+	}
+
+	private String parseJobType(String[] lines) {
+		Pattern p = Pattern.compile("Building (\\S+) job executor");
+		
+		for (String line : lines) {
+			Matcher m = p.matcher(line);
+			if (m.find()) {
+				return m.group(1);
+			}
+		}
+		
+		return null;
+	}
+	
+	private boolean parseCommand(String[] lines) {
+		int commandStartIndex = -1;
+		for (int i = 0; i < lines.length; i++) {
+			if (lines[i].startsWith("Command: ")) {
+				commandStartIndex = i;
+				break;
+			}
+		}
+		
+		if (commandStartIndex != -1) {
+			String command = lines[commandStartIndex].substring(9);
+			commandProperties.add(new Pair<String,String>("Command", command));
+			
+			// Parse classpath
+			Pattern p = Pattern.compile("(?:-cp|-classpath)\\s+(\\S+)");
+			Matcher m = p.matcher(command);
+			StringBuilder sb = new StringBuilder();
+			if (m.find()) {
+				sb.append(StringUtils.join((Collection<String>)Arrays.asList(m.group(1).split(":")), "<br/>"));
+				commandProperties.add(new Pair<String,String>("Classpath", sb.toString()));
+			}
+			
+			// Parse environment variables
+			p = Pattern.compile("-D(\\S+)");
+			m = p.matcher(command);
+			sb = new StringBuilder();
+			while (m.find()) {
+				sb.append(m.group(1) + "<br/>");
+			}
+			if (sb.length() > 0) {
+				commandProperties.add(new Pair<String,String>("-D", sb.toString()));
+			}
+			
+			// Parse memory settings
+			p = Pattern.compile("(-Xm\\S+)");
+			m = p.matcher(command);
+			sb = new StringBuilder();
+			while (m.find()) {
+				sb.append(m.group(1) + "<br/>");
+			}
+			if (sb.length() > 0) {
+				commandProperties.add(new Pair<String,String>("Memory Settings", sb.toString()));
+			}
+			
+			// Parse Pig params
+			p = Pattern.compile("-param\\s+(\\S+)");
+			m = p.matcher(command);
+			sb = new StringBuilder();
+			while (m.find()) {
+				sb.append(m.group(1) + "<br/>");
+			}
+			if (sb.length() > 0) {
+				commandProperties.add(new Pair<String,String>("Params", sb.toString()));
+			}
+			
+			return true;
+		}
+		
+		return false;
+	}
+	
+	private void parsePigJobSummary(String[] lines) {
+		int jobSummaryStartIndex = -1;
+		for (int i = 0; i < lines.length; i++) {
+			if (lines[i].startsWith("HadoopVersion")) {
+				jobSummaryStartIndex = i;
+				break;
+			}
+		}
+		
+		if (jobSummaryStartIndex != -1) {
+			String headerLine = lines[jobSummaryStartIndex];
+			pigSummaryTableHeaders = headerLine.split("\t");
+			
+			int tableRowIndex = jobSummaryStartIndex + 1;
+			String line;
+			while (!(line = lines[tableRowIndex]).equals("")) {
+				pigSummaryTableData.add(line.split("\t"));
+				tableRowIndex++;
+			}
+		}
+	}
+	
+	/**
+	 * Parses the Pig Job Stats table that includes the max/min mapper and reduce times.
+	 * Adds links to the job details pages on the job tracker.
+	 * @param lines
+	 */
+	private void parsePigJobStats(String[] lines) {
+		int jobStatsStartIndex = -1;
+		
+		Map<String, String> jobDetailUrls = new HashMap<String, String>();
+
+		
+		
+		for (int i = 0; i < lines.length; i++) {
+			String line = lines[i];
+			Matcher m = jobTrackerUrl.matcher(line);
+			
+			if (m.find()) {
+				jobDetailUrls.put(m.group(1), m.group(0));
+			}
+			else if (line.startsWith("Job Stats (time in seconds):")) {
+				jobStatsStartIndex = i+1;
+				break;
+			}
+		}
+		
+		if (jobStatsStartIndex != -1) {
+			String headerLine = lines[jobStatsStartIndex];
+			pigStatTableHeaders = headerLine.split("\t");
+			
+			int tableRowIndex = jobStatsStartIndex + 1;
+			String line;
+			while (!(line = lines[tableRowIndex]).equals("")) {
+				String[] stats = line.split("\t");
+				if (jobDetailUrls.containsKey(stats[0])) {
+					stats[0] = "<a href=\"" + jobDetailUrls.get(stats[0]) + "\">" + stats[0] + "</a>";
+				}
+				pigStatTableData.add(stats);
+				tableRowIndex++;
+			}
+		}
+	}
+	
+	private void parseHiveQueries(String[] lines) {
+		for (int i = 0; i < lines.length;) {
+			String line = lines[i];
+			int parsingCommandIndex = line.indexOf(HIVE_PARSING_START);
+			if (parsingCommandIndex != -1) {
+				// parse query text
+				int queryStartIndex = parsingCommandIndex + HIVE_PARSING_START.length();
+				StringBuilder query = new StringBuilder(line.substring(queryStartIndex) + "\n");
+				
+				i++;
+				while (i < lines.length && !(line = lines[i]).contains(HIVE_PARSING_END)) {
+					query.append(line + "\n");
+					i++;
+				}
+				String queryString = query.toString().trim().replaceAll("\n","<br/>");
+				hiveQueries.add(queryString);
+				i++;
+				
+				// parse the query's Map-Reduce jobs, if any.
+				int numMRJobs = 0;
+				List<String> jobTrackerUrls = new ArrayList<String>();
+				while (i < lines.length) {
+					line = lines[i];
+					if (line.contains(HIVE_NUM_MAP_REDUCE_JOBS_STRING)) {
+						// query involves map reduce jobs
+						numMRJobs = Integer.parseInt(line.substring(HIVE_NUM_MAP_REDUCE_JOBS_STRING.length()));
+						i++;
+						
+						// get the job tracker URLs
+						String lastUrl = "";
+						int numJobsSeen = 0;
+						while (numJobsSeen < numMRJobs && i < lines.length) {
+							line = lines[i];
+							if (line.contains(HIVE_MAP_REDUCE_JOB_START)) {
+								Matcher m = jobTrackerUrl.matcher(line);
+								if (m.find() && !lastUrl.equals(m.group(1))) {
+									jobTrackerUrls.add(m.group(0));
+									lastUrl = m.group(1);
+									numJobsSeen++;
+								}
+							}
+							i++;
+						}
+						
+						// get the map reduce jobs summary
+						while (i < lines.length) {
+							line = lines[i];
+							if (line.contains(HIVE_MAP_REDUCE_JOBS_SUMMARY)) {
+								// job summary table found
+								i++;
+								
+								List<List<String>> queryJobs = new ArrayList<List<String>>();
+								
+								Pattern p = Pattern.compile(
+									"Job (\\d+): Map: (\\d+)  Reduce: (\\d+)   HDFS Read: (\\d+) HDFS Write: (\\d+)"
+								);
+								
+								int previousJob = -1;
+								numJobsSeen = 0;
+								while (numJobsSeen < numMRJobs && i < lines.length) {
+									line = lines[i];
+									Matcher m = p.matcher(line);
+									if (m.find()) {
+										int currJob = Integer.parseInt(m.group(1));
+										if (currJob == previousJob) {
+											i++;
+											continue;
+										}
+										
+										List<String> job = new ArrayList<String>();
+										job.add("<a href=\"" + jobTrackerUrls.get(currJob) +
+												"\">" + currJob + "</a>");
+										job.add(m.group(2));
+										job.add(m.group(3));
+										job.add(m.group(4));
+										job.add(m.group(5));
+										queryJobs.add(job);
+										previousJob = currJob;
+										numJobsSeen++;
+									}
+									i++;
+								}
+								
+								if (numJobsSeen == numMRJobs) {
+									hiveQueryJobs.add(queryJobs);
+								}
+								
+								break;
+							}
+							i++;
+						} 
+						break;
+					}
+					else if (line.contains(HIVE_PARSING_START)) {
+						if (numMRJobs == 0) {
+							hiveQueryJobs.add(null);
+						}
+						break;
+					}
+					i++;
+				}
+				continue;
+			}
+			
+			i++;
+		}
+		return;
+	}
+	
+	public String[] getPigStatTableHeaders() {
+		return pigStatTableHeaders;
+	}
+
+	public List<String[]> getPigStatTableData() {
+		return pigStatTableData;
+	}
+
+	public String[] getPigSummaryTableHeaders() {
+		return pigSummaryTableHeaders;
+	}
+
+	public List<String[]> getPigSummaryTableData() {
+		return pigSummaryTableData;
+	}
+	
+	public String getJobType() {
+		return jobType;
+	}
+	
+	public List<Pair<String,String>> getCommandProperties() {
+		return commandProperties;
+	}
+
+	public List<String> getHiveQueries() {
+		return hiveQueries;
+	}
+
+	public List<List<List<String>>> getHiveQueryJobs() {
+		return hiveQueryJobs;
+	}
+}
diff --git a/src/java/azkaban/utils/Md5Hasher.java b/src/java/azkaban/utils/Md5Hasher.java
index 832f223..f7e55e7 100644
--- a/src/java/azkaban/utils/Md5Hasher.java
+++ b/src/java/azkaban/utils/Md5Hasher.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 import java.io.BufferedInputStream;
diff --git a/src/java/azkaban/utils/Pair.java b/src/java/azkaban/utils/Pair.java
index c1a67a0..b3564a7 100644
--- a/src/java/azkaban/utils/Pair.java
+++ b/src/java/azkaban/utils/Pair.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 /**
diff --git a/src/java/azkaban/utils/Props.java b/src/java/azkaban/utils/Props.java
index 12da4a7..7c0df22 100644
--- a/src/java/azkaban/utils/Props.java
+++ b/src/java/azkaban/utils/Props.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/utils/PropsUtils.java b/src/java/azkaban/utils/PropsUtils.java
index 5324cb7..2281ccf 100644
--- a/src/java/azkaban/utils/PropsUtils.java
+++ b/src/java/azkaban/utils/PropsUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/utils/SplitterOutputStream.java b/src/java/azkaban/utils/SplitterOutputStream.java
index 81821c6..896d900 100644
--- a/src/java/azkaban/utils/SplitterOutputStream.java
+++ b/src/java/azkaban/utils/SplitterOutputStream.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 import java.io.IOException;
@@ -38,15 +54,31 @@ public class SplitterOutputStream extends OutputStream {
 
 	@Override
 	public void flush() throws IOException {
+		IOException exception = null;
 		for (OutputStream output : outputs) {
-			output.flush();
+			try {
+				output.flush();
+			} catch (IOException e) {
+				exception = e;
+			}
+		}
+		if (exception != null) {
+			throw exception;
 		}
 	}
 
 	@Override
 	public void close() throws IOException {
+		IOException exception = null;
 		for (OutputStream output : outputs) {
-			output.close();
+			try {
+				output.close();
+			} catch (IOException e) {
+				exception = e;
+			}
+		}
+		if (exception != null) {
+			throw exception;
 		}
 	}
 
diff --git a/src/java/azkaban/utils/StringUtils.java b/src/java/azkaban/utils/StringUtils.java
index 6684b00..77c5941 100644
--- a/src/java/azkaban/utils/StringUtils.java
+++ b/src/java/azkaban/utils/StringUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package azkaban.utils;
 
 import java.util.Collection;
@@ -38,6 +39,17 @@ public class StringUtils {
 		return buf.toString();
 	}
 	
+	@Deprecated
+	public static String join(List<String> list, String delimiter) {
+		StringBuffer buffer = new StringBuffer();
+		for (String str: list) {
+			buffer.append(str);
+			buffer.append(delimiter);
+		}
+		
+		return buffer.toString();
+	}
+	
 	/**
 	 * Use this when you don't want to include Apache Common's string for
 	 * plugins.
diff --git a/src/java/azkaban/utils/Triple.java b/src/java/azkaban/utils/Triple.java
index 69966ce..a880d45 100644
--- a/src/java/azkaban/utils/Triple.java
+++ b/src/java/azkaban/utils/Triple.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 /**
diff --git a/src/java/azkaban/utils/UndefinedPropertyException.java b/src/java/azkaban/utils/UndefinedPropertyException.java
index a63aabb..acc142a 100644
--- a/src/java/azkaban/utils/UndefinedPropertyException.java
+++ b/src/java/azkaban/utils/UndefinedPropertyException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/utils/Utils.java b/src/java/azkaban/utils/Utils.java
index 03b1bab..44edfaa 100644
--- a/src/java/azkaban/utils/Utils.java
+++ b/src/java/azkaban/utils/Utils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -35,6 +35,15 @@ import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 
 import org.apache.commons.io.IOUtils;
+import org.joda.time.Days;
+import org.joda.time.DurationFieldType;
+import org.joda.time.Hours;
+import org.joda.time.Minutes;
+import org.joda.time.Months;
+import org.joda.time.ReadablePeriod;
+import org.joda.time.Seconds;
+import org.joda.time.Weeks;
+import org.joda.time.Years;
 
 /**
  * A util helper class full of static methods that are commonly used.
@@ -82,6 +91,17 @@ public class Utils {
 			return t;
 		}
 	}
+	
+	public static File findFilefromDir(File dir, String fn){
+		if(dir.isDirectory()) {
+			for(File f : dir.listFiles()) {
+				if(f.getName().equals(fn)) {
+					return f;
+				}
+			}
+		}
+		return null;
+	}
 
 	/**
 	 * Print the message and then exit with the given exit code
@@ -116,6 +136,18 @@ public class Utils {
 		zOut.close();
 	}
 
+	public static void zipFolderContent(File folder, File output) throws IOException {
+		FileOutputStream out = new FileOutputStream(output);
+		ZipOutputStream zOut = new ZipOutputStream(out);
+		File[] files = folder.listFiles();
+		if (files != null) {
+			for (File f : files) {
+				zipFile("", f, zOut);
+			}
+		}
+		zOut.close();
+	}
+
 	private static void zipFile(String path, File input, ZipOutputStream zOut) throws IOException {
 		if (input.isDirectory()) {
 			File[] files = input.listFiles();
@@ -271,6 +303,7 @@ public class Utils {
 		
 		Class<?>[] argTypes = new Class[args.length];
 		for (int i=0; i < args.length; ++i) {
+			//argTypes[i] = args[i].getClass();
 			argTypes[i] = args[i].getClass();
 		}
 		
@@ -285,4 +318,76 @@ public class Utils {
 			output.write(buffer, 0, bytesRead);
 		}
 	}
-}
\ No newline at end of file
+	
+	public static ReadablePeriod parsePeriodString(String periodStr) {
+		ReadablePeriod period;
+		char periodUnit = periodStr.charAt(periodStr.length() - 1);
+		if (periodStr.equals("null") || periodUnit == 'n') {
+			return null;
+		}
+
+		int periodInt = Integer.parseInt(periodStr.substring(0,
+				periodStr.length() - 1));
+		switch (periodUnit) {
+		case 'y':
+			period = Years.years(periodInt);
+			break;
+		case 'M':
+			period = Months.months(periodInt);
+			break;
+		case 'w':
+			period = Weeks.weeks(periodInt);
+			break;
+		case 'd':
+			period = Days.days(periodInt);
+			break;
+		case 'h':
+			period = Hours.hours(periodInt);
+			break;
+		case 'm':
+			period = Minutes.minutes(periodInt);
+			break;
+		case 's':
+			period = Seconds.seconds(periodInt);
+			break;
+		default:
+			throw new IllegalArgumentException("Invalid schedule period unit '"
+					+ periodUnit);
+		}
+
+		return period;
+	}
+
+	public static String createPeriodString(ReadablePeriod period) {
+		String periodStr = "null";
+
+		if (period == null) {
+			return "null";
+		}
+
+		if (period.get(DurationFieldType.years()) > 0) {
+			int years = period.get(DurationFieldType.years());
+			periodStr = years + "y";
+		} else if (period.get(DurationFieldType.months()) > 0) {
+			int months = period.get(DurationFieldType.months());
+			periodStr = months + "M";
+		} else if (period.get(DurationFieldType.weeks()) > 0) {
+			int weeks = period.get(DurationFieldType.weeks());
+			periodStr = weeks + "w";
+		} else if (period.get(DurationFieldType.days()) > 0) {
+			int days = period.get(DurationFieldType.days());
+			periodStr = days + "d";
+		} else if (period.get(DurationFieldType.hours()) > 0) {
+			int hours = period.get(DurationFieldType.hours());
+			periodStr = hours + "h";
+		} else if (period.get(DurationFieldType.minutes()) > 0) {
+			int minutes = period.get(DurationFieldType.minutes());
+			periodStr = minutes + "m";
+		} else if (period.get(DurationFieldType.seconds()) > 0) {
+			int seconds = period.get(DurationFieldType.seconds());
+			periodStr = seconds + "s";
+		}
+
+		return periodStr;
+	}
+}
diff --git a/src/java/azkaban/utils/WebUtils.java b/src/java/azkaban/utils/WebUtils.java
index ef63fb6..31322e6 100644
--- a/src/java/azkaban/utils/WebUtils.java
+++ b/src/java/azkaban/utils/WebUtils.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.utils;
 
 import java.text.NumberFormat;
@@ -99,13 +115,17 @@ public class WebUtils {
 	
 	public String formatPeriod(ReadablePeriod period)
 	{
-        String periodStr = "n";
+        String periodStr = "null";
 
         if (period == null) {
             return periodStr;
         }
 
-        if (period.get(DurationFieldType.months()) > 0) {
+        if (period.get(DurationFieldType.years()) > 0) {
+        	int years = period.get(DurationFieldType.years());
+        	periodStr = years + " year(s)";
+        }
+        else if (period.get(DurationFieldType.months()) > 0) {
             int months = period.get(DurationFieldType.months());
             periodStr = months + " month(s)";
         }
@@ -154,6 +174,4 @@ public class WebUtils {
         else
             return sizeBytes + " B";
     }
-    
-    
 }
diff --git a/src/java/azkaban/webapp/AzkabanServer.java b/src/java/azkaban/webapp/AzkabanServer.java
index 5da23ee..edb7d19 100644
--- a/src/java/azkaban/webapp/AzkabanServer.java
+++ b/src/java/azkaban/webapp/AzkabanServer.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.webapp;
 
 import java.io.File;
@@ -119,4 +135,5 @@ public abstract class AzkabanServer {
 	public abstract VelocityEngine getVelocityEngine();
 	
 	public abstract UserManager getUserManager();
-}
\ No newline at end of file
+	
+}
diff --git a/src/java/azkaban/webapp/AzkabanSingleServer.java b/src/java/azkaban/webapp/AzkabanSingleServer.java
index b08139d..4ddd032 100644
--- a/src/java/azkaban/webapp/AzkabanSingleServer.java
+++ b/src/java/azkaban/webapp/AzkabanSingleServer.java
@@ -1,5 +1,20 @@
-package azkaban.webapp;
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 
+package azkaban.webapp;
 
 import org.apache.log4j.Logger;
 
@@ -33,4 +48,4 @@ public class AzkabanSingleServer {
 		AzkabanExecutorServer.main(args);
 		logger.info("Azkaban Exec Server started...");
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/webapp/AzkabanWebServer.java b/src/java/azkaban/webapp/AzkabanWebServer.java
index edbf237..c24ea11 100644
--- a/src/java/azkaban/webapp/AzkabanWebServer.java
+++ b/src/java/azkaban/webapp/AzkabanWebServer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -27,7 +27,9 @@ import java.net.URLClassLoader;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.TimeZone;
 
 import javax.management.MBeanInfo;
@@ -49,23 +51,33 @@ import org.mortbay.jetty.servlet.DefaultServlet;
 import org.mortbay.jetty.servlet.ServletHolder;
 import org.mortbay.thread.QueuedThreadPool;
 
+import azkaban.alert.Alerter;
 import azkaban.database.AzkabanDatabaseSetup;
 import azkaban.executor.ExecutorManager;
 import azkaban.executor.JdbcExecutorLoader;
 import azkaban.jmx.JmxExecutorManager;
 import azkaban.jmx.JmxJettyServer;
-import azkaban.jmx.JmxSLAManager;
-import azkaban.jmx.JmxScheduler;
+import azkaban.jmx.JmxTriggerManager;
 import azkaban.project.JdbcProjectLoader;
 import azkaban.project.ProjectManager;
 
-import azkaban.scheduler.JdbcScheduleLoader;
+import azkaban.scheduler.ScheduleLoader;
 import azkaban.scheduler.ScheduleManager;
-import azkaban.sla.JdbcSLALoader;
-import azkaban.sla.SLAManager;
-import azkaban.sla.SLAManagerException;
+import azkaban.scheduler.TriggerBasedScheduleLoader;
+import azkaban.trigger.JdbcTriggerLoader;
+import azkaban.trigger.TriggerLoader;
+import azkaban.trigger.TriggerManager;
+import azkaban.trigger.TriggerManagerException;
+import azkaban.trigger.builtin.BasicTimeChecker;
+import azkaban.trigger.builtin.CreateTriggerAction;
+import azkaban.trigger.builtin.ExecuteFlowAction;
+import azkaban.trigger.builtin.ExecutionChecker;
+import azkaban.trigger.builtin.KillExecutionAction;
+import azkaban.trigger.builtin.SlaAlertAction;
+import azkaban.trigger.builtin.SlaChecker;
 import azkaban.user.UserManager;
 import azkaban.user.XmlUserManager;
+import azkaban.utils.Emailer;
 import azkaban.utils.FileIOUtils;
 import azkaban.utils.Props;
 import azkaban.utils.PropsUtils;
@@ -74,11 +86,14 @@ import azkaban.webapp.servlet.AzkabanServletContextListener;
 
 import azkaban.webapp.servlet.AbstractAzkabanServlet;
 import azkaban.webapp.servlet.ExecutorServlet;
+import azkaban.webapp.servlet.IndexRedirectServlet;
 import azkaban.webapp.servlet.JMXHttpServlet;
 import azkaban.webapp.servlet.ScheduleServlet;
 import azkaban.webapp.servlet.HistoryServlet;
-import azkaban.webapp.servlet.IndexServlet;
+import azkaban.webapp.servlet.ProjectServlet;
 import azkaban.webapp.servlet.ProjectManagerServlet;
+import azkaban.webapp.servlet.TriggerManagerServlet;
+import azkaban.webapp.servlet.TriggerPlugin;
 import azkaban.webapp.servlet.ViewerPlugin;
 import azkaban.webapp.session.SessionCache;
 
@@ -127,16 +142,19 @@ public class AzkabanWebServer extends AzkabanServer {
 	private final Server server;
 	private UserManager userManager;
 	private ProjectManager projectManager;
+//	private ExecutorManagerAdapter executorManager;
 	private ExecutorManager executorManager;
 	private ScheduleManager scheduleManager;
-	private SLAManager slaManager;
-
+	private TriggerManager triggerManager;
+	private Map<String, Alerter> alerters;
+	
 	private final ClassLoader baseClassLoader;
 	
 	private Props props;
 	private SessionCache sessionCache;
 	private File tempDir;
 	private List<ViewerPlugin> viewerPlugins;
+	private Map<String, TriggerPlugin> triggerPlugins;
 	
 	private MBeanServer mbeanServer;
 	private ArrayList<ObjectName> registeredMBeans = new ArrayList<ObjectName>();
@@ -158,20 +176,33 @@ public class AzkabanWebServer extends AzkabanServer {
 		velocityEngine = configureVelocityEngine(props.getBoolean(VELOCITY_DEV_MODE_PARAM, false));
 		sessionCache = new SessionCache(props);
 		userManager = loadUserManager(props);
-		projectManager = loadProjectManager(props);
+		
+		alerters = loadAlerters(props);
+		
 		executorManager = loadExecutorManager(props);
-		slaManager = loadSLAManager(props);
-		scheduleManager = loadScheduleManager(executorManager, slaManager, props);
-		baseClassLoader = getBaseClassloader();
+		projectManager = loadProjectManager(props);
+		
+		triggerManager = loadTriggerManager(props);
+		loadBuiltinCheckersAndActions();		
+		
+		// load all trigger agents here
+		scheduleManager = loadScheduleManager(triggerManager, props);
+		
+		String triggerPluginDir = props.getString("trigger.plugin.dir", "plugins/triggers");
+		
+		loadPluginCheckersAndActions(triggerPluginDir);
+		
+		//baseClassLoader = getBaseClassloader();
+		baseClassLoader = this.getClassLoader();
 		
 		tempDir = new File(props.getString("azkaban.temp.dir", "temp"));
 
 		// Setup time zone
 		if (props.containsKey(DEFAULT_TIMEZONE_ID)) {
 			String timezone = props.getString(DEFAULT_TIMEZONE_ID);
+			System.setProperty("user.timezone", timezone);
 			TimeZone.setDefault(TimeZone.getTimeZone(timezone));
 			DateTimeZone.setDefault(DateTimeZone.forID(timezone));
-
 			logger.info("Setting timezone to " + timezone);
 		}
 		
@@ -182,13 +213,15 @@ public class AzkabanWebServer extends AzkabanServer {
 		this.viewerPlugins = viewerPlugins;
 	}
 	
+	private void setTriggerPlugins(Map<String, TriggerPlugin> triggerPlugins) {
+		this.triggerPlugins = triggerPlugins;
+	}
+	
 	private UserManager loadUserManager(Props props) {
 		Class<?> userManagerClass = props.getClass(USER_MANAGER_CLASS_PARAM, null);
 		logger.info("Loading user manager class " + userManagerClass.getName());
 		UserManager manager = null;
-
 		if (userManagerClass != null && userManagerClass.getConstructors().length > 0) {
-
 			try {
 				Constructor<?> userManagerConstructor = userManagerClass.getConstructor(Props.class);
 				manager = (UserManager) userManagerConstructor.newInstance(props);
@@ -201,34 +234,323 @@ public class AzkabanWebServer extends AzkabanServer {
 		else {
 			manager = new XmlUserManager(props);
 		}
-
 		return manager;
 	}
 	
 	private ProjectManager loadProjectManager(Props props) {
 		logger.info("Loading JDBC for project management");
-
 		JdbcProjectLoader loader = new JdbcProjectLoader(props);
 		ProjectManager manager = new ProjectManager(loader, props);
-		
 		return manager;
 	}
 
 	private ExecutorManager loadExecutorManager(Props props) throws Exception {
 		JdbcExecutorLoader loader = new JdbcExecutorLoader(props);
-		ExecutorManager execManager = new ExecutorManager(props, loader);
+		ExecutorManager execManager = new ExecutorManager(props, loader, alerters);
 		return execManager;
 	}
+	
+//	private ExecutorManagerAdapter loadExecutorManagerAdapter(Props props) throws Exception {
+//		String executorMode = props.getString("executor.manager.mode", "local");
+//		ExecutorManagerAdapter adapter;
+//		if(executorMode.equals("local")) {
+//			adapter = loadExecutorManager(props);
+//		} else {
+//			throw new Exception("Unknown ExecutorManager mode " + executorMode);
+//		}
+//		return adapter;
+//	}
 
-	private ScheduleManager loadScheduleManager(ExecutorManager execManager, SLAManager slaManager, Props props ) throws Exception {
-		ScheduleManager schedManager = new ScheduleManager(execManager, projectManager, slaManager, new JdbcScheduleLoader(props));
+	private ScheduleManager loadScheduleManager(TriggerManager tm, Props props ) throws Exception {
+		logger.info("Loading trigger based scheduler");
+		ScheduleLoader loader = new TriggerBasedScheduleLoader(tm, ScheduleManager.triggerSource);
+		return new ScheduleManager(loader);
+	}
 
-		return schedManager;
+	private TriggerManager loadTriggerManager(Props props) throws TriggerManagerException {
+		TriggerLoader loader = new JdbcTriggerLoader(props);
+		return new TriggerManager(props, loader, executorManager);
+	}
+	
+	private void loadBuiltinCheckersAndActions() {
+		logger.info("Loading built-in checker and action types");
+		if(triggerManager instanceof TriggerManager) {
+			SlaChecker.setExecutorManager(executorManager);
+			ExecuteFlowAction.setExecutorManager(executorManager);
+			ExecuteFlowAction.setProjectManager(projectManager);
+			ExecuteFlowAction.setTriggerManager(triggerManager);
+			KillExecutionAction.setExecutorManager(executorManager);
+			SlaAlertAction.setExecutorManager(executorManager);
+			//Map<String, azkaban.alert.Alerter> alerters = loadAlerters(props);
+			SlaAlertAction.setAlerters(alerters);
+			SlaAlertAction.setExecutorManager(executorManager);
+			CreateTriggerAction.setTriggerManager(triggerManager);
+			ExecutionChecker.setExecutorManager(executorManager);
+		}
+		triggerManager.registerCheckerType(BasicTimeChecker.type, BasicTimeChecker.class);
+		triggerManager.registerCheckerType(SlaChecker.type, SlaChecker.class);
+		triggerManager.registerCheckerType(ExecutionChecker.type, ExecutionChecker.class);
+		triggerManager.registerActionType(ExecuteFlowAction.type, ExecuteFlowAction.class);
+		triggerManager.registerActionType(KillExecutionAction.type, KillExecutionAction.class);
+		triggerManager.registerActionType(SlaAlertAction.type, SlaAlertAction.class);
+		triggerManager.registerActionType(CreateTriggerAction.type, CreateTriggerAction.class);
+	}
+	
+	private Map<String, Alerter> loadAlerters(Props props) {
+		Map<String, Alerter> allAlerters = new HashMap<String, Alerter>();
+		// load built-in alerters
+		Emailer mailAlerter = new Emailer(props);
+		allAlerters.put("email", mailAlerter);
+		// load all plugin alerters
+		String pluginDir = props.getString("alerter.plugin.dir", "plugins/alerter");
+		allAlerters.putAll(loadPluginAlerters(pluginDir));
+		return allAlerters;
 	}
+	
+	private Map<String, Alerter> loadPluginAlerters(String pluginPath) {
+		File alerterPluginPath = new File(pluginPath);
+		if (!alerterPluginPath.exists()) {
+			return Collections.<String, Alerter>emptyMap();
+		}
+			
+		Map<String, Alerter> installedAlerterPlugins = new HashMap<String, Alerter>();
+		ClassLoader parentLoader = getClass().getClassLoader();
+		File[] pluginDirs = alerterPluginPath.listFiles();
+		ArrayList<String> jarPaths = new ArrayList<String>();
+		for (File pluginDir: pluginDirs) {
+			if (!pluginDir.isDirectory()) {
+				logger.error("The plugin path " + pluginDir + " is not a directory.");
+				continue;
+			}
+			
+			// Load the conf directory
+			File propertiesDir = new File(pluginDir, "conf");
+			Props pluginProps = null;
+			if (propertiesDir.exists() && propertiesDir.isDirectory()) {
+				File propertiesFile = new File(propertiesDir, "plugin.properties");
+				File propertiesOverrideFile = new File(propertiesDir, "override.properties");
+				
+				if (propertiesFile.exists()) {
+					if (propertiesOverrideFile.exists()) {
+						pluginProps = PropsUtils.loadProps(null, propertiesFile, propertiesOverrideFile);
+					}
+					else {
+						pluginProps = PropsUtils.loadProps(null, propertiesFile);
+					}
+				}
+				else {
+					logger.error("Plugin conf file " + propertiesFile + " not found.");
+					continue;
+				}
+			}
+			else {
+				logger.error("Plugin conf path " + propertiesDir + " not found.");
+				continue;
+			}
+			
+			String pluginName = pluginProps.getString("alerter.name");
+			List<String> extLibClasspath = pluginProps.getStringList("alerter.external.classpaths", (List<String>)null);
+			
+			String pluginClass = pluginProps.getString("alerter.class");
+			if (pluginClass == null) {
+				logger.error("Alerter class is not set.");
+			}
+			else {
+				logger.info("Plugin class " + pluginClass);
+			}
+			
+			URLClassLoader urlClassLoader = null;
+			File libDir = new File(pluginDir, "lib");
+			if (libDir.exists() && libDir.isDirectory()) {
+				File[] files = libDir.listFiles();
+				
+				ArrayList<URL> urls = new ArrayList<URL>();
+				for (int i=0; i < files.length; ++i) {
+					try {
+						URL url = files[i].toURI().toURL();
+						urls.add(url);
+					} catch (MalformedURLException e) {
+						logger.error(e);
+					}
+				}
+				if (extLibClasspath != null) {
+					for (String extLib : extLibClasspath) {
+						try {
+							File file = new File(pluginDir, extLib);
+							URL url = file.toURI().toURL();
+							urls.add(url);
+						} catch (MalformedURLException e) {
+							logger.error(e);
+						}
+					}
+				}
+				
+				urlClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), parentLoader);
+			}
+			else {
+				logger.error("Library path " + propertiesDir + " not found.");
+				continue;
+			}
+			
+			Class<?> alerterClass = null;
+			try {
+				alerterClass = urlClassLoader.loadClass(pluginClass);
+			}
+			catch (ClassNotFoundException e) {
+				logger.error("Class " + pluginClass + " not found.");
+				continue;
+			}
 
-	private SLAManager loadSLAManager(Props props) throws SLAManagerException {
-		SLAManager slaManager = new SLAManager(executorManager, new JdbcSLALoader(props), props);
-		return slaManager;
+			String source = FileIOUtils.getSourcePathFromClass(alerterClass);
+			logger.info("Source jar " + source);
+			jarPaths.add("jar:file:" + source);
+			
+			Constructor<?> constructor = null;
+			try {
+				constructor = alerterClass.getConstructor(Props.class);
+			} catch (NoSuchMethodException e) {
+				logger.error("Constructor not found in " + pluginClass);
+				continue;
+			}
+			
+			Object obj = null;
+			try {
+				obj = constructor.newInstance(pluginProps);
+			} catch (Exception e) {
+				logger.error(e);
+			} 
+			
+			if (!(obj instanceof Alerter)) {
+				logger.error("The object is not an Alerter");
+				continue;
+			}
+			
+			Alerter plugin = (Alerter) obj;
+			installedAlerterPlugins.put(pluginName, plugin);
+		}
+		
+		return installedAlerterPlugins;
+		
+	}
+	
+	private void loadPluginCheckersAndActions(String pluginPath) {
+		logger.info("Loading plug-in checker and action types");
+		File triggerPluginPath = new File(pluginPath);
+		if (!triggerPluginPath.exists()) {
+			logger.error("plugin path " + pluginPath + " doesn't exist!");
+			return;
+		}
+			
+		ClassLoader parentLoader = this.getClassLoader();
+		File[] pluginDirs = triggerPluginPath.listFiles();
+		ArrayList<String> jarPaths = new ArrayList<String>();
+		for (File pluginDir: pluginDirs) {
+			if (!pluginDir.exists()) {
+				logger.error("Error! Trigger plugin path " + pluginDir.getPath() + " doesn't exist.");
+				continue;
+			}
+			
+			if (!pluginDir.isDirectory()) {
+				logger.error("The plugin path " + pluginDir + " is not a directory.");
+				continue;
+			}
+			
+			// Load the conf directory
+			File propertiesDir = new File(pluginDir, "conf");
+			Props pluginProps = null;
+			if (propertiesDir.exists() && propertiesDir.isDirectory()) {
+				File propertiesFile = new File(propertiesDir, "plugin.properties");
+				File propertiesOverrideFile = new File(propertiesDir, "override.properties");
+				
+				if (propertiesFile.exists()) {
+					if (propertiesOverrideFile.exists()) {
+						pluginProps = PropsUtils.loadProps(null, propertiesFile, propertiesOverrideFile);
+					}
+					else {
+						pluginProps = PropsUtils.loadProps(null, propertiesFile);
+					}
+				}
+				else {
+					logger.error("Plugin conf file " + propertiesFile + " not found.");
+					continue;
+				}
+			}
+			else {
+				logger.error("Plugin conf path " + propertiesDir + " not found.");
+				continue;
+			}
+			
+			List<String> extLibClasspath = pluginProps.getStringList("trigger.external.classpaths", (List<String>)null);
+			
+			String pluginClass = pluginProps.getString("trigger.class");
+			if (pluginClass == null) {
+				logger.error("Trigger class is not set.");
+			}
+			else {
+				logger.error("Plugin class " + pluginClass);
+			}
+			
+			URLClassLoader urlClassLoader = null;
+			File libDir = new File(pluginDir, "lib");
+			if (libDir.exists() && libDir.isDirectory()) {
+				File[] files = libDir.listFiles();
+				
+				ArrayList<URL> urls = new ArrayList<URL>();
+				for (int i=0; i < files.length; ++i) {
+					try {
+						URL url = files[i].toURI().toURL();
+						urls.add(url);
+					} catch (MalformedURLException e) {
+						logger.error(e);
+					}
+				}
+				if (extLibClasspath != null) {
+					for (String extLib : extLibClasspath) {
+						try {
+							File file = new File(pluginDir, extLib);
+							URL url = file.toURI().toURL();
+							urls.add(url);
+						} catch (MalformedURLException e) {
+							logger.error(e);
+						}
+					}
+				}
+				
+				urlClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), parentLoader);
+			}
+			else {
+				logger.error("Library path " + propertiesDir + " not found.");
+				continue;
+			}
+			
+			Class<?> triggerClass = null;
+			try {
+				triggerClass = urlClassLoader.loadClass(pluginClass);
+			}
+			catch (ClassNotFoundException e) {
+				logger.error("Class " + pluginClass + " not found.");
+				continue;
+			}
+
+			String source = FileIOUtils.getSourcePathFromClass(triggerClass);
+			logger.info("Source jar " + source);
+			jarPaths.add("jar:file:" + source);
+			
+			try {
+				Utils.invokeStaticMethod(urlClassLoader, pluginClass, "initiateCheckerTypes", pluginProps, app);
+			} catch (Exception e) {
+				logger.error("Unable to initiate checker types for " + pluginClass);
+				continue;
+			}
+			
+			try {
+				Utils.invokeStaticMethod(urlClassLoader, pluginClass, "initiateActionTypes", pluginProps, app);
+			} catch (Exception e) {
+				logger.error("Unable to initiate action types for " + pluginClass);
+				continue;
+			}
+			
+		}
 	}
 	
 	/**
@@ -272,14 +594,18 @@ public class AzkabanWebServer extends AzkabanServer {
 		return executorManager;
 	}
 	
-	public SLAManager getSLAManager() {
-		return slaManager;
-	}
-	
 	public ScheduleManager getScheduleManager() {
 		return scheduleManager;
 	}
 	
+	public TriggerManager getTriggerManager() {
+		return triggerManager;
+	}
+	
+//	public TriggerBasedScheduler getScheduler() {
+//		return scheduler;
+//	}
+//	
 	/**
 	 * Creates and configures the velocity engine.
 	 * 
@@ -311,28 +637,28 @@ public class AzkabanWebServer extends AzkabanServer {
 		return engine;
 	}
 
-	private ClassLoader getBaseClassloader() throws MalformedURLException {
-		final ClassLoader retVal;
-
-		String hadoopHome = System.getenv("HADOOP_HOME");
-		String hadoopConfDir = System.getenv("HADOOP_CONF_DIR");
-
-		if (hadoopConfDir != null) {
-			logger.info("Using hadoop config found in " + hadoopConfDir);
-			retVal = new URLClassLoader(new URL[] { new File(hadoopConfDir)
-					.toURI().toURL() }, getClass().getClassLoader());
-		} else if (hadoopHome != null) {
-			logger.info("Using hadoop config found in " + hadoopHome);
-			retVal = new URLClassLoader(
-					new URL[] { new File(hadoopHome, "conf").toURI().toURL() },
-					getClass().getClassLoader());
-		} else {
-			logger.info("HADOOP_HOME not set, using default hadoop config.");
-			retVal = getClass().getClassLoader();
-		}
-
-		return retVal;
-	}
+//	private ClassLoader getBaseClassloader() throws MalformedURLException {
+//		final ClassLoader retVal;
+//
+//		String hadoopHome = System.getenv("HADOOP_HOME");
+//		String hadoopConfDir = System.getenv("HADOOP_CONF_DIR");
+//
+//		if (hadoopConfDir != null) {
+//			logger.info("Using hadoop config found in " + hadoopConfDir);
+//			retVal = new URLClassLoader(new URL[] { new File(hadoopConfDir)
+//					.toURI().toURL() }, getClass().getClassLoader());
+//		} else if (hadoopHome != null) {
+//			logger.info("Using hadoop config found in " + hadoopHome);
+//			retVal = new URLClassLoader(
+//					new URL[] { new File(hadoopHome, "conf").toURI().toURL() },
+//					getClass().getClassLoader());
+//		} else {
+//			logger.info("HADOOP_HOME not set, using default hadoop config.");
+//			retVal = getClass().getClassLoader();
+//		}
+//
+//		return retVal;
+//	}
 
 	public ClassLoader getClassLoader() {
 		return baseClassLoader;
@@ -420,16 +746,19 @@ public class AzkabanWebServer extends AzkabanServer {
 		logger.info("Setting up web resource dir " + staticDir);
 		Context root = new Context(server, "/", Context.SESSIONS);
 		root.setMaxFormContentSize(MAX_FORM_CONTENT_SIZE);
-		
+
+		String defaultServletPath = azkabanSettings.getString("azkaban.default.servlet.path", "/index");
 		root.setResourceBase(staticDir);
-		ServletHolder index = new ServletHolder(new IndexServlet());
+		ServletHolder indexRedirect = new ServletHolder(new IndexRedirectServlet(defaultServletPath));
+		root.addServlet(indexRedirect, "/");
+		ServletHolder index = new ServletHolder(new ProjectServlet());
 		root.addServlet(index, "/index");
-		root.addServlet(index, "/");
 
 		ServletHolder staticServlet = new ServletHolder(new DefaultServlet());
 		root.addServlet(staticServlet, "/css/*");
 		root.addServlet(staticServlet, "/js/*");
 		root.addServlet(staticServlet, "/images/*");
+		root.addServlet(staticServlet, "/fonts/*");
 		root.addServlet(staticServlet, "/favicon.ico");
 		
 		root.addServlet(new ServletHolder(new ProjectManagerServlet()),"/manager");
@@ -437,9 +766,25 @@ public class AzkabanWebServer extends AzkabanServer {
 		root.addServlet(new ServletHolder(new HistoryServlet()), "/history");
 		root.addServlet(new ServletHolder(new ScheduleServlet()),"/schedule");
 		root.addServlet(new ServletHolder(new JMXHttpServlet()),"/jmx");
+		root.addServlet(new ServletHolder(new TriggerManagerServlet()),"/triggers");
 		
 		String viewerPluginDir = azkabanSettings.getString("viewer.plugin.dir", "plugins/viewer");
 		app.setViewerPlugins(loadViewerPlugins(root, viewerPluginDir, app.getVelocityEngine()));
+		
+		// triggerplugin
+		String triggerPluginDir = azkabanSettings.getString("trigger.plugin.dir", "plugins/triggers");
+		Map<String, TriggerPlugin> triggerPlugins = loadTriggerPlugins(root, triggerPluginDir, app);
+		app.setTriggerPlugins(triggerPlugins);
+		// always have basic time trigger
+		//TODO: find something else to do the job
+//		app.getTriggerManager().addTriggerAgent(app.getScheduleManager().getTriggerSource(), app.getScheduleManager());
+		// add additional triggers
+//		for(TriggerPlugin plugin : triggerPlugins.values()) {
+//			TriggerAgent agent = plugin.getAgent();
+//			app.getTriggerManager().addTriggerAgent(agent.getTriggerSource(), agent);
+//		}
+		// fire up
+		app.getTriggerManager().start();
 
 		root.setAttribute(AzkabanServletContextListener.AZKABAN_SERVLET_CONTEXT_KEY, app);
 		try {
@@ -468,6 +813,150 @@ public class AzkabanWebServer extends AzkabanServer {
 		logger.info("Server running on " + (ssl ? "ssl" : "") + " port " + port + ".");
 	}
 
+	private static Map<String, TriggerPlugin> loadTriggerPlugins(Context root, String pluginPath, AzkabanWebServer azkabanWebApp) {
+		File triggerPluginPath = new File(pluginPath);
+		if (!triggerPluginPath.exists()) {
+			//return Collections.<String, TriggerPlugin>emptyMap();
+			return new HashMap<String, TriggerPlugin>();
+		}
+			
+		Map<String, TriggerPlugin> installedTriggerPlugins = new HashMap<String, TriggerPlugin>();
+		ClassLoader parentLoader = AzkabanWebServer.class.getClassLoader();
+		File[] pluginDirs = triggerPluginPath.listFiles();
+		ArrayList<String> jarPaths = new ArrayList<String>();
+		for (File pluginDir: pluginDirs) {
+			if (!pluginDir.exists()) {
+				logger.error("Error! Trigger plugin path " + pluginDir.getPath() + " doesn't exist.");
+				continue;
+			}
+			
+			if (!pluginDir.isDirectory()) {
+				logger.error("The plugin path " + pluginDir + " is not a directory.");
+				continue;
+			}
+			
+			// Load the conf directory
+			File propertiesDir = new File(pluginDir, "conf");
+			Props pluginProps = null;
+			if (propertiesDir.exists() && propertiesDir.isDirectory()) {
+				File propertiesFile = new File(propertiesDir, "plugin.properties");
+				File propertiesOverrideFile = new File(propertiesDir, "override.properties");
+				
+				if (propertiesFile.exists()) {
+					if (propertiesOverrideFile.exists()) {
+						pluginProps = PropsUtils.loadProps(null, propertiesFile, propertiesOverrideFile);
+					}
+					else {
+						pluginProps = PropsUtils.loadProps(null, propertiesFile);
+					}
+				}
+				else {
+					logger.error("Plugin conf file " + propertiesFile + " not found.");
+					continue;
+				}
+			}
+			else {
+				logger.error("Plugin conf path " + propertiesDir + " not found.");
+				continue;
+			}
+			
+			String pluginName = pluginProps.getString("trigger.name");
+//			String pluginWebPath = pluginProps.getString("trigger.web.path");
+//			int pluginOrder = pluginProps.getInt("trigger.order", 0);
+//			boolean pluginHidden = pluginProps.getBoolean("trigger.hidden", false);
+			List<String> extLibClasspath = pluginProps.getStringList("trigger.external.classpaths", (List<String>)null);
+			
+			String pluginClass = pluginProps.getString("trigger.class");
+			if (pluginClass == null) {
+				logger.error("Trigger class is not set.");
+			}
+			else {
+				logger.error("Plugin class " + pluginClass);
+			}
+			
+			URLClassLoader urlClassLoader = null;
+			File libDir = new File(pluginDir, "lib");
+			if (libDir.exists() && libDir.isDirectory()) {
+				File[] files = libDir.listFiles();
+				
+				ArrayList<URL> urls = new ArrayList<URL>();
+				for (int i=0; i < files.length; ++i) {
+					try {
+						URL url = files[i].toURI().toURL();
+						urls.add(url);
+					} catch (MalformedURLException e) {
+						logger.error(e);
+					}
+				}
+				if (extLibClasspath != null) {
+					for (String extLib : extLibClasspath) {
+						try {
+							File file = new File(pluginDir, extLib);
+							URL url = file.toURI().toURL();
+							urls.add(url);
+						} catch (MalformedURLException e) {
+							logger.error(e);
+						}
+					}
+				}
+				
+				urlClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), parentLoader);
+			}
+			else {
+				logger.error("Library path " + propertiesDir + " not found.");
+				continue;
+			}
+			
+			Class<?> triggerClass = null;
+			try {
+				triggerClass = urlClassLoader.loadClass(pluginClass);
+			}
+			catch (ClassNotFoundException e) {
+				logger.error("Class " + pluginClass + " not found.");
+				continue;
+			}
+
+			String source = FileIOUtils.getSourcePathFromClass(triggerClass);
+			logger.info("Source jar " + source);
+			jarPaths.add("jar:file:" + source);
+			
+			Constructor<?> constructor = null;
+			try {
+				constructor = triggerClass.getConstructor(String.class, Props.class, Context.class, AzkabanWebServer.class);
+			} catch (NoSuchMethodException e) {
+				logger.error("Constructor not found in " + pluginClass);
+				continue;
+			}
+			
+			Object obj = null;
+			try {
+				obj = constructor.newInstance(pluginName, pluginProps, root, azkabanWebApp);
+			} catch (Exception e) {
+				logger.error(e);
+			} 
+			
+			if (!(obj instanceof TriggerPlugin)) {
+				logger.error("The object is not an TriggerPlugin");
+				continue;
+			}
+			
+			TriggerPlugin plugin = (TriggerPlugin) obj;
+			installedTriggerPlugins.put(pluginName, plugin);
+		}
+		
+		// Velocity needs the jar resource paths to be set.
+		String jarResourcePath = StringUtils.join(jarPaths, ", ");
+		logger.info("Setting jar resource path " + jarResourcePath);
+		VelocityEngine ve = azkabanWebApp.getVelocityEngine();
+		ve.addProperty("jar.resource.loader.path", jarResourcePath);
+		
+		return installedTriggerPlugins;
+	}
+	
+	public Map<String, TriggerPlugin> getTriggerPlugins() {
+		return triggerPlugins;
+	}
+	
 	private static List<ViewerPlugin> loadViewerPlugins(Context root, String pluginPath, VelocityEngine ve) {
 		File viewerPluginPath = new File(pluginPath);
 		if (!viewerPluginPath.exists()) {
@@ -542,14 +1031,36 @@ public class AzkabanWebServer extends AzkabanServer {
 						logger.error(e);
 					}
 				}
+				
+				// Load any external libraries.
 				if (extLibClasspath != null) {
 					for (String extLib : extLibClasspath) {
-						try {
-							File file = new File(pluginDir, extLib);
-							URL url = file.toURI().toURL();
-							urls.add(url);
-						} catch (MalformedURLException e) {
-							logger.error(e);
+						File extLibFile = new File(pluginDir, extLib);
+						if (extLibFile.exists()) {
+							if (extLibFile.isDirectory()) {
+								// extLibFile is a directory; load all the files in the directory.
+								File[] extLibFiles = extLibFile.listFiles();
+								for (int i=0; i < extLibFiles.length; ++i) {
+									try {
+										URL url = extLibFiles[i].toURI().toURL();
+										urls.add(url);
+									} catch (MalformedURLException e) {
+										logger.error(e);
+									}
+								}
+							}
+							else { // extLibFile is a file
+								try {
+									URL url = extLibFile.toURI().toURL();
+									urls.add(url);
+								} catch (MalformedURLException e) {
+									logger.error(e);
+								}
+							}
+						}
+						else {
+							logger.error("External library path " + extLibFile.getAbsolutePath() + " not found.");
+							continue;
 						}
 					}
 				}
@@ -557,7 +1068,7 @@ public class AzkabanWebServer extends AzkabanServer {
 				urlClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), parentLoader);
 			}
 			else {
-				logger.error("Library path " + propertiesDir + " not found.");
+				logger.error("Library path " + libDir.getAbsolutePath() + " not found.");
 				continue;
 			}
 			
@@ -686,9 +1197,12 @@ public class AzkabanWebServer extends AzkabanServer {
 		mbeanServer = ManagementFactory.getPlatformMBeanServer();
 
 		registerMbean("jetty", new JmxJettyServer(server));
-		registerMbean("scheduler", new JmxScheduler(scheduleManager));
-		registerMbean("slaManager", new JmxSLAManager(slaManager));
-		registerMbean("executorManager", new JmxExecutorManager(executorManager));
+		registerMbean("triggerManager", new JmxTriggerManager(triggerManager));
+		if(executorManager instanceof ExecutorManager) {
+			registerMbean("executorManager", new JmxExecutorManager((ExecutorManager) executorManager));
+		}
+//		registerMbean("executorManager", new JmxExecutorManager(executorManager));
+//		registerMbean("executorManager", new JmxExecutorManager(executorManager));
 	}
 	
 	public void close() {
@@ -701,7 +1215,9 @@ public class AzkabanWebServer extends AzkabanServer {
 			logger.error("Failed to cleanup MBeanServer", e);
 		}
 		scheduleManager.shutdown();
-		slaManager.shutdown();
+//		if(executorManager instanceof ExecutorManagerLocalAdapter) {
+//			((ExecutorManagerLocalAdapter)executorManager).getExecutorManager().shutdown();
+//		}
 		executorManager.shutdown();
 	}
 	
diff --git a/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java b/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
index 8e87919..8ac470e 100644
--- a/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
+++ b/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -55,7 +55,7 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
 	public static final String XML_MIME_TYPE = "application/xhtml+xml";
 	public static final String JSON_MIME_TYPE = "application/json";
 
-	private static final WebUtils utils = new WebUtils();
+	protected static final WebUtils utils = new WebUtils();
 	
 	private AzkabanServer application;
 	private String name;
@@ -63,7 +63,9 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
 	private String color;
 
 	private List<ViewerPlugin> viewerPlugins;
-
+	private List<TriggerPlugin> triggerPlugins;
+	
+	
 	/**
 	 * To retrieve the application for the servlet
 	 * 
@@ -90,6 +92,7 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
 		if (application instanceof AzkabanWebServer) {
 			AzkabanWebServer server = (AzkabanWebServer)application;
 			viewerPlugins = server.getViewerPlugins();
+			triggerPlugins = new ArrayList<TriggerPlugin>(server.getTriggerPlugins().values());
 		}
 	}
 	
@@ -302,6 +305,10 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
 //			page.add("viewerPath", plugin.getPluginPath());
 		}
 		
+		if(triggerPlugins != null && !triggerPlugins.isEmpty()) {
+			page.add("triggerPlugins", triggerPlugins);
+		}
+		
 		return page;
 	}
 
@@ -330,6 +337,10 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
 			page.add("viewerPath", plugin.getPluginPath());
 		}
 		
+		if(triggerPlugins != null && !triggerPlugins.isEmpty()) {
+			page.add("triggers", triggerPlugins);
+		}
+		
 		return page;
 	}
 	
diff --git a/src/java/azkaban/webapp/servlet/AbstractServiceServlet.java b/src/java/azkaban/webapp/servlet/AbstractServiceServlet.java
new file mode 100644
index 0000000..eb1013e
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/AbstractServiceServlet.java
@@ -0,0 +1,91 @@
+package azkaban.webapp.servlet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.codehaus.jackson.map.ObjectMapper;
+import azkaban.webapp.AzkabanServer;
+
+public class AbstractServiceServlet extends HttpServlet{
+	
+	private static final long serialVersionUID = 1L;
+	public static final String JSON_MIME_TYPE = "application/json";
+	
+	private AzkabanServer application;
+
+	@Override
+	public void init(ServletConfig config) throws ServletException {
+		application = (AzkabanServer) config.getServletContext().getAttribute(AzkabanServletContextListener.AZKABAN_SERVLET_CONTEXT_KEY);
+
+		if (application == null) {
+			throw new IllegalStateException(
+					"No batch application is defined in the servlet context!");
+		}
+	}
+
+	protected void writeJSON(HttpServletResponse resp, Object obj) throws IOException {
+		resp.setContentType(JSON_MIME_TYPE);
+		ObjectMapper mapper = new ObjectMapper();
+		OutputStream stream = resp.getOutputStream();
+		mapper.writeValue(stream, obj);
+	}
+
+	public boolean hasParam(HttpServletRequest request, String param) {
+		return request.getParameter(param) != null;
+	}
+
+	public String getParam(HttpServletRequest request, String name)
+			throws ServletException {
+		String p = request.getParameter(name);
+		if (p == null)
+			throw new ServletException("Missing required parameter '" + name + "'.");
+		else
+			return p;
+	}
+	
+	public String getParam(HttpServletRequest request, String name, String defaultVal ) {
+		String p = request.getParameter(name);
+		if (p == null) {
+			return defaultVal;
+		}
+
+		return p;
+	}
+
+	public int getIntParam(HttpServletRequest request, String name) throws ServletException {
+		String p = getParam(request, name);
+		return Integer.parseInt(p);
+	}
+	
+	public int getIntParam(HttpServletRequest request, String name, int defaultVal) {
+		if (hasParam(request, name)) {
+			try {
+				return getIntParam(request, name);
+			} catch (Exception e) {
+				return defaultVal;
+			}
+		}
+		return defaultVal;
+	}
+	
+	public long getLongParam(HttpServletRequest request, String name) throws ServletException {
+		String p = getParam(request, name);
+		return Long.parseLong(p);
+	}
+	
+	public long getLongParam(HttpServletRequest request, String name, long defaultVal) {
+		if (hasParam(request, name)) {
+			try {
+				return getLongParam(request, name);
+			} catch (Exception e) {
+				return defaultVal;
+			}
+		}
+		return defaultVal;
+	}
+	
+}
diff --git a/src/java/azkaban/webapp/servlet/AzkabanServletContextListener.java b/src/java/azkaban/webapp/servlet/AzkabanServletContextListener.java
index b8fad67..db4437d 100644
--- a/src/java/azkaban/webapp/servlet/AzkabanServletContextListener.java
+++ b/src/java/azkaban/webapp/servlet/AzkabanServletContextListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/webapp/servlet/ExecutorServlet.java b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
index a542a82..6cc091b 100644
--- a/src/java/azkaban/webapp/servlet/ExecutorServlet.java
+++ b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -30,8 +30,8 @@ import javax.servlet.http.HttpServletResponse;
 import azkaban.executor.ExecutableFlow;
 import azkaban.executor.ExecutableNode;
 import azkaban.executor.ExecutionOptions;
+import azkaban.executor.ExecutorManagerAdapter;
 import azkaban.executor.ExecutionOptions.FailureAction;
-import azkaban.executor.ExecutorManager;
 import azkaban.executor.ExecutorManagerException;
 import azkaban.executor.Status;
 import azkaban.flow.Flow;
@@ -39,18 +39,19 @@ import azkaban.project.Project;
 import azkaban.project.ProjectManager;
 import azkaban.scheduler.Schedule;
 import azkaban.scheduler.ScheduleManager;
+import azkaban.scheduler.ScheduleManagerException;
 import azkaban.user.Permission;
 import azkaban.user.User;
 import azkaban.user.Permission.Type;
-import azkaban.utils.FileIOUtils.JobMetaData;
 import azkaban.utils.FileIOUtils.LogData;
+import azkaban.utils.LogSummary;
 import azkaban.webapp.AzkabanWebServer;
 import azkaban.webapp.session.Session;
 
 public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 	private static final long serialVersionUID = 1L;
 	private ProjectManager projectManager;
-	private ExecutorManager executorManager;
+	private ExecutorManagerAdapter executorManager;
 	private ScheduleManager scheduleManager;
 	private ExecutorVelocityHelper velocityHelper;
 
@@ -65,13 +66,14 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 	}
 
 	@Override
-	protected void handleGet(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
+	protected void handleGet(HttpServletRequest req, HttpServletResponse resp, 
+			Session session) throws ServletException, IOException {
 		if (hasParam(req, "ajax")) {
 			handleAJAXAction(req, resp, session);
 		}
 		else if (hasParam(req, "execid")) {
 			if (hasParam(req, "job")) {
-				handleExecutionJobPage(req, resp, session);
+				handleExecutionJobDetailsPage(req, resp, session);
 			}
 			else {
 				handleExecutionFlowPage(req, resp, session);
@@ -82,8 +84,100 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 		}
 	}
 	
-	private void handleExecutionJobPage(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
-		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/joblogpage.vm");
+	private void handleAJAXAction(HttpServletRequest req, 
+			HttpServletResponse resp, Session session) 
+			throws ServletException, IOException {
+		HashMap<String, Object> ret = new HashMap<String, Object>();
+		String ajaxName = getParam(req, "ajax");
+		
+		if (hasParam(req, "execid")) {
+			int execid = getIntParam(req, "execid");
+			ExecutableFlow exFlow = null;
+
+			try {
+				exFlow = executorManager.getExecutableFlow(execid);
+			} catch (ExecutorManagerException e) {
+				ret.put("error", "Error fetching execution '" + execid + "': " + e.getMessage());
+			}
+
+			if (exFlow == null) {
+				ret.put("error", "Cannot find execution '" + execid + "'");
+			}
+			else {
+				if (ajaxName.equals("fetchexecflow")) {
+					ajaxFetchExecutableFlow(req, resp, ret, session.getUser(), exFlow);
+				}
+				else if (ajaxName.equals("fetchexecflowupdate")) {
+					ajaxFetchExecutableFlowUpdate(req, resp, ret, session.getUser(), exFlow);
+				}
+				else if (ajaxName.equals("cancelFlow")) {
+					ajaxCancelFlow(req, resp, ret, session.getUser(), exFlow);
+				}
+				else if (ajaxName.equals("restartFlow")) {
+					ajaxRestartFlow(req, resp, ret, session.getUser(), exFlow);
+				}
+				else if (ajaxName.equals("pauseFlow")) {
+					ajaxPauseFlow(req, resp, ret, session.getUser(), exFlow);
+				}
+				else if (ajaxName.equals("resumeFlow")) {
+					ajaxResumeFlow(req, resp, ret, session.getUser(), exFlow);
+				}
+				else if (ajaxName.equals("fetchExecFlowLogs")) {
+					ajaxFetchExecFlowLogs(req, resp, ret, session.getUser(), exFlow);
+				}
+				else if (ajaxName.equals("fetchExecJobLogs")) {
+					ajaxFetchJobLogs(req, resp, ret, session.getUser(), exFlow);
+				}
+				else if (ajaxName.equals("fetchExecJobSummary")) {
+					ajaxFetchJobSummary(req, resp, ret, session.getUser(), exFlow);
+				}
+				else if (ajaxName.equals("retryFailedJobs")) {
+					ajaxRestartFailed(req, resp, ret, session.getUser(), exFlow);
+				}
+//				else if (ajaxName.equals("fetchLatestJobStatus")) {
+//					ajaxFetchLatestJobStatus(req, resp, ret, session.getUser(), exFlow);
+//				}
+				else if (ajaxName.equals("flowInfo")) {
+					//String projectName = getParam(req, "project");
+					//Project project = projectManager.getProject(projectName);
+					//String flowName = getParam(req, "flow");
+					ajaxFetchExecutableFlowInfo(req, resp, ret, session.getUser(), exFlow);
+				}
+			}
+		}
+		else if (ajaxName.equals("getRunning")) {
+			String projectName = getParam(req, "project");
+			String flowName = getParam(req, "flow");
+			ajaxGetFlowRunning(req, resp, ret, session.getUser(), projectName, flowName);
+		}
+		else if (ajaxName.equals("flowInfo")) {
+			String projectName = getParam(req, "project");
+			String flowName = getParam(req, "flow");
+			ajaxFetchFlowInfo(req, resp, ret, session.getUser(), projectName, flowName);
+		}
+		else {
+			String projectName = getParam(req, "project");
+			
+			ret.put("project", projectName);
+			if (ajaxName.equals("executeFlow")) {
+				ajaxAttemptExecuteFlow(req, resp, ret, session.getUser());
+			}
+		}
+		if (ret != null) {
+			this.writeJSON(resp, ret);
+		}
+	}
+
+	@Override
+	protected void handlePost(HttpServletRequest req, HttpServletResponse resp, 
+			Session session) throws ServletException, IOException {
+		if (hasParam(req, "ajax")) {
+			handleAJAXAction(req, resp, session);
+		}
+	}
+	
+	private void handleExecutionJobDetailsPage(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
+		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/jobdetailspage.vm");
 		User user = session.getUser();
 		int execId = getIntParam(req, "execid");
 		String jobId = getParam(req, "job");
@@ -96,7 +190,7 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 		try {
 			flow = executorManager.getExecutableFlow(execId);
 			if (flow == null) {
-				page.add("errorMsg", "Error loading executing flow " + execId + " not found.");
+				page.add("errorMsg", "Error loading executing flow " + execId + ": not found.");
 				page.render();
 				return;
 			}
@@ -211,93 +305,7 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 		}
 		
 		return null;
-	}
-	
-	@Override
-	protected void handlePost(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
-		if (hasParam(req, "ajax")) {
-			handleAJAXAction(req, resp, session);
-		}
-	}
-
-	private void handleAJAXAction(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
-		HashMap<String, Object> ret = new HashMap<String, Object>();
-		String ajaxName = getParam(req, "ajax");
-		
-		if (hasParam(req, "execid")) {
-			int execid = getIntParam(req, "execid");
-			ExecutableFlow exFlow = null;
-
-			try {
-				exFlow = executorManager.getExecutableFlow(execid);
-			} catch (ExecutorManagerException e) {
-				ret.put("error", "Error fetching execution '" + execid + "': " + e.getMessage());
-			}
-
-			if (exFlow == null) {
-				ret.put("error", "Cannot find execution '" + execid + "'");
-			}
-			else {
-				if (ajaxName.equals("fetchexecflow")) {
-					ajaxFetchExecutableFlow(req, resp, ret, session.getUser(), exFlow);
-				}
-				else if (ajaxName.equals("fetchexecflowupdate")) {
-					ajaxFetchExecutableFlowUpdate(req, resp, ret, session.getUser(), exFlow);
-				}
-				else if (ajaxName.equals("cancelFlow")) {
-					ajaxCancelFlow(req, resp, ret, session.getUser(), exFlow);
-				}
-				else if (ajaxName.equals("restartFlow")) {
-					ajaxRestartFlow(req, resp, ret, session.getUser(), exFlow);
-				}
-				else if (ajaxName.equals("pauseFlow")) {
-					ajaxPauseFlow(req, resp, ret, session.getUser(), exFlow);
-				}
-				else if (ajaxName.equals("resumeFlow")) {
-					ajaxResumeFlow(req, resp, ret, session.getUser(), exFlow);
-				}
-				else if (ajaxName.equals("fetchExecFlowLogs")) {
-					ajaxFetchExecFlowLogs(req, resp, ret, session.getUser(), exFlow);
-				}
-				else if (ajaxName.equals("fetchExecJobLogs")) {
-					ajaxFetchJobLogs(req, resp, ret, session.getUser(), exFlow);
-				}
-				else if (ajaxName.equals("retryFailedJobs")) {
-					ajaxRestartFailed(req, resp, ret, session.getUser(), exFlow);
-				}
-//				else if (ajaxName.equals("fetchLatestJobStatus")) {
-//					ajaxFetchLatestJobStatus(req, resp, ret, session.getUser(), exFlow);
-//				}
-				else if (ajaxName.equals("flowInfo")) {
-					//String projectName = getParam(req, "project");
-					//Project project = projectManager.getProject(projectName);
-					//String flowName = getParam(req, "flow");
-					ajaxFetchExecutableFlowInfo(req, resp, ret, session.getUser(), exFlow);
-				}
-			}
-		}
-		else if (ajaxName.equals("getRunning")) {
-			String projectName = getParam(req, "project");
-			String flowName = getParam(req, "flow");
-			ajaxGetFlowRunning(req, resp, ret, session.getUser(), projectName, flowName);
-		}
-		else if (ajaxName.equals("flowInfo")) {
-			String projectName = getParam(req, "project");
-			String flowName = getParam(req, "flow");
-			ajaxFetchFlowInfo(req, resp, ret, session.getUser(), projectName, flowName);
-		}
-		else {
-			String projectName = getParam(req, "project");
-			
-			ret.put("project", projectName);
-			if (ajaxName.equals("executeFlow")) {
-				ajaxAttemptExecuteFlow(req, resp, ret, session.getUser());
-			}
-		}
-		if (ret != null) {
-			this.writeJSON(resp, ret);
-		}
-	}
+	}	
 
 //	private void ajaxFetchLatestJobStatus(HttpServletRequest req,HttpServletResponse resp, HashMap<String, Object> ret, User user, ExecutableFlow exFlow) {
 //		Project project = getProjectAjaxByPermission(ret, exFlow.getProjectId(), user, Type.READ);
@@ -442,9 +450,9 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 			throw new ServletException(e);
 		}
 	}
-
+	
 	/**
-	 * Gets the job metadata through ajax plain text stream to reduce memory overhead.
+	 * Gets the job summary.
 	 * 
 	 * @param req
 	 * @param resp
@@ -452,15 +460,12 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 	 * @param exFlow
 	 * @throws ServletException
 	 */
-	private void ajaxFetchJobMetaData(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, ExecutableFlow exFlow) throws ServletException {
+	private void ajaxFetchJobSummary(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, ExecutableFlow exFlow) throws ServletException {
 		Project project = getProjectAjaxByPermission(ret, exFlow.getProjectId(), user, Type.READ);
 		if (project == null) {
 			return;
 		}
 		
-		int offset = this.getIntParam(req, "offset");
-		int length = this.getIntParam(req, "length");
-		
 		String jobId = this.getParam(req, "jobId");
 		resp.setCharacterEncoding("utf-8");
 
@@ -472,22 +477,27 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 			}
 			
 			int attempt = this.getIntParam(req, "attempt", node.getAttempt());
-			JobMetaData data = executorManager.getExecutionJobMetaData(exFlow, jobId, offset, length, attempt);
-			if (data == null) {
-				ret.put("length", 0);
-				ret.put("offset", offset);
-				ret.put("data", "");
-			}
-			else {
-				ret.put("length", data.getLength());
-				ret.put("offset", data.getOffset());
-				ret.put("data", data.getData());
+			LogData data = executorManager.getExecutionJobLog(exFlow, jobId, 0, Integer.MAX_VALUE, attempt);
+			
+			LogSummary summary = new LogSummary(data);
+			ret.put("commandProperties", summary.getCommandProperties());
+			
+			String jobType = summary.getJobType();
+			
+			if (jobType.contains("pig")) {
+				ret.put("summaryTableHeaders", summary.getPigSummaryTableHeaders());
+				ret.put("summaryTableData", summary.getPigSummaryTableData());
+				ret.put("statTableHeaders", summary.getPigStatTableHeaders());
+				ret.put("statTableData", summary.getPigStatTableData());
+			} else if (jobType.contains("hive")) {
+				ret.put("hiveQueries", summary.getHiveQueries());
+				ret.put("hiveQueryJobs", summary.getHiveQueryJobs());
 			}
 		} catch (ExecutorManagerException e) {
 			throw new ServletException(e);
 		}
 	}
-	
+
 	private void ajaxFetchFlowInfo(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, String projectName, String flowId) throws ServletException {
 		Project project = getProjectAjaxByPermission(ret, projectName, user, Type.READ);
 		if (project == null) {
@@ -504,11 +514,16 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 		ret.put("failureEmails", flow.getFailureEmails());
 		
 		Schedule sflow = null;
-		for (Schedule sched: scheduleManager.getSchedules()) {
-			if (sched.getProjectId() == project.getId() && sched.getFlowName().equals(flowId)) {
-				sflow = sched;
-				break;
+		try {
+			for (Schedule sched: scheduleManager.getSchedules()) {
+				if (sched.getProjectId() == project.getId() && sched.getFlowName().equals(flowId)) {
+					sflow = sched;
+					break;
+				}
 			}
+		} catch (ScheduleManagerException e) {
+			// TODO Auto-generated catch block
+			throw new ServletException(e);
 		}
 		
 		if (sflow != null) {
@@ -721,7 +736,7 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 			return;
 		}
 		
-		ret.put("flow",  flowId);
+		ret.put("flow", flowId);
 		Flow flow = project.getFlow(flowId);
 		if (flow == null) {
 			ret.put("error", "Flow '" + flowId + "' cannot be found in project " + project);
@@ -741,7 +756,7 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 			return;
 		}
 		
-		ret.put("flow",  flowId);
+		ret.put("flow", flowId);
 		Flow flow = project.getFlow(flowId);
 		if (flow == null) {
 			ret.put("error", "Flow '" + flowId + "' cannot be found in project " + project);
@@ -760,9 +775,10 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 		if (!options.isSuccessEmailsOverridden()) {
 			options.setSuccessEmails(flow.getSuccessEmails());
 		}
+		options.setMailCreator(flow.getMailCreator());
 		
 		try {
-			String message = executorManager.submitExecutableFlow(exflow);
+			String message = executorManager.submitExecutableFlow(exflow, user.getUserId());
 			ret.put("message", message);
 		}
 		catch (ExecutorManagerException e) {
diff --git a/src/java/azkaban/webapp/servlet/HistoryServlet.java b/src/java/azkaban/webapp/servlet/HistoryServlet.java
index aa6d2ad..a46d645 100644
--- a/src/java/azkaban/webapp/servlet/HistoryServlet.java
+++ b/src/java/azkaban/webapp/servlet/HistoryServlet.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -14,7 +14,6 @@
  * the License.
  */
 
-
 package azkaban.webapp.servlet;
 
 import java.io.IOException;
@@ -32,7 +31,7 @@ import org.joda.time.format.DateTimeFormat;
 
 
 import azkaban.executor.ExecutableFlow;
-import azkaban.executor.ExecutorManager;
+import azkaban.executor.ExecutorManagerAdapter;
 import azkaban.executor.ExecutorManagerException;
 import azkaban.project.Project;
 import azkaban.project.ProjectManager;
@@ -42,7 +41,7 @@ import azkaban.webapp.session.Session;
 public class HistoryServlet extends LoginAbstractAzkabanServlet {
 
 	private static final long serialVersionUID = 1L;
-	private ExecutorManager executorManager;
+	private ExecutorManagerAdapter executorManager;
 	private ProjectManager projectManager;
 	private ExecutorVMHelper vmHelper;
 	
diff --git a/src/java/azkaban/webapp/servlet/HttpRequestUtils.java b/src/java/azkaban/webapp/servlet/HttpRequestUtils.java
index 4e71930..896d93b 100644
--- a/src/java/azkaban/webapp/servlet/HttpRequestUtils.java
+++ b/src/java/azkaban/webapp/servlet/HttpRequestUtils.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.webapp.servlet;
 
 import java.util.Arrays;
@@ -10,6 +26,7 @@ import javax.servlet.http.HttpServletRequest;
 
 import azkaban.executor.ExecutionOptions;
 import azkaban.executor.ExecutionOptions.FailureAction;
+import azkaban.executor.mail.DefaultMailCreator;
 
 public class HttpRequestUtils {
 	public static ExecutionOptions parseFlowOptions(HttpServletRequest req) throws ServletException {
@@ -36,7 +53,7 @@ public class HttpRequestUtils {
 			boolean override = getBooleanParam(req, "successEmailsOverride", false);
 			execOptions.setSuccessEmailsOverridden(override);
 		}
-		
+
 		if (hasParam(req, "failureEmails")) {
 			String emails = getParam(req, "failureEmails");
 			if (!emails.isEmpty()) {
@@ -72,6 +89,11 @@ public class HttpRequestUtils {
 				execOptions.setPipelineLevel(queueLevel);
 			}
 		}
+		String mailCreator = DefaultMailCreator.DEFAULT_MAIL_CREATOR;
+		if (hasParam(req, "mailCreator")) {
+			mailCreator = getParam(req, "mailCreator");
+			execOptions.setMailCreator(mailCreator);
+		}
 		
 		Map<String, String> flowParamGroup = getParamGroup(req, "flowOverride");
 		execOptions.setFlowParameters(flowParamGroup);
diff --git a/src/java/azkaban/webapp/servlet/IndexRedirectServlet.java b/src/java/azkaban/webapp/servlet/IndexRedirectServlet.java
new file mode 100644
index 0000000..f788b40
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/IndexRedirectServlet.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.webapp.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import azkaban.webapp.session.Session;
+
+/**
+ * The main page
+ */
+public class IndexRedirectServlet extends LoginAbstractAzkabanServlet {
+	private static final long serialVersionUID = -1;
+	private String defaultServletPath;
+
+	public IndexRedirectServlet(String defaultServletPath) {
+		this.defaultServletPath = defaultServletPath;
+		if (this.defaultServletPath.isEmpty() || this.defaultServletPath.equals("/")) {
+			this.defaultServletPath = "/index";
+		}
+	}
+
+	@Override
+	protected void handleGet(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
+		resp.sendRedirect(defaultServletPath);
+	}
+
+	@Override
+	protected void handlePost(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
+		resp.sendRedirect(defaultServletPath);
+	}
+}
diff --git a/src/java/azkaban/webapp/servlet/JMXHttpServlet.java b/src/java/azkaban/webapp/servlet/JMXHttpServlet.java
index 75982d1..616cc76 100644
--- a/src/java/azkaban/webapp/servlet/JMXHttpServlet.java
+++ b/src/java/azkaban/webapp/servlet/JMXHttpServlet.java
@@ -1,9 +1,24 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.webapp.servlet;
 
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Set;
 
 import javax.management.MBeanAttributeInfo;
 import javax.management.MBeanInfo;
@@ -16,7 +31,8 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.log4j.Logger;
 
 import azkaban.executor.ConnectorParams;
-import azkaban.executor.ExecutorManager;
+import azkaban.executor.ExecutorManagerAdapter;
+import azkaban.trigger.TriggerManager;
 import azkaban.user.Permission;
 import azkaban.user.Role;
 import azkaban.user.User;
@@ -37,7 +53,8 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet implements Conne
 
 	private UserManager userManager;
 	private AzkabanWebServer server;
-	private ExecutorManager executorManager;
+	private ExecutorManagerAdapter executorManager;
+	private TriggerManager triggerManager;
 	
 	@Override
 	public void init(ServletConfig config) throws ServletException {
@@ -46,6 +63,8 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet implements Conne
 		server = (AzkabanWebServer)getApplication();
 		userManager = server.getUserManager();
 		executorManager = server.getExecutorManager();
+
+		triggerManager = server.getTriggerManager();
 	}
 	
 	@Override
@@ -53,7 +72,7 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet implements Conne
 		if (hasParam(req, "ajax")){
 			Map<String,Object> ret = new HashMap<String,Object>();
 
-			if(!hasAdminRole(session.getUser())) {
+			if(!hasPermission(session.getUser(), Permission.Type.METRICS)) {
 				ret.put("error", "User " + session.getUser().getUserId() + " has no permission.");
 				this.writeJSON(resp, ret, true);
 				return;
@@ -71,6 +90,17 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet implements Conne
 				Map<String, Object> result = executorManager.callExecutorJMX(hostPort, JMX_GET_ALL_MBEAN_ATTRIBUTES, mbean);
 				ret = result;
 			}
+//			else 
+//				if (TriggerConnectorParams.JMX_GET_ALL_TRIGGER_SERVER_ATTRIBUTES.equals(ajax)) {
+//				if(!hasParam(req, JMX_MBEAN) || !hasParam(req, JMX_HOSTPORT)) {
+//					ret.put("error", "Parameters '" + JMX_MBEAN + "' and '"+ JMX_HOSTPORT +"' must be set");
+//					this.writeJSON(resp, ret, true);
+//					return;
+//				}
+////				String hostPort = getParam(req, JMX_HOSTPORT);
+////				String mbean = getParam(req, JMX_MBEAN);
+//				ret = triggerManager.getJMX().getAllJMXMbeans();
+//			}
 			else if (JMX_GET_MBEANS.equals(ajax)) {
 				ret.put("mbeans", server.getMbeanNames());
 			}
@@ -151,7 +181,7 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet implements Conne
 	private void handleJMXPage(HttpServletRequest req, HttpServletResponse resp, Session session) throws IOException {
 		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/jmxpage.vm");
 		
-		if(!hasAdminRole(session.getUser())) {
+		if(!hasPermission(session.getUser(), Permission.Type.METRICS)) {
 			page.add("errorMsg", "User " + session.getUser().getUserId() + " has no permission.");
 			page.render();
 			return;
@@ -160,24 +190,47 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet implements Conne
 		page.add("mbeans", server.getMbeanNames());
 		
 		Map<String, Object> executorMBeans = new HashMap<String,Object>();
-		Set<String> primaryServerHosts = executorManager.getPrimaryServerHosts();
+//		Set<String> primaryServerHosts = executorManager.getPrimaryServerHosts();
 		for (String hostPort: executorManager.getAllActiveExecutorServerHosts()) {
 			try {
 				Map<String, Object> mbeans = executorManager.callExecutorJMX(hostPort, JMX_GET_MBEANS, null);
 	
-				if (primaryServerHosts.contains(hostPort)) {
-					executorMBeans.put(hostPort, mbeans.get("mbeans"));
-				}
-				else {
-					executorMBeans.put(hostPort, mbeans.get("mbeans"));
-				}
+				executorMBeans.put(hostPort, mbeans.get("mbeans"));
+//				if (primaryServerHosts.contains(hostPort)) {
+//					executorMBeans.put(hostPort, mbeans.get("mbeans"));
+//				}
+//				else {
+//					executorMBeans.put(hostPort, mbeans.get("mbeans"));
+//				}
 			}
 			catch (IOException e) {
 				logger.error("Cannot contact executor " + hostPort, e);
 			}
 		}
 		
-		page.add("remoteMBeans", executorMBeans);
+		page.add("executorRemoteMBeans", executorMBeans);
+		
+		Map<String, Object> triggerserverMBeans = new HashMap<String,Object>();
+//		Set<String> primaryTriggerServerHosts = triggerManager.getPrimaryServerHosts();
+//		for (String hostPort: triggerManager.getAllActiveTriggerServerHosts()) {
+//			try {
+//				Map<String, Object> mbeans = triggerManager.callTriggerServerJMX(hostPort, TriggerConnectorParams.JMX_GET_MBEANS, null);
+//				
+//				if (primaryTriggerServerHosts.contains(hostPort)) {
+//					triggerserverMBeans.put(hostPort, mbeans.get("mbeans"));
+//				}
+//				else {
+//					triggerserverMBeans.put(hostPort, mbeans.get("mbeans"));
+//				}
+//			}
+//			catch (IOException e) {
+//				logger.error("Cannot contact executor " + hostPort, e);
+//			}
+//		}
+		triggerserverMBeans.put(triggerManager.getJMX().getPrimaryServerHost(), triggerManager.getJMX().getAllJMXMbeans());
+		
+		page.add("triggerserverRemoteMBeans", triggerserverMBeans);
+		
 		page.render();
 	}
 	
@@ -186,11 +239,22 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet implements Conne
 
 	}
 	
-	private boolean hasAdminRole(User user) {
+//	private boolean hasAdminRole(User user) {
+//		for(String roleName: user.getRoles()) {
+//			Role role = userManager.getRole(roleName);
+//			Permission perm = role.getPermission();
+//			if (perm.isPermissionSet(Permission.Type.ADMIN)) {
+//				return true;
+//			}
+//		}
+//		
+//		return false;
+//	}
+	
+	protected boolean hasPermission(User user, Permission.Type type) {	
 		for(String roleName: user.getRoles()) {
 			Role role = userManager.getRole(roleName);
-			Permission perm = role.getPermission();
-			if (perm.isPermissionSet(Permission.Type.ADMIN)) {
+			if (role.getPermission().isPermissionSet(type) || role.getPermission().isPermissionSet(Permission.Type.ADMIN)) {
 				return true;
 			}
 		}
diff --git a/src/java/azkaban/webapp/servlet/LoginAbstractAzkabanServlet.java b/src/java/azkaban/webapp/servlet/LoginAbstractAzkabanServlet.java
index 8ce912b..b48e2c9 100644
--- a/src/java/azkaban/webapp/servlet/LoginAbstractAzkabanServlet.java
+++ b/src/java/azkaban/webapp/servlet/LoginAbstractAzkabanServlet.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -62,10 +62,14 @@ public abstract class LoginAbstractAzkabanServlet extends AbstractAzkabanServlet
 		contextType.put(".css", "text/css");
 		contextType.put(".png", "image/png");
 		contextType.put(".jpeg", "image/jpeg");
+		contextType.put(".gif", "image/gif");
 		contextType.put(".jpg", "image/jpeg");
+		contextType.put(".eot", "application/vnd.ms-fontobject");
+		contextType.put(".svg", "image/svg+xml");
+		contextType.put(".ttf", "application/octet-stream");
+		contextType.put(".woff", "application/x-font-woff");
 	}
 	
-	
 	private File webResourceDirectory = null;
 	
 	private MultipartParser multipartParser;
@@ -121,10 +125,10 @@ public abstract class LoginAbstractAzkabanServlet extends AbstractAzkabanServlet
 		String prefix = req.getContextPath() + req.getServletPath();
 		String path = req.getRequestURI().substring(prefix.length());
 		int index = path.lastIndexOf('.');
-		if (index == -1 ) {
+		if (index == -1) {
 			return false;
 		}
-		
+
 		String extension = path.substring(index);
 		if (contextType.containsKey(extension)) {
 			File file = new File(webResourceDirectory, path);
@@ -291,9 +295,10 @@ public abstract class LoginAbstractAzkabanServlet extends AbstractAzkabanServlet
 			return true;
 		}
 		
-		for(String roleName: user.getRoles()) {
+		for (String roleName: user.getRoles()) {
 			Role role = userManager.getRole(roleName);
-			if (role.getPermission().isPermissionSet(type) || role.getPermission().isPermissionSet(Permission.Type.ADMIN)) {
+			if (role.getPermission().isPermissionSet(type) || 
+					role.getPermission().isPermissionSet(Permission.Type.ADMIN)) {
 				return true;
 			}
 		}
@@ -375,4 +380,4 @@ public abstract class LoginAbstractAzkabanServlet extends AbstractAzkabanServlet
 	 */
 	protected void handleMultiformPost(HttpServletRequest req, HttpServletResponse resp, Map<String,Object> multipart, Session session) throws ServletException, IOException {
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/webapp/servlet/MultipartParser.java b/src/java/azkaban/webapp/servlet/MultipartParser.java
index 88ddc62..5bad2fc 100644
--- a/src/java/azkaban/webapp/servlet/MultipartParser.java
+++ b/src/java/azkaban/webapp/servlet/MultipartParser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/webapp/servlet/Page.java b/src/java/azkaban/webapp/servlet/Page.java
index d8d5194..5e48880 100644
--- a/src/java/azkaban/webapp/servlet/Page.java
+++ b/src/java/azkaban/webapp/servlet/Page.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -61,6 +61,8 @@ public class Page {
 	 */
 	public void render() {
 		try {
+			response.setHeader ("Content-type", "text/html; charset=UTF-8");
+			response.setCharacterEncoding ("UTF-8");
 			response.setContentType(mimeType);
 			engine.mergeTemplate(template, "UTF-8", context, response.getWriter());
 		} catch (Exception e) {
diff --git a/src/java/azkaban/webapp/servlet/PageRenderException.java b/src/java/azkaban/webapp/servlet/PageRenderException.java
index dd63b5f..c05c3ab 100644
--- a/src/java/azkaban/webapp/servlet/PageRenderException.java
+++ b/src/java/azkaban/webapp/servlet/PageRenderException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index 0b2d413..662bad0 100644
--- a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -30,6 +30,7 @@ import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.HashSet;
 import java.util.Set;
 
 import javax.servlet.ServletConfig;
@@ -44,7 +45,7 @@ import org.apache.log4j.Logger;
 
 import azkaban.executor.ExecutableFlow;
 import azkaban.executor.ExecutableJobInfo;
-import azkaban.executor.ExecutorManager;
+import azkaban.executor.ExecutorManagerAdapter;
 import azkaban.executor.ExecutorManagerException;
 import azkaban.flow.Edge;
 import azkaban.flow.Flow;
@@ -56,6 +57,7 @@ import azkaban.project.ProjectManager;
 import azkaban.project.ProjectManagerException;
 import azkaban.scheduler.Schedule;
 import azkaban.scheduler.ScheduleManager;
+import azkaban.scheduler.ScheduleManagerException;
 import azkaban.user.Permission;
 import azkaban.user.Role;
 import azkaban.user.UserManager;
@@ -75,7 +77,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 	private static final String LOCKDOWN_CREATE_PROJECTS_KEY = "lockdown.create.projects";
 	
 	private ProjectManager projectManager;
-	private ExecutorManager executorManager;
+	private ExecutorManagerAdapter executorManager;
 	private ScheduleManager scheduleManager;
 	private UserManager userManager;
 
@@ -105,7 +107,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 
 	@Override
 	protected void handleGet(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
-		if ( hasParam(req, "project") ) {
+		if (hasParam(req, "project") ) {
 			if (hasParam(req, "ajax")) {
 				handleAJAXAction(req, resp, session);
 			}
@@ -115,9 +117,6 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 			else if (hasParam(req, "permissions")) {
 				handlePermissionPage(req, resp, session);
 			}
-			else if (hasParam(req, "staging")) {
-				handleFlowStagingPage(req, resp, session);
-			}
 			else if (hasParam(req, "prop")) {
 				handlePropertyPage(req, resp, session);
 			}
@@ -198,6 +197,11 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 			if (handleAjaxPermission(project, user, Type.READ, ret)) {
 				ajaxFetchFlow(project, ret, req, resp);
 			}
+    }
+		else if (ajaxName.equals("fetchflowdetails")) {
+			if (handleAjaxPermission(project, user, Type.READ, ret)) {
+				ajaxFetchFlowDetails(project, ret, req);
+			}
 		}
 		else if (ajaxName.equals("fetchflowgraph")) {
 			if (handleAjaxPermission(project, user, Type.READ, ret)) {
@@ -304,7 +308,37 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		
 		ret.put("logData", eventData);
 	}
+
+  private List<String> getFlowJobTypes(Flow flow) {
+    Set<String> jobTypeSet = new HashSet<String>();
+    for (Node node : flow.getNodes()) {
+      jobTypeSet.add(node.getType());
+    }
+    List<String> jobTypes = new ArrayList<String>();
+    jobTypes.addAll(jobTypeSet);
+    return jobTypes;
+  }
 	
+	private void ajaxFetchFlowDetails(Project project, 
+      HashMap<String, Object> ret, HttpServletRequest req) 
+      throws ServletException {
+		String flowName = getParam(req, "flow");
+
+		Flow flow = null;
+		try {
+      flow = project.getFlow(flowName);
+      if (flow == null) {
+        ret.put("error", "Flow " + flowName + " not found.");
+        return;
+      }
+
+      ret.put("jobTypes", getFlowJobTypes(flow));
+		}
+		catch (AccessControlException e) {
+			ret.put("error", e.getMessage());
+		}
+  }
+
 	private void ajaxFetchFlowExecutions(Project project, HashMap<String, Object> ret, HttpServletRequest req) throws ServletException {
 		String flowId = getParam(req, "flow");
 		int from = Integer.valueOf(getParam(req, "start"));
@@ -360,13 +394,20 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		
 		// Check if scheduled
 		Schedule sflow = null;
-		for (Schedule flow: scheduleManager.getSchedules()) {
+		try {
+			for (Schedule flow: scheduleManager.getSchedules()) {
 
-			if (flow.getProjectId() == project.getId()) {
-				sflow = flow;
-				break;
+				if (flow.getProjectId() == project.getId()) {
+					sflow = flow;
+					break;
+				}
 			}
+		} 
+    catch (ScheduleManagerException e) {
+			// TODO Auto-generated catch block
+			throw new ServletException(e);
 		}
+		
 		if (sflow != null) {
 			this.setErrorMessageInCookie(resp, "Cannot delete. Please unschedule " + sflow.getScheduleName() + ".");
 
@@ -391,7 +432,8 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		//project.info("Project removing by '" + user.getUserId() + "'");
 		try {
 			projectManager.removeProject(project, user);
-		} catch (ProjectManagerException e) {
+		} 
+    catch (ProjectManagerException e) {
 			this.setErrorMessageInCookie(resp, e.getMessage());
 			resp.sendRedirect(req.getRequestURI() + "?project=" + projectName);
 			return;
@@ -612,17 +654,20 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 				ret.put("error", "Group permission already exists.");
 				return;
 			}
-		}
-		else {
-			if (!userManager.validateUser(name)) {
-				ret.put("error", "User is invalid.");
+			if (!userManager.validateGroup(name)) {
+				ret.put("error", "Group is invalid.");
 				return;
 			}
-			
+		}
+		else {
 			if (project.getUserPermission(name) != null) {
 				ret.put("error", "User permission already exists.");
 				return;
 			}
+			if (!userManager.validateUser(name)) {
+				ret.put("error", "User is invalid.");
+				return;
+			}
 		}
 		
 		boolean admin = Boolean.parseBoolean(getParam(req, "permissions[admin]"));
@@ -720,21 +765,49 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 	
 	private void handleProjectLogsPage(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
 		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/projectlogpage.vm");
-
 		String projectName = getParam(req, "project");
-		
-		Project project = projectManager.getProject(projectName);
-		if (project == null) {
-			page.add("errorMsg", "Project " + projectName + " doesn't exist.");
+
+		User user = session.getUser();
+		Project project = null;
+		try {
+			project = projectManager.getProject(projectName);
+			if (project == null) {
+				page.add("errorMsg", "Project " + projectName + " doesn't exist.");
+			}
+			else {
+				if (!hasPermission(project,user,Type.READ)) {
+					throw new AccessControlException( "No permission to view project " + projectName + ".");
+				}
+				
+				page.add("project", project);
+				page.add("admins", Utils.flattenToString(project.getUsersWithPermission(Type.ADMIN), ","));
+				Permission perm = this.getPermissionObject(project, user, Type.ADMIN);
+				page.add("userpermission", perm);
+	
+				boolean adminPerm = perm.isPermissionSet(Type.ADMIN);
+				if (adminPerm) {
+					page.add("admin", true);
+				}
+				// Set this so we can display execute buttons only to those who have access.
+				if (perm.isPermissionSet(Type.EXECUTE) || adminPerm) {
+					page.add("exec", true);
+				}
+				else {
+					page.add("exec", false);
+				}
+			}
 		}
-		page.add("projectName", projectName);
+		catch (AccessControlException e) {
+			page.add("errorMsg", e.getMessage());
+		}
+
 		//page.add("projectManager", projectManager);
 		//int bytesSkip = 0;
 		int numBytes = 1024;
 
-		// Really sucks if we do a lot of these because it'll eat up memory fast. But it's expected
-		// that this won't be a heavily used thing. If it is, then we'll revisit it to make it more stream
-		// friendly.
+		// Really sucks if we do a lot of these because it'll eat up memory fast. 
+		// But it's expected that this won't be a heavily used thing. If it is, 
+		// then we'll revisit it to make it more stream friendly.
 		StringBuffer buffer = new StringBuffer(numBytes);
 		page.add("log", buffer.toString());
 
@@ -817,7 +890,6 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		pageStartValue++;
 		page.add("page5", new PageSelection(String.valueOf(pageStartValue), pageSize, pageStartValue > maxPage, pageStartValue == pageNum, Math.min(pageStartValue, maxPage)));
 
-		
 		page.render();
 	}
 	
@@ -885,97 +957,95 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		Flow flow = null;
 		try {
 			project = projectManager.getProject(projectName);
-			
 			if (project == null) {
 				page.add("errorMsg", "Project " + projectName + " not found.");
+        page.render();
+        return;
 			}
-			else {
-				if (!hasPermission(project, user, Type.READ)) {
-					throw new AccessControlException( "No permission to view project " + projectName + ".");
-				}
-				
-				page.add("project", project);
-				
-				flow = project.getFlow(flowName);
-				if (flow == null) {
-					page.add("errorMsg", "Flow " + flowName + " not found.");
-				}
-				else {
-					page.add("flowid", flow.getId());
-					
-					Node node = flow.getNode(jobName);
-					
-					if (node == null) {
-						page.add("errorMsg", "Job " + jobName + " not found.");
-					}
-					else {
-						Props prop = projectManager.getProperties(project, node.getJobSource());
-						Props overrideProp = projectManager.getJobOverrideProperty(project, jobName);
-						if(overrideProp == null) {
-							overrideProp = new Props();
-						}
-						Props comboProp = new Props(prop);
-						for(String key : overrideProp.getKeySet()) {
-							comboProp.put(key, overrideProp.get(key));
-						}
-						page.add("jobid", node.getId());
-						page.add("jobtype", node.getType());
-						
-						ArrayList<String> dependencies = new ArrayList<String>();
-						Set<Edge> inEdges = flow.getInEdges(node.getId());
-						if (inEdges != null) {
-							for ( Edge dependency: inEdges ) {
-								dependencies.add(dependency.getSourceId());
-							}
-						}
-						if (!dependencies.isEmpty()) {
-							page.add("dependencies", dependencies);
-						}
-						
-						ArrayList<String> dependents = new ArrayList<String>();
-						Set<Edge> outEdges = flow.getOutEdges(node.getId());
-						if (outEdges != null) {
-							for ( Edge dependent: outEdges ) {
-								dependents.add(dependent.getTargetId());
-							}
-						}
-						if (!dependents.isEmpty()) {
-							page.add("dependents", dependents);
-						}
-						
-						// Resolve property dependencies
-						ArrayList<String> source = new ArrayList<String>(); 
-						String nodeSource = node.getPropsSource();
-						if(nodeSource != null) {
-							source.add(nodeSource);
-							FlowProps parent = flow.getFlowProps(nodeSource);
-							while(parent.getInheritedSource() != null) {
-								source.add(parent.getInheritedSource());
-								parent = flow.getFlowProps(parent.getInheritedSource()); 
-							}
-						}
-						if(!source.isEmpty()) {
-							page.add("properties", source);
-						}
-						
-						ArrayList<Pair<String,String>> parameters = new ArrayList<Pair<String, String>>();
-						// Parameter
-						for (String key : comboProp.getKeySet()) {
-							String value = comboProp.get(key);
-							parameters.add(new Pair<String,String>(key, value));
-						}
-						
-						page.add("parameters", parameters);
-					}
-				}
-			}
+      if (!hasPermission(project, user, Type.READ)) {
+        throw new AccessControlException( "No permission to view project " + projectName + ".");
+      }
+      
+      page.add("project", project);
+      flow = project.getFlow(flowName);
+      if (flow == null) {
+        page.add("errorMsg", "Flow " + flowName + " not found.");
+        page.render();
+        return;
+      }
+
+      page.add("flowid", flow.getId());
+      Node node = flow.getNode(jobName);
+      if (node == null) {
+        page.add("errorMsg", "Job " + jobName + " not found.");
+        page.render();
+        return;
+      }
+
+      Props prop = projectManager.getProperties(project, node.getJobSource());
+      Props overrideProp = projectManager.getJobOverrideProperty(project, jobName);
+      if (overrideProp == null) {
+        overrideProp = new Props();
+      }
+      Props comboProp = new Props(prop);
+      for (String key : overrideProp.getKeySet()) {
+        comboProp.put(key, overrideProp.get(key));
+      }
+      page.add("jobid", node.getId());
+      page.add("jobtype", node.getType());
+      
+      ArrayList<String> dependencies = new ArrayList<String>();
+      Set<Edge> inEdges = flow.getInEdges(node.getId());
+      if (inEdges != null) {
+        for (Edge dependency: inEdges) {
+          dependencies.add(dependency.getSourceId());
+        }
+      }
+      if (!dependencies.isEmpty()) {
+        page.add("dependencies", dependencies);
+      }
+      
+      ArrayList<String> dependents = new ArrayList<String>();
+      Set<Edge> outEdges = flow.getOutEdges(node.getId());
+      if (outEdges != null) {
+        for (Edge dependent: outEdges) {
+          dependents.add(dependent.getTargetId());
+        }
+      }
+      if (!dependents.isEmpty()) {
+        page.add("dependents", dependents);
+      }
+      
+      // Resolve property dependencies
+      ArrayList<String> source = new ArrayList<String>(); 
+      String nodeSource = node.getPropsSource();
+      if (nodeSource != null) {
+        source.add(nodeSource);
+        FlowProps parent = flow.getFlowProps(nodeSource);
+        while (parent.getInheritedSource() != null) {
+          source.add(parent.getInheritedSource());
+          parent = flow.getFlowProps(parent.getInheritedSource()); 
+        }
+      }
+      if (!source.isEmpty()) {
+        page.add("properties", source);
+      }
+      
+      ArrayList<Pair<String,String>> parameters = new ArrayList<Pair<String, String>>();
+      // Parameter
+      for (String key : comboProp.getKeySet()) {
+        String value = comboProp.get(key);
+        parameters.add(new Pair<String,String>(key, value));
+      }
+      
+      page.add("parameters", parameters);
 		}
 		catch (AccessControlException e) {
 			page.add("errorMsg", e.getMessage());
-		} catch (ProjectManagerException e) {
+		}
+		catch (ProjectManagerException e) {
 			page.add("errorMsg", e.getMessage());
 		}
-		
 		page.render();
 	}
 	
@@ -991,68 +1061,65 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		Flow flow = null;
 		try {
 			project = projectManager.getProject(projectName);
-			
 			if (project == null) {
 				page.add("errorMsg", "Project " + projectName + " not found.");
+        page.render();
+        return;
 			}
-			else {
-				if (!hasPermission(project,user,Type.READ)) {
-					throw new AccessControlException( "No permission to view project " + projectName + ".");
-				}
-				
-				page.add("project", project);
-				
-				flow = project.getFlow(flowName);
-				if (flow == null) {
-					page.add("errorMsg", "Flow " + flowName + " not found.");
-				}
-				else {
-					page.add("flowid", flow.getId());
-					
-					Node node = flow.getNode(jobName);
-					
-					if (node == null) {
-						page.add("errorMsg", "Job " + jobName + " not found.");
-					}
-					else {
-						Props prop = projectManager.getProperties(project, propSource);
-						
-						page.add("property", propSource);
-						
-						page.add("jobid", node.getId());
-						
-						// Resolve property dependencies
-						ArrayList<String> inheritProps = new ArrayList<String>(); 
-						FlowProps parent = flow.getFlowProps(propSource);
-						while(parent.getInheritedSource() != null) {
-							inheritProps.add(parent.getInheritedSource());
-							parent = flow.getFlowProps(parent.getInheritedSource()); 
-						}
-						if(!inheritProps.isEmpty()) {
-							page.add("inheritedproperties", inheritProps);
-						}
-						
-						ArrayList<String> dependingProps = new ArrayList<String>(); 
-						FlowProps child = flow.getFlowProps(flow.getNode(jobName).getPropsSource());
-						while(!child.getSource().equals(propSource)) {
-							dependingProps.add(child.getSource());
-							child = flow.getFlowProps(child.getInheritedSource()); 
-						}
-						if(!dependingProps.isEmpty()) {
-							page.add("dependingproperties", dependingProps);
-						}
-						
-						ArrayList<Pair<String,String>> parameters = new ArrayList<Pair<String, String>>();
-						// Parameter
-						for (String key : prop.getKeySet()) {
-							String value = prop.get(key);
-							parameters.add(new Pair<String,String>(key, value));
-						}
-						
-						page.add("parameters", parameters);
-					}
-				}
-			}
+
+      if (!hasPermission(project,user,Type.READ)) {
+        throw new AccessControlException( "No permission to view project " + projectName + ".");
+      }
+      page.add("project", project);
+      
+      flow = project.getFlow(flowName);
+      if (flow == null) {
+        page.add("errorMsg", "Flow " + flowName + " not found.");
+        page.render();
+        return;
+      }
+
+      page.add("flowid", flow.getId());
+      Node node = flow.getNode(jobName);
+      if (node == null) {
+        page.add("errorMsg", "Job " + jobName + " not found.");
+        page.render();
+        return;
+      }
+
+      Props prop = projectManager.getProperties(project, propSource);
+      page.add("property", propSource);
+      page.add("jobid", node.getId());
+      
+      // Resolve property dependencies
+      ArrayList<String> inheritProps = new ArrayList<String>(); 
+      FlowProps parent = flow.getFlowProps(propSource);
+      while (parent.getInheritedSource() != null) {
+        inheritProps.add(parent.getInheritedSource());
+        parent = flow.getFlowProps(parent.getInheritedSource()); 
+      }
+      if (!inheritProps.isEmpty()) {
+        page.add("inheritedproperties", inheritProps);
+      }
+      
+      ArrayList<String> dependingProps = new ArrayList<String>(); 
+      FlowProps child = flow.getFlowProps(flow.getNode(jobName).getPropsSource());
+      while (!child.getSource().equals(propSource)) {
+        dependingProps.add(child.getSource());
+        child = flow.getFlowProps(child.getInheritedSource()); 
+      }
+      if (!dependingProps.isEmpty()) {
+        page.add("dependingproperties", dependingProps);
+      }
+      
+      ArrayList<Pair<String,String>> parameters = new ArrayList<Pair<String, String>>();
+      // Parameter
+      for (String key : prop.getKeySet()) {
+        String value = prop.get(key);
+        parameters.add(new Pair<String,String>(key, value));
+      }
+      
+      page.add("parameters", parameters);
 		}
 		catch (AccessControlException e) {
 			page.add("errorMsg", e.getMessage());
@@ -1073,66 +1140,23 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		Flow flow = null;
 		try {
 			project = projectManager.getProject(projectName);
-			
 			if (project == null) {
 				page.add("errorMsg", "Project " + projectName + " not found.");
+        page.render();
+        return;
 			}
-			else {
-				if (!hasPermission(project, user, Type.READ)) {
-					throw new AccessControlException( "No permission Project " + projectName + ".");
-				}
-				
-				page.add("project", project);
-				
-				flow = project.getFlow(flowName);
-				if (flow == null) {
-					page.add("errorMsg", "Flow " + flowName + " not found.");
-				}
-				
-				page.add("flowid", flow.getId());
-			}
-		}
-		catch (AccessControlException e) {
-			page.add("errorMsg", e.getMessage());
-		}
-		
-		page.render();
-	}
 
-	private void handleFlowStagingPage(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException {
-		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/flowstagingpage.vm");
-		String projectName = getParam(req, "project");
-		String flowName = getParam(req, "flow");
-		
-		String retryFlow = null;
-		if (hasParam(req, "retry")) {
-			retryFlow = getParam(req, "retry");
-		}
-		
-		User user = session.getUser();
-		Project project = null;
-		Flow flow = null;
-		try {
-			project = projectManager.getProject(projectName);
-			
-			if (project == null) {
-				page.add("errorMsg", "Project " + projectName + " not found.");
-			}
-			else {
-				if (!hasPermission(project, user, Type.EXECUTE)) {
-					throw new AccessControlException( "No permission Project " + projectName + " to Execute.");
-				}
-				
-				page.add("project", project);
-				
-				flow = project.getFlow(flowName);
-				if (flow == null) {
-					page.add("errorMsg", "Flow " + flowName + " not found.");
-				}
-				
-				page.add("flowid", flow.getId());
-				page.add("retry", retryFlow);
-			}
+      if (!hasPermission(project, user, Type.READ)) {
+        throw new AccessControlException( "No permission Project " + projectName + ".");
+      }
+      
+      page.add("project", project);
+      flow = project.getFlow(flowName);
+      if (flow == null) {
+        page.add("errorMsg", "Flow " + flowName + " not found.");
+      }
+      
+      page.add("flowid", flow.getId());
 		}
 		catch (AccessControlException e) {
 			page.add("errorMsg", e.getMessage());
@@ -1140,7 +1164,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		
 		page.render();
 	}
-	
+
 	private void handleProjectPage(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException {
 		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/projectpage.vm");
 		String projectName = getParam(req, "project");
@@ -1249,7 +1273,10 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 			String type = null;
 			
 			final String contentType = item.getContentType();
-			if(contentType != null && (contentType.startsWith("application/zip") || contentType.startsWith("application/x-zip-compressed"))) {
+            if (contentType != null
+                && (contentType.startsWith("application/zip")
+                    || contentType.startsWith("application/x-zip-compressed")
+                    || contentType.startsWith("application/octet-stream"))) {
 				type = "zip";
 			}
 			else {
diff --git a/src/java/azkaban/webapp/servlet/ScheduleServlet.java b/src/java/azkaban/webapp/servlet/ScheduleServlet.java
index 4722257..c373608 100644
--- a/src/java/azkaban/webapp/servlet/ScheduleServlet.java
+++ b/src/java/azkaban/webapp/servlet/ScheduleServlet.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -16,6 +16,8 @@
 
 package azkaban.webapp.servlet;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -33,6 +35,7 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.log4j.Logger;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
@@ -43,7 +46,7 @@ import org.joda.time.format.DateTimeFormat;
 
 import azkaban.executor.ExecutableFlow;
 import azkaban.executor.ExecutionOptions;
-import azkaban.executor.ExecutorManager;
+import azkaban.executor.ExecutorManagerAdapter;
 import azkaban.executor.ExecutorManagerException;
 import azkaban.flow.Flow;
 import azkaban.flow.Node;
@@ -54,11 +57,7 @@ import azkaban.scheduler.Schedule;
 import azkaban.scheduler.ScheduleManager;
 import azkaban.scheduler.ScheduleManagerException;
 import azkaban.scheduler.ScheduleStatisticManager;
-import azkaban.sla.SLA;
-import azkaban.sla.SLA.SlaAction;
-import azkaban.sla.SLA.SlaRule;
-import azkaban.sla.SLA.SlaSetting;
-import azkaban.sla.SlaOptions;
+import azkaban.sla.SlaOption;
 import azkaban.user.Permission;
 import azkaban.user.Permission.Type;
 import azkaban.user.User;
@@ -103,19 +102,21 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 		if (ajaxName.equals("slaInfo")) {
 			ajaxSlaInfo(req, ret, session.getUser());
 		}
-		else if(ajaxName.equals("setSla")) {
+		else if (ajaxName.equals("setSla")) {
 			ajaxSetSla(req, ret, session.getUser());
-		}
-		else if(ajaxName.equals("loadFlow")) {
+		} else if(ajaxName.equals("loadFlow")) {
 			ajaxLoadFlows(req, ret, session.getUser());
 		}
-		else if(ajaxName.equals("loadHistory")) {
+		else if (ajaxName.equals("loadHistory")) {
 			ajaxLoadHistory(req, resp, session.getUser());
 			ret = null;
 		}
-		else if(ajaxName.equals("scheduleFlow")) {
+		else if (ajaxName.equals("scheduleFlow")) {
 			ajaxScheduleFlow(req, ret, session.getUser());
 		}
+    else if (ajaxName.equals("fetchSchedule")) {
+      ajaxFetchSchedule(req, ret, session.getUser());
+    }
 
 		if (ret != null) {
 			this.writeJSON(resp, ret);
@@ -124,9 +125,7 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 
 	private void ajaxSetSla(HttpServletRequest req, HashMap<String, Object> ret, User user) {
 		try {
-			
 			int scheduleId = getIntParam(req, "scheduleId");
-			
 			Schedule sched = scheduleManager.getSchedule(scheduleId);
 			
 			Project project = projectManager.getProject(sched.getProjectId());
@@ -134,39 +133,30 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 				ret.put("error", "User " + user + " does not have permission to set SLA for this flow.");
 				return;
 			}
-			
-			
-			SlaOptions slaOptions= new SlaOptions();
-			
-			String slaEmails = getParam(req, "slaEmails");
-			String[] emailSplit = slaEmails.split("\\s*,\\s*|\\s*;\\s*|\\s+");
+				
+			String emailStr = getParam(req, "slaEmails");
+			String[] emailSplit = emailStr.split("\\s*,\\s*|\\s*;\\s*|\\s+");
+			List<String> slaEmails = Arrays.asList(emailSplit);
 			
 			Map<String, String> settings = getParamGroup(req, "settings");
-			List<SlaSetting> slaSettings = new ArrayList<SlaSetting>();
+			
+			List<SlaOption> slaOptions = new ArrayList<SlaOption>();
 			for(String set : settings.keySet()) {
-				SlaSetting s;
+				SlaOption sla;
 				try {
-				s = parseSlaSetting(settings.get(set));
+				sla = parseSlaSetting(settings.get(set));
+				sla.getInfo().put(SlaOption.INFO_FLOW_NAME, sched.getFlowName());
+				sla.getInfo().put(SlaOption.INFO_EMAIL_LIST, slaEmails);
 				}
 				catch (Exception e) {
 					throw new ServletException(e);
 				}
-				if(s != null) {
-					slaSettings.add(s);
+				if(sla != null) {
+					sla.getInfo().put("SlaEmails", slaEmails);
+					slaOptions.add(sla);
 				}
 			}
 			
-			if(slaSettings.size() > 0) {
-				if(slaEmails.equals("")) {
-					ret.put("error", "Please put correct email settings for your SLA actions");
-					return;
-				}
-				slaOptions.setSlaEmails(Arrays.asList(emailSplit));
-				slaOptions.setSettings(slaSettings);
-			}
-			else {
-				slaOptions = null;
-			}
 			sched.setSlaOptions(slaOptions);
 			scheduleManager.insertSchedule(sched);
 
@@ -176,22 +166,50 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 			
 		} catch (ServletException e) {
 			ret.put("error", e.getMessage());
+		} catch (ScheduleManagerException e) {
+			ret.put("error", e.getMessage());
 		}
 		
 	}
 	
-	private SlaSetting parseSlaSetting(String set) throws ScheduleManagerException {
+	private SlaOption parseSlaSetting(String set) throws ScheduleManagerException {
 		// "" + Duration + EmailAction + KillAction
+		logger.info("Tryint to set sla with the following set: " + set);
+		
+		String slaType;
+		List<String> slaActions = new ArrayList<String>();
+		Map<String, Object> slaInfo = new HashMap<String, Object>();
 		String[] parts = set.split(",", -1);
 		String id = parts[0];
-		String rule = parts[1];
+		String rule = parts[1];	
 		String duration = parts[2];
 		String emailAction = parts[3];
 		String killAction = parts[4];
 		if(emailAction.equals("true") || killAction.equals("true")) {
-			SlaSetting r = new SlaSetting();			
-			r.setId(id);
-			r.setRule(SlaRule.valueOf(rule));
+			//String type = id.equals("") ? SlaOption.RULE_FLOW_RUNTIME_SLA : SlaOption.RULE_JOB_RUNTIME_SLA ;
+			if(emailAction.equals("true")) {
+				slaActions.add(SlaOption.ACTION_ALERT);
+				slaInfo.put(SlaOption.ALERT_TYPE, "email");
+			}
+			if(killAction.equals("true")) {
+				slaActions.add(SlaOption.ACTION_CANCEL_FLOW);
+			}
+			if(id.equals("")) {
+				if(rule.equals("SUCCESS")) {
+					slaType = SlaOption.TYPE_FLOW_SUCCEED;
+				}
+				else {
+					slaType = SlaOption.TYPE_FLOW_FINISH;
+				}
+			} else {
+				slaInfo.put(SlaOption.INFO_JOB_NAME, id);
+				if(rule.equals("SUCCESS")) {
+					slaType = SlaOption.TYPE_JOB_SUCCEED;
+				} else {
+					slaType = SlaOption.TYPE_JOB_FINISH;
+				}
+			}
+			
 			ReadablePeriod dur;
 			try {
 				dur = parseDuration(duration);
@@ -199,15 +217,10 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 			catch (Exception e) {
 				throw new ScheduleManagerException("Unable to parse duration for a SLA that needs to take actions!", e);
 			}
-			r.setDuration(dur);
-			List<SlaAction> actions = new ArrayList<SLA.SlaAction>();
-			if(emailAction.equals("true")) {
-				actions.add(SlaAction.EMAIL);
-			}
-			if(killAction.equals("true")) {
-				actions.add(SlaAction.KILL);
-			}
-			r.setActions(actions);
+
+			slaInfo.put(SlaOption.INFO_DURATION, Utils.createPeriodString(dur));
+			SlaOption r = new SlaOption(slaType, slaActions, slaInfo);
+			logger.info("Parsing sla as id:" + id + " type:" + slaType + " rule:" + rule + " Duration:" + duration + " actions:" + slaActions);
 			return r;
 		}
 		return null;
@@ -218,14 +231,38 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 		int min = Integer.parseInt(duration.split(":")[1]);
 		return Minutes.minutes(min+hour*60).toPeriod();
 	}
-
+  
+  private void ajaxFetchSchedule(HttpServletRequest req, 
+      HashMap<String, Object> ret, User user) throws ServletException {
+		
+		int projectId = getIntParam(req, "projectId");
+	  String flowId = getParam(req, "flowId");	
+		try {
+      Schedule schedule = scheduleManager.getSchedule(
+					projectId, flowId);
+    
+			if (schedule != null) {
+				Map<String, String> jsonObj = new HashMap<String, String>();
+				jsonObj.put("scheduleId", Integer.toString(schedule.getScheduleId()));
+				jsonObj.put("submitUser", schedule.getSubmitUser());
+				jsonObj.put("firstSchedTime", 
+						utils.formatDateTime(schedule.getFirstSchedTime()));
+				jsonObj.put("nextExecTime", 
+						utils.formatDateTime(schedule.getNextExecTime()));
+				jsonObj.put("period", utils.formatPeriod(schedule.getPeriod()));
+				ret.put("schedule", jsonObj);
+			}
+		}
+    catch (ScheduleManagerException e) {
+      ret.put("error", e);
+		}
+	}
+	
 	private void ajaxSlaInfo(HttpServletRequest req, HashMap<String, Object> ret, User user) {
 		int scheduleId;
 		try {
 			scheduleId = getIntParam(req, "scheduleId");
-			
 			Schedule sched = scheduleManager.getSchedule(scheduleId);
-			
 			Project project = getProjectAjaxByPermission(ret, sched.getProjectId(), user, Type.READ);
 			if (project == null) {
 				ret.put("error", "Error loading project. Project " + sched.getProjectId() + " doesn't exist");
@@ -238,15 +275,15 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 				return;
 			}
 			
-			SlaOptions slaOptions = sched.getSlaOptions();
+			List<SlaOption> slaOptions = sched.getSlaOptions();
 			ExecutionOptions flowOptions = sched.getExecutionOptions();
 			
-			if(slaOptions != null) {
-				ret.put("slaEmails", slaOptions.getSlaEmails());
-				List<SlaSetting> settings = slaOptions.getSettings();
+			if(slaOptions != null && slaOptions.size() > 0) {
+				ret.put("slaEmails", slaOptions.get(0).getInfo().get(SlaOption.INFO_EMAIL_LIST));
+				
 				List<Object> setObj = new ArrayList<Object>();
-				for(SlaSetting set: settings) {
-					setObj.add(set.toObject());
+				for(SlaOption sla: slaOptions) {
+					setObj.add(sla.toWebObject());
 				}
 				ret.put("settings", setObj);
 			}
@@ -284,8 +321,9 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 			ret.put("allJobNames", allJobs);
 		} catch (ServletException e) {
 			ret.put("error", e);
+		} catch (ScheduleManagerException e) {
+			ret.put("error", e);
 		}
-		
 	}
 
 	protected Project getProjectAjaxByPermission(Map<String, Object> ret, int projectId, User user, Permission.Type type) {
@@ -303,13 +341,19 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 		
 		return null;
 	}
-	
+
 	private void handleGetAllSchedules(HttpServletRequest req, HttpServletResponse resp,
 			Session session) throws ServletException, IOException{
 		
 		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/scheduledflowpage.vm");
 		
-		List<Schedule> schedules = scheduleManager.getSchedules();
+		List<Schedule> schedules;
+		try {
+			schedules = scheduleManager.getSchedules();
+		} catch (ScheduleManagerException e) {
+			// TODO Auto-generated catch block
+			throw new ServletException(e);
+		}
 		page.add("schedules", schedules);
 //		
 //		List<SLA> slas = slaManager.getSLAs();
@@ -323,7 +367,13 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 		
 		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/scheduledflowcalendarpage.vm");
 		
-		List<Schedule> schedules = scheduleManager.getSchedules();
+		List<Schedule> schedules;
+		try {
+			schedules = scheduleManager.getSchedules();
+		} catch (ScheduleManagerException e) {
+			// TODO Auto-generated catch block
+			throw new ServletException(e);
+		}
 		page.add("schedules", schedules);
 //		
 //		List<SLA> slas = slaManager.getSLAs();
@@ -344,7 +394,7 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 				if (action.equals("scheduleFlow")) {
 					ajaxScheduleFlow(req, ret, session.getUser());
 				}
-				else if(action.equals("removeSched")){
+				else if (action.equals("removeSched")){
 					ajaxRemoveSched(req, ret, session.getUser());
 				}
 			}
@@ -357,10 +407,15 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 			this.writeJSON(resp, ret);
 		}
 	}
-
+	
 	private void ajaxLoadFlows(HttpServletRequest req, HashMap<String, Object> ret, User user) throws ServletException {
-		
-		List<Schedule> schedules = scheduleManager.getSchedules();
+		List<Schedule> schedules;
+		try {
+			schedules = scheduleManager.getSchedules();
+		} catch (ScheduleManagerException e) {
+			// TODO Auto-generated catch block
+			throw new ServletException(e);
+		}
 		// See if anything is scheduled
 		if (schedules.size() <= 0)
 			return;
@@ -369,11 +424,16 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 		ret.put("items", output);
 
 		for (Schedule schedule : schedules) {
-			writeScheduleData(output, schedule);
+			try {
+				writeScheduleData(output, schedule);
+			} catch (ScheduleManagerException e) {
+				// TODO Auto-generated catch block
+				throw new ServletException(e);
+			}
 		}
 	}
 
-	private void writeScheduleData(List<HashMap<String, Object>> output, Schedule schedule) {
+	private void writeScheduleData(List<HashMap<String, Object>> output, Schedule schedule) throws ScheduleManagerException {
 		Map<String, Object> stats = ScheduleStatisticManager.getStatistics(schedule.getScheduleId(), (AzkabanWebServer) getApplication());
 		HashMap<String, Object> data = new HashMap<String, Object>();
 		data.put("scheduleid", schedule.getScheduleId());
@@ -416,7 +476,9 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 		int loadAll = getIntParam(req, "loadAll");
 
 		// Cache file
-		File cache = new File("cache/schedule-history/" + startTime + ".cache");
+		String cacheDir = getApplication().getServerProps().getString("cache.directory", "cache");
+		File cacheDirFile = new File(cacheDir, "schedule-history");
+		File cache = new File(cacheDirFile, startTime + ".cache");
 		cache.getParentFile().mkdirs();
 
 		if (useCache) {
@@ -427,8 +489,8 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 			}
 			if (cacheExists) {
 				// Send the cache instead
-				InputStream cacheInput = new FileInputStream(cache);
-				Utils.copyStream(cacheInput, resp.getOutputStream());
+				InputStream cacheInput = new BufferedInputStream(new FileInputStream(cache));
+				IOUtils.copy(cacheInput, resp.getOutputStream());
 				// System.out.println("Using cache copy for " + start);
 				return;
 			}
@@ -438,11 +500,12 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 		List<ExecutableFlow> history = null;
 		try {
 			AzkabanWebServer server = (AzkabanWebServer) getApplication();
-			ExecutorManager executorManager = server.getExecutorManager();
+			ExecutorManagerAdapter executorManager = server.getExecutorManager();
 			history = executorManager.getExecutableFlows(null, null, null, 0, startTime, endTime, -1, -1);
 		} catch (ExecutorManagerException e) {
-			// Return empty should suffice
+			logger.error(e);
 		}
+		
 		HashMap<String, Object> ret = new HashMap<String, Object>();
 		List<HashMap<String, Object>> output = new ArrayList<HashMap<String, Object>>();
 		ret.put("items", output);
@@ -462,14 +525,13 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 		}
 		
 		//Create cache file
-		File cacheTemp = new File("cache/schedule-history/" + startTime + ".tmp");
+		File cacheTemp = new File(cacheDirFile, startTime + ".tmp");
 		cacheTemp.createNewFile();
-		OutputStream cacheOutput = new FileOutputStream(cacheTemp);
-
+		OutputStream cacheOutput = new BufferedOutputStream(new FileOutputStream(cacheTemp));
+		OutputStream outputStream = new SplitterOutputStream(cacheOutput, resp.getOutputStream());
 		// Write to both the cache file and web output
-		JSONUtils.toJSON(ret, new SplitterOutputStream(cacheOutput, resp.getOutputStream()), false);
-		// System.out.println("Writing cache file for " + start);
-		// JSONUtils.toJSON(ret, new JSONCompressorOutputStream(resp.getOutputStream()), false);
+		JSONUtils.toJSON(ret, outputStream, false);
+		cacheOutput.close();
 		
 		//Move cache file
 		synchronized (this) {
@@ -499,7 +561,13 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 
 	private void ajaxRemoveSched(HttpServletRequest req, Map<String, Object> ret, User user) throws ServletException{
 		int scheduleId = getIntParam(req, "scheduleId");
-		Schedule sched = scheduleManager.getSchedule(scheduleId);
+		Schedule sched;
+		try {
+			sched = scheduleManager.getSchedule(scheduleId);
+		} catch (ScheduleManagerException e) {
+			// TODO Auto-generated catch block
+			throw new ServletException(e);
+		}
 		if(sched == null) {
 			ret.put("message", "Schedule with ID " + scheduleId + " does not exist");
 			ret.put("status", "error");
@@ -584,7 +652,8 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 		catch (Exception e) {
 			ret.put("error", e.getMessage());
 		}
-		SlaOptions slaOptions = null;
+		
+		List<SlaOption> slaOptions = null;
 		
 		Schedule schedule = scheduleManager.scheduleFlow(-1, projectId, projectName, flowName, "ready", firstSchedTime.getMillis(), firstSchedTime.getZone(), thePeriod, DateTime.now().getMillis(), firstSchedTime.getMillis(), firstSchedTime.getMillis(), user.getUserId(), flowOptions, slaOptions);
 		logger.info("User '" + user.getUserId() + "' has scheduled " + "[" + projectName + flowName +  " (" + projectId +")" + "].");
@@ -601,7 +670,7 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
 		int minutes = Integer.parseInt(parts[1]);
 		boolean isPm = parts[2].equalsIgnoreCase("pm");
 		
-		DateTimeZone timezone = parts[3].equals("UTC") ? DateTimeZone.UTC : DateTimeZone.forID("America/Los_Angeles");
+		DateTimeZone timezone = parts[3].equals("UTC") ? DateTimeZone.UTC : DateTimeZone.getDefault();
 
 		// scheduleDate: 02/10/2013
 		DateTime day = null;
diff --git a/src/java/azkaban/webapp/servlet/TriggerManagerServlet.java b/src/java/azkaban/webapp/servlet/TriggerManagerServlet.java
new file mode 100644
index 0000000..44f0f1a
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/TriggerManagerServlet.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2012 LinkedIn, Inc
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.webapp.servlet;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+import azkaban.trigger.Trigger;
+import azkaban.trigger.TriggerManager;
+import azkaban.trigger.TriggerManagerException;
+import azkaban.user.User;
+import azkaban.webapp.AzkabanWebServer;
+import azkaban.webapp.session.Session;
+
+public class TriggerManagerServlet extends LoginAbstractAzkabanServlet {
+	private static final long serialVersionUID = 1L;
+	private static final Logger logger = Logger.getLogger(TriggerManagerServlet.class);
+	private TriggerManager triggerManager;
+
+	@Override
+	public void init(ServletConfig config) throws ServletException {
+		super.init(config);
+		AzkabanWebServer server = (AzkabanWebServer)getApplication();
+		triggerManager = server.getTriggerManager();
+	}
+	
+	@Override
+	protected void handleGet(HttpServletRequest req, HttpServletResponse resp,
+			Session session) throws ServletException, IOException {
+		if (hasParam(req, "ajax")) {
+			handleAJAXAction(req, resp, session);
+		} else {
+			handleGetAllSchedules(req, resp, session);
+		}
+	}
+	
+	private void handleAJAXAction(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
+		HashMap<String, Object> ret = new HashMap<String, Object>();
+		String ajaxName = getParam(req, "ajax");
+		
+		try {
+			if (ajaxName.equals("expireTrigger")) {
+				ajaxExpireTrigger(req, ret, session.getUser());
+			}
+		} catch (Exception e) {
+			ret.put("error", e.getMessage());
+		}
+		
+		if (ret != null) {
+			this.writeJSON(resp, ret);
+		}
+	}
+
+	private void handleGetAllSchedules(HttpServletRequest req, HttpServletResponse resp,
+			Session session) throws ServletException, IOException{
+		
+		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/triggerspage.vm");
+		
+		List<Trigger> triggers = triggerManager.getTriggers();
+		page.add("triggers", triggers);
+//		
+//		List<SLA> slas = slaManager.getSLAs();
+//		page.add("slas", slas);
+
+		page.render();
+	}
+	
+	@Override
+	protected void handlePost(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
+		if (hasParam(req, "ajax")) {
+			handleAJAXAction(req, resp, session);
+		}
+	}
+
+	private void ajaxExpireTrigger(HttpServletRequest req, Map<String, Object> ret, User user) throws ServletException, TriggerManagerException{
+		int triggerId = getIntParam(req, "triggerId");
+		Trigger t = triggerManager.getTrigger(triggerId);
+		if(t == null) {
+			ret.put("message", "Trigger with ID " + triggerId + " does not exist");
+			ret.put("status", "error");
+			return;
+		}
+		
+//		if(!hasPermission(project, user, Type.SCHEDULE)) {
+//			ret.put("status", "error");
+//			ret.put("message", "Permission denied. Cannot remove trigger with id " + triggerId);
+//			return;
+//		}
+
+		triggerManager.expireTrigger(triggerId);
+		logger.info("User '" + user.getUserId() + " has removed trigger " + t.getDescription());
+//		projectManager.postProjectEvent(project, EventType.SCHEDULE, user.getUserId(), "Schedule " + sched.toString() + " has been removed.");
+		
+		ret.put("status", "success");
+		ret.put("message", "trigger " + triggerId + " removed from Schedules.");
+		return;
+	}
+
+}
+
diff --git a/src/java/azkaban/webapp/servlet/TriggerPlugin.java b/src/java/azkaban/webapp/servlet/TriggerPlugin.java
new file mode 100644
index 0000000..b2d9db9
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/TriggerPlugin.java
@@ -0,0 +1,33 @@
+package azkaban.webapp.servlet;
+
+import azkaban.trigger.TriggerAgent;
+
+public interface TriggerPlugin {
+	
+//	public TriggerPlugin(String pluginName, Props props, AzkabanWebServer azkabanWebApp) {
+//		this.pluginName = pluginName;
+//		this.pluginPath = props.getString("trigger.path");
+//		this.order = props.getInt("trigger.order", 0);
+//		this.hidden = props.getBoolean("trigger.hidden", false);
+//
+//	}
+
+	public AbstractAzkabanServlet getServlet();
+	public TriggerAgent getAgent();
+	public void load();
+	
+	public String getPluginName();
+
+	public String getPluginPath();
+
+	public int getOrder();
+	
+	public boolean isHidden();
+
+	public void setHidden(boolean hidden);
+	
+	public String getInputPanelVM();
+	
+	
+	
+}
diff --git a/src/java/azkaban/webapp/servlet/velocity/alerts.vm b/src/java/azkaban/webapp/servlet/velocity/alerts.vm
new file mode 100644
index 0000000..8877728
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/alerts.vm
@@ -0,0 +1,36 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+## Alert message set by servlet.
+
+#if ($error_message != "null")
+      <div class="alert alert-danger alert-dismissable">
+				<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
+        $error_message
+      </div>
+#elseif ($success_message != "null")
+      <div class="alert alert-success">
+				<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
+        $success_message
+      </div>
+#end
+
+## Alert message triggered by JavaScript.
+
+			<div class="alert alert-dismissable alert-messaging" id="messaging">
+				<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
+				<p id="messaging-message"></p>
+			</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/errormsg.vm b/src/java/azkaban/webapp/servlet/velocity/errormsg.vm
new file mode 100644
index 0000000..4c745c8
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/errormsg.vm
@@ -0,0 +1,27 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+		<div class="az-page-header">
+			<div class="container-full">
+        <h1 class="danger">Something's wrong</h1>
+			</div>
+    </div>
+    <div class="container-full">
+			<div class="alert alert-danger">
+        <h4>Error</h4>
+        $errorMsg
+      </div>
+    </div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
index de86051..693a707 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,25 +15,23 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-1.10.1.custom.js"></script>
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
 
+#parse("azkaban/webapp/servlet/velocity/style.vm")
+#parse("azkaban/webapp/servlet/velocity/javascript.vm")
+
+		<script type="text/javascript" src="${context}/js/moment.min.js"></script>
+		<script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.common.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.date.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.context.menu.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.job.status.utils.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.exflow.view.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.flow.job.view.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.svg.graph.view.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.exflow.view.js"></script>
 		<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
@@ -46,126 +44,150 @@
 			var flowId = "${flowid}";
 			var execId = "${execid}";
 		</script>
-		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
+		<link rel="stylesheet" type="text/css" href="${context}/css/azkaban-svg.css">
+		<link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
 	</head>
 	<body>
-#set($current_page="all")
-#set($show_schedule="false")
-
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
-		<div class="content">
-#if($errorMsg)
-				<div class="box-error-message">$errorMsg</div>
+
+#set ($current_page="all")
+#set ($show_schedule="false")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
 #else
-#if($error_message != "null")
-				<div class="box-error-message">$error_message</div>
-#elseif($success_message != "null")
-				<div class="box-success-message">$success_message</div>
-#end
 
-				<div id="all-jobs-content">
-					<div class="section-hd flow-header">
-						<h2><a href="${context}/executor?execid=${execid}">Flow Execution <span>$execid</span></a></h2>
-						<div class="section-sub-hd">
-							<h4><a href="${context}/manager?project=${projectName}">Project <span>$projectName</span></a></h4>
-							<h4 class="separator">&gt;</h4>
-							<h4><a href="${context}/manager?project=${projectName}&flow=${flowid}">Flow <span>$flowid</span></a></h4>
+	## Page header
+
+		<div class="az-page-header">
+			<div class="container-full" id="flow-status">
+        <div class="row">
+          <div class="col-lg-7">
+            <h1>
+              <a href="${context}/executor?execid=${execid}">
+                Flow Execution <small>$execid <span id="flowStatus">-</span></small>
+              </a>
+            </h1>
+          </div>
+          <div class="col-lg-5">
+            <div class="az-exflow-stats">
+              <div class="col-md-5">
+                <p><strong>Submit User</strong> <span id="submitUser">-</span></p>
+                <p><strong>Duration</strong> <span id="duration">-</span></p>
+              </div>
+              <div class="col-md-7">
+                <p><strong>Start Time</strong> <span id="startTime">-</span></p>
+                <p><strong>End Time</strong> <span id="endTime">-</span></p>
+              </div>
+            </div>
+            <div class="clearfix"></div>
+          </div>
+        </div>
+			</div>
+		</div>
+
+		<div class="container-full">
+
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+
+	## Breadcrumb
+
+			<ol class="breadcrumb">
+				<li><a href="${context}/manager?project=${projectName}"><strong>Project</strong> $projectName</a></li>
+				<li><a href="${context}/manager?project=${projectName}&flow=${flowid}"><strong>Flow</strong> $flowid</a></li>
+				<li class="active"><strong>Execution</strong> $execid</li>
+			</ol>
+		
+	## Tabs and buttons.
+
+			<ul class="nav nav-tabs" id="headertabs">
+				<li id="graphViewLink"><a href="#graph">Graph</a></li>
+				<li id="jobslistViewLink"><a href="#jobslist">Job List</a></li>
+				<li id="flowLogViewLink"><a href="#log">Flow Log</a></li>
+				<li class="nav-button pull-right"><button type="button" id="pausebtn" class="btn btn-primary">Pause</button></li>
+				<li class="nav-button pull-right"><button type="button" id="resumebtn" class="btn btn-primary">Resume</button></li>
+				<li class="nav-button pull-right"><button type="button" id="cancelbtn" class="btn btn-danger">Cancel</button></li>
+				<li class="nav-button pull-right"><button type="button" id="retrybtn" class="btn btn-success">Retry Failed</button></li>
+				<li class="nav-button pull-right"><button type="button" id="executebtn" class="btn btn-success">Prepare Execution</button></li>
+			</ul>
+    </div>
+
+	## Graph View
+
+	#parse ("azkaban/webapp/servlet/velocity/flowgraphview.vm")
+	
+	## Job List View
+
+    <div class="container-full" id="jobListView">
+			<div class="row">
+				<div class="col-lg-12">
+					<table class="table table-striped table-bordered table-condensed table-hover executions-table">
+						<thead>
+							<tr>
+								<th>Name</th>
+								<th class="timeline">Timeline</th>
+								<th class="date">Start Time</th>
+								<th class="date">End Time</th>
+								<th class="elapse">Elapsed</th>
+								<th class="status">Status</th>
+								<th class="logs">Details</th>
+							</tr>
+						</thead>
+						<tbody id="executableBody">
+						</tbody>
+					</table>
+        </div><!-- /.col-lg-12 -->
+      </div><!-- /.row -->
+    </div><!-- /.container-full -->
+
+	## Flow Log View
+
+    <div class="container-full container-fill" id="flowLogView">
+			<div class="row">
+				<div class="col-lg-12 col-content">
+          <div class="log-viewer">
+            <div class="panel panel-default">
+              <div class="panel-heading">
+                <div class="pull-right">
+                  <button type="button" id="updateLogBtn" class="btn btn-xs btn-info">Refresh</button>
+                </div>
+                Flow log
+              </div>
+              <div class="panel-body">
+                <pre id="logSection"></pre>
+              </div>
+            </div><!-- /.panel -->
+          </div><!-- /.log-viewer -->
+        </div><!-- /.col-lg-12 -->
+      </div><!-- /.row -->
+    </div><!-- /. -->
+	
+	## Error message message dialog.
+
+    <div class="container-full">
+			<div class="modal" id="messageDialog">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header" id="messageTitle">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Error</h4>
 						</div>
-					</div>
-					
-					<div id="headertabs" class="headertabs">
-						<ul>
-							<li><a id="graphViewLink" href="#graph">Graph</a></li>
-							<li class="lidivider">|</li>
-							<li><a id="jobslistViewLink" href="#jobslist">Job List</a></li>
-							<li class="lidivider">|</li>
-							<li><a id="flowLogViewLink" href="#log">Flow Log</a></li>
-						</ul>
-						<ul id="actionsBtns" class="buttons">
-							<li><div id="pausebtn" class="btn2">Pause</div></li>
-							<li><div id="resumebtn" class="btn2">Resume</div></li>
-							<li><div id="cancelbtn" class="btn6">Cancel</div></li>
-							<li><div id="retrybtn" class="btn1">Retry Failed</div></li>
-							<li><div id="executebtn" class="btn1">Prepare Execution</div></li>
-						</ul>
-					</div>
-					<div id="graphView">
-						<div class="relative">
-							<div id="jobList" class="jobList">
-								<div id="filterList" class="filterList">
-									<input id="filter" class="filter" placeholder="  Job Filter" />
-								</div>
-								<div id="list" class="list">
-								</div>
-								<div id="resetPanZoomBtn" class="btn5 resetPanZoomBtn" >Reset Pan Zoom</div>
-							</div>
-							<div id="svgDiv" class="svgDiv">
-								<svg id="svgGraph" class="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
-								</svg>
-							</div>
+						<div class="modal-body" id="messageDiv">
+							<p id="messageBox"></p>
 						</div>
-					</div>
-					<div id="jobListView" class="executionInfo">
-						<table>
-							<thead>
-								<tr>
-									<th>Name</th>
-									<th class="timeline">Timeline</th>
-									<th class="date">Start Time</th>
-									<th class="date">End Time</th>
-									<th class="elapse">Elapsed</th>
-									<th class="status">Status</th>
-									<th class="logs">Logs</th>
-								</tr>
-							</thead>
-							<tbody id="executableBody">
-							</tbody>
-						</table>
-					</div>
-					<div id="flowLogView" class="logView">
-						<div class="logHeader"><div class="logButtonRow"><div id="updateLogBtn" class="btn7">Refresh</div></div></div>
-						<div class="logViewer">
-							<pre id="logSection" class="log"></pre>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-primary" data-dismiss="modal">Dismiss</button>
 						</div>
 					</div>
 				</div>
-				
-				<div id="flow-status">
-					<table class="status">
-						<tr><td class="first">Status</td><td id="flowStatus">-</td></tr>
-						<tr><td class="first">Submit User</td><td id="submitUser">-</td></tr>
-					</table>
-					<table class="time">
-						<tr><td class="first">Start Time</td><td id="startTime">-</td></tr>
-						<tr><td class="first">End Time</td><td id="endTime">-</td></tr>
-						<tr><td class="first">Duration</td><td id="duration">-</td></tr>
-					</table>
-				</div>
+			</div>
 
-#parse( "azkaban/webapp/servlet/velocity/flowexecutionpanel.vm" )
-#end
-		</div>
+      <div id="contextMenu"></div>
 
-		
-		<div id="messageDialog" class="modal">
-			<h3 id="messageTitle">Error</h3>
-			<div class="messageDiv">
-				<p id="messageBox"></p>
-			</div>
-		</div>
-		
-		<div id="invalid-session" class="modal">
-			<h3>Invalid Session</h3>
-			<p>Session has expired. Please re-login.</p>
-			<div class="actions">
-				<a class="yes btn2" id="login-btn" href="#">Re-login</a>
-			</div>
-		</div>
-		
-		<div id="contextMenu">
+	#parse ("azkaban/webapp/servlet/velocity/invalidsessionmodal.vm")
+	#parse ("azkaban/webapp/servlet/velocity/flowexecutionpanel.vm")
+	#parse ("azkaban/webapp/servlet/velocity/messagedialog.vm")
 		</div>
-		#parse( "azkaban/webapp/servlet/velocity/messagedialog.vm" )
+#end
 	</body>
 </html>
-
diff --git a/src/java/azkaban/webapp/servlet/velocity/executionspage.vm b/src/java/azkaban/webapp/servlet/velocity/executionspage.vm
index dc550a1..91d1d0c 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executionspage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executionspage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,16 +15,13 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
 
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
+#parse("azkaban/webapp/servlet/velocity/style.vm")
+#parse("azkaban/webapp/servlet/velocity/javascript.vm")
+	
+		<script type="text/javascript" src="${context}/js/azkaban.executions.view.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
 			var currentTime = ${currentTime};
@@ -34,103 +31,123 @@
 		</script>
 	</head>
 	<body>
-		#set($current_page="executing")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
 
-		<div class="content">
-			<div id="all-jobs-content">
-				<div class="section-hd">
-					<h2>Executing Flows</h2>
-				</div>
+#set ($current_page="executing")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+## Page header.
+
+		<div class="az-page-header">
+			<div class="container-full">
+        <h1><a href="${context}/executor">Executing Flows</a></h1>
 			</div>
-			
-			<h3 class="subhead">Currently Running Jobs</h3>
-			<div class="executionInfo">
-				<table id="executingJobs">
-					<thead>
-						<tr>
-							<th class="execid">Execution Id</th>
-							<th>Flow</th>
-							<th>Project</th>
-							<th class="user">User</th>
-							<th class="user">Proxy User</th>
-							<th class="date">Start Time</th>
-							<th class="date">End Time</th>
-							<th class="elapse">Elapsed</th>
-							<th class="status">Status</th>
-							<th class="action">Action</th>
-						</tr>
-					</thead>
-					<tbody>
-						#if($runningFlows)
-#foreach($flow in $runningFlows)
-						<tr class="row" >
-							<td class="tb-name">
-								<a href="${context}/executor?execid=${flow.executionId}">${flow.executionId}</a>
-							</td>
-							<td><a href="${context}/manager?project=$vmutils.getProjectName(${flow.projectId})&flow=${flow.flowId}">${flow.flowId}</a></td>
-							<td>
-								<a href="${context}/manager?project=$vmutils.getProjectName(${flow.projectId})">$vmutils.getProjectName(${flow.projectId})</a>
-							</td>
-							<td>${flow.submitUser}</td>
-							<td>${flow.proxyUsers}</td>
-							<td>$utils.formatDate(${flow.startTime})</td>
-							<td>$utils.formatDate(${flow.endTime})</td>
-							<td>$utils.formatDuration(${flow.startTime}, ${flow.endTime})</td>
-							<td><div class="status ${flow.status}">$utils.formatStatus(${flow.status})</div></td>
-							<td></td>
-						</tr>
-#end
+		</div>
+
+		<div class="container-full">
+      
+#parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+
+## Page Content
+
+      <ul class="nav nav-tabs" id="header-tabs">
+        <li id="currently-running-view-link"><a href="#currently-running">Currently Running</a></li>
+        <li id="recently-finished-view-link"><a href="#recently-finished">Recently Finished</a></li>
+      </ul>
+
+			<div class="row" id="currently-running-view">
+				<div class="col-lg-12">
+          <table id="executingJobs" class="table table-striped table-bordered table-hover table-condensed executions-table">
+            <thead>
+              <tr>
+                <th class="execid">Execution Id</th>
+                <th>Flow</th>
+                <th>Project</th>
+                <th class="user">User</th>
+                <th class="user">Proxy</th>
+                <th class="date">Start Time</th>
+                <th class="date">End Time</th>
+                <th class="elapse">Elapsed</th>
+                <th class="status">Status</th>
+                <th class="action">Action</th>
+              </tr>
+            </thead>
+            <tbody>
+#if ($runningFlows)
+	#foreach ($flow in $runningFlows)
+              <tr>
+                <td class="tb-name">
+                  <a href="${context}/executor?execid=${flow.executionId}">${flow.executionId}</a>
+                </td>
+                <td><a href="${context}/manager?project=$vmutils.getProjectName(${flow.projectId})&flow=${flow.flowId}">${flow.flowId}</a></td>
+                <td>
+                  <a href="${context}/manager?project=$vmutils.getProjectName(${flow.projectId})">$vmutils.getProjectName(${flow.projectId})</a>
+                </td>
+                <td>${flow.submitUser}</td>
+                <td>${flow.proxyUsers}</td>
+                <td>$utils.formatDate(${flow.startTime})</td>
+                <td>$utils.formatDate(${flow.endTime})</td>
+                <td>$utils.formatDuration(${flow.startTime}, ${flow.endTime})</td>
+                <td><div class="status ${flow.status}">$utils.formatStatus(${flow.status})</div></td>
+                <td></td>
+              </tr>
+	#end
 #else
-						<tr><td></td><td class="last">No Executing Flows</td></tr>
-#end
-					</tbody>
-				</table>
-			</div>
-			<h3 class="subhead">Recently Finished Jobs</h3>
-			<div class="executionInfo">
-				<table id="recentlyFinished">
-					<thead>
-						<tr>
-							<th class="execid">Execution Id</th>
-							<th>Flow</th>
-							<th>Project</th>
-							<th class="user">User</th>
-							<th class="user">Proxy User</th>
-							<th class="date">Start Time</th>
-							<th class="date">End Time</th>
-							<th class="elapse">Elapsed</th>
-							<th class="status">Status</th>
-							<th class="action">Action</th>
-						</tr>
-					</thead>
-					<tbody>
-						#if($recentlyFinished)
-#foreach($flow in $recentlyFinished)
-						<tr class="row" >
-							<td class="tb-name execId">
-								<a href="${context}/executor?execid=${flow.executionId}">${flow.executionId}</a>
-							</td>
-							<td><a href="${context}/manager?project=$vmutils.getProjectName(${flow.projectId})&flow=${flow.flowId}">${flow.flowId}</a></td>
-							<td>
-								<a href="${context}/manager?project=$vmutils.getProjectName(${flow.projectId})">$vmutils.getProjectName(${flow.projectId})</a>
-							</td>
-							<td>${flow.submitUser}</td>
-							<td>${flow.proxyUsers}</td>
-							<td>$utils.formatDate(${flow.startTime})</td>
-							<td>$utils.formatDate(${flow.endTime})</td>
-							<td>$utils.formatDuration(${flow.startTime}, ${flow.endTime})</td>
-							<td><div class="status ${flow.status}">$utils.formatStatus(${flow.status})</div></td>
-							<td></td>
-						</tr>
+              <tr>
+                <td colspan="10">No Executing Flows</td>
+              </tr>
 #end
+            </tbody>
+          </table>
+				</div><!-- /col-lg-12 -->
+			</div><!-- /row -->
+
+			<div class="row" id="recently-finished-view">
+				<div class="col-lg-12">
+          <table id="recentlyFinished" class="table table-striped table-bordered table-hover table-condensed executions-table">
+            <thead>
+              <tr>
+                <th class="execid">Execution Id</th>
+                <th>Flow</th>
+                <th>Project</th>
+                <th class="user">User</th>
+                <th class="user">Proxy</th>
+                <th class="date">Start Time</th>
+                <th class="date">End Time</th>
+                <th class="elapse">Elapsed</th>
+                <th class="status">Status</th>
+                <th class="action">Action</th>
+              </tr>
+            </thead>
+            <tbody>
+#if ($recentlyFinished.isEmpty())
+	#foreach ($flow in $recentlyFinished)
+              <tr>
+                <td class="tb-name execId">
+                  <a href="${context}/executor?execid=${flow.executionId}">${flow.executionId}</a>
+                </td>
+                <td><a href="${context}/manager?project=$vmutils.getProjectName(${flow.projectId})&flow=${flow.flowId}">${flow.flowId}</a></td>
+                <td>
+                  <a href="${context}/manager?project=$vmutils.getProjectName(${flow.projectId})">$vmutils.getProjectName(${flow.projectId})</a>
+                </td>
+                <td>${flow.submitUser}</td>
+                <td>${flow.proxyUsers}</td>
+                <td>$utils.formatDate(${flow.startTime})</td>
+                <td>$utils.formatDate(${flow.endTime})</td>
+                <td>$utils.formatDuration(${flow.startTime}, ${flow.endTime})</td>
+                <td><div class="status ${flow.status}">$utils.formatStatus(${flow.status})</div></td>
+                <td></td>
+              </tr>
+	#end
 #else
-						<tr><td></td><td class="last">No Recently Finished</td></tr>
+              <tr>
+                <td colspan="10">No Recently Finished</td>
+              </tr>
 #end	
-					</tbody>
-				</table>
-			</div>
-		</div>
+            </tbody>
+          </table>
+				</div><!-- /col-lg-12 -->
+			</div><!-- /row -->
+		
+		</div><!-- /container-full -->
 	</body>
 </html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
index 6d4ddcb..cda497a 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
@@ -1,151 +1,223 @@
-<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
-<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
-<script type="text/javascript" src="${context}/js/azkaban.context.menu.js"></script>
-<script type="text/javascript" src="${context}/js/azkaban.svg.graph.view.js"></script>
-<script type="text/javascript" src="${context}/js/azkaban.flow.execute.view.js"></script>
-
-<div id="modalBackground" class="modalBackground2">
-<div id="execute-flow-panel" class="modal modalContainer2">
-	<h3 id="execute-flow-panel-title"></h3>
-	<a title="Close" class="modal-close closeExecPanel">x</a>
-	<div id="execute-message" class="message">
-	</div>
-	
-	<div class="panel">
-		<div id="executionGraphOptions">
-			<div id="graphOptions" class="sideMenu">
-				<h3 id="flowOption" viewpanel="svgDivCustom">Flow View</h3>
-				<div>
-					<p>Right click on the jobs to disable and enable jobs in the flow.</p>
-				</div>
-				<h3 viewpanel="notificationPanel">Notification</h3>
-				<div>
-					<p>Change the addresses where success and failure emails will be sent.</p>
-				</div>
-				<h3 viewpanel="failureOptions">Failure Options</h3>
-				<div>
-					<p>Select flow behavior when a failure is detected.</p>
-				</div>
-				<h3 viewpanel="concurrentPanel">Concurrent</h3>
-				<div>
-					<p>Change the behavior of the flow if it is already running.</p>
-				</div>
-				<h3 viewpanel="flowParametersPanel">Flow Parameters</h3>
-				<div>
-					<p>Add temporary flow parameters that are used to override global properties for each job.</p>
-				</div>
-			</div>
-		</div>
-		<div id="executionGraphOptionsPanel" class="rightPanel">
-			<div id="svgDivCustom" class="svgDiv sidePanel" >
-				<svg class="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
-				</svg>
-			</div>
-			<div id="notificationPanel" class="sidePanel">
-				<div>
-					<h4>Notify on Failure</h4>
-					<p>On a job failure, notify on either the first failure, and/or when the failed flow finishes.</p>
-					<input id="notifyFailureFirst" class="checkbox" type="checkbox" name="notify" value="first" checked /> <label for="notify">First Failure</label>
-					<input id="notifyFailureLast" class="checkbox" type="checkbox" name="notify" value="last"></input> <label for="notify">Flow Finished</label>
-					
-
-					<h4>Failure Emails</h4>
-					<div>
-						<input id="overrideFailureEmails" type="checkbox" name="overrideFailureEmails" value="overrideFailureEmails" />
-						<label for="overrideFailureEmails">Override flow email settings</label>
-					</div>
-					<p>Notify these addresses on failure. Comma, space or semi-colon delimited list.</p>
-					<textarea id="failureEmails"></textarea>
-				</div>
-			
-				<div>
-					<h4>Success Emails</h4>
-					<div>
-						<input id="overrideSuccessEmails" type="checkbox" name="overrideSuccessEmails" value="overrideSuccessEmails" />
-						<label for="overrideSuccessEmails">Override flow email settings</label>
-					</div>
-					<p>Notify when the flow finishes successfully. Comma, space or semi-colon delimited list.</p>
-					<textarea id="successEmails"></textarea>
-				</div>
-			</div> 
-			<div id="failureOptions" class="failureOptions sidePanel">
-				<h4>Failure Options</h4>
-				<p>When a failure first occurs in the flow, select the execution behavior.</p>
-				<ul>
-					<li><span class="bold">Finish Current Running</span> finishes only the currently running jobs. It will not start any new jobs.</p></li>
-					<li><span class="bold">Cancel All</span> immediately kills all jobs and fails the flow.</p></li>
-					<li><span class="bold">Finish All Possible</span> will keep executing jobs as long as its dependencies are met.</p></li>
-				</ul>
-
-				<select id="failureAction" name="failureAction">
-					<option value="finishCurrent">Finish Current Running</option>
-					<option value="cancelImmediately">Cancel All</option>
-					<option value="finishPossible">Finish All Possible</option>
-				</select>
-			</div>
-			<div id="concurrentPanel" class="sidePanel">
-				<h4>Concurrent Execution Options</h4>
-				<p>If the flow is currently running, these are the options that can be set.</p>
-
-				<input id="skip" class="radio" type="radio" name="concurrent" value="skip" checked /><label for="skip">Skip Execution</label>
-				<p>Do not run flow if it is already running.</p>
-				
-				<input id="ignore" class="radio" type="radio" name="concurrent" value="ignore" checked /><label for="ignore">Run Concurrently</label>
-				<p>Run the flow anyways. Previous execution is unaffected.</p>
-
-				<input id="pipeline" class="radio" type="radio" name="concurrent" value="pipeline" /><label for="pipeline">Pipeline</label>
-				<select id="pipelineLevel" name="pipelineLevel">
-					<option value="1">Level 1</option>
-					<option value="2">Level 2</option>
-				</select>
-				<p>Pipeline the flow, so the current execution will not be overrun.</p>
-				<ul>
-					<li>Level 1: block job A until the previous flow job A has completed.</li>
-					<li>Level 2: block job A until the previous flow job A's children have completed.</li>
-				</ul>
-				<!--
-				<input id="queue" class="radio" type="radio" name="concurrent" value="queue" /><label for="queue">Queue Job</label>
-				<select id="queueLevel" name="queueLevel">
-					<option value="1">1</option>
-					<option value="2">2</option>
-				</select>
-				<p>Queue up to 2. Wait until the previous execution has completed before running.</p>
-				-->
-			</div>
-			<div id="flowParametersPanel" class="sidePanel">
-				<h4>Flow Property Override</h4>
-				<div id="editTable" class="tableDiv">
-					<table>
-						<thead>
-							<tr>
-								<th>Name</th>
-								<th>Value</th>
-							</tr>
-						</thead>
-						<tbody>
-							<tr id="addRow" class="addRow"><td id="addRow-col" colspan="2"><span class="addIcon"></span><a>Add Row</a></td></tr>
-						</tbody>
-					</table>
-				</div>
-			</div>
-		</div>
-	</div>
-	
-	<div class="actions">
-		#if(!$show_schedule || $show_schedule == 'true') 
-		<a class="btn2" id="schedule-btn">Schedule</a>
-		#end
-
-		<a class="yes btn1" id="execute-btn">Execute</a>
-		<a class="no simplemodal-close btn3 closeExecPanel">Cancel</a>
-	</div>
-</div>
-</div>
-
-#if(!$show_schedule || $show_schedule == 'true') 
-#parse( "azkaban/webapp/servlet/velocity/schedulepanel.vm" )
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+			<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
+			<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
+			<script type="text/javascript" src="${context}/js/azkaban.context.menu.js"></script>
+			<script type="text/javascript" src="${context}/js/azkaban.common.utils.js"></script>
+			<script type="text/javascript" src="${context}/js/azkaban.svg.graph.view.js"></script>
+			<script type="text/javascript" src="${context}/js/azkaban.flow.execute.view.js"></script>
+
+			<div class="modal modal-wide" id="execute-flow-panel">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title" id="execute-flow-panel-title"></h4>
+						</div><!-- /modal-header -->
+						<div class="modal-body row">
+							<div class="col-md-4">
+								<ul class="nav nav-pills nav-stacked" id="graph-options">
+									<li id="flow-option" viewpanel="svg-div-custom">
+										<a href="#">Flow View</a>
+										<div class="menu-caption">Right click on the jobs to disable and enable jobs in the flow.</div>
+									</li>
+									<li viewpanel="notification-panel">
+										<a href="#">Notification</a>
+										<div class="menu-caption">Change the address where success and failure emails will be sent.</div>
+									</li>
+									<li viewpanel="failure-options">
+										<a href="#">Failure Options</a>
+										<div class="menu-caption">Select flow behavior when a failure is detected.</div>
+									</li>
+									<li viewpanel="concurrent-panel">
+										<a href="#">Concurrent</a>
+										<div class="menu-caption">Change the behavior of the flow if it is already running.</div>
+									</li>
+									<li viewpanel="flow-parameters-panel">
+										<a href="#">Flow Parameters</a>
+										<div class="menu-caption">Add temporary flow parameters that are used to override global settings for each job.</div>
+									</li>
+								</ul>
+							</div><!-- /col-md-4 -->
+							<div class="col-md-8">
+								<div id="execution-graph-options-panel">
+
+## SVG graph panel.
+
+									<div id="svg-div-custom" class="side-panel">
+										<svg id="flow-executing-graph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed">
+										</svg>
+									</div>
+
+## Notification panel.
+
+									<div id="notification-panel" class="side-panel">
+										<h4>Notify on failure</h4>
+										<p>On a job failure, notify on either the first failure, and/or when the failed flow finishes.</p>
+										<hr>
+										<label class="checkbox-inline">
+											<input id="notify-failure-first" type="checkbox" name="notify" value="first">First failure
+										</label>
+										<label class="checkbox-inline">
+											<input id="notify-failure-last" type="checkbox" name="notify" value="last">Flow finished
+										</label>
+
+										<h4>Failure Emails</h4>
+										<div class="checkbox">
+											<label>
+												<input type="checkbox" id="override-failure-emails" name="overrideFailureEmails" value="overrideFailureEmails">
+												Override flow email settings.
+											</label>
+										</div>
+										<label>Notify these addresses on failure. Comma, space, or semi-colon delimited list.</label>
+										<textarea class="form-control" rows="3" id="failure-emails"></textarea>
+
+										<h4>Success Emails</h4>
+										<div class="checkbox">
+											<label>
+												<input type="checkbox" name="overrideSuccessEmails" value="overrideSuccessEmails">
+												Override flow email settings.
+											</label>
+										</div>
+										<label>Notify when the flow finishes successfully. Comma, space, or semi-colon delimited list.</label>
+										<textarea class="form-control" rows="3" id="success-emails"></textarea>
+									</div>
+
+## Failure options panel.
+
+									<div id="failure-options" class="side-panel">
+										<h4>Failure Options</h4>
+										<p>When a failure first occurs in the flow, select the execution behavior.</p>
+										<hr>
+										<ul>
+											<li><strong>Finish Current Running</strong> finishes only the currently running job. It will not start any new jobs.</li>
+											<li><strong>Cancel All</strong> immediately kills all jobs and fails the flow.</li>
+											<li><strong>Finish All Possible</strong> will keep executing jobs as long as its dependencies are met.</li>
+										</ul>
+										<select id="failure-action" name="failureAction">
+											<option value="finishCurrent">Finish Current Running</option>
+											<option value="cancelImmediately">Cancel All</option>
+											<option value="finishPossible">Finish All Possible</option>
+										</select>
+									</div>
+
+## Concurrent execution options panel.
+
+									<div id="concurrent-panel" class="side-panel">
+										<h4>Concurrent Execution Options</h4>
+										<p>If the flow is currently running, these are the options that can be set.</p>
+										<hr>
+										<div class="radio">
+											<label>
+												<input type="radio" id="skip" name="concurrent" value="skip" checked="checked">
+												Skip Execution
+											</label>
+											<span class="help-block">Do not run flow if it is already running.</span>
+										</div>
+
+										<div class="radio">
+											<label>
+												<input type="radio" id="ignore" name="concurrent" value="ignore">
+												Run Concurrently
+											</label>
+											<span class="help-block">Do not run flow if it is already running.</span>
+										</div>
+
+										<div class="radio">
+											<label>
+												<input type="radio" id="pipeline" name="concurrent" value="pipeline">
+												Pipeline
+											</label>
+											<select id="pipelineLevel" name="pipelineLevel">
+												<option value="1">Level 1</option>
+												<option value="2">Level 2</option>
+											</select>
+											<span class="help-block">
+												Pipeline the flow, so the current execution will not be overrun.
+												<ul>
+													<li>Level 1: block job A until the previous flow job A has completed.</li>
+													<li>Level 2: block job A until the previous flow job A's children have completed.</li>
+												</li>
+											</span>
+										</div>
+									</div>
+
+## Flow parameters panel
+
+									<div id="flow-parameters-panel" class="side-panel">
+										<h4>Flow Property Override</h4>
+										<hr>
+										<div id="editTable">
+											<table class="table table-striped">
+												<thead>
+													<tr>
+														<th class="property-key">Name</th>
+														<th>Value</th>
+													</tr>
+												</thead>
+												<tbody>
+													<tr id="addRow" class="addRow">
+														<td id="addRow-col" colspan="2">
+															<button type="button" class="btn btn-success btn-xs">Add Row</button>
+														</td>
+													</tr>
+												</tbody>
+											</table>
+										</div>
+									</div>
+
+								</div><!-- /execution-graph-options-panel -->
+							</div><!-- /col-md-8 -->
+						</div><!-- /modal-body -->
+
+						<div class="modal-footer">
+#if (!$show_schedule || $show_schedule == 'true') 
+              <div class="pull-left">
+                <button type="button" class="btn btn-success" id="schedule-btn">Schedule</button>
+              </div>
+#end
+
+#*
+#if ($triggerPlugins.size() > 0)
+	#foreach ($triggerPlugin in $triggerPlugins)
+							<button type="button" class="btn btn-default" id=set-$triggerPlugin.pluginName>$triggerPlugin.pluginName</button>
+	#end
+#end
+*#
+							<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+							<button type="button" class="btn btn-success" id="execute-btn">Execute</button>
+						</div><!-- /modal-footer -->
+					</div><!-- /modal-content -->
+				</div><!-- /modal-dialog -->
+			</div><!-- /modal -->
+
+#if (!$show_schedule || $show_schedule == 'true') 
+	#parse ("azkaban/webapp/servlet/velocity/schedulepanel.vm")
 #end
 
-<div id="contextMenu">
-	
-</div>
+#*
+#if ($triggerPlugins.size() > 0)
+	#foreach ($triggerPlugin in $triggerPlugins)
+		#set ($prefix = $triggerPlugin.pluginName)
+		#set ($webpath = $triggerPlugin.pluginPath)
+		#parse ($triggerPlugin.inputPanelVM)
+	#end
+#end
+*#
+
+			<div id="contextMenu"></div>
+
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowgraphview.vm b/src/java/azkaban/webapp/servlet/velocity/flowgraphview.vm
new file mode 100644
index 0000000..60cdb96
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/flowgraphview.vm
@@ -0,0 +1,40 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+
+ 	## Graph view.
+
+    <div class="container-full container-fill" id="graphView">
+			<div class="row row-offcanvas row-offcanvas-left">
+				<div class="col-xs-6 col-sm-3 sidebar-offcanvas graph-sidebar">
+          <div class="panel panel-default" id="jobList">
+            <div class="panel-heading">
+              <input id="filter" type="text" placeholder="Job Filter" class="form-control">
+            </div>
+            <div id="list" class="list-group"></div>
+            <div class="panel-footer">
+              <button type="button" class="btn btn-sm btn-default" id="resetPanZoomBtn">Reset Pan Zoom</button>
+            </div>
+          </div><!-- /.panel -->
+        </div><!-- /.col-sidebar -->
+				<div class="col-xs-12 col-sm-9 col-content">
+					<div id="svgDiv" class="well well-clear well-sm">
+						<svg id="flow-graph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed">
+						</svg>
+					</div>
+				</div>
+			</div>
+    </div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index d1ab610..83dfff0 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,24 +15,25 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-1.10.1.custom.js"></script>
-		
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
+
+#parse("azkaban/webapp/servlet/velocity/style.vm")
+#parse("azkaban/webapp/servlet/velocity/javascript.vm")
+
+		<script type="text/javascript" src="${context}/js/moment.min.js"></script>
+		<script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
 		
+    <script type="text/javascript" src="${context}/js/dust-core-2.2.2.min.js"></script>
+		<script type="text/javascript" src="${context}/js/flowsummary.js"></script>
+
 		<script type="text/javascript" src="${context}/js/azkaban.date.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.common.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.flow.view.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.flow.job.view.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.flow.graph.view.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.flow.view.js"></script>
 		<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
@@ -46,105 +47,108 @@
 			var flowId = "${flowid}";
 			var execId = null;
 		</script>
-		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
+		<link rel="stylesheet" type="text/css" href="${context}/css/azkaban-svg.css" />
+		<link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
 	</head>
 	<body>
-#set($current_page="all")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
-		<div class="content">
-#if($errorMsg)
-				<div class="box-error-message">$errorMsg</div>
+
+#set ($current_page="all")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
 #else
-#if($error_message != "null")
-				<div class="box-error-message">$error_message</div>
-#elseif($success_message != "null")
-				<div class="box-success-message">$success_message</div>
-#end
+	
+	## Page header.
 
-				<div id="all-jobs-content">
-					<div class="section-hd flow-header">
-						<h2><a href="${context}/manager?project=${project.name}&flow=${flowid}">Flow <span>$flowid</span></a></h2>
-						<div class="section-sub-hd">
-							<h4><a href="${context}/manager?project=${project.name}">Project <span>$project.name</span></a></h4>
-						</div>
-						
-						<div id="executebtn" class="btn1">Schedule / Execute Flow</div>
-					</div>
-					
-					<div id="headertabs" class="headertabs">
-						<ul>
-							<li><a id="graphViewLink" href="#graph">Graph</a></li>
-							<li class="lidivider">|</li>
-							<li><a id="executionsViewLink" href="#executions">Executions</a></li>
-						</ul>
+		<div class="az-page-header">
+			<div class="container-full">
+				<div class="row">
+					<div class="col-lg-6">
+						<h1><a href="${context}/manager?project=${project.name}&flow=${flowid}">Flow <small>$flowid</small></a></h1>
 					</div>
-					<div id="graphView">
-						<div class="relative">
-							<div id="jobList" class="jobList">
-								<div id="filterList" class="filterList">
-									<input id="filter" class="filter" placeholder="  Job Filter" />
-								</div>
-								<div id="list" class="list">
-								</div>
-								<div id="resetPanZoomBtn" class="btn5 resetPanZoomBtn" >Reset Pan Zoom</div>
-							</div>
-							<div id="svgDiv" class="svgDiv">
-								<svg id="svgGraph" class="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
-								</svg>
-							</div>
-						</div>
-					</div>
-					<div id="executionsView">
-						<div id="executionDiv" class="all-jobs executionInfo">
-							<table id="execTable">
-								<thead>
-									<tr>
-										<th>Execution Id</th>
-										<th>User</th>
-										<th class="date">Start Time</th>
-										<th class="date">End Time</th>
-										<th class="elapse">Elapsed</th>
-										<th class="status">Status</th>
-										<th class="action">Action</th>
-									</tr>
-								</thead>
-								<tbody id="execTableBody">
-								</tbody>
-							</table>
-						</div>
-					
-						<div id="pageSelection">
-							<ul>
-								<li id="previous" class="first"><a><span class="arrow">&larr;</span>Previous</a></li>
-								<li id="page1"><a href="#page1">1</a></li>
-								<li id="page2"><a href="#page2">2</a></li>
-								<li id="page3"><a href="#page3">3</a></li>
-								<li id="page4"><a href="#page4">4</a></li>
-								<li id="page5"><a href="#page5">5</a></li>
-								<li id="next"><a>Next<span class="arrow">&rarr;</span></a></li>
-							</ul>
+					<div class="col-lg-6">
+						<div class="pull-right az-page-header-form">
+							<button type="button" class="btn btn-sm btn-success" id="executebtn">Schedule / Execute Flow</button>
 						</div>
+						<div class="clearfix"></div>
 					</div>
 				</div>
-		<!-- modal content -->
+			</div>
+		</div>
 
-				<div id="invalid-session" class="modal">
-					<h3>Invalid Session</h3>
-						<p>Session has expired. Please re-login.</p>
-						<div class="actions">
-							<a class="yes btn3" id="login-btn" href="#">Re-login</a>
-						</div>
+		<div class="container-full">
+      
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+  
+  ## Breadcrumbs
+
+      <ol class="breadcrumb">
+        <li><a href="${context}/manager?project=${project.name}"><strong>Project</strong> $project.name</a></li>
+        <li class="active"><strong>Flow</strong> $flowid</li>
+      </ol>
+
+  ## Tabs
+
+			<ul class="nav nav-tabs" id="headertabs">
+				<li id="graphViewLink"><a href="#graph">Graph</a></li>
+				<li id="executionsViewLink"><a href="#executions">Executions</a></li>
+				<li id="summaryViewLink"><a href="#summary">Summary</a></li>
+			</ul>
+    </div>
+					
+	## Graph view.
+
+	#parse ("azkaban/webapp/servlet/velocity/flowgraphview.vm")
+
+	## Executions view.
+
+    <div class="container-full" id="executionsView">
+			<div class="row">
+				<div class="col-lg-12">
+					<table class="table table-striped table-bordered table-condensed table-hover" id="execTable">
+						<thead>
+							<tr>
+								<th>Execution Id</th>
+								<th>User</th>
+								<th class="date">Start Time</th>
+								<th class="date">End Time</th>
+								<th class="elapse">Elapsed</th>
+								<th class="status">Status</th>
+								<th class="action">Action</th>
+							</tr>
+						</thead>
+						<tbody id="execTableBody">
+						</tbody>
+					</table>
+					<ul id="pageSelection" class="pagination">
+						<li id="previous" class="first"><a><span class="arrow">&larr;</span>Previous</a></li>
+						<li id="page1"><a href="#page1">1</a></li>
+						<li id="page2"><a href="#page2">2</a></li>
+						<li id="page3"><a href="#page3">3</a></li>
+						<li id="page4"><a href="#page4">4</a></li>
+						<li id="page5"><a href="#page5">5</a></li>
+						<li id="next"><a>Next<span class="arrow">&rarr;</span></a></li>
+					</ul>
 				</div>
+			</div>
+    </div><!-- /.container-fill -->
 
-#parse( "azkaban/webapp/servlet/velocity/flowexecutionpanel.vm" )
+	## Summary view.
 
-#end
+    <div class="container-full" id="summaryView">
+			<div class="row" id="summary-view-content">
+			</div><!-- /.row -->
+    </div><!-- /.container-fill -->
 
-		</div>
-		<div id="contextMenu">
-		</div>
-		#parse( "azkaban/webapp/servlet/velocity/messagedialog.vm" )
-	</body>
-</html>
+    <div class="container-full">
+			<div id="contextMenu">
+			</div>
 
+	#parse ("azkaban/webapp/servlet/velocity/invalidsessionmodal.vm")
+	#parse ("azkaban/webapp/servlet/velocity/flowexecutionpanel.vm")
+	#parse ("azkaban/webapp/servlet/velocity/messagedialog.vm")
+		</div><!-- /.container -->
+#end
+	</body>
+</body>
diff --git a/src/java/azkaban/webapp/servlet/velocity/historypage.vm b/src/java/azkaban/webapp/servlet/velocity/historypage.vm
index 03bdf6b..63db454 100644
--- a/src/java/azkaban/webapp/servlet/velocity/historypage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/historypage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,103 +15,110 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-1.10.1.custom.js"></script>
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
-		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-timepicker-addon.js"></script> 
-		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-sliderAccess.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>		
+
+#parse("azkaban/webapp/servlet/velocity/style.vm")
+#parse("azkaban/webapp/servlet/velocity/javascript.vm")
+
+		<link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
+
+		<script type="text/javascript" src="${context}/js/moment.min.js"></script>
+		<script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.history.view.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
 			var currentTime = ${currentTime};
 			var timezone = "${timezone}";
 		</script>
-		
-		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
-		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-timepicker-addon.css" />
 	</head>
 	<body>
-		#set($current_page="history")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
-
-		#if($errorMsg)
-					<div class="box-error-message">$errorMsg</div>
-		#else
-			#if($error_message != "null")
-	                <div class="box-error-message">$error_message</div>
-			#elseif($success_message != "null")
-        	        <div class="box-success-message">$success_message</div>
-			#end
-		#end	
 		
+#set ($current_page="history")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
 
-		<div class="content">
-			<div id="all-jobs-content">
-				<div class="section-hd">
-					<h2>History</h2>
-					<a id="adv-filter-btn" class="btn1 "  href="#">Advanced Filter</a>
-					<form id="search-form" method="get">
-						<input type="hidden" name="search" value="true">
-						<input type="submit" value="Quick Search" class="search-btn">
-						<input id="searchtextbox" type="text" placeholder="flow name containing ..." value=#if($search_term) ${search_term} #else "" #end class="search-input" name="searchterm">
-					</form>
-				</div>
-			</div>
-			
-			<div class="executionInfo">
-				<table id="executingJobs">
-					<thead>
-						<tr>
-							<th class="execid">Execution Id</th>
-							<th>Flow</th>
-							<th>Project</th>
-							<th>User</th>
-							<th class="date">Start Time</th>
-							<th class="date">End Time</th>
-							<th class="elapse">Elapsed</th>
-							<th class="status">Status</th>
-							<th class="action">Action</th>
-						</tr>
-					</thead>
-					<tbody>
-						#if($flowHistory)
-#foreach($flow in $flowHistory)
-						<tr class="row" >
-							<td class="tb-name execId">
-								<a href="${context}/executor?execid=${flow.executionId}">${flow.executionId}</a>
-							</td>
-							<td class="tb-name">
-								<a href="${context}/executor?execid=${flow.executionId}">${flow.flowId}</a>
-							</td>
-							<td>
-								<a href="${context}/manager?project=$vmutils.getProjectName(${flow.projectId})">$vmutils.getProjectName(${flow.projectId})</a>
-							</td>
-							<td>${flow.submitUser}</td>
-							<td>$utils.formatDate(${flow.startTime})</td>
-							<td>$utils.formatDate(${flow.endTime})</td>
-							<td>$utils.formatDuration(${flow.startTime}, ${flow.endTime})</td>
-							<td><div class="status ${flow.status}">$utils.formatStatus(${flow.status})</div></td>
-							<td></td>
-						</tr>
-#end
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
 #else
-						<tr><td class="last">No History Results Found</td></tr>
-#end
-					</tbody>
-				</table>
-				
-				<div id="pageSelection" class="nonjavascript">
-					<ul>
-		
-						#if($search)
+
+  ## Page header.
+
+		<div class="az-page-header">
+      <div class="container-full">
+        <div class="row">
+          <div class="col-lg-6">
+            <h1><a href="${context}/history">History</a></h1>
+          </div>
+          <div class="col-lg-6">
+            <form id="search-form" method="get" class="form-inline az-page-header-form" role="form">
+              <input type="hidden" name="search" value="true">
+              <div class="form-group">
+                <div class="input-group">
+                  <input type="text" id="searchtextbox" placeholder="flow name containing ..." value=#if($search_term) ${search_term} #else "" #end class="form-control input-sm" name="searchterm">
+                  <span class="input-group-btn">
+                    <button class="btn btn-primary btn-sm">Quick Search</button>
+                    <button type="button" class="btn btn-success btn-sm" id="adv-filter-btn">Advanced Filter</button>
+                  </span>
+                </div>
+              </div>
+            </form>
+          </div>
+        </div>
+			</div>
+		</div>
+
+    <div class="container-full">
+
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+
+			<div class="row">
+				<div class="col-lg-12">
+          <table id="executingJobs" class="table table-striped table-bordered table-hover table-condensed executions-table">
+            <thead>
+              <tr>
+                <th class="execid">Execution Id</th>
+                <th>Flow</th>
+                <th>Project</th>
+                <th>User</th>
+                <th class="date">Start Time</th>
+                <th class="date">End Time</th>
+                <th class="elapse">Elapsed</th>
+                <th class="status">Status</th>
+                <th class="action">Action</th>
+              </tr>
+            </thead>
+            <tbody>
+  #if (!$flowHistory.isEmpty())
+    #foreach ($flow in $flowHistory)
+              <tr>
+                <td class="tb-name execId">
+                  <a href="${context}/executor?execid=${flow.executionId}">${flow.executionId}</a>
+                </td>
+                <td class="tb-name">
+                  <a href="${context}/executor?execid=${flow.executionId}">${flow.flowId}</a>
+                </td>
+                <td>
+                  <a href="${context}/manager?project=$vmutils.getProjectName(${flow.projectId})">$vmutils.getProjectName(${flow.projectId})</a>
+                </td>
+                <td>${flow.submitUser}</td>
+                <td>$utils.formatDate(${flow.startTime})</td>
+                <td>$utils.formatDate(${flow.endTime})</td>
+                <td>$utils.formatDuration(${flow.startTime}, ${flow.endTime})</td>
+                <td>
+                  <div class="status ${flow.status}">$utils.formatStatus(${flow.status})</div>
+                </td>
+                <td></td>
+              </tr>
+    #end
+  #else
+              <tr>
+                <td class="last" colspan="9">No History Results Found</td>
+              </tr>
+  #end
+            </tbody>
+          </table>
+					<ul class="pagination" id="pageSelection">
+  #if ($search)
 						<li id="previous" class="first"><a href="${context}/history?page=${previous.page}&size=${previous.size}&search=true&searchterm=${search_term}"><span class="arrow">&larr;</span>Previous</a></li>
 						<li id="page1" #if($page1.selected) class="selected" #end><a href="${context}/history?page=${page1.page}&size=${page1.size}&search=true&searchterm=${search_term}">${page1.page}</a></li>
 						<li id="page2" #if($page2.selected) class="selected" #end><a href="${context}/history?page=${page2.page}&size=${page2.size}&search=true&searchterm=${search_term}">${page2.page}</a></li>
@@ -119,7 +126,7 @@
 						<li id="page4" #if($page4.selected) class="selected" #end><a href="${context}/history?page=${page4.page}&size=${page4.size}&search=true&searchterm=${search_term}">${page4.page}</a></li>
 						<li id="page5" #if($page5.selected) class="selected" #end><a href="${context}/history?page=${page5.page}&size=${page5.size}&search=true&searchterm=${search_term}">${page5.page}</a></li>
 						<li id="next"><a href="${context}/history?page=${next.page}&size=${next.size}&search=true&searchterm=${search_term}">Next<span class="arrow">&rarr;</span></a></li>
-						#elseif($advfilter)
+  #elseif($advfilter)
 						<li id="previous" class="first"><a href="${context}/history?page=${previous.page}&size=${previous.size}&advfilter=true&projcontain=${projcontain}&flowcontain=${flowcontain}&usercontain=${usercontain}&status=${status}&begin=${begin}&end=${end}"><span class="arrow">&larr;</span>Previous</a></li>
 						<li id="page1" #if($page1.selected) class="selected" #end><a href="${context}/history?page=${page1.page}&size=${page1.size}&advfilter=true&projcontain=${projcontain}&flowcontain=${flowcontain}&usercontain=${usercontain}&status=${status}&begin=${begin}&end=${end}">${page1.page}</a></li>
 						<li id="page2" #if($page2.selected) class="selected" #end><a href="${context}/history?page=${page2.page}&size=${page2.size}&advfilter=true&projcontain=${projcontain}&flowcontain=${flowcontain}&usercontain=${usercontain}&status=${status}&begin=${begin}&end=${end}">${page2.page}</a></li>
@@ -127,7 +134,7 @@
 						<li id="page4" #if($page4.selected) class="selected" #end><a href="${context}/history?page=${page4.page}&size=${page4.size}&advfilter=true&projcontain=${projcontain}&flowcontain=${flowcontain}&usercontain=${usercontain}&status=${status}&begin=${begin}&end=${end}">${page4.page}</a></li>
 						<li id="page5" #if($page5.selected) class="selected" #end><a href="${context}/history?page=${page5.page}&size=${page5.size}&advfilter=true&projcontain=${projcontain}&flowcontain=${flowcontain}&usercontain=${usercontain}&status=${status}&begin=${begin}&end=${end}">${page5.page}</a></li>
 						<li id="next"><a href="${context}/history?page=${next.page}&size=${next.size}&advfilter=true&projcontain=${projcontain}&flowcontain=${flowcontain}&usercontain=${usercontain}&status=${status}&begin=${begin}&end=${end}">Next<span class="arrow">&rarr;</span></a></li>
-						#else
+  #else
 						<li id="previous" class="first"><a href="${context}/history?page=${previous.page}&size=${previous.size}"><span class="arrow">&larr;</span>Previous</a></li>
 						<li id="page1" #if($page1.selected) class="selected" #end><a href="${context}/history?page=${page1.page}&size=${page1.size}">${page1.page}</a></li>
 						<li id="page2" #if($page2.selected) class="selected" #end><a href="${context}/history?page=${page2.page}&size=${page2.size}">${page2.page}</a></li>
@@ -135,67 +142,82 @@
 						<li id="page4" #if($page4.selected) class="selected" #end><a href="${context}/history?page=${page4.page}&size=${page4.size}">${page4.page}</a></li>
 						<li id="page5" #if($page5.selected) class="selected" #end><a href="${context}/history?page=${page5.page}&size=${page5.size}">${page5.page}</a></li>
 						<li id="next"><a href="${context}/history?page=${next.page}&size=${next.size}">Next<span class="arrow">&rarr;</span></a></li>
-						#end
-						
+  #end
 					</ul>
+				</div><!-- /col-lg-12 -->
+			</div><!-- /row -->
+
+  ## Advanced Filter Modal.
+
+			<div class="modal" id="adv-filter">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Advanced Filter</h4>
+						</div>
+						<div class="modal-body">
+							<div class="alert alert-danger" id="adv-filter-error-msg">$error_msg</div>
+							<fieldset class="form-horizontal">
+								<div class="form-group">
+									<label for="projcontain" class="col-sm-2 control-label">Project</label>
+									<div class="col-sm-10">
+										<input id="projcontain" type="text" placeholder="Project name containing ..." value="" class="form-control" name="projcontain">
+									</div>
+								</div>
+								<div class="form-group">
+									<label for="flowcontain" class="col-sm-2 control-label">Flow</label>
+									<div class="col-sm-10">
+										<input id="flowcontain" type="text" placeholder="Flow name containing ..." value="" class="form-control" name="flowcontain">
+									</div>
+								</div>
+								<div class="form-group">
+									<label for="usercontain" class="col-sm-2 control-label">User</label>
+									<div class="col-sm-10">
+										<input id="usercontain" type="text" placeholder="User name containing ..." value="" class="form-control" name="usercontain">
+									</div>
+								</div>
+								<div class="form-group">
+									<label for="status" class="col-sm-2 control-label">Status</label>
+									<div class="col-sm-10">
+										<select id="status" class="form-control">
+											<option value=0>All Status</option>
+											<option value=10>Ready</option>
+											<option value=20>Preapring</option>
+											<option value=30>Running</option>
+											<option value=40>Paused</option>
+											<option value=50>Succeed</option>
+											<option value=60>Killed</option>
+											<option value=70>Failed</option>
+											<option value=80>Failed Finishing</option>
+											<option value=90>Skipped</option>
+											<option value=100>Disabled</option>
+											<option value=110>Queued</option>
+										</select>
+									</div>
+								</div>
+								<div class="form-group">
+									<label for="datetimebegin" class="col-sm-2 control-label">Between</label>
+									<div class="col-sm-4">
+										<input type="text" id="datetimebegin" value="" class="ui-datetime-container form-control"> 
+									</div>
+									<label for="datetimeend" class="col-sm-2 control-label control-label-center">and</label>
+									<div class="col-sm-4">
+										<input type="text" id="datetimeend" value="" class="ui-datetime-container form-control"> 
+									</div>
+								</div>
+							</fieldset>
+						</div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+							<button class="btn btn-success" id="filter-btn">Filter</button>
+						</div>
+					</div>
 				</div>
 			</div>
-		</div>
-		
-		<!-- modal content -->
-		<div id="adv-filter" class="modal">
-			<h3>Advanced Filter</h3>
-			<div id="errorMsg" class="box-error-message">$errorMsg</div>
 			
-			<div class="message">
-				<fieldset>
-					<dl>
-						<dt><label for="path" >Project Name</label></dt>
-						<dd><input id="projcontain" type="text" placeholder="project name containing ..." value = ""  class="filter-input" name="projcontain"/></dd>
-						<dt><label for="path">Flow Name</label></dt>
-						<dd><input id="flowcontain" type="text" placeholder="flow name containing ..." value = ""  class="filter-input" name="flowcontain"/></dd>
-						<dt><label for="path">User Name</label></dt>
-						<dd><input id="usercontain" type="text" placeholder="user name containing ..." value = ""  class="filter-input" name="usercontain"/></dd>
-						<dt><label for="path">Status</label></dt>
-						<!--dd><input id="status" type="text" placeholder="Flow status is ..." value = ""  class="filter-input" name="status"/></dd-->
-						<dd>
-							<select id="status">
-								<option value=0>All Status</option>
-								<option value=10>Ready</option>
-								<option value=20>Preapring</option>
-								<option value=30>Running</option>
-								<option value=40>Paused</option>
-								<option value=50>Succeed</option>
-								<option value=60>Killed</option>
-								<option value=70>Failed</option>
-								<option value=80>Failed Finishing</option>
-								<option value=90>Skipped</option>
-								<option value=100>Disabled</option>
-								<option value=110>Queued</option>
-							</select>
-						</dd>
-						<dt>Date between</dt>
-						<dd><div class="ui-datetime-container"> 
-					 		<input type="text" name="basic_example_1" id="datetimebegin" value="" /> 
-						</div></dd>	
-						<!--dd><input type="text" class="ui-datetime-container" id="datetimebegin" value=""/></dd-->
-						<dt>and</dt>
-						<dd><input type="text" class="ui-datetime-container" id="datetimeend" value=""/></dd>
-					</dl>
-				</fieldset>
-			</div>
-			<div class="actions">
-				<a class="yes btn2" id="filter-btn" href="#">Filter</a>
-				<a class="no simplemodal-close btn3" href="#">Cancel</a>
-			</div>
-			<div id="invalid-session" class="modal">
-				<h3>Invalid Session</h3>
-				<p>Session has expired. Please re-login.</p>
-				<div class="actions">
-					<a class="yes btn2" id="login-btn" href="#">Re-login</a>
-				</div>
-			</div>
-		</div>
-		
+  #parse ("azkaban/webapp/servlet/velocity/invalidsessionmodal.vm")
+		</div><!-- /container-full -->
+#end
 	</body>
-</html>
\ No newline at end of file
+<html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/index.vm b/src/java/azkaban/webapp/servlet/velocity/index.vm
index 46e2ed0..159bd1a 100644
--- a/src/java/azkaban/webapp/servlet/velocity/index.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/index.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,16 +15,13 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
+
+#parse("azkaban/webapp/servlet/velocity/style.vm")
+#parse("azkaban/webapp/servlet/velocity/javascript.vm")
+
 		<script type="text/javascript" src="${context}/js/azkaban.table.sort.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.main.view.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
@@ -35,111 +32,140 @@
 		</script>
 	</head>
 	<body>
-		#set($current_page="all")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
 
-		<div class="content">
-			#if($error_message != "null")
-				<div class="box-error-message">$error_message</div>
-			#elseif($success_message != "null")
-				<div class="box-success-message">$success_message</div>
-			#end
-			<div id="all-jobs-content">
-				<div class="section-hd">
+#set ($current_page="all")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+## Page Header and project search form.
+
+		<div class="az-page-header">
+      <div class="container-full">
+        <div class="row">
+          <div class="col-lg-6">
 #if ($allProjects)
-					<h2>All Projects</h2>
-					<div class="section-sub-hd">
-						<h4><a href="${context}/">My Projects</a></h4>
-					</div>
+            <h1><a href="${context}/index">All Projects</a></h1>
 #else
-					<h2>My Projects</h2>
-					<div class="section-sub-hd">
-						<h4><a href="${context}/?all">All Projects</a></h4>
-					</div>
+            <h1><a href="${context}/index">My Projects</a></h1>
 #end
-					<form id="search-form" method="get">
-						<input type="hidden" name="doaction" value="search">
-						<input type="submit" value="Quick Search" class="search-btn">
+          </div>
+          <div class="col-lg-6">
+            <form id="search-form" method="get" class="form-inline az-page-header-form" role="form">
+              <input type="hidden" name="doaction" value="search">
 #if ($allProjects)
-						<input type="hidden" name="all" value="true">			
-#end			
-						<input id="searchtextbox" type="text" placeholder="project name containing ..." value=#if($search_term) ${search_term} #else "" #end class="search-input" name="searchterm">
-					</form>
-
+              <input type="hidden" name="all" value="true">				
+#end
+              <div class="form-group col-md-9">
+                <div class="input-group">
+                  <input id="search-textbox" type="text" placeholder="Project name containing..." value=#if($search_term) ${search_term} #else "" #end class="form-control input-sm" name="searchterm">
+                  <span class="input-group-btn">
+                    <button class="btn btn-sm btn-primary">Quick Search</button>
+                  </span>
+                </div>
+              </div>
 #if (!$hideCreateProject)
-					<a id="create-project-btn" class="btn1 " href="#">Create Project</a>
+              <div class="form-group col-md-3" id="create-project">
+## Note: The Create Project button is not completely flush to the right because
+## form-group has padding.
+                <div class="pull-right">
+                  <button type="button" id="create-project-btn" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-plus"></span> Create Project</button>
+                </div>
+              </div>
 #end
-				</div><!-- end .section-hd -->
+            </form>
+          </div>
+        </div>
 			</div>
-			<table id="all-jobs" class="all-jobs job-table project-table">
-				<thead>
-					<tr>
-						<th class="tb-name">Name</th>
-						<th class="tb-up-date">Modified Date</th>
-						<th class="tb-owner">Modified By</th>
-					</tr>
-				</thead>
-				<tbody>
-#if($projects)
-#foreach($project in $projects)
-					<tr class="row">
-						<td class="tb-name expand project-expand" id="${project.name}">
-							<span class="state-icon"></span>
-							<a href="${context}/manager?project=${project.name}">$project.name</a>
-						</td>
-						<td class="tb-up-date">$utils.formatDate($project.lastModifiedTimestamp)</td>
-						<td class="tb-owner">$project.lastModifiedUser</td>
-					</tr>
-					<tr class="childrow" id="${project.name}-child" style="display: none;">
-						<td class="expandedFlow" colspan="3">
-							<table class="innerTable">
-								<thead>
-									<tr><th class="tb-name">Flows</th></tr>
-								</thead>
-								<tbody id="${project.name}-tbody">
-								</tbody>
-							</table>
-						</td>
-					</tr>
-#end
-#else
-					<tr><td class="last">No viewable projects found.</td></tr>
-#end
-				</tbody>
-			</table>
 		</div>
 
-		<!-- modal content -->
-		<div id="create-project" class="modal">
-			<h3>Create Project</h3>
-			<div id="errorMsg" class="box-error-message">$errorMsg</div>
-			
-			<div class="message">
-				<fieldset>
-					<dl>
-						<dt><label for="path">Project Name</label></dt>
-						<dd><input id="path" name="project" type="text" size="20" title="The project name."/></dd>
-						<dt>Description</dt>
-						<dd><textarea id="description" name="description" rows="2" cols="40"></textarea></dd>
-					
-						<input name="action" type="hidden" value="create" />
-						<input name="redirect" type="hidden" value="$!context/" />
-					</dl>
-				</fieldset>
-			</div>
-			<div class="actions">
-				<a class="yes btn2" id="create-btn" href="#">Create Project</a>
-				<a class="no simplemodal-close btn3" href="#">Cancel</a>
+		<div class="container-full">
+      
+#parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+
+## Table of projects.
+
+			<div class="row">
+				<div class="col-lg-12">
+          <table class="table table-condensed table-striped table-bordered table-hover" id="all-jobs">
+            <thead>
+              <tr>
+                <th class="tb-name">Name</th>
+                <th class="tb-up-date">Modified Date</th>
+                <th class="tb-owner">Modified By</th>
+              </tr>
+            </thead>
+            <tbody>
+#if (!$projects.isEmpty())
+	#foreach ($project in $projects)
+              <tr class="az-project-row">
+                <td id="${project.name}" class="tb-name project-expand expanded">
+                  <span class="state-icon state-icon-expand az-expander"></span>
+                  <a href="${context}/manager?project=${project.name}">$project.name</a>
+                </td>
+                <td class="tb-up-date">$utils.formatDate($project.lastModifiedTimestamp)</td>
+                <td class="tb-owner">$project.lastModifiedUser</td>
+              </tr>
+              <tr class="childrow collapse" id="${project.name}-child">
+                <td colspan="3">
+                  <table class="table table-bordered">
+                    <thead>
+                      <tr>
+                        <th class="tb-name">Flows</th>
+                      </tr>
+                    </thead>
+                    <tbody id="${project.name}-tbody">
+                    </tbody>
+                  </table>
+                </td>
+              </tr>
+	#end
+#else
+              <tr>
+                <td colspan="3">No viewable projects found.</td>
+              </tr>
+#end
+            </tbody>
+          </table>
+				</div>
 			</div>
-			<div id="invalid-session" class="modal">
-				<h3>Invalid Session</h3>
-				<p>Session has expired. Please re-login.</p>
-				<div class="actions">
-					<a class="yes btn2" id="login-btn" href="#">Re-login</a>
+
+## Modal dialog to be displayed to create a new project.
+
+			<div class="modal" id="create-project-modal">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Create Project</h4>
+						</div>
+						<div class="modal-body">
+							<div class="alert alert-danger" id="modal-error-msg">$error_msg</div>
+							<fieldset class="form-horizontal">
+								<div class="form-group">
+									<label for="path" class="col-sm-2 control-label">Name</label>
+									<div class="col-sm-10">
+										<input id="path" name="project" type="text" class="form-control" placeholder="Project name">
+									</div>
+								</div>
+								<div class="form-group">
+									<label for="description" class="col-sm-2 control-label">Description</label>
+									<div class="col-sm-10">
+										<textarea id="description" name="description" rows="2" cols="40" class="form-control" placeholder="Project description"></textarea>
+									</div>
+								</div>
+							</fieldset>
+						</div>
+						<div class="modal-footer">
+							<input name="action" type="hidden" value="create">
+							<input name="redirect" type="hidden" value="$!context/">
+							<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+							<button type="button" class="btn btn-primary" id="create-btn">Create Project</button>
+						</div>
+					</div>
 				</div>
 			</div>
-		</div>
+
+#parse ("azkaban/webapp/servlet/velocity/invalidsessionmodal.vm")
+		</div><!-- /container -->
 	</body>
 </html>
 
diff --git a/src/java/azkaban/webapp/servlet/velocity/invalidsessionmodal.vm b/src/java/azkaban/webapp/servlet/velocity/invalidsessionmodal.vm
new file mode 100644
index 0000000..6f0651d
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/invalidsessionmodal.vm
@@ -0,0 +1,34 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+	## Modal dialog to be displayed when the user sesion is invalid.
+			
+			<div class="modal" id="invalid-session-modal">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Invalid Session</h4>
+						</div>
+						<div class="modal-body">
+							<p>Session has expired. Please re-login.</p>
+						</div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-primary" id="login-btn">Re-login</button>
+						</div>
+					</div>
+				</div>
+			</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/javascript.vm b/src/java/azkaban/webapp/servlet/velocity/javascript.vm
new file mode 100644
index 0000000..2b2c316
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/javascript.vm
@@ -0,0 +1,21 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
+		<script type="text/javascript" src="${context}/js/bootstrap.min.js"></script>    
+		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
+		<script type="text/javascript" src="${context}/js/namespace.js"></script>
+		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
diff --git a/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm b/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm
index 2a0e945..30c8f74 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,14 +15,12 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
+		
+#parse ("azkaban/webapp/servlet/velocity/style.vm")
+#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
+
 		<script type="text/javascript" src="${context}/js/azkaban.jmx.view.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
@@ -33,104 +31,173 @@
 		</script>
 	</head>
 	<body>
-#set($current_page="all")
-#set($counter=0)
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-#if($errorMsg)
-<div class="box-error-message"><pre>$errorMsg</pre></div>
-#end
+		
+#set ($current_page="all")
+#set ($counter = 0)
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
 
-		<div class="content">
-			<div id="all-jobs-content">
-				<div class="section-hd">
-					<h2><a href="${context}/jmx">Admin JMX Http Page</span></a></h2>
-				</div>
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
+#else
+
+		<div class="az-page-header">
+			<div class="container-full">
+				<h1>Admin JMX Http Page</h1>
 			</div>
-			<h3 class="subhead">Web Client JMX</h3>
+		</div>
 
-			<table id="all-jobs" class="all-jobs job-table">
-				<thead>
-					<tr>
-						<th>Name</th>
-						<th>Domain</th>
-						<th>Canonical Name</th>
-						<th></th>
-					</tr>
-				</thead>
-				<tbody>
-#foreach($bean in $mbeans)
-				<tr>
-					<td>${bean.keyPropertyList.get("name")}</td>
-					<td>${bean.domain}</td>
-					<td>${bean.canonicalName}</td>
-					<td><div class="btn4 querybtn" id="expandBtn-$counter" domain="${bean.domain}" name="${bean.keyPropertyList.get("name")}">Query</div></td>
-				</tr>
+		<div class="container-full">
 
-				<tr class="childrow" id="expandBtn-${counter}-child"  style="display: none;">
-					<td class="expandedFlow" colspan="3">
-						<table class="innerTable">
+  ## Web Client JMX 
+
+			<div class="row">
+				<div class="col-lg-12">
+					<div class="panel panel-default">
+						<div class="panel-heading">Web Client JMX</div>
+						<table id="all-jmx" class="table table-condensed table-bordered table-striped table-hover">
 							<thead>
 								<tr>
-									<th>Attribute Name</th>
-									<th>Value</th>
+									<th>Name</th>
+									<th>Domain</th>
+									<th>Canonical Name</th>
+									<th></th>
 								</tr>
 							</thead>
-							<tbody id="expandBtn-${counter}-tbody">
+							<tbody>
+  #foreach ($bean in $mbeans)
+							<tr>
+								<td>${bean.keyPropertyList.get("name")}</td>
+								<td>${bean.domain}</td>
+								<td>${bean.canonicalName}</td>
+								<td><button type="button" class="btn btn-default btn-sm query-btn" id="expandBtn-$counter" domain="${bean.domain}" name="${bean.keyPropertyList.get("name")}">Query</button></td>
+							</tr>
+
+							<tr class="childrow" id="expandBtn-${counter}-child"  style="display: none;">
+								<td class="expandedFlow" colspan="3">
+									<table class="table table-condensed table-bordered table-striped table-hover">
+										<thead>
+											<tr>
+												<th>Attribute Name</th>
+												<th>Value</th>
+											</tr>
+										</thead>
+										<tbody id="expandBtn-${counter}-tbody">
+										</tbody>
+									</table>
+								</td>
+
+								<td>
+									<button type="button" class="btn btn-default btn-sm collapse-btn">Collapse</button>
+								</td>
+							</tr>
+    #set ($counter = $counter + 1)
+  #end
 							</tbody>
 						</table>
-					</td>
 
-					<td>
-						<div class="btn4 collapse">Collapse</div>
-					</td>
-				</tr>
-#set($counter=$counter + 1)
-#end
-				</tbody>
-			</table>
+					</div>
+				</div>
+			</div>
+			
+  #foreach ($executor in $executorRemoteMBeans.entrySet())
+			<div class="row">
+				<div class="col-lg-12">
+					<div class="panel panel-default">
+						<div class="panel-heading">Remote Executor JMX $executor.key</div>
+						<table class="remoteJMX table table-striped table-condensed table-bordered table-hover">
+							<thead>
+								<tr>
+									<th>Name</th>
+									<th>Domain</th>
+									<th>Canonical Name</th>
+									<th></th>
+								</tr>
+							</thead>
+							<tbody>
+    #foreach ($bean in $executor.value)
+								<tr>
+									<td>${bean.get("keyPropertyList").get("name")}</td>
+									<td>${bean.get("domain")}</td>
+									<td>${bean.get("canonicalName")}</td>
+									<td><button type="button" class="btn btn-default btn-sm query-btn" id="expandBtn-$counter" domain="${bean.get("domain")}" name="${bean.get("keyPropertyList").get("name")}" hostport="$executor.key">Query</button></td>
+								</tr>
+								<tr class="childrow" id="expandBtn-${counter}-child"  style="display: none;">
+									<td class="expandedFlow" colspan="3">
+										<table class="table table-striped table-condensed table-bordered table-hover">
+											<thead>
+												<tr>
+													<th>Attribute Name</th>
+													<th>Value</th>
+												</tr>
+											</thead>
+											<tbody id="expandBtn-${counter}-tbody">
+											</tbody>
+										</table>
+									</td>
 
-#foreach($executor in $remoteMBeans.entrySet())
-			<h3 class="subhead">Remote Executor JMX $executor.key</h3>
-			<table class="all-jobs job-table remoteJMX">
-				<thead>
-					<tr>
-						<th>Name</th>
-						<th>Domain</th>
-						<th>Canonical Name</th>
-						<th></th>
-					</tr>
-				</thead>
-				<tbody>
-					#foreach($bean in $executor.value)
-						<tr>
-							<td>${bean.get("keyPropertyList").get("name")}</td>
-							<td>${bean.get("domain")}</td>
-							<td>${bean.get("canonicalName")}</td>
-							<td><div class="btn4 querybtn" id="expandBtn-$counter" domain="${bean.get("domain")}" name="${bean.get("keyPropertyList").get("name")}" hostport="$executor.key">Query</div></td>
-						</tr>
-					<tr class="childrow" id="expandBtn-${counter}-child"  style="display: none;">
-						<td class="expandedFlow" colspan="3">
-							<table class="innerTable">
-								<thead>
-									<tr>
-										<th>Attribute Name</th>
-										<th>Value</th>
-									</tr>
-								</thead>
-								<tbody id="expandBtn-${counter}-tbody">
-								</tbody>
-							</table>
-						</td>
+									<td>
+										<button type="button" class="btn btn-default btn-sm collapse-btn">Collapse</button>
+									</td>
+							</tr>
+      #set ($counter = $counter + 1)
+    #end 
+							</tbody>
+						</table>
+					</div>
+				</div>
+			</div>
+  #end
+			
+  #foreach ($triggerserver in $triggerserverRemoteMBeans.entrySet())
+			<div class="row">
+				<div class="col-lg-12">
+					<div class="panel panel-default">
+						<div class="panel-heading">Remote Trigger Server JMX $triggerserver.key</div>
+						<table class="remoteJMX table table-condensed table-striped table-bordered table-hover">
+							<thead>
+								<tr>
+									<th>Name</th>
+									<th>Domain</th>
+									<th>Canonical Name</th>
+									<th></th>
+								</tr>
+							</thead>
+							<tbody>
+    #foreach ($bean in $triggerserver.value)
+								<tr>
+									<td>${bean.get("keyPropertyList").get("name")}</td>
+									<td>${bean.get("domain")}</td>
+									<td>${bean.get("canonicalName")}</td>
+									<td><button type="button" class="btn btn-default btn-sm querybtn" id="expandBtn-$counter" domain="${bean.get("domain")}" name="${bean.get("keyPropertyList").get("name")}" hostport="$triggerserver.key">Query</button></td>
+								</tr>
+								<tr class="childrow" id="expandBtn-${counter}-child"  style="display: none;">
+									<td class="expandedFlow" colspan="3">
+										<table class="table table-striped table-condensed table-bordered table-hover">
+											<thead>
+												<tr>
+													<th>Attribute Name</th>
+													<th>Value</th>
+												</tr>
+											</thead>
+											<tbody id="expandBtn-${counter}-tbody">
+											</tbody>
+										</table>
+									</td>
+
+									<td>
+										<button type="button" class="btn btn-default btn-sm collapse-btn">Collapse</button>
+									</td>
+							</tr>
+      #set ($counter = $counter + 1)
+    #end 
+							</tbody>
+						</table>
 
-						<td>
-							<div class="btn4 collapse">Collapse</div>
-						</td>
-				</tr>
-				#set($counter=$counter + 1)
-					#end 
-				</tbody>
-			</table>
+					</div>
+				</div>
+			</div>
+  #end
+  </div>
 #end
-		</div>
 	</body>
 </html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/jobdetailspage.vm b/src/java/azkaban/webapp/servlet/velocity/jobdetailspage.vm
new file mode 100644
index 0000000..a8df9c2
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/jobdetailspage.vm
@@ -0,0 +1,173 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+<!DOCTYPE html> 
+<html lang="en">
+	<head>
+
+#parse("azkaban/webapp/servlet/velocity/style.vm")
+#parse("azkaban/webapp/servlet/velocity/javascript.vm")
+
+		<script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.jobdetails.view.js"></script>
+		<script type="text/javascript">
+			var contextURL = "${context}";
+			var currentTime = ${currentTime};
+			var timezone = "${timezone}";
+			var errorMessage = null;
+			var successMessage = null;
+			
+			var projectName = "${projectName}";
+			var flowName = "${flowid}";
+			var execId = "${execid}";
+			var jobId = "${jobid}";
+			var attempt = ${attempt};
+		</script>
+	</head>
+	<body>
+
+#set ($current_page="executing")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
+#else
+
+	## Page header.
+
+		<div class="az-page-header">
+			<div class="container-full">
+				<div class="row">
+					<div class="col-lg-6">
+						<h1><a href="${context}/executor?execid=${execid}&job=${jobid}">Job Execution <small>$jobid</small></a></h1>
+					</div>
+					<div class="col-lg-6">
+						<div class="pull-right az-page-header-form">
+							<a href="${context}/manager?project=${projectName}&flow=${flowid}&job=$jobid" class="btn btn-info">Job Properties</a>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	
+    <div class="container-full">
+
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+
+  ## Breadcrumb
+
+			<ol class="breadcrumb">
+				<li><a href="${context}/manager?project=${projectName}"><strong>Project</strong> $projectName</a></li>
+				<li><a href="${context}/manager?project=${projectName}&flow=${flowid}"><strong>Flow</strong> $flowid</a></li>
+				<li><a href="${context}/executor?execid=${execid}#jobslist"><strong>Execution</strong> $execid</a></li>
+        <li class="active"><strong>Job</strong> $jobid</li>
+			</ol>
+
+  ## Tabs
+
+			<ul class="nav nav-tabs" id="headertabs">
+				<li id="jobSummaryViewLink"><a href="#jobsummary">Summary</a></li>
+				<li id="jobLogViewLink"><a href="#joblog">Log</a></li>
+				<li><a href="${context}/pigvisualizer?execid=${execid}&jobid=${jobid}">Visualization</a></li>
+			</ul>
+    </div>
+
+	## Log content.
+
+    <div class="container-full container-fill" id="jobLogView">
+			<div class="row">
+				<div class="col-lg-12 col-content">
+          <div class="log-viewer">
+            <div class="panel panel-default">
+              <div class="panel-heading">
+                <div class="pull-right">
+                  <button type="button" id="updateLogBtn" class="btn btn-xs btn-default">Refresh</button>
+                </div>
+                Job Logs
+              </div>
+              <div class="panel-body">
+                <pre id="logSection"></pre>
+              </div>
+            </div>
+          </div>
+        </div>
+			</div>
+    </div>
+
+	## Job Summary
+
+    <div class="container-full" id="jobSummaryView">
+			<div class="row">
+				<div class="col-lg-12">
+					<table id="commandTable" class="table table-striped table-bordered table-hover">
+					</table>
+				
+					<div class="panel panel-default" id="jobsummary">
+						<div class="panel-heading">Job Summary</div>
+						<table class="table table-striped table-bordered table-hover">
+							<thead id="summaryHeader">
+							</thead>
+							<tbody id="summaryBody">
+							</tbody>
+						</table>
+					</div>
+				
+					<div class="panel panel-default" id="jobstats">
+						<div class="panel-heading">Job Stats</div>
+            <div class="panel-body panel-body-stats">
+              <table class="table table-striped table-bordered table-hover table-condensed">
+                <thead id="statsHeader">
+                </thead>
+                <tbody id="statsBody">
+                </tbody>
+              </table>
+            </div>
+					</div>
+					
+					<div class="panel panel-default" id="hiveTable">
+						<div class="panel-heading">Job Summary</div>
+						<table class="table table-striped table-bordered table-hover">
+							<thead id="hiveTableHeader">
+							</thead>
+							<tbody id="hiveTableBody">
+							</tbody>
+						</table>
+					</div>
+				</div>
+			</div>
+    </div>
+			
+	## Error message message dialog.
+
+    <div class="container-full">
+			<div class="modal" id="messageDialog">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header" id="messageTitle">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Error</h4>
+						</div>
+						<div class="modal-body" id="messageDiv">
+							<p id="messageBox"></p>
+						</div>
+					</div>
+				</div>
+			</div>
+
+		</div>
+#end
+	</body>
+</html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm b/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm
index fed5e17..0177383 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,16 +15,13 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
-		<script type="text/javascript" src="${context}/js/d3.v2.min.js"></script>
+
+#parse("azkaban/webapp/servlet/velocity/style.vm")
+#parse("azkaban/webapp/servlet/velocity/javascript.vm")
+
+		<script type="text/javascript" src="${context}/js/d3.v3.min.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.date.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.jobhistory.view.js"></script>
 		<script type="text/javascript">
@@ -59,90 +56,106 @@
 		</style>
 	</head>
 	<body>
-#set($current_page="all")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
 
-#if($errorMsg)
-			<div class="box-error-message">$errorMsg</div>
+#set ($current_page="all")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
 #else
-	<div class="content">
-	#if($error_message != "null")
-			<div class="box-error-message">$error_message</div>
-	#elseif($success_message != "null")
-			<div class="box-success-message">$success_message</div>
-	#end
-			<div id="all-jobs-content">
-				<div class="section-hd">
-					<h2><a href="${context}/manager?project=${projectName}&job=${jobid}&history">Job <span>$jobid</span> History</a></h2>
-					<div class="section-sub-hd">
-						<h4><a href="${context}/manager?project=${projectName}">Project <span>$projectName</span></a></h4>
-					</div>
-					
-				</div>
-			</div>
-			
-			<div id="timeGraph">
+	
+	## Page header
+
+		<div class="az-page-header">
+			<div class="container-full">
+        <h1><a href="${context}/manager?project=${projectName}&job=${jobid}&history">Job History <small>$jobid</small></a></h1>
 			</div>
-			
-			<div class="executionInfo">
-				<table id="all-jobs" class="all-jobs job-table">
-					<thead>
-						<tr>
-							<th class="execid">Execution Id</th>
-							<th class="jobid">Job</th>
-							<th class="flowid">Flow</th>
-							<th class="date">Start Time</th>
-							<th class="date">End Time</th>
-							<th class="elapse">Elapse</th>		
-							<th class="status">Status</th>
-							<th class="logs">Logs</th>
-						</tr>
-					</thead>
-					<tbody>
-	#foreach($job in $history)
-						<tr>
-							<td class="first">
-								#if ($job.attempt > 0)
-								<a href="${context}/executor?execid=${job.execId}">${job.execId}.${job.attempt}</a>
-								#else
-								<a href="${context}/executor?execid=${job.execId}">${job.execId}</a>
-								#end
-							</td>
-							<td>
-								<a href="${context}/manager?project=${projectName}&flow=${job.flowId}&job=${jobid}">${jobid}</a>
-							</td>
-							<td>
-								<a href="${context}/manager?project=${projectName}&flow=${job.flowId}">${job.flowId}</a>
-							</td>
-							<td>$utils.formatDate(${job.startTime})</td>
-							<td>$utils.formatDate(${job.endTime})</td>
-							<td>$utils.formatDuration(${job.startTime}, ${job.endTime})</td>
-							<td><div class="status ${job.status}">$utils.formatStatus(${job.status})</div></td>
-							<td class="logLink">
-								<a href="${context}/executor?execid=${job.execId}&job=${jobid}&attempt=${job.attempt}">Logs</a>
-							</td>
-						</tr>
+		</div>
+
+		<div class="container-full">
+	
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+
+  ## Breadcrumb
+
+      <ol class="breadcrumb">
+        <li><a href="${context}/manager?project=${projectName}"><strong>Project</strong> $projectName</a></li>
+        <li class="active"><strong>Job History</strong> $jobid</li>
+      </ol>
+
+  ## Time graph and job history table.
+
+			<div class="row">
+				<div class="col-lg-12">
+					<div class="well well-clear well-sm">
+            <div id="timeGraph"></div>
+          </div>
+
+          <table id="all-jobs" class="table table-striped table-bordered table-condensed table-hover">
+            <thead>
+              <tr>
+                <th class="execid">Execution Id</th>
+                <th class="jobid">Job</th>
+                <th class="flowid">Flow</th>
+                <th class="date">Start Time</th>
+                <th class="date">End Time</th>
+                <th class="elapse">Elapse</th>		
+                <th class="status">Status</th>
+                <th class="logs">Logs</th>
+              </tr>
+            </thead>
+            <tbody>
+	#if ($history)
+		#foreach ($job in $history)
+              <tr>
+                <td class="first">
+			#if ($job.attempt > 0)
+                  <a href="${context}/executor?execid=${job.execId}">${job.execId}.${job.attempt}</a>
+			#else
+                  <a href="${context}/executor?execid=${job.execId}">${job.execId}</a>
+			#end
+                </td>
+                <td>
+                  <a href="${context}/manager?project=${projectName}&flow=${job.flowId}&job=${jobid}">${jobid}</a>
+                </td>
+                <td>
+                  <a href="${context}/manager?project=${projectName}&flow=${job.flowId}">${job.flowId}</a>
+                </td>
+                <td>$utils.formatDate(${job.startTime})</td>
+                <td>$utils.formatDate(${job.endTime})</td>
+                <td>$utils.formatDuration(${job.startTime}, ${job.endTime})</td>
+                <td>
+                  <div class="status ${job.status}">
+                    $utils.formatStatus(${job.status})
+                  </div>
+                </td>
+                <td class="logLink">
+                  <a href="${context}/executor?execid=${job.execId}&job=${jobid}&attempt=${job.attempt}">Logs</a>
+                </td>
+              </tr>
+		#end
+	#else
+              <tr>
+                <td colspan="8">No history</td>
+              </tr>
 	#end
-					</tbody>
-				</table>
-				
-				<div id="pageSelection" class="nonjavascript">
-					<ul>
+            </tbody>
+          </table>
+
+					<ul class="pagination" id="pageSelection">
 						<li id="previous" class="first"><a href="${context}/manager?project=${projectId}&job=${jobid}&history&page=${previous.page}&size=${previous.size}"><span class="arrow">&larr;</span>Previous</a></li>
-				
 						<li id="page1" #if($page1.selected) class="selected" #elseif ($page1.disabled) class="disabled" #end><a href="${context}/manager?project=${projectName}&job=${jobid}&history&page=${page1.nextPage}&size=${page1.size}">${page1.page}</a></li>
 						<li id="page2" #if($page2.selected) class="selected" #elseif ($page2.disabled) class="disabled" #end><a href="${context}/manager?project=${projectName}&job=${jobid}&history&page=${page2.nextPage}&size=${page2.size}">${page2.page}</a></li>
 						<li id="page3" #if($page3.selected) class="selected" #elseif ($page3.disabled) class="disabled" #end><a href="${context}/manager?project=${projectName}&job=${jobid}&history&page=${page3.nextPage}&size=${page3.size}">${page3.page}</a></li>
 						<li id="page4" #if($page4.selected) class="selected" #elseif ($page4.disabled) class="disabled" #end><a href="${context}/manager?project=${projectName}&job=${jobid}&history&page=${page4.nextPage}&size=${page4.size}">${page4.page}</a></li>
 						<li id="page5" #if($page5.selected) class="selected" #elseif ($page5.disabled) class="disabled" #end><a href="${context}/manager?project=${projectName}&job=${jobid}&history&page=${page5.nextPage}&size=${page5.size}">${page5.page}</a></li>
-	
 						<li id="next"><a href="${context}/manager?project=${projectName}&job=${jobid}&history&page=${next.page}&size=${next.size}">Next<span class="arrow">&rarr;</span></a></li>
 					</ul>
-				</div>
-			</div>
-	</div>
 
+				</div><!-- /.col-lg-12 -->
+			</div><!-- /.row -->
+
+		</div>
 #end
 	</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/jobpage.vm b/src/java/azkaban/webapp/servlet/velocity/jobpage.vm
index e5efa4f..5dbec1f 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jobpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jobpage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,19 +15,13 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-1.10.1.custom.js"></script>
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
-
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
+
+#parse ("azkaban/webapp/servlet/velocity/style.vm")
+#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
+
 		<script type="text/javascript" src="${context}/js/azkaban.jobedit.view.js"></script>
-		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
 		<script type="text/javascript">
 			var contextURL = "${context}";
 			var currentTime = ${currentTime};
@@ -40,134 +34,183 @@
 		</script>
 	</head>
 	<body>
-#set($current_page="all")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
+		
+#set ($current_page="all")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
 
-#if($errorMsg)
-		<div class="box-error-message">$errorMsg</div>
-#else
-	<div class="content">
-	#if($error_message != "null")
-			<div class="box-error-message">$error_message</div>
-	#elseif($success_message != "null")
-			<div class="box-success-message">$success_message</div>
-	#end
-			<div id="all-jobs-content">
-				<div class="section-hd">
-					<h2><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}">Job <span>$jobid</span></a></h2>
-					<div class="section-sub-hd">
-						<h4><a href="${context}/manager?project=${project.name}">Project <span>$project.name</span></a></h4>
-						<h4 class="separator">&gt;</h4>
-						<h4><a href="${context}/manager?project=${project.name}&flow=${flowid}">Flow <span>$flowid</span></a></h4>
-					</div>
-					
-					<a id="jobs-logs-btn" class="btn2" href="${context}/manager?project=${project.name}&job=$jobid&history">Job History</a>
-					<a id="edit-job-btn" class="btn1" onclick='jobEditView.show("${project.name}", "${flowid}", "${jobid}")'>Job Edit</a>
-				</div>
-			</div>
-			
-			<div id="job-summary">
-				<table class="summary-table">
-					<tr><td class="first">Type:</td><td>$jobtype</td></tr>
-					<tr><td class="first">Dependencies:</td><td>
-#if ($dependencies) 
-#foreach($dependency in $dependencies)
-	<a href="${context}/manager?project=${project.name}&flow=${flowid}&job=$dependency">$dependency</a>
-#end
-#else
-	<span>No Dependencies</span>
-#end
-					</td></tr>
-					<tr><td class="first">Dependents:</td><td>
-#if ($dependents) 
-#foreach($dependent in $dependents)
-						<span class="nowrap"><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=$dependent">$dependent</a></span>
-#end
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
 #else
-						<span>No Dependencies</span>
-#end
-					</td></tr>
-					<tr><td class="first">Properties:</td><td>
-#if ($properties) 
-#foreach($property in $properties)
-						<!--a>$property</a><span>,</span-->
-						<span class="nowrap"><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}&prop=$property">$property</a></span>
-#end
-#else
-						<span>No Property Files For This Job</span>
-#end
-					</td></tr>
-				</table>
+
+	## Page header
+
+		<div class="az-page-header">
+			<div class="container-full">
+        <div class="row">
+          <div class="col-lg-6">
+            <h1><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}">Job <small>$jobid</small></a></h1>
+          </div>
+          <div class="col-lg-6">
+            <div class="pull-right az-page-header-form">
+              <a href="${context}/manager?project=${project.name}&job=$jobid&history" class="btn btn-info btn-sm">History</a>
+            </div>
+            <div class="clearfix"></div>
+          </div>
+        </div>
 			</div>
-			
-			<table id="all-jobs" class="all-jobs job-table parameters">
-				<thead>
-					<tr>
-						<th class="tb-pname">Parameter Name</th>
-						<th class="tb-pvalue">Value</th>
-					</tr>
-				</thead>
-				<tbody>
-#foreach($parameter in $parameters)
-					<tr>
-						<td class="first">$parameter.first</td><td>$parameter.second</td>
-					</tr>
-#end
-				</tbody>
-			</table>
-	</div>
+		</div>
 
-#end
+		<div class="container-full">
+	
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+  
+  ## Breadcrumb
 
-		<!-- modal content -->
-
-		<div id="jobEditModalBackground" class="modalBackground2">
-			<div id="job-edit-pane" class="modal modalContainer2">
-				<a href='#' title='Close' class='modal-close'>x</a>
-					<h3>Job Description Edit</h3>					
-					<div class="optionsPane">
-						<div id="generalPanel" class="generalPanel panel">
-							<div id="mustHave">
-								<h4>Job Essentials</h4>
-								<dl>
-									<dt>Job Name</dt>
-									<dd>
-										<label id="jobName"></label>
-									</dd>
-									<dt>Job Type</dt>
-									<dd>
-										<label id="jobType"></label>
-									</dd>
-								</dl>
-							</div>
-							<br></br>
-							<!--div id="jobTypeSpecific">
-								<h4>Job Type Specific parameters</h4>
-							</div-->
-							<div id="generalJobSetting">
-								<h4>General Job Settings (Be Aware: A Job May Be Shared By Multiple Flows. The Change Will Be Global!)</h4>
-								<div class="tableDiv">
-									<table id="generalProps">
-										<thead>
-											<tr>
-												<th>Name</th>
-												<th>Value</th>
-											</tr>
-										</thead>
-										<tbody>
-											<tr id="addRow"><td id="addRow-col" colspan="2"><span class="addIcon"></span><a href="#">Add Row</a></td></tr>
-										</tbody>
-									</table>
-								</div>
+      <ol class="breadcrumb">
+        <li><a href="${context}/manager?project=${project.name}"><strong>Project</strong> $project.name</a></li>
+        <li><a href="${context}/manager?project=${project.name}&flow=${flowid}"><strong>Flow</strong> $flowid</a></li>
+        <li class="active"><strong>Job</strong> $jobid</li>
+      </ol>
+
+			<div class="row row-offcanvas row-offcanvas-right">
+				<div class="col-xs-12 col-sm-9">
+	
+	## Job details table
+
+					<div class="panel panel-default">
+						<div class="panel-heading">
+							<div class="pull-right">
+								<button id="edit-job-btn" class="btn btn-xs btn-primary" onclick='jobEditView.show("${project.name}", "${flowid}", "${jobid}")'>Edit</button>
 							</div>
+							Job Properties
 						</div>
+
+						<table class="table table-striped table-bordered properties-table">
+							<thead>
+								<tr>
+									<th class="tb-pname">Parameter Name</th>
+									<th class="tb-pvalue">Value</th>
+								</tr>
+							</thead>
+							<tbody>
+	#foreach ($parameter in $parameters)
+								<tr>
+									<td class="property-key">$parameter.first</td>
+                  <td>$parameter.second</td>
+								</tr>
+	#end
+							</tbody>
+						</table>
 					</div>
-					<div class="actions">
-						<a class="yes btn1" id="set-btn" >Set/Change Job Description</a>
-						<a class="no simplemodal-close btn3" id="cancel-btn" >Cancel</a>
+				</div><!-- /col-lg-8 -->
+				<div class="col-xs-6 col-sm-3 sidebar-offcanvas">
+					<div class="well" id="job-summary">
+						<h3>Job <small>$jobid</small></h3>
+						<p><strong>Job Type</strong> $jobtype</p>
 					</div>
+
+	## Dependencies
+
+					<div class="panel panel-default">
+						<div class="panel-heading">Dependencies</div>
+						<ul class="list-group">
+	#if ($dependencies) 
+		#foreach($dependency in $dependencies)
+							<li class="list-group-item">
+								<a href="${context}/manager?project=${project.name}&flow=${flowid}&job=$dependency">$dependency</a>
+							</li>
+		#end
+	#else
+							<li class="list-group-item">No Dependencies</li>
+	#end
+						</ul>
+					</div><!-- /panel -->
+
+	## Dependents
+
+					<div class="panel panel-default">
+						<div class="panel-heading">Dependents</div>
+						<ul class="list-group">
+	#if ($dependents) 
+		#foreach($dependent in $dependents)
+							<li class="list-group-item">
+								<a href="${context}/manager?project=${project.name}&flow=${flowid}&job=$dependent">$dependent</a>
+							</li>
+		#end
+	#else
+							<li class="list-group-item">No Dependencies</li>
+	#end
+
+						</ul>
+					</div><!-- /panel -->
+
+					<div class="panel panel-default">
+						<div class="panel-heading">Properties</div>
+						<ul class="list-group">
+	#if ($properties) 
+		#foreach($property in $properties)
+							<li class="list-group-item">
+								<a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}&prop=$property">$property</a>
+							</li>
+		#end
+	#else
+							<li class="list-group-item">No Property Files For This Job</li>
+	#end
+						</ul>
+					</div><!-- /panel -->
+				</div><!-- /col-lg-4 -->
+			</div><!-- /row -->
+
+## Edit job modal.
+
+			<div class="modal modal-wide" id="job-edit-pane">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true" id="close-btn">&times;</button>
+							<h4 class="modal-title">Edit Job</h4>
+						</div>
+						<div class="modal-body">
+							<h4>Job Essentials</h4>
+              <table class="table table-bordered table-condensed">
+                <tbody>
+                  <tr>
+                    <td class="property-key">Job Name</td>
+                    <td id="jobName"></td>
+                  </tr>
+                  <tr>
+                    <td class="property-key">Job Type</td>
+                    <td id="jobType"></td>
+                  </tr>
+                </tbody>
+              <table>
+							<h4>General Job Settings</h4>
+							<p><strong>Be Aware:</strong> A job may be shared by multiple flows. The change will be global!</p>
+							<table id="generalProps" class="table table-striped table-bordered">
+								<thead>
+									<tr>
+										<th class="property-key">Name</th>
+										<th>Value</th>
+									</tr>
+								</thead>
+								<tbody>
+									<tr id="addRow">
+										<td id="addRow-col" colspan="2">
+											<button type="button" class="btn btn-xs btn-success">Add Row</button>
+										</td>
+									</tr>
+								</tbody>
+							</table>
+						</div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-default" id="cancel-btn" data-dismiss="modal">Cancel</button>
+							<button type="button" class="btn btn-primary" id="set-btn">Set/Change Job Description</button>
+						</div>
+					</div>
+				</div>
 			</div>
-		</div>
+
+		</div><!-- /container-full -->
+#end
 	</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/login.vm b/src/java/azkaban/webapp/servlet/velocity/login.vm
index 9d8e069..bba573e 100644
--- a/src/java/azkaban/webapp/servlet/velocity/login.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/login.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -13,51 +13,41 @@
  * License for the specific language governing permissions and limitations under
  * the License.
 *#
-<!DOCTYPE html> 
-<html>
+
+<!DOCTYPE html>
+<html lang="en">
   <head>
-    <head>
-    <title>#appname()</title>
-    <link rel="stylesheet" type="text/css" href="${context}/css/azkaban.css">    
-    <link rel="shortcut icon" href="${context}/favicon.ico" />
-	<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-	<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-	<script type="text/javascript" src="${context}/js/namespace.js"></script>
-	<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-	<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
+
+#parse("azkaban/webapp/servlet/velocity/style.vm")
+#parse("azkaban/webapp/servlet/velocity/javascript.vm")
 
     <script type="text/javascript" src="${context}/js/azkaban.login.js"></script>
-    #parse( "azkaban/webapp/servlet/velocity/style.vm" )
-	<script type="text/javascript">
-		var contextURL = "${context}";
-	</script>
+		<script type="text/javascript">
+			var contextURL = "${context}";
+		</script>
   </head>
   <body>
-    <div class="header">
-    #parse("azkaban/webapp/servlet/velocity/title.vm" )
-    </div>
-    
-    <div id="login" class="shadow-box">
-    	<div class="shadow-box-header">
-    		<div class="box-title">Login</div>
- 		</div>
-    	<br>
-    	<div id="errorMsg" class="box-error-message">$errorMsg</div>
-    	<div id="loginForm" class="login-form">
-    	    <div id="login-password">
-		    	<p class="login-label">Username</p>
-		    	<input id="username" type="text" name="username" class="login-textbox" ></input>
-	
-				<br/>
-		    	<p class="login-label">Password</p>
-		    	<input id="password" type="password" name="password" class="login-textbox"></input>
-			</div>
 
-	    	<div class="shadow-box-footer">
-	    	  <div id="loginSubmit" class="btn2 button1" class="login-submit">Login</div>
-	    	</div>
-    	</div>
-    </div>
+#set ($current_page = "all")
+#set ($navbar_disabled = 1)
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+		<div class="container-full">
+      <div class="login">
+        <div class="alert alert-danger" id="error-msg"></div>
+        <div class="well">
+          <form id="login-form" role="form">
+            <fieldset>
+              <legend>Login</legend>
+              <input type="text" class="form-control" name="username" id="username" placeholder="Username">
+              <input type="password" class="form-control" name="password" id="password" placeholder="Password">
+              <button type="button" class="btn btn-primary btn-lg btn-block" id="login-submit">Login</button>
+            </fieldset>
+          </form>
+        </div><!-- /.well -->
+      </div><!-- /.login -->
+
+		</div><!-- /container -->
   </body>
 </html>
 
diff --git a/src/java/azkaban/webapp/servlet/velocity/macros.vm b/src/java/azkaban/webapp/servlet/velocity/macros.vm
index b52d023..5c99c64 100644
--- a/src/java/azkaban/webapp/servlet/velocity/macros.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/macros.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/webapp/servlet/velocity/messagedialog.vm b/src/java/azkaban/webapp/servlet/velocity/messagedialog.vm
index a7932b3..3068df5 100644
--- a/src/java/azkaban/webapp/servlet/velocity/messagedialog.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/messagedialog.vm
@@ -1,9 +1,34 @@
-<script type="text/javascript" src="${context}/js/azkaban.message.dialog.view.js"></script>
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
 
-<div id="azkabanMessageDialog" class="modal">
-	<h3 id="azkabanMessageDialogTitle"></h3>
-	<div id="azkabanMessageDialogText"></div>
-	<div class="actions">
-		<a class="yes btn2 continueclass" id="continue-btn" href="#">Continue</a>
-	</div>
-</div>
\ No newline at end of file
+			<script type="text/javascript" src="${context}/js/azkaban.message.dialog.view.js"></script>
+			
+			<div class="modal" id="azkaban-message-dialog">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title" id="azkaban-message-dialog-title"></h4>
+						</div><!-- /modal-header -->
+						<div class="modal-body">
+							<p id="azkaban-message-dialog-text"></p>
+						</div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-primary" data-dismiss="modal">Continue</button>
+						</div>
+					</div>
+				</div>
+			</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/nav.vm b/src/java/azkaban/webapp/servlet/velocity/nav.vm
index 8019467..f709390 100644
--- a/src/java/azkaban/webapp/servlet/velocity/nav.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/nav.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -13,34 +13,59 @@
  * License for the specific language governing permissions and limitations under
  * the License.
 *#
-		<div id="header" class="header">
-#parse( "azkaban/webapp/servlet/velocity/title.vm" )
-			<script type="text/javascript">
-				function navMenuClick(url) {
-					window.location.href=url;
-				}
 
-			</script>
-
-			<ul id="nav" class="nav">
-				<li id="all-jobs-tab" #if($current_page == 'all')class="selected"#end onClick="navMenuClick('$!context/')"><a href="$!context/">Projects</a></li>
-				<li id="scheduled-jobs-tab" #if($current_page == 'schedule')class="selected"#end onClick="navMenuClick('$!context/schedule')"><a href="$!context/schedule">Scheduled</a></li>
-				<li id="executing-jobs-tab" #if($current_page == 'executing')class="selected"#end onClick="navMenuClick('$!context/executor')"><a href="$!context/executor">Executing</a></li>
-				<li id="history-jobs-tab" #if($current_page == 'history')class="selected"#end onClick="navMenuClick('$!context/history')"><a href="$!context/history">History</a></li>
-				
-				#foreach($viewer in $viewers)
-					#if(!$viewer.hidden)
-					<li #if($current_page == $viewer.pluginName) class="selected"#end onClick="navMenuClick('$!context/$viewer.pluginPath')">
-						<a href="$!context/$viewer.pluginPath">$viewer.pluginName</a>
-					</li>
-					#end
-				#end
-			</ul>
-			
-			<div id="user-id">
-				<a>${user_id}<div id="user-down"></div></a>    
-				<div id="user-menu">
-					<ul><li><a id="logout" href="$!context?logout">logout</a></li></ul>
+    <script type="text/javascript">
+      function navMenuClick(url) {
+        window.location.href = url;
+      }
+    </script>
+    <div class="navbar navbar-inverse navbar-static-top">
+      <div class="container-full">
+        <div class="navbar-header">
+#if ($navbar_disabled != 1)
+          <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+          </button>
+#end
+					<div class="navbar-logo">
+						<a href="${context}/">Azkaban</a>
+					</div>
+        </div>
+				<div class="navbar-left navbar-enviro">
+					<div class="navbar-enviro-name">${azkaban_name}</div>
+					<div class="navbar-enviro-server">${azkaban_label}</div>
 				</div>
-			</div>
-		</div>
\ No newline at end of file
+        <div class="navbar-collapse collapse">
+#if ($navbar_disabled != 1)
+          <ul class="nav navbar-nav">
+						<li#if($current_page == 'all') class="active"#end onClick="navMenuClick('$!context/')"><a href="$!context/index">Projects</a></li>
+						<li#if($current_page == 'schedule') class="active"#end onClick="navMenuClick('$!context/schedule')"><a href="$!context/schedule">Scheduling</a></li>
+						<!--<li#if($current_page == 'triggers') class="active"#end onClick="navMenuClick('$!context/triggers')"><a href="$!context/triggers">Triggers</a></li>-->
+						<li#if($current_page == 'executing') class="active"#end onClick="navMenuClick('$!context/executor')"><a href="$!context/executor">Executing</a></li>
+						<li#if($current_page == 'history') class="active"#end onClick="navMenuClick('$!context/history')"><a href="$!context/history">History</a></li>
+	#foreach ($viewer in $viewers)
+		#if (!$viewer.hidden)
+						<li#if($current_page == $viewer.pluginName) class="active"#end onClick="navMenuClick('$!context/$viewer.pluginPath')"><a href="$!context/$viewer.pluginPath">$viewer.pluginName</a></li>
+		#end
+	#end
+
+	#foreach ($trigger in $triggerPlugins)
+		#if (!$trigger.hidden)
+						<li#if($current_page == $trigger.pluginName) class="active"#end onClick="navMenuClick('$!context/$trigger.pluginPath')"><a href="$!context/$trigger.pluginPath">$trigger.pluginName</a></li>
+		#end
+	#end
+          </ul>
+          <ul class="nav navbar-nav navbar-right">
+            <li class="dropdown">
+							<a href="#" class="dropdown-toggle" data-toggle="dropdown">${user_id} <b class="caret"></b></a>
+              <ul class="dropdown-menu">
+                <li><a href="$!context?logout">Logout</a></li>
+              </ul>
+            </li>
+          </ul>
+#end
+        </div><!--/.nav-collapse -->
+      </div>
+    </div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm b/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
index 3d73924..43d29c4 100644
--- a/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,17 +15,14 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
 
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
+#parse ("azkaban/webapp/servlet/velocity/style.vm")
+#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
+
 		<script type="text/javascript" src="${context}/js/azkaban.permission.view.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.projectmodals.view.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
 			var currentTime = ${currentTime};
@@ -37,229 +34,314 @@
 		</script>
 	</head>
 	<body>
-#set($current_page="all")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
 
-			<div class="content">
-#if($errorMsg)
-				<div class="box-error-message">$errorMsg</div>
+#set ($current_page = "all")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
 #else
-#if($error_message != "null")
-				<div class="box-error-message">$error_message</div>
-#elseif($success_message != "null")
-				<div class="box-success-message">$success_message</div>
-#end
 
-				<div id="all-jobs-content">
-					<div class="section-hd">
-						<h2><a href="${context}/manager?project=${project.name}&permissions">Permissions</a></h2>
-						<div class="section-sub-hd">
-							<h4><a href="${context}/manager?project=${project.name}">Project <span>$project.name</span></a></h4>
-						</div>
-					</div>
-				</div>
+	## Page header.
+	
+  #parse ("azkaban/webapp/servlet/velocity/projectpageheader.vm")
 
-				<div id="project-users">
-					<table class="user-table">
-						<tr><td class="first">Project Admins:</td><td>$admins</td></tr>
-						<tr><td class="first">Your Permissions:</td><td>$userpermission.toString()</td></tr>
-						
-						<tr><td class="first"></td></tr>
-					</table>
-					
-					#if($isAdmin)
-						<button id="addUser" class="btn1">Add User</button>
-						<button id="addGroup" class="btn1">Add Group</button>
-						<button id="addProxyUser" class="btn2">Add Proxy User</button>
-					#end
-				</div>
+  ## Page content.
 
-				<div id="project-summary">
-					<table class="summary-table">
-						<tr><td class="first">Name:</td><td>$project.name</td></tr>
-						<tr><td class="first">Created Date:</td><td>$utils.formatDate($project.lastModifiedTimestamp)</td></tr>
-						<tr><td class="first">Modified Date:</td><td>$utils.formatDate($project.createTimestamp)</td></tr>
-						<tr><td class="first">Last Modified by:</td><td>$project.lastModifiedUser</td></tr>
-						<tr><td class="first">Description:</td><td id="pdescription">$project.description</td>
-						</tr>
-					</table>
-				</div>
+    <div class="container-full">
 
-		<table id="permissions-table" class="all-jobs permission-table">
-			<thead>
-				<tr>
-					<th class="tb-username">User</th>
-					<th class="tb-perm">Admin</th>
-					<th class="tb-read">Read</th>
-					<th class="tb-write">Write</th>
-					<th class="tb-execute">Execute</th>
-					<th class="tb-schedule">Schedule</th>					
-					#if($isAdmin)
-						<th class="tb-action"></th>
-					#end
-				</tr>
-			</thead>
-			<tbody>
-#if($permissions)
-#foreach($perm in $permissions)
-	<tr>
-		<td class="tb-username">#if($perm.first == $username) ${perm.first} <span class="sublabel">(you)</span> #else $perm.first #end</td>
-		#if ($perm.second.isPermissionNameSet("ADMIN")) 
-			<td><input id="${perm.first}-admin-checkbox" type="checkbox" name="admin" disabled="disabled" checked="true"></input></td>
-			<td><input id="${perm.first}-read-checkbox" type="checkbox" name="read" disabled="disabled" checked="true"></input></td>
-			<td><input id="${perm.first}-write-checkbox" type="checkbox" name="write" disabled="disabled" checked="true"></input></td>
-			<td><input id="${perm.first}-execute-checkbox" type="checkbox" name="execute" disabled="disabled" checked="true"></input></td>
-			<td><input id="${perm.first}-schedule-checkbox" type="checkbox" name="schedule" disabled="disabled" checked="true"></input></td>
-		#else
-			<td><input id="${perm.first}-admin-checkbox" type="checkbox" name="admin" disabled="disabled"></input></td>
-			<td><input id="${perm.first}-read-checkbox" type="checkbox" name="read" disabled="disabled"  #if ($perm.second.isPermissionNameSet("READ")) checked="true" #end></input></td>
-			<td><input id="${perm.first}-write-checkbox" type="checkbox" name="write" disabled="disabled" #if ($perm.second.isPermissionNameSet("WRITE")) checked="true" #end></input></td>
-			<td><input id="${perm.first}-execute-checkbox" type="checkbox" name="execute" disabled="disabled" #if ($perm.second.isPermissionNameSet("EXECUTE")) checked="true" #end></input></td>
-			<td><input id="${perm.first}-schedule-checkbox" type="checkbox" name="schedule" disabled="disabled" #if ($perm.second.isPermissionNameSet("SCHEDULE")) checked="true" #end></input></td>
-		#end
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
 
-		#if($isAdmin)
-			<td><button id="$perm.first" #if($perm.first == $username) disabled="disabled" class="change-btn btn-disabled" #else class="change-btn btn2" #end >Change</button></td>
-		#end
-	</tr>
-#end
-#else
-	<tr><td class="last">No Users Found.</td></tr>
-#end
-			</tbody>
-		</table>
+			<div class="row row-offcanvas row-offcanvas-right">
+				<div class="col-xs-12 col-sm-9">
+	
+	#set ($project_page = "permissions")
+	#parse ("azkaban/webapp/servlet/velocity/projectnav.vm")
 
-		<table id="group-permissions-table" class="all-jobs permission-table">
-			<thead>
-				<tr>
-					<th class="tb-username">Group</th>
-					<th class="tb-perm">Admin</th>
-					<th class="tb-read">Read</th>
-					<th class="tb-write">Write</th>
-					<th class="tb-execute">Execute</th>
-					<th class="tb-schedule">Schedule</th>
-					#if($isAdmin)
-						<th class="tb-action"></th>
-					#end
-				</tr>
-			</thead>
-			<tbody>
-#if($groupPermissions)
-#foreach($perm in $groupPermissions)
-	<tr>
-		<td class="tb-username">#if($perm.first == $username) ${perm.first} <span class="sublabel">(you)</span> #else $perm.first #end</td>
-		#if ($perm.second.isPermissionNameSet("ADMIN")) 
-			<td><input id="group-${perm.first}-admin-checkbox" type="checkbox" name="admin" disabled="disabled" checked="true"></input></td>
-			<td><input id="group-${perm.first}-read-checkbox" type="checkbox" name="read" disabled="disabled" checked="true"></input></td>
-			<td><input id="group-${perm.first}-write-checkbox" type="checkbox" name="write" disabled="disabled" checked="true"></input></td>
-			<td><input id="group-${perm.first}-execute-checkbox" type="checkbox" name="execute" disabled="disabled" checked="true"></input></td>
-			<td><input id="group-${perm.first}-schedule-checkbox" type="checkbox" name="schedule" disabled="disabled" checked="true"></input></td>
-		#else
-			<td><input id="group-${perm.first}-admin-checkbox" type="checkbox" name="admin" disabled="disabled"></input></td>
-			<td><input id="group-${perm.first}-read-checkbox" type="checkbox" name="read" disabled="disabled"  #if ($perm.second.isPermissionNameSet("READ")) checked="true" #end></input></td>
-			<td><input id="group-${perm.first}-write-checkbox" type="checkbox" name="write" disabled="disabled" #if ($perm.second.isPermissionNameSet("WRITE")) checked="true" #end></input></td>
-			<td><input id="group-${perm.first}-execute-checkbox" type="checkbox" name="execute" disabled="disabled" #if ($perm.second.isPermissionNameSet("EXECUTE")) checked="true" #end></input></td>
-			<td><input id="group-${perm.first}-schedule-checkbox" type="checkbox" name="schedule" disabled="disabled" #if ($perm.second.isPermissionNameSet("SCHEDULE")) checked="true" #end></input></td>
-		#end
+	## User permissions table.
 
-		#if($isAdmin)
-			<td><button id="group-$perm.first" class="change-btn btn2">Change</button></td>
-		#end
-	</tr>
-#end
-#else
-	<tr><td class="last">No Groups Found.</td></tr>
-#end
-			</tbody>
-		</table>
-		
-		<br/>
-		<table id="proxy-user-table" class="all-jobs permission-table">
-			<thead>
-				<tr>
-					<th class="tb-username">Proxy User</th>
-					#if($isAdmin)
-						<th class="tb-action"></th>
-					#end
-				</tr>
-			</thead>
-			<tbody>
-#if($proxyUsers)
-#foreach($proxyUser in $proxyUsers)
-	<tr>
-		<td class="tb-username">#if($proxyUser == $username) ${proxyUser} <span class="sublabel">(you)</span> #else $proxyUser #end</td>
-		#if($isAdmin)
-			<td><button id="proxy-${proxyUser}" name="${proxyUser}" class="remove-btn btn2">Remove</button></td>
+          <div class="panel panel-success">
+            <div class="panel-heading">
+              User
+  #if ($isAdmin)
+              <div class="pull-right">
+                <button id="addUser" class="btn btn-xs btn-success">Add</button>
+              </div>
+  #end
+            </div>
+            <table class="table table-striped permission-table" id="permissions-table">
+              <thead>
+                <tr>
+                  <th class="tb-username">User</th>
+                  <th class="tb-perm">Admin</th>
+                  <th class="tb-read">Read</th>
+                  <th class="tb-write">Write</th>
+                  <th class="tb-execute">Execute</th>
+                  <th class="tb-schedule">Schedule</th>					
+	#if ($isAdmin)
+                  <th class="tb-action"></th>
+  #end
+                </tr>
+              </thead>
+              <tbody>
+
+	#if ($permissions)
+		#foreach ($perm in $permissions)
+                <tr>
+                  <td class="tb-username">
+			#if ($perm.first == $username) 
+                    ${perm.first} <span class="sublabel">(you)</span> 
+			#else 
+                    $perm.first 
+			#end
+                  </td>
+			#if ($perm.second.isPermissionNameSet("ADMIN")) 
+                  <td><input id="${perm.first}-admin-checkbox" type="checkbox" name="admin" disabled="disabled" checked="true"></input></td>
+                  <td><input id="${perm.first}-read-checkbox" type="checkbox" name="read" disabled="disabled" checked="true"></input></td>
+                  <td><input id="${perm.first}-write-checkbox" type="checkbox" name="write" disabled="disabled" checked="true"></input></td>
+                  <td><input id="${perm.first}-execute-checkbox" type="checkbox" name="execute" disabled="disabled" checked="true"></input></td>
+                  <td><input id="${perm.first}-schedule-checkbox" type="checkbox" name="schedule" disabled="disabled" checked="true"></input></td>
+			#else
+                  <td><input id="${perm.first}-admin-checkbox" type="checkbox" name="admin" disabled="disabled"></input></td>
+                  <td><input id="${perm.first}-read-checkbox" type="checkbox" name="read" disabled="disabled"  #if ($perm.second.isPermissionNameSet("READ")) checked="true" #end></input></td>
+                  <td><input id="${perm.first}-write-checkbox" type="checkbox" name="write" disabled="disabled" #if ($perm.second.isPermissionNameSet("WRITE")) checked="true" #end></input></td>
+                  <td><input id="${perm.first}-execute-checkbox" type="checkbox" name="execute" disabled="disabled" #if ($perm.second.isPermissionNameSet("EXECUTE")) checked="true" #end></input></td>
+                  <td><input id="${perm.first}-schedule-checkbox" type="checkbox" name="schedule" disabled="disabled" #if ($perm.second.isPermissionNameSet("SCHEDULE")) checked="true" #end></input></td>
+			#end
+
+			#if ($isAdmin)
+                  <td><button id="$perm.first" #if($perm.first == $username) disabled="disabled" class="btn btn-xs btn-disabled" #else class="btn btn-xs btn-default" #end >Change</button></td>
+			#end
+                </tr>
 		#end
-	</tr>
-#end
-#else
-	<tr><td class="last">No Proxy User Found.</td></tr>
-#end
-			</tbody>
-		</table>
-#end
+	#else
+    #if ($isAdmin)
+                <tr><td colspan="7">No Users Found.</td></tr>
+    #else
+                <tr><td colspan="6">No Users Found.</td></tr>
+    #end
+	#end
+              </tbody>
+            </table>
+          </div>
 
-		</div>
-	
-		<div id="remove-proxy" class="modal">
-			<h3>Remove Proxy User</h3>
-			<div id="removeProxyErrorMsg" class="box-error-message"></div>
-			<p id="proxyRemoveMsg">Removing Proxy User </p>
-			<div class="actions">
-				<a class="yes btn2" id="remove-proxy-btn" href="#">Remove Proxy User</a>
-				<a class="no simplemodal-close btn3" href="#">Cancel</a>
-			</div>
-		</div>
+	## Group permissions table.
+							
+          <div class="panel panel-warning">
+            <div class="panel-heading">
+              Group
+  #if ($isAdmin)
+              <div class="pull-right">
+                <button id="addGroup" class="btn btn-xs btn-warning">Add</button>
+              </div>
+  #end
+            </div>
+            <table class="table table-striped permission-table" id="group-permissions-table">
+              <thead>
+                <tr>
+                  <th class="tb-username">Group</th>
+                  <th class="tb-perm">Admin</th>
+                  <th class="tb-read">Read</th>
+                  <th class="tb-write">Write</th>
+                  <th class="tb-execute">Execute</th>
+                  <th class="tb-schedule">Schedule</th>
+	#if ($isAdmin)
+                  <th class="tb-action"></th>
+	#end
+                </tr>
+              </thead>
+              <tbody>
+	#if ($groupPermissions)
+		#foreach ($perm in $groupPermissions)
+                <tr>
+                  <td class="tb-username">
+			#if ($perm.first == $username) 
+                    ${perm.first} <span class="sublabel">(you)</span> 
+			#else 
+                    $perm.first 
+			#end
+                  </td>
+			#if ($perm.second.isPermissionNameSet("ADMIN")) 
+                  <td><input id="group-${perm.first}-admin-checkbox" type="checkbox" name="admin" disabled="disabled" checked="true"></input></td>
+                  <td><input id="group-${perm.first}-read-checkbox" type="checkbox" name="read" disabled="disabled" checked="true"></input></td>
+                  <td><input id="group-${perm.first}-write-checkbox" type="checkbox" name="write" disabled="disabled" checked="true"></input></td>
+                  <td><input id="group-${perm.first}-execute-checkbox" type="checkbox" name="execute" disabled="disabled" checked="true"></input></td>
+                  <td><input id="group-${perm.first}-schedule-checkbox" type="checkbox" name="schedule" disabled="disabled" checked="true"></input></td>
+			#else
+                  <td><input id="group-${perm.first}-admin-checkbox" type="checkbox" name="admin" disabled="disabled"></input></td>
+                  <td><input id="group-${perm.first}-read-checkbox" type="checkbox" name="read" disabled="disabled"  #if ($perm.second.isPermissionNameSet("READ")) checked="true" #end></input></td>
+                  <td><input id="group-${perm.first}-write-checkbox" type="checkbox" name="write" disabled="disabled" #if ($perm.second.isPermissionNameSet("WRITE")) checked="true" #end></input></td>
+                  <td><input id="group-${perm.first}-execute-checkbox" type="checkbox" name="execute" disabled="disabled" #if ($perm.second.isPermissionNameSet("EXECUTE")) checked="true" #end></input></td>
+                  <td><input id="group-${perm.first}-schedule-checkbox" type="checkbox" name="schedule" disabled="disabled" #if ($perm.second.isPermissionNameSet("SCHEDULE")) checked="true" #end></input></td>
+			#end
+
+			#if ($isAdmin)
+                  <td><button id="group-$perm.first" class="btn btn-xs btn-default">Change</button></td>
+			#end
+                </tr>
+		#end
+	#else
+    #if ($isAdmin)
+                <tr><td colspan="7">No Groups Found.</td></tr>
+    #else
+                <tr><td colspan="6">No Groups Found.</td></tr>
+    #end
+	#end
+              </tbody>
+            </table>
+          </div>
 	
-		<div id="add-proxy" class="modal">
-			<h3 id="proxy-title">Add Proxy User</h3>
-			<div id="proxyErrorMsg" class="box-error-message"></div>
-			<dl>
-				<dt>Proxy</dt>
-				<dd><input id="proxy-user-box" name="proxyid" type="text" /></dd>
-			</dl>
-			<div class="actions">
-				<a class="yes btn2" id="add-proxy-btn" href="#">Add Proxy User</a>
-				<a class="no simplemodal-close btn3" href="#">Cancel</a>
-			</div>
-		</div>
+	## Proxy users table.
+							
+          <div class="panel panel-info">
+            <div class="panel-heading">
+              Proxy Users
+  #if ($isAdmin)
+              <div class="pull-right">                
+                <button id="addProxyUser" class="btn btn-xs btn-info">Add</button>
+              </div>
+	#end
+            </div>
+            <table class="table table-striped permission-table" id="proxy-user-table">
+              <thead>
+                <tr>
+                  <th class="tb-username">Proxy User</th>
+	#if ($isAdmin)
+                  <th class="tb-action"></th>
+	#end
+                </tr>
+              </thead>
+              <tbody>
+	#if ($proxyUsers)
+		#foreach ($proxyUser in $proxyUsers)
+                <tr>
+                  <td class="tb-username">#if($proxyUser == $username) ${proxyUser} <span class="sublabel">(you)</span> #else $proxyUser #end</td>
+			#if ($isAdmin)
+                  <td><button id="proxy-${proxyUser}" name="${proxyUser}" class="btn btn-xs btn-danger">Remove</button></td>
+			#end
+                </tr>
+		#end
+	#else
+    #if ($isAdmin)
+                <tr><td colspan="2">No Proxy User Found.</td></tr>
+    #else
+                <tr><td>No Proxy User Found.</td></tr>
+    #end
+	#end
+              </tbody>
+            </table>
+          </div>
 	
-		<div id="change-permission" class="modal">
-			<h3 id="change-title">Change Permissions</h3>
-			<div id="errorMsg" class="box-error-message"></div>
-			<div class="message">
-				<fieldset>
-					<dl>
-						<dt>User</dt>
-						<dd><input id="user-box" name="userid" type="text" /></dd>
-					<div id="otherCheckBoxes">
-						<dt class="nextline">Admin</dt>
-						<dd><input id="admin-change" name="admin" type="checkbox" /></dd>
-						<dt>Read</dt>
-					    <dd><input id="read-change" name="read" type="checkbox" /></dd>
-					    <dt>Write</dt>
-					    <dd><input id="write-change" name="write" type="checkbox" /></dd>
-					    <dt>Execute</dt>
-					    <dd><input id="execute-change" name="execute" type="checkbox" /></dd>
-					    <dt>Schedule</dt>
-					    <dd><input id="schedule-change" name="schedule" type="checkbox" /></dd>
+				</div><!-- /col-lg-8 -->
+				<div class="col-xs-6 col-sm-3 sidebar-offcanvas">
+	#parse ("azkaban/webapp/servlet/velocity/projectsidebar.vm")
+				</div><!-- /col-lg-4 -->
+			</div><!-- /row -->
+
+## Remove proxy user modal dialog.
+
+			<div class="modal" id="remove-proxy">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Remove Proxy User</h4>
+						</div>
+						<div class="modal-body">
+							<div class="alert alert-danger" id="remove-proxy-error-msg"></div>
+              <p><strong>Warning:</strong> Removing Proxy User</p>
+						</div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+							<button type="button" class="btn btn-danger" id="remove-proxy-btn">Remove Proxy User</a>
+						</div>
 					</div>
-					</dl>
-				</fieldset>
+				</div>
 			</div>
-			<div class="actions">
-				<a class="yes btn2" id="change-btn" href="#">Commit</a>
-				<a class="no simplemodal-close btn3" href="#">Cancel</a>
+
+## Add proxy user modal dialog.
+
+			<div class="modal" id="add-proxy">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Add Proxy User</h4>
+						</div>
+						<div class="modal-body">
+							<div class="alert alert-danger" id="add-proxy-error-msg"></div>
+							<fieldset class="form-horizontal">
+								<div class="form-group">
+									<label for="path" class="col-sm-2 control-label">Proxy</label>
+									<div class="col-sm-10">
+										<input type="text" name="proxyid" id="proxy-user-box" class="form-control">
+									</div>
+								</div>
+							</fieldset>
+						</div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+							<button type="button" class="btn btn-primary" id="add-proxy-btn">Add Proxy User</button>
+						</div>
+					</div>
+				</div>
 			</div>
-		</div>
-		<div id="invalid-session" class="modal">
-			<h3>Invalid Session</h3>
-			<p>Session has expired. Please re-login.</p>
-			<div class="actions">
-				<a class="yes btn2" id="login-btn" href="#">Re-login</a>
+			
+## Change permissions modal dialog.
+
+			<div class="modal" id="change-permission">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Change Permissions</h4>
+						</div>
+						<div class="modal-body">
+							<div class="alert alert-danger" id="change-permission-error-msg"></div>
+							<fieldset class="form-horizontal">
+								<div class="form-group">
+									<label for="path" class="col-sm-2 control-label">User</label>
+									<div class="col-sm-10">
+										<input type="text" name="userid" id="user-box" class="form-control">
+									</div>
+								</div>
+								<div class="form-group">
+                  <div class="col-sm-offset-2 col-sm-10">
+                    <label class="checkbox-inline">
+                      <input id="admin-change" name="admin" type="checkbox">
+                      Admin
+                    </label>
+                    <label class="checkbox-inline">
+                      <input id="read-change" name="read" type="checkbox">
+                      Read
+                    </label>
+                    <label class="checkbox-inline">
+                      <input id="write-change" name="write" type="checkbox">
+                      Write
+                    </label>
+                    <label class="checkbox-inline">
+                      <input id="execute-change" name="execute" type="checkbox">
+                      Execute
+                    </label>
+                    <label class="checkbox-inline">
+                      <input id="schedule-change" name="schedule" type="checkbox">
+                      Schedule
+                    </label>
+                  </div>
+								</div>
+							</fieldset>
+						</div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+							<button type="button" class="btn btn-primary" id="change-btn">Commit</button>
+						</div>
+					</div>
+				</div>
 			</div>
-		</div>
+
+	#parse ("azkaban/webapp/servlet/velocity/projectmodals.vm")
+	#parse ("azkaban/webapp/servlet/velocity/invalidsessionmodal.vm")
+		</div><!-- /container-full -->
+#end
 	</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectlogpage.vm b/src/java/azkaban/webapp/servlet/velocity/projectlogpage.vm
index 4392510..dbb998a 100644
--- a/src/java/azkaban/webapp/servlet/velocity/projectlogpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/projectlogpage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,71 +15,81 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
+
+#parse ("azkaban/webapp/servlet/velocity/style.vm")
+#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
+
 		<script type="text/javascript" src="${context}/js/azkaban.date.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.projectlog.view.js"></script>
-		
+		<script type="text/javascript" src="${context}/js/azkaban.projectmodals.view.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
 			var currentTime = ${currentTime};
 			var timezone = "${timezone}";
 			var errorMessage = null;
 			var successMessage = null;
-			
-			var projectName = "${projectName}";
-
+		
+			var projectId = ${project.id};
+			var projectName = "$project.name";
 		</script>
 	</head>
 	<body>
-		#set($current_page="executing")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="content">
-#if($errorMsg)
-				<div class="box-error-message">$errorMsg</div>
+
+#set ($current_page="all")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
 #else
-#if($error_message != "null")
-				<div class="box-error-message">$error_message</div>
-#elseif($success_message != "null")
-				<div class="box-success-message">$success_message</div>
-#end
-		
-			<div id="all-jobs-content">
-				<div class="section-hd flow-header">
-					<h2><a href="${context}/manager?project=${projectName}">Project Audit Logs <span>$projectName</span></a></h2>
-				</div>
-			</div>
-			
-			<div id="headertabs" class="headertabs">
-				<ul>
-					<li><a id="logViewLink" href="#log">Log</a></li>
-				</ul>
-			</div>
 
-			<div id="projectLogView" class="logView">
-				<div class="logHeader"><div class="logButtonRow"><div id="updateLogBtn" class="btn7">Refresh</div></div></div>
-				<div id="logViewer" class="logViewer">
-					<table id="logTable">
-						
-					</table>
+	## Page header.
+	
+  #parse ("azkaban/webapp/servlet/velocity/projectpageheader.vm")
+
+	## Page content.
+
+    <div class="container-full">
+
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+
+			<div class="row row-offcanvas row-offcanvas-right">
+				<div class="col-xs-12 col-sm-9">
+	
+	#set ($project_page = "logs")
+	#parse ("azkaban/webapp/servlet/velocity/projectnav.vm")
+
+					<div class="panel panel-default" id="flow-tabs">
+						<div class="panel-heading">
+							<div class="pull-right" id="project-options">
+								<button type="button" id="updateLogBtn" class="btn btn-xs btn-info">Refresh</button>
+							</div>
+							Audit Logs
+						</div>
+            <table class="table table-striped" id="logTable">
+              <thead>
+                <tr>
+                  <th>Time</th>
+                  <th>User</th>
+                  <th>Type</th>
+                  <th>Message</th>
+                </tr>
+              </thead>
+              <tbody>
+              </tbody>
+						</table>
+					</div>
 				</div>
-			</div>
-#end
-			<div id="messageDialog" class="modal">
-				<h3 id="messageTitle">Error</h3>
-				<div class="messageDiv">
-					<p id="messageBox"></p>
+				<div class="col-xs-6 col-sm-3 sidebar-offcanvas">
+	#parse ("azkaban/webapp/servlet/velocity/projectsidebar.vm")
 				</div>
 			</div>
 
-		</div>
+	#parse ("azkaban/webapp/servlet/velocity/projectmodals.vm")
+
+		</div><!-- /container-full -->
+#end
 	</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectmodals.vm b/src/java/azkaban/webapp/servlet/velocity/projectmodals.vm
new file mode 100644
index 0000000..2dc064a
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/projectmodals.vm
@@ -0,0 +1,71 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+  ## Upload project modal
+
+			<div class="modal" id="upload-project-modal">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<form id="upload-project-form" enctype="multipart/form-data" method="post" action="$!context/manager">
+							<div class="modal-header">
+								<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+								<h4 class="modal-title">Upload Project Files</h4>
+							</div>
+							<div class="modal-body">
+								<div class="alert alert-danger" id="upload-project-modal-error-msg">$error_msg</div>
+								<fieldset class="form-horizontal">
+									<div class="form-group">
+										<label for="file" class="col-sm-3 control-label">Job Archive</label>
+										<div class="col-sm-9">
+											<input type="file" class="form-control" id="file" name="file">
+										</div>
+									</div>
+								</fieldset>
+							</div>
+							<div class="modal-footer">
+								<input type="hidden" name="project" value="$project.name">
+								<input type="hidden" name="action" value="upload">
+								<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+								<button type="button" class="btn btn-primary" id="upload-project-btn">Upload</button>
+							</div>
+						</form>
+					</div>
+				</div>
+			</div>
+
+	## Delete project modal.
+			
+			<div class="modal" id="delete-project-modal">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Delete Project</h4>
+						</div>
+						<div class="modal-body">
+							<p><strong>Warning:</strong> This project will be deleted and may not be recoverable.</p>
+						</div>
+						<div class="modal-footer">
+							<form id="delete-form">
+								<input type="hidden" name="project" value="$project.name">
+								<input type="hidden" name="delete" value="true">
+								<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+								<button type="button" class="btn btn-danger" id="delete-btn">Delete Project</button>
+							</form>
+						</div>
+					</div>
+				</div>
+			</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectnav.vm b/src/java/azkaban/webapp/servlet/velocity/projectnav.vm
new file mode 100644
index 0000000..c19a6ab
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/projectnav.vm
@@ -0,0 +1,23 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+					<ul class="nav nav-tabs">
+						<li#if($project_page == 'flows') class="active"#end><a href="${context}/manager?project=${project.name}">Flows</a></li>
+						<li#if($project_page == 'permissions') class="active"#end><a id="project-permission-btn" href="${context}/manager?project=${project.name}&permissions">Permissions</a></li>
+	#if ($admin)
+						<li#if($project_page == 'logs') class="active"#end><a id="project-logs-btn" href="${context}/manager?project=${project.name}&logs">Project Logs</a></li>
+	#end
+					</ul>
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectpage.vm b/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
index 6c82bfb..8d6e865 100644
--- a/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,22 +15,19 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-1.10.1.custom.js"></script>
-		
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.project.view.js"></script>
 
-		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
+#parse ("azkaban/webapp/servlet/velocity/style.vm")
+#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
 
+		<link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
+		<link rel="stylesheet" type="text/css" href="${context}/css/azkaban-svg.css" />
+		<script type="text/javascript" src="${context}/js/moment.min.js"></script>
+		<script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.project.view.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.projectmodals.view.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
 			var currentTime = ${currentTime};
@@ -39,149 +36,75 @@
 			var successMessage = null;
 
 			var projectId = ${project.id};
-
 			var execAccess = ${exec};
 			var projectName = "$project.name";
 		</script>
 	</head>
 	<body>
-#set($current_page="all")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
 
-			<div class="content">
-#if($errorMsg)
-				<div class="box-error-message"><pre>$errorMsg</pre></div>
+#set ($current_page="all")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
 #else
-#if($error_message != "null")
-				<div class="box-error-message"><pre>$error_message</pre></div>
-#elseif($success_message != "null")
-				<div class="box-success-message"><pre>$success_message</pre></div>
-#end
 
-				<div id="all-jobs-content">
-					<div class="section-hd">
-						<h2><a href="${context}/manager?project=${project.name}">Project <span>$project.name</span></a></h2>
-						<a id="project-upload-btn" class="btn1 projectupload">Upload</a>
-						<a id="project-permission-btn" class="btn5 projectpermission" href="${context}/manager?project=${project.name}&permissions">Permissions</a>
-						#if($admin)
-						<a id="project-logs-btn" class="btn2" href="${context}/manager?project=${project.name}&logs">Project Logs</a>
-						<a id="project-delete-btn" class="btn6">Delete Project</a>
-						#end
-					</div><!-- end .section-hd -->
-				</div>
+	## Page header.
+	
+  #parse ("azkaban/webapp/servlet/velocity/projectpageheader.vm")
 
-				<div id="project-users">
-					<table class="user-table">
-						<tr><td class="first">Project Admins:</td><td>$admins</td></tr>
-						<tr><td class="first">Your Permissions:</td><td>$userpermission.toString()</td></tr>
-					</table>
-				</div>
+    <div class="container-full">
+      
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
 
-				<div id="project-summary">
-					<table class="summary-table">
-						<tr><td class="first">Name:</td><td>$project.name</td></tr>
-						<tr><td class="first">Created Date:</td><td>$utils.formatDate($project.createTimestamp)</td></tr>
-						<tr><td class="first">Modified Date:</td><td>$utils.formatDate($project.lastModifiedTimestamp)</td></tr>
-						<tr><td class="first">Modified by:</td><td>$project.lastModifiedUser</td></tr>
-						<tr><td class="first">Description:</td><td id="pdescription">$project.description</td>
-							#if($admin)
-								<td><div id="edit" class="btn5">Edit Description</div></td>
-							#end
-						</tr>
-					</table>
-				</div>
+  ## Page content.
 
-			<div id="flow-tabs">
-				<table id="all-jobs" class="all-jobs job-table">
-					<thead>
-						<tr>
-							<th class="tb-name">Flow Name</th>
-						</tr>
-					</thead>
-					<tbody>
-#if($flows)
-#foreach($flow in $flows)
-						<tr class="row flowrow">
-							<td class="tb-name" flow="${flow.id}" project="${project.name}">
-									<div class="jobfolder expand" id="${flow.id}">
-										<span class="state-icon"></span>
-										<a href="${context}/manager?project=${project.name}&flow=${flow.id}">${flow.id}</a>
-									</div>
-									#if (${exec})
-									<div class="job-hover-menu">
-										<div class="btn1 executeFlow" flowId="${flow.id}">Execute Flow</div>
-									</div>
-									#end
-							</td>
-						</tr>
-						<tr class="childrow" id="${flow.id}-child" style="display: none;">
-							<td class="expandedFlow">
-								<table class="innerTable">
-									<thead>
-										<tr><th class="tb-name">Jobs</th></tr>
-									</thead>
-									<tbody id="${flow.id}-tbody">
-									</tbody>
-								</table>
-							</td>
-						</tr>
-#end
-#else
-						<tr><td class="last">No flows uploaded to this project.</td></tr>
-#end
-					</tbody>
-				</table>
-			</div>
+			<div class="row row-offcanvas row-offcanvas-right">
+				<div class="col-xs-12 col-sm-9" id="flow-tabs">
+					
+	#set ($project_page = "flows")
+	#parse ("azkaban/webapp/servlet/velocity/projectnav.vm")
+
+            <div id="flow-tabs">
+	#if ($flows)
+		#foreach ($flow in $flows)
+              <div class="panel panel-default" flow="${flow.id}" project="${project.name}">
+                <div class="panel-heading flow-expander" id="${flow.id}">
+			#if (${exec})
+                <div class="pull-right">
+                  <button type="button" class="btn btn-xs btn-success execute-flow" flowId="${flow.id}">Execute Flow</button>
+                  <a href="${context}/manager?project=${project.name}&flow=${flow.id}#executions" class="btn btn-info btn-xs">Executions</a>
+                  <a href="${context}/manager?project=${project.name}&flow=${flow.id}#summary" class="btn btn-info btn-xs">Summary</a>
+                </div>
+			#end
+                <a href="${context}/manager?project=${project.name}&flow=${flow.id}">${flow.id}</a>
+              </div>
+              <div id="${flow.id}-child" class="panel-collapse panel-list collapse">
+                <ul class="list-group list-group-collapse expanded-flow-job-list" id="${flow.id}-tbody"></ul>
+              </div>
+            </div>
+		#end
+	#else
+            <div class="alert alert-info">
+              <h4>No Flows</h4>
+              <p>No flows have been uploaded to this project yet.</p>
+            </div>
+	#end
+          </div><!-- /#flow-tabs -->
+				</div><!-- /col-lg-8 -->
+
+				<div class="col-xs-6 col-sm-3 sidebar-offcanvas">
+	#parse ("azkaban/webapp/servlet/velocity/projectsidebar.vm")
+				</div><!-- /col-lg-4 -->
+			</div><!-- /row -->
+
+	#parse ("azkaban/webapp/servlet/velocity/projectmodals.vm")
+	#parse ("azkaban/webapp/servlet/velocity/invalidsessionmodal.vm")
+	#parse ("azkaban/webapp/servlet/velocity/flowexecutionpanel.vm")
+	#parse ("azkaban/webapp/servlet/velocity/messagedialog.vm")
+
+		</div><!-- /container -->
 #end
-		</div>
-	
-		<div id="upload-project" class="modal">
-			<h3>Upload Project Files</h3>
-			<div id="errorMsg" class="box-error-message">$errorMsg</div>
-			<div class="message">
-				<form id="upload-form" enctype="multipart/form-data" method="post" action="$!context/manager">
-					<fieldset>
-						<dl>
-							<dt>Job Archive</dt>
-							<dd><input id="file" name="file" class="file" type="file" /></dd>
-							<input type="hidden" name="project" value="$project.name" />
-							<input type="hidden" name="action" value="upload" />
-						</dl>
-					</fieldset>
-				</form>
-			</div>
-			<div class="actions">
-				<a class="yes btn2" id="upload-btn" href="#">Upload</a>
-				<a class="no simplemodal-close btn3" href="#">Cancel</a>
-			</div>
-			<div id="invalid-session" class="modal">
-				<h3>Invalid Session</h3>
-				<p>Session has expired. Please re-login.</p>
-				<div class="actions">
-					<a class="yes btn2" id="login-btn" href="#">Re-login</a>
-				</div>
-			</div>
-		</div>
-		<div id="delete-project" class="modal">
-			<h3>Delete Project</h3>
-			<div class="warn">
-				<div class="warning-icon"></div>
-				<div class="warning-message"><p>Warning: This project will be deleted and may not be recoverable.</p></div>
-			</div>
-			<form id="delete-form">
-				<input type="hidden" name="project" value="$project.name" />
-				<input type="hidden" name="delete" value="true" />
-			</form>
-			
-			<div class="actions">
-				<a class="no simplemodal-close btn3" href="#">Cancel</a>
-				<a class="yes btn6" id="delete-btn" href="#">Yes</a>
-			</div>
-		</div>
-		#parse( "azkaban/webapp/servlet/velocity/flowexecutionpanel.vm" )
-		#parse( "azkaban/webapp/servlet/velocity/messagedialog.vm" )
 	</body>
-	
 </html>
 
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectpageheader.vm b/src/java/azkaban/webapp/servlet/velocity/projectpageheader.vm
new file mode 100644
index 0000000..0936995
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/projectpageheader.vm
@@ -0,0 +1,44 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+		<div class="az-page-header">
+			<div class="container-full">
+        <div class="row">
+          <div class="col-lg-6" id="project-page-header">
+            <h1><a href="${context}/manager?project=${project.name}">Project <small>$project.name</small></a></h1>
+            <p class="editable" id="project-description">$project.description</p>
+            <div id="project-description-form" class="editable-form">
+              <div class="input-group">
+                <input type="text" class="form-control input-sm" id="project-description-edit" placeholder="Project description">
+                <span class="input-group-btn">
+                  <button class="btn btn-primary btn-sm" type="button" id="project-description-btn">Save</button>
+                </span>
+              </div>
+            </div>
+          </div>
+          <div class="col-lg-6">
+            <div class="pull-right az-page-header-form" id="project-options">
+              <button id="project-delete-btn" class="btn btn-sm btn-danger">
+                <span class="glyphicon glyphicon-trash"></span> Delete Project
+              </button>
+              <button id="project-upload-btn" class="btn btn-sm btn-primary">
+                <span class="glyphicon glyphicon-upload"></span> Upload
+              </button>
+            </div>
+          </div>
+        </div>
+			</div>
+		</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectsidebar.vm b/src/java/azkaban/webapp/servlet/velocity/projectsidebar.vm
new file mode 100644
index 0000000..20bb3bf
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/projectsidebar.vm
@@ -0,0 +1,27 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+					<div class="well">
+						<h3>$project.name</h3>
+						<p><strong>Created on</strong> $utils.formatDate($project.createTimestamp)</p>
+						<p><strong>Last modified by</strong> $utils.formatDate($project.lastModifiedTimestamp)</p>
+						<p><strong>Modified by</strong> $project.lastModifiedUser</p>
+
+						<hr>
+
+						<p><strong>Project admins:</strong> $admins</p>
+						<p><strong>Your Permissions:</strong> $userpermission.toString()</p>
+					</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/propertypage.vm b/src/java/azkaban/webapp/servlet/velocity/propertypage.vm
index 3f25510..c1d3097 100644
--- a/src/java/azkaban/webapp/servlet/velocity/propertypage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/propertypage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,16 +15,12 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
-
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
+
+#parse("azkaban/webapp/servlet/velocity/style.vm")
+#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
+
 		<script type="text/javascript">
 			var contextURL = "${context}";
 			var currentTime = ${currentTime};
@@ -36,73 +32,96 @@
 		</script>
 	</head>
 	<body>
-#set($current_page="all")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
 
-#if($errorMsg)
-			<div class="box-error-message">$errorMsg</div>
+#set($current_page="all")
+#parse("azkaban/webapp/servlet/velocity/nav.vm")
+		
+#if ($errorMsg)
+  #parse("azkaban/webapp/servlet/velocity/errormsg.vm")
 #else
-	<div class="content">
-	#if($error_message != "null")
-			<div class="box-error-message">$error_message</div>
-	#elseif($success_message != "null")
-			<div class="box-success-message">$success_message</div>
+
+	## Page header
+
+		<div class="az-page-header">
+			<div class="container-full">
+        <h1><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}&prop=${property}">Properties <small>$property</small></a></h1>
+			</div>
+		</div>
+
+    <div class="container-full">
+
+  #parse("azkaban/webapp/servlet/velocity/alerts.vm")
+
+			<div class="row row-offcanvas row-offcanvas-right">
+				<div class="col-xs-12 col-sm-9">
+
+	## Breadcrumb
+
+					<ol class="breadcrumb">
+						<li><a href="${context}/manager?project=${project.name}"><strong>Project</strong> $project.name</a></li>
+						<li><a href="${context}/manager?project=${project.name}&flow=${flowid}"><strong>Flow</strong> $flowid</a></li>
+						<li><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}"><strong>Job</strong> $jobid</a></li>
+            <li class="active"><strong>Properties</strong> $property</li>
+					</ol>
+	
+	## Properties
+
+					<div class="panel panel-default">
+						<div class="panel-heading">$property</div>
+
+						<table class="table table-striped table-bordered properties-table">
+							<thead>
+								<tr>
+									<th class="tb-pname">Parameter Name</th>
+									<th class="tb-pvalue">Value</th>
+								</tr>
+							</thead>
+							<tbody>
+	#foreach ($parameter in $parameters)
+								<tr>
+									<td class="property-key">$parameter.first</td>
+                  <td>$parameter.second</td>
+								</tr>
 	#end
-			<div id="all-jobs-content">
-				<div class="section-hd">
-					<h2><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}&prop=${property}">Property <span>$property</span></a></h2>
-					<div class="section-sub-hd">
-						<h4><a href="${context}/manager?project=${project.name}">Project <span>$project.name</span></a></h4>
-						<h4 class="separator">&gt;</h4>
-						<h4><a href="${context}/manager?project=${project.name}&flow=${flowid}">Flow <span>$flowid</span></a></h4>
-						<h4 class="separator">&gt;</h4>
-						<h4><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}">Job <span>$jobid</span></a></h4>
+							</tbody>
+						</table>
+					</div>
+				</div><!-- /col-lg-8 -->
+				<div class="col-xs-6 col-sm-3 sidebar-offcanvas">
+					<div class="well" id="job-summary">
+						<h4>Properties <small>$property</small></h4>
+						<p><strong>Job</strong> $jobid</p>
 					</div>
-				</div>
-			</div>
-			
-			<div id="property-summary">
-				<table class="summary-table">
-					<tr><td class="first">Inherited From:</td><td>
-#if ($inheritedproperties) 
-#foreach($inheritedproperty in $inheritedproperties)
-					<a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}&prop=$inheritedproperty">$inheritedproperty</a>
-#end
-#else
-					<span>No Inherited Properties</span>
-#end
-					</td></tr>
-					<tr><td class="first">Source Of:</td><td>
-#if ($dependingproperties) 
-#foreach($dependingproperty in $dependingproperties)
-					<span class="nowrap"><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}&prop=$dependingproperty">$dependingproperty</a></span>
-#end
-#else
-					<span>No Dependents</span>
-#end
-					</td></tr>
 
-				</table>
-			</div>
-			
-			<table id="all-jobs" class="all-jobs job-table parameters">
-				<thead>
-					<tr>
-						<th class="tb-pname">Parameter Name</th>
-						<th class="tb-pvalue">Value</th>
-					</tr>
-				</thead>
-				<tbody>
-#foreach($parameter in $parameters)
-					<tr>
-						<td class="first">$parameter.first</td><td>$parameter.second</td>
-					</tr>
-#end
-				</tbody>
-			</table>
-	</div>
+					<div class="panel panel-default">
+						<div class="panel-heading">Inherited From</div>
+						<ul class="list-group">
+	#if ($inheritedproperties) 
+		#foreach ($inheritedproperty in $inheritedproperties)
+							<li class="list-group-item"><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}&prop=$inheritedproperty">$inheritedproperty</a></li>
+		#end
+	#else
+							<li class="list-group-item">No inherited properties.</li>
+	#end
+						</ul>
+					</div>
+
+					<div class="panel panel-default">
+						<div class="panel-heading">Source of</div>
+						<ul class="list-group">
+	#if ($dependingproperties) 
+		#foreach ($dependingproperty in $dependingproperties)
+							<li class="list-group-item"><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=${jobid}&prop=$dependingproperty">$dependingproperty</a></li>
+		#end
+	#else
+							<li class="list-group-item">No dependents.</li>
+	#end
+						</ul>
+					</div>
+				</div>
+			</div><!-- /row -->
 
+		</div><!-- /container-full -->
 #end
 	</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/scheduledflowcalendarpage.vm b/src/java/azkaban/webapp/servlet/velocity/scheduledflowcalendarpage.vm
index 2652467..761350d 100644
--- a/src/java/azkaban/webapp/servlet/velocity/scheduledflowcalendarpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/scheduledflowcalendarpage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,24 +15,20 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
+
+#parse ("azkaban/webapp/servlet/velocity/style.vm")
+#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
+
 		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
 		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui.css" />
 		
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
 		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-1.10.1.custom.js"></script>
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
 		<script type="text/javascript" src="${context}/js/jquery/jquery.svg.min.js"></script>    
-		
 		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-timepicker-addon.js"></script> 
 		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-sliderAccess.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.table.sort.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.schedule.svg.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.context.menu.js"></script>
 		<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
@@ -46,39 +42,36 @@
 		<link rel="stylesheet" type="text/css" href="${context}/css/jquery.svg.css" />
 	</head>
 	<body>
-#set($current_page="schedule")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
 
-		<div class="content">
-		
-#if($errorMsg)
-		<div class="box-error-message">$errorMsg</div>
+#set ($current_page="schedule")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
 #else
-#if($error_message != "null")
-		<div class="box-error-message">$error_message</div>
-#elseif($success_message != "null")
-		<div class="box-success-message">$success_message</div>
+
+    <div class="az-page-header">
+      <div class="container-full">
+        <h1>Scheduled Flows</h1>
+      </div>
+    </div>
+
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+
+    <div class="container-full">
+      <div class="row">
+        <div class="col-lg-12">
+          <div class="pull-right">
+            <button type="button" class="nav-prev-week btn btn-default">Previous Week</button>
+            <button type="button" class="nav-this-week btn btn-default">Today</button>
+            <button type="button" class="nav-next-week btn btn-default">Next Week</button>
+          </div>
+          <div id="svgDivCustom"></div>
+        </div>
+      </div>
+      
+      <div id="contextMenu"></div>
+    </div>
 #end
-#end		
-		
-		<div id="all-scheduledFlows-content">
-			<div class="section-hd">
-				<h2>Scheduled Flows</h2>
-			</div>
-		</div>
-		
-		<div class="scheduledFlows">
-			<span class="nav-prev-week btn1" style="margin: 20px; margin-left: 50px;"><a>Previous Week</a></span>
-			<span class="nav-this-week btn1" style="margin: 20px;"><a>Today</a></span>
-			<span class="nav-next-week btn1" style="margin: 20px; margin-right: 50px;"><a>Next Week</a></span>
-			<div id="svgDivCustom">
-			
-			</div>
-		</div>
-		</div>
-		
-		<div id="contextMenu">
-		</div>
 	</body>
 </html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm
index 4e2fbd8..87285cf 100644
--- a/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,23 +15,17 @@
 *#
 
 <!DOCTYPE html> 
-<html>
-	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
-		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui.css" />
-		
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-1.10.1.custom.js"></script>
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
+<html lang="en">
+  <head>
+
+#parse ("azkaban/webapp/servlet/velocity/style.vm")
+#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
+
+		<link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
 		
-		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-timepicker-addon.js"></script> 
-		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-sliderAccess.js"></script>
+		<script type="text/javascript" src="${context}/js/moment.min.js"></script>
+		<script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.table.sort.js"></script>
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.scheduled.view.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
@@ -42,123 +36,122 @@
 		</script>
 	</head>
 	<body>
-#set($current_page="schedule")
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
 
-		<div class="content">
-		
-#if($errorMsg)
-		<div class="box-error-message">$errorMsg</div>
-#else
-#if($error_message != "null")
-		<div class="box-error-message">$error_message</div>
-#elseif($success_message != "null")
-		<div class="box-success-message">$success_message</div>
-#end
-#end		
-		
-			<div id="all-scheduledFlows-content">
-				<div class="section-hd">
-					<h2>Scheduled Flows</h2>
-				</div>
-			</div>
-			
-			<div class="scheduledFlows">
-				<table id="scheduledFlowsTbl">
-					<thead>
-						<tr>
-							<!--th class="execid">Execution Id</th-->
-							<th>ID</th>
-							<th>Flow</th>
-							<th>Project</th>
-							<th>Submitted By</th>
-							<th class="date">First Scheduled to Run</th>
-							<th class="date">Next Execution Time</th>
-							<th class="date">Repeats Every</th>
-							<th>Has SLA</th>
-							<th colspan="2" class="action ignoresort">Action</th>
-						</tr>
-					</thead>
-					<tbody>
-						#if($schedules)
-#foreach($sched in $schedules)
-						<tr class="row" >
+#set ($current_page="schedule")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
 
-							<td>${sched.scheduleId}</td>
-							<td class="tb-name">
-								<a href="${context}/manager?project=${sched.projectName}&flow=${sched.flowName}">${sched.flowName}</a>
-							</td>
-							<td>
-								<a href="${context}/manager?project=${sched.projectName}">${sched.projectName}</a>
-							</td>
-							<td>${sched.submitUser}</td>
-							<td>$utils.formatDateTime(${sched.firstSchedTime})</td>
-							<td>$utils.formatDateTime(${sched.nextExecTime})</td>
-							<td>$utils.formatPeriod(${sched.period})</td>
-							<td>#if(${sched.slaOptions}) true #else false #end</td>
-							<td><button id="removeSchedBtn" onclick="removeSched(${sched.scheduleId})" >Remove Schedule</button></td>
-							<td><button id="addSlaBtn" onclick="slaView.initFromSched(${sched.scheduleId}, '${sched.flowName}')" >Set SLA</button></td>
-						</tr>
-#end
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
 #else
-						<tr><td class="last">No Scheduled Flow Found</td></tr>
-#end
-					</tbody>
-				</table>
+
+	## Page header.
+
+		<div class="az-page-header">
+			<div class="container-full">
+        <h1><a href="${context}/schedule">Scheduled Flows</a></h1>
 			</div>
+		</div>
 
-		<!-- modal content -->
+	## Page content.
 
-		<div id="slaModalBackground" class="modalBackground2">
-			<div id="sla-options" class="modal modalContainer2">
-				<a href='#' title='Close' class='modal-close'>x</a>
-					<h3>SLA Options</h3>
-					<div>
-						<ul class="optionsPicker">
-							<li id="slaOptions">General SLA Options</li>
-						</ul>
-					</div>
-					<div class="optionsPane">
-						<div id="generalPanel" class="generalPanel panel">
-							<div id="slaActions">
-								<h4>SLA Alert Emails</h4>
-								<dl>
-									<dt >SLA Alert Emails</dt>
-									<dd>
-										<textarea id="slaEmails"></textarea>
-									</dd>
-								</dl>
-							</div>
-							<br></br>
-							<div id="slaRules">
-								<h4>Flow SLA Rules</h4>
-								<div class="tableDiv">
-									<table id="flowRulesTbl">
-										<thead>
-											<tr>
-												<th>Flow/Job</th>
-												<th>Sla Rule</th>
-												<th>Duration</th>
-												<th>Email Action</th>
-												<th>Kill Action</th>
-											</tr>
-										</thead>
-										<tbody>
-											<tr id="addRow"><td id="addRow-col" colspan="5"><span class="addIcon"></span><a href="#">Add Row</a></td></tr>
-										</tbody>
-									</table>
+		<div class="container-full">
+
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+      
+      <div class="row">
+				<div class="col-lg-12">
+          <table id="scheduledFlowsTbl" class="table table-striped table-condensed table-bordered table-hover">
+            <thead>
+              <tr>
+                <!--th class="execid">Execution Id</th-->
+                <th>ID</th>
+                <th>Flow</th>
+                <th>Project</th>
+                <th>Submitted By</th>
+                <th class="date">First Scheduled to Run</th>
+                <th class="date">Next Execution Time</th>
+                <th class="date">Repeats Every</th>
+                <th>Has SLA</th>
+                <th colspan="2" class="action ignoresort">Action</th>
+              </tr>
+            </thead>
+            <tbody>
+	#if(!$schedules.isEmpty())
+		#foreach($sched in $schedules)
+              <tr>
+                <td>${sched.scheduleId}</td>
+                <td class="tb-name">
+                  <a href="${context}/manager?project=${sched.projectName}&flow=${sched.flowName}">${sched.flowName}</a>
+                </td>
+                <td>
+                  <a href="${context}/manager?project=${sched.projectName}">${sched.projectName}</a>
+                </td>
+                <td>${sched.submitUser}</td>
+                <td>$utils.formatDateTime(${sched.firstSchedTime})</td>
+                <td>$utils.formatDateTime(${sched.nextExecTime})</td>
+                <td>$utils.formatPeriod(${sched.period})</td>
+                <td>#if(${sched.slaOptions}) true #else false #end</td>
+                <td><button type="button" id="removeSchedBtn" class="btn btn-sm btn-danger" onclick="removeSched(${sched.scheduleId})" >Remove Schedule</button></td>
+                <td><button type="button" id="addSlaBtn" class="btn btn-sm btn-primary" onclick="slaView.initFromSched(${sched.scheduleId}, '${sched.flowName}')" >Set SLA</button></td>
+              </tr>
+		#end
+	#else
+              <tr>
+                <td colspan="10">No scheduled flow found.</td>
+              </tr>
+	#end
+            </tbody>
+          </table>
+				</div><!-- /col-lg-12 -->
+			</div><!-- /row -->
+
+  ## Set SLA modal.
+
+			<div class="modal modal-wide" id="sla-options">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">SLA Options</h4>
+						</div>
+						<div class="modal-body">
+							<h4>SLA Alert Emails</h4>
+							<fieldset>
+								<div class="form-group">
+									<label>SLA Alert Emails</label>
+									<textarea id="slaEmails" class="form-control"></textarea>
 								</div>
-							
-							</div>
+							</fieldset>
+							<h4>Flow SLA Rules</h4>
+							<table class="table table-striped" id="flowRulesTbl">
+								<thead>
+									<tr>
+										<th>Flow/Job</th>
+										<th>Sla Rule</th>
+										<th>Duration</th>
+										<th>Email Action</th>
+										<th>Kill Action</th>
+									</tr>
+								</thead>
+								<tbody>
+									<tr id="addRow">
+										<td id="addRow-col" colspan="5">
+											<span class="addIcon"></span>
+											<button type="button" class="btn btn-xs btn-success">Add Row</button>
+										</td>
+									</tr>
+								</tbody>
+							</table>
+						</div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+							<!--<button type="button" class="btn btn-danger" id="remove-sla-btn">Remove SLA</button>-->
+							<button type="button" class="btn btn-primary" id="set-sla-btn">Set/Change SLA</button>
 						</div>
 					</div>
-					<div class="actions">
-						<!--a class="yes btn1" id="remove-sla-btn" href="#">Remove SLA</a-->
-						<a class="yes btn1" id="set-sla-btn" href="#">Set/Change SLA</a>
-						<a class="no simplemodal-close btn3" id="sla-cancel-btn" href="#">Cancel</a>
-					</div>
+				</div>
 			</div>
 		</div>
+#end
 	</body>
 </html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/scheduleoptionspanel.vm b/src/java/azkaban/webapp/servlet/velocity/scheduleoptionspanel.vm
index fc6bc91..908eaf3 100644
--- a/src/java/azkaban/webapp/servlet/velocity/scheduleoptionspanel.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/scheduleoptionspanel.vm
@@ -1,3 +1,19 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
 <div id="scheduleModalBackground" class="modalBackground2">
 	<div id="schedule-options" class="modal modalContainer2">
 		<a href='#' title='Close' class='modal-close'>x</a>
@@ -156,4 +172,4 @@
 	<a class="no simplemodal-close btn3" id="schedule-cancel-btn" href="#">Cancel</a>
 	</div>
 	</div>
-</div>
\ No newline at end of file
+</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/schedulepanel.vm b/src/java/azkaban/webapp/servlet/velocity/schedulepanel.vm
index f40a1f3..3381d91 100644
--- a/src/java/azkaban/webapp/servlet/velocity/schedulepanel.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/schedulepanel.vm
@@ -1,49 +1,76 @@
-<script type="text/javascript" src="${context}/js/azkaban.date.utils.js"></script>  
-<script type="text/javascript" src="${context}/js/azkaban.schedule.panel.view.js"></script>
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
 
-<div id="scheduleModalBackground" class="modalBackground3">
-	<div id="schedule-panel-top">
-		<div id="schedule-panel" class="modal">
-			<a href='#' title='Close' class='modal-close closeSchedule'>x</a>
-			<h3>Schedule Flow Options</h3>
-
-			<div id="scheduleInfo">
-				<dl>
-					<dt>Schedule Time</dt>
-					<dd>
-						<input id="hour" type="text" size="2" value="12"/>
-						<input id="minutes" type="text" size="2" value="00"/>
-						<select id="am_pm">
-						  <option>pm</option>
-						  <option>am</option>
-						</select>
-						<select id="timezone">
-						  <option>${timezone}</option>
-						  <option>UTC</option>
-						</select>
-					</dd>
-					<dt>Schedule Date</dt><dd><input type="text" id="datepicker" /></dd>
-					<dt>Recurrence</dt>
-					<dd>
-						<input id="is_recurring" type="checkbox" checked  />
-						<span>repeat every</span>
-					 	<input id="period" type="text" size="2" value="1"/>
-						<select id="period_units">
-						  <option value="d">Days</option>
-						  <option value="h">Hours</option>
-						  <option value="m">Minutes</option>
-						  <option value="M">Months</option>
-						  <option value="w">Weeks</option>
-						</select>
-					</dd>
-				</dl>
-			</div>
-	
-
-			<div class="actions">
-				<a class="yes btn1" id="schedule-button" href="#">Schedule</a>
-				<a class="no simplemodal-close btn3 closeSchedule" id="cancel-schedule-btn" href="#">Cancel</a>
+			<script type="text/javascript" src="${context}/js/azkaban.date.utils.js"></script>  
+			<script type="text/javascript" src="${context}/js/azkaban.schedule.panel.view.js"></script>
+			
+			<div class="modal" id="schedule-modal">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Schedule Flow Options</h4>
+						</div><!-- /modal-header -->
+						<div class="modal-body">
+							<fieldset class="form-horizontal">
+								<div class="form-group">
+                  <label class="col-sm-2 control-label">Time</label>
+                  <div class="col-sm-7">
+                    <input type="text" id="timepicker" class="form-control">
+                  </div>
+                  <div class="col-sm-3">
+                    <select id="timezone" class="form-control">
+                      <option>${timezone}</option>
+                      <option>UTC</option>
+                    </select>
+                  </div>
+								</div>
+								<div class="form-group">
+                  <label class="col-sm-2 control-label">Date</label>
+                  <div class="col-sm-10">
+                    <input type="text" id="datepicker" class="form-control">
+                  </div>
+								</div>
+								<div class="form-group">
+                  <label class="col-sm-2">Recurrence</label>
+                  <div class="col-sm-3">
+                    <div class="checkbox">
+                      <input id="is_recurring" type="checkbox" checked="checked">
+                      <label>repeat every</label>
+                    </div>
+                  </div>
+                  <div class="col-sm-2">
+                    <input id="period" type="text" size="2" value="1" class="form-control">
+                  </div>
+                  <div class="col-sm-3">
+                    <select id="period_units" class="form-control">
+                      <option value="d">Days</option>
+                      <option value="h">Hours</option>
+                      <option value="m">Minutes</option>
+                      <option value="M">Months</option>
+                      <option value="w">Weeks</option>
+                    </select>
+                  </div>
+								</div>
+							</fieldset>
+						</div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+							<button type="button" class="btn btn-success" id="schedule-button">Schedule</button>
+						</div>
+					</div>
+				</div>
 			</div>
-		</div>
-	</div>
-</div>
\ No newline at end of file
diff --git a/src/java/azkaban/webapp/servlet/velocity/style.vm b/src/java/azkaban/webapp/servlet/velocity/style.vm
index 5f41009..1cebf28 100644
--- a/src/java/azkaban/webapp/servlet/velocity/style.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/style.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -14,19 +14,27 @@
  * the License.
 *#
 
-		<title>#appname()
-		</title>
-		<link rel="stylesheet" type="text/css" href="${context}/css/azkaban.css" /> 
+    <meta charset="utf-8">
+    <title>#appname()</title>
+    
 		<link rel="shortcut icon" href="${context}/favicon.ico" />
+    <!-- Bootstrap core CSS -->
+    <link href="/css/bootstrap.css" rel="stylesheet">
+		<link href="/css/azkaban.css" rel="stylesheet">
 		<style type="text/css">
-			.enviro-name {
+			.navbar-enviro .navbar-enviro-name {
 				color: ${azkaban_color};
 			}
-			.header {
-				border-top-color: ${azkaban_color};
+			.navbar-inverse {
+				border-top: 5px solid ${azkaban_color};
 			}
-			.nav .selected a {
-				border-bottom-color: ${azkaban_color};
+			.navbar-inverse .navbar-nav > .active >a {
+				border-bottom: 1px solid ${azkaban_color};
 			}
 		</style>
-		<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
+
+    <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+    <!--[if lt IE 9]>
+      <script src="/js/html5shiv.js"></script>
+      <script src="/js/respond.min.js"></script>
+    <![endif]-->
diff --git a/src/java/azkaban/webapp/servlet/velocity/title.vm b/src/java/azkaban/webapp/servlet/velocity/title.vm
index 092aed7..6244263 100644
--- a/src/java/azkaban/webapp/servlet/velocity/title.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/title.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/webapp/servlet/velocity/triggerspage.vm b/src/java/azkaban/webapp/servlet/velocity/triggerspage.vm
new file mode 100644
index 0000000..542e699
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/triggerspage.vm
@@ -0,0 +1,94 @@
+#*
+ * Copyright 2012 LinkedIn, Inc
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+<!DOCTYPE html> 
+<html lang="en">
+	<head>
+
+#parse("azkaban/webapp/servlet/velocity/style.vm")
+#parse("azkaban/webapp/servlet/velocity/javascript.vm")
+
+		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
+		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui.css" />
+		
+		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-1.10.1.custom.js"></script>
+		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-timepicker-addon.js"></script> 
+		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-sliderAccess.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.table.sort.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.triggers.view.js"></script>
+		<script type="text/javascript">
+			var contextURL = "${context}";
+			var currentTime = ${currentTime};
+			var timezone = "${timezone}";
+			var errorMessage = null;
+			var successMessage = null;
+		</script>
+	</head>
+	<body>
+
+#set ($current_page="triggers")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
+#else
+
+    <div class="az-page-header">
+      <div class="container-full">
+        <h1>All Triggers</h1>
+      </div>
+    </div>
+			
+    <div class="container-full">
+
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+
+			<div class="row">
+        <div class="col-lg-12">
+          <table id="triggersTbl" class="table table-striped table-bordered table-condensed table-hover">
+            <thead>
+              <tr>
+                <th>ID</th>
+                <th>Source</th>
+                <th>Submitted By</th>
+                <th>Description</th>
+                <th>Status</th>
+                <!--th colspan="2" class="action ignoresort">Action</th-->
+              </tr>
+            </thead>
+            <tbody>
+  #if ($triggers)
+    #foreach ($trigger in $triggers)
+              <tr>
+                <td>${trigger.triggerId}</td>
+                <td>${trigger.source}</td>
+                <td>${trigger.submitUser}</td>
+                <td>${trigger.getDescription()}</td>
+                <td>${trigger.getStatus()}</td>
+                <!--td><button id="expireTriggerBtn" onclick="expireTrigger(${trigger.triggerId})" >Expire Trigger</button></td-->
+              </tr>
+    #end
+  #else
+              <tr><td class="last">No Trigger Found</td></tr>
+  #end
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+#end
+	</body>
+</html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/viewer.vm b/src/java/azkaban/webapp/servlet/velocity/viewer.vm
index 09c5e2d..cb5e8d6 100644
--- a/src/java/azkaban/webapp/servlet/velocity/viewer.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/viewer.vm
@@ -1,5 +1,5 @@
 #*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -17,14 +17,10 @@
 <!DOCTYPE html> 
 <html>
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
 
-		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
+#parse ("azkaban/webapp/servlet/velocity/style.vm")
+#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
+
 		<script type="text/javascript">
 			var contextURL = "${context}";
 			var currentTime = ${currentTime};
@@ -41,23 +37,18 @@
 		</style>
 	</head>
 	<body>
-#set($current_page="viewer")
 
-#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
-		<div class="content">
-		
-		#if($errorMsg)
-                	<div class="box-error-message">$errorMsg</div>
-		#else
-			#if($error_message != "null")
-	                <div class="box-error-message">$error_message</div>
-			#elseif($success_message != "null")
-        	        <div class="box-success-message">$success_message</div>
-			#end
-		#end
+#set ($current_page="viewer")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+#if ($errorMsg)
+  #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
+#else
+    <div class="container-full">
+  #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
 
-#parse($viewervm)
-		
+  #parse ($viewervm)
 		</div>
+#end
 	</body>
 </html>
diff --git a/src/java/azkaban/webapp/servlet/VelocityUtils.java b/src/java/azkaban/webapp/servlet/VelocityUtils.java
index 2cdad0e..01b411a 100644
--- a/src/java/azkaban/webapp/servlet/VelocityUtils.java
+++ b/src/java/azkaban/webapp/servlet/VelocityUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -38,4 +38,4 @@ public class VelocityUtils {
 		DateTimeFormatter f = DateTimeFormat.forPattern(format);
 		return f.print(date);
 	}
-}
\ No newline at end of file
+}
diff --git a/src/java/azkaban/webapp/servlet/ViewerPlugin.java b/src/java/azkaban/webapp/servlet/ViewerPlugin.java
index e54c4aa..58dcc1e 100644
--- a/src/java/azkaban/webapp/servlet/ViewerPlugin.java
+++ b/src/java/azkaban/webapp/servlet/ViewerPlugin.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package azkaban.webapp.servlet;
 
 public class ViewerPlugin {
diff --git a/src/java/azkaban/webapp/session/Session.java b/src/java/azkaban/webapp/session/Session.java
index 38f4aa0..be29b57 100644
--- a/src/java/azkaban/webapp/session/Session.java
+++ b/src/java/azkaban/webapp/session/Session.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
diff --git a/src/java/azkaban/webapp/session/SessionCache.java b/src/java/azkaban/webapp/session/SessionCache.java
index be76c55..2d033d5 100644
--- a/src/java/azkaban/webapp/session/SessionCache.java
+++ b/src/java/azkaban/webapp/session/SessionCache.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -20,7 +20,6 @@ import azkaban.utils.Props;
 import azkaban.utils.cache.Cache;
 import azkaban.utils.cache.CacheManager;
 import azkaban.utils.cache.Cache.EjectionPolicy;
-import azkaban.utils.cache.Element;
 
 
 /**
@@ -33,7 +32,7 @@ import azkaban.utils.cache.Element;
  */
 public class SessionCache {
 	private static final int MAX_NUM_SESSIONS = 10000;
-	private static final int SESSION_TIME_TO_LIVE = 10000;
+	private static final long SESSION_TIME_TO_LIVE = 24*60*60*1000L;
 //	private CacheManager manager = CacheManager.create();
 	private Cache cache;
 
@@ -48,7 +47,7 @@ public class SessionCache {
 		cache = manager.createCache();
 		cache.setEjectionPolicy(EjectionPolicy.LRU);
 		cache.setMaxCacheSize(props.getInt("max.num.sessions", MAX_NUM_SESSIONS));
-		cache.setExpiryTimeToLiveMs(props.getInt("session.time.to.live", SESSION_TIME_TO_LIVE));
+		cache.setExpiryTimeToLiveMs(props.getLong("session.time.to.live", SESSION_TIME_TO_LIVE));
 	}
 
 	/**
@@ -82,4 +81,4 @@ public class SessionCache {
 	public boolean removeSession(String id) {
 		return cache.remove(id);
 	}
-}
\ No newline at end of file
+}
diff --git a/src/less/azkaban.less b/src/less/azkaban.less
new file mode 100644
index 0000000..da43720
--- /dev/null
+++ b/src/less/azkaban.less
@@ -0,0 +1,14 @@
+@import "base.less";
+@import "offcanvas.less";
+
+@import "navbar.less";
+@import "header.less";
+
+@import "contextmenu.less";
+@import "tables.less";
+
+@import "login.less";
+@import "project.less";
+@import "flow.less";
+@import "job.less";
+@import "log.less";
diff --git a/src/less/azkaban-svg.less b/src/less/azkaban-svg.less
new file mode 100644
index 0000000..31811f9
--- /dev/null
+++ b/src/less/azkaban-svg.less
@@ -0,0 +1,133 @@
+svg {
+  .edge {
+    stroke: #777;
+    stroke-width: 2;
+    &:hover {
+      stroke: #009FC9;
+      stroke-width: 4;
+    }
+  }
+
+  .node {
+    &.disabled {
+      opacity: 0.3;
+    }
+    .backboard {
+      fill: #FFF;
+      opacity: 0.05;
+    }
+    circle {
+      fill: #888;
+      stroke: #777;
+      stroke-width: 2;
+    }
+
+    &:hover {
+      cursor: pointer;
+      .backboard {
+        opacity: 0.7;
+      }
+      circle {
+        stroke: #009FC9;
+      }
+      text {
+        fill: #009FC9;
+      }
+    }
+  }
+
+  .selected {
+    .backboard {
+      opacity: 0.4;
+    }
+
+    text {
+      fill: #338AB0;
+    }
+
+    circle {
+      stroke: #009FC9;
+      stroke-width: 4;
+    }
+  }
+
+  .READY circle {
+    fill: #CCC;
+  }
+
+  .RUNNING circle {
+    fill: #009FC9;
+  }
+
+  .QUEUED circle {
+    opacity: 0.5;
+    fill: #009FC9;
+  }
+
+  .FAILED circle {
+    fill: #CC0000;
+  }
+
+  .KILLED circle {
+    fill: #CC0000;
+  }
+
+  .SUCCEEDED circle {
+    fill: #00CC33;
+  }
+
+  .DISABLED circle {
+    opacity: 0.3;
+  }
+
+  .SKIPPED circle {
+    opacity: 0.3;
+  }
+
+  // Used for charts.
+  circle {
+    &.READY {
+      stroke: #CCC;
+      stroke-width: 2px;
+      fill: #FFF;
+    }
+
+    &.RUNNING {
+      stroke: #009FC9;
+      stroke-width: 2px;
+      fill: #FFF;
+    }
+
+    &.FAILED {
+      stroke: #CC0000;
+      stroke-width: 2px;
+      fill: #FFF;
+    }
+
+    &.KILLED {
+      stroke: #CC0000;
+      stroke-width: 2px;
+      fill: #FFF;
+    }
+
+    &.SUCCEEDED {
+      stroke: #00CC33;
+      stroke-width: 2px;
+      fill: #FFF;
+    }
+
+    &.DISABLED {
+      stroke: #CCC;
+      opacity: 0.3;
+      stroke-width: 2px;
+      fill: #FFF;
+    }
+
+    &.SKIPPED {
+      stroke: #CCC;
+      opacity: 0.3;
+      stroke-width: 2px;
+      fill: #FFF;
+    }
+  }
+}

src/less/base.less 101(+101 -0)

diff --git a/src/less/base.less b/src/less/base.less
new file mode 100644
index 0000000..573ccc1
--- /dev/null
+++ b/src/less/base.less
@@ -0,0 +1,101 @@
+.container-full {
+  padding: 0 105px;
+  margin: 0 auto;
+  width: 100%;
+}
+
+.container-fill {
+  position: absolute;
+  top: 300px;
+  bottom: 50px;
+
+  .row {
+    height: 100%;
+  }
+
+  .col-sidebar {
+    height: 100%;
+  }
+
+  .col-content {
+    height: 100%;
+  }
+}
+
+// Wide modal used for certain panels such as executing flow panel.
+.modal-wide .modal-dialog {
+  width: 80%;
+}
+
+// Hide messaging alert by default.
+.alert-messaging {
+  display: none;
+}
+
+// Add additional space under navs.
+.nav-tabs, .nav-pills {
+  margin-bottom: 15px;
+}
+
+.panel-list {
+  border: 0;
+}
+
+.list-group-collapse {
+  margin: 0;
+  .list-group-item {
+    border-radius: 0;
+    border-left: 0;
+    border-right: 0;
+    
+    &:first-child {
+      border-top: 0;
+    }
+    
+    &:last-child {
+      border-bottom: 0;
+    }
+
+    button {
+      margin-left: 3px;
+    }
+  }
+}
+
+@media (min-width: 768px) {
+  .form-horizontal .control-label-center {
+    text-align: center;
+  }
+}
+
+.well-clear {
+  background-color: transparent;
+}
+
+.nav {
+	.nav-button {
+		margin-left: 5px;
+	}
+}
+
+.state-icon {
+  background-image: url("../css/images/ui-icons_cccccc_256x240.png");
+  cursor: pointer;
+  display: block;
+  float: left;
+  height: 16px;
+  width: 16px;
+  margin-right: 5px;
+
+  &.state-icon-expand {
+    background-position: -32px -16px;
+  }
+
+  &.state-icon-collapse {
+    background-position: -64px -16px;
+  }
+
+  &.state-icon-wait {
+    background-position: -64px -80px;
+  }
+}
diff --git a/src/less/contextmenu.less b/src/less/contextmenu.less
new file mode 100644
index 0000000..e0e8761
--- /dev/null
+++ b/src/less/contextmenu.less
@@ -0,0 +1,43 @@
+.contextMenu {
+  position: absolute;
+  background-color: #FFF;
+  border: 1px solid #DDD;
+  -moz-box-shadow: 2px 2px 5px #888;
+  -webkit-box-shadow: 2px 2px 5px #888;
+  box-shadow: 2px 2px 5px #888;
+  z-index: 2010;
+
+  ul {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+  }
+
+  li {
+    &.menuitem {
+      background-color: #FFF;
+      padding: 3px 20px;
+      min-width: 50px;
+      font-size: 10pt;
+      cursor: pointer;
+    
+      .expandSymbol {
+        background-image: url("../css/images/ui-icons_cccccc_256x240.png");
+        background-position: -32px -16px;
+        height: 16px;
+        width: 16px;
+        float:right;
+      }
+      
+      &:hover {
+        background-color: #555;
+        color: #FFF;
+      }
+    }
+
+    &.break {
+      border-bottom: 1px solid #BBB;
+      margin: 2px 5px;
+    }
+  }
+}

src/less/flow.less 170(+170 -0)

diff --git a/src/less/flow.less b/src/less/flow.less
new file mode 100644
index 0000000..931f47c
--- /dev/null
+++ b/src/less/flow.less
@@ -0,0 +1,170 @@
+#svgDiv {
+  height: 100%;
+}
+
+#flow-graph {
+  width: 100%;
+  height: 100%;
+}
+
+#flow-executing-graph {
+  width: 100%;
+  height: 500px;
+}
+
+.flow-progress {
+	width: 280px;
+	margin: 4px;
+  background-color: #f5f5f5;
+  height: 24px;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.flow-progress-bar {
+	height: 100%;
+  background-color: #ccc;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-transition: width 0.6s ease;
+          transition: width 0.6s ease;
+
+  &.attempt {
+    opacity: 0.70;
+    &:hover {
+      opacity: 1;
+    }
+  }
+
+  &.SUCCEEDED {
+    background-color: #5cb85c;
+  }
+
+  &.FAILED {
+    background-color: #d9534f;
+  }
+
+  &.RUNNING {
+    background-color: #3398cc;	
+    background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+    background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+    background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+    background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+    background-size: 40px 40px;
+    -webkit-animation: progress-bar-stripes 2s linear infinite;
+            animation: progress-bar-stripes 2s linear infinite;
+  }
+
+  &.QUEUED {
+    background-color: #009fc9;
+  }
+}
+
+td {
+  .status {
+    -moz-border-radius: 2px;
+    border-radius: 2px;
+
+    padding: 2px 2px;
+    color: #FFF;
+    text-align: center;
+    margin-top: 2px;
+    
+    &.SUCCEEDED {
+      background-color: #5cb85c;
+    }
+
+    &.FAILED {
+      background-color: #d9534f;
+    }
+
+    &.PAUSED {
+      background-color: #c82123;
+    }
+
+    &.READY {
+      background-color: #ccc;
+    }
+
+    &.RUNNING {
+      background-color: #3398cc;	
+    }
+
+    &.FAILED_FINISHING {
+      background-color: #f19153;	
+    }
+
+    &.DISABLED {
+      background-color: #aaa;	
+    }
+
+    &.SKIPPED {
+      background-color: #aaa;	
+    }
+
+    &.KILLED {
+      background-color: #d9534f;
+    }
+
+    &.UNKNOWN {
+      background-color: #ccc;
+    }
+  }
+}
+
+.graph-sidebar {
+  height: 100%;
+  overflow-y: auto;
+}
+
+// TODO: Rename this as #job-list
+#list {
+  height: 100%;
+
+  a {
+    &.nodedisabled,
+    &.DISABLED {
+      opacity: 0.3;
+    }
+
+    &.DISABLED .icon {
+      background-position: 16px 0px;
+    }
+
+    &.READY .icon {
+      background-position: 16px 0px;
+    }
+
+    &.QUEUED .icon {
+      opacity: 0.5;
+      background-position: 32px 0px;
+    }
+
+    &.RUNNING .icon {
+      background-position: 32px 0px;
+    }
+
+    &.SUCCEEDED .icon {
+      background-position: 48px 0px;
+    }
+
+    &.FAILED .icon {
+      background-position: 0px 0px;
+    }
+
+    &.KILLED .icon {
+      background-position: 0px 0px;
+    }
+
+    .icon {
+      float: left;
+      width: 16px;
+      height: 16px;
+      margin: 2px 4px 0px -5px;
+      background-image: url("./images/dot-icon.png");
+      background-position: 16px 0px;
+    }
+  }
+}
diff --git a/src/less/header.less b/src/less/header.less
new file mode 100644
index 0000000..3ca6d00
--- /dev/null
+++ b/src/less/header.less
@@ -0,0 +1,38 @@
+.az-page-header {
+  padding: 5px 0 10px;
+  margin: 0 0 30px;
+  border-bottom: 1px solid #dddddd;
+  background-color: #f4f4f4;
+
+  h1 {
+    font-size: 32px;
+    margin-bottom: 5px;
+  }
+  
+  .az-page-header-form {
+    margin-top: 20px;
+  }
+
+  .az-exflow-stats {
+    margin-top: 10px;
+
+    p {
+      margin-bottom: 5px;
+    }
+  }
+
+  .editable {
+    margin: 0px;
+    cursor: pointer;
+    &:hover {
+      background-color: #fcfcfc;
+    }
+    &.editable-placeholder {
+      color: #a0a0a0;
+    }
+  }
+
+  .editable-form {
+    display: none;
+  }
+}
diff --git a/src/less/job.less b/src/less/job.less
new file mode 100644
index 0000000..d531488
--- /dev/null
+++ b/src/less/job.less
@@ -0,0 +1,8 @@
+.panel-body-stats {
+  padding: 0;
+  overflow: auto;
+
+  table {
+    margin-bottom: 0;
+  }
+}

src/less/log.less 41(+41 -0)

diff --git a/src/less/log.less b/src/less/log.less
new file mode 100644
index 0000000..20d2019
--- /dev/null
+++ b/src/less/log.less
@@ -0,0 +1,41 @@
+.log-viewer {
+  height: 100%;
+  margin: 0;
+  padding: 0;
+
+  .panel {
+    position: relative;
+    height: 100%;
+
+    .panel-heading {
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      height: 40px;
+    }
+
+    .panel-body {
+      position: absolute;
+      top: 40px;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      
+      padding: 0;
+      background-color: #fcfcfc;
+
+      pre {
+        margin: 0;
+        border: 0;
+        height: 100%;
+        font-size: 12px;
+        background-color: transparent;
+        overflow: auto;
+        width: auto;
+        word-wrap: normal;
+        white-space: pre;
+      }
+    }
+  }
+}

src/less/login.less 32(+32 -0)

diff --git a/src/less/login.less b/src/less/login.less
new file mode 100644
index 0000000..80dc4af
--- /dev/null
+++ b/src/less/login.less
@@ -0,0 +1,32 @@
+// Since the azkaban navbar no longer has the 20px bottom margin so that the
+// page header sits flush below the navbar, add a 20px top margin to the login
+// form.
+.login {
+  max-width: 360px;
+  margin: 20px auto;
+  .form-control {
+    position: relative;
+    font-size: 16px;
+    height: auto;
+    padding: 10px;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+
+    &:focus {
+      z-index: 2;
+    }
+  }
+
+  input[type="text"] {
+    margin-bottom: -1px;
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+  }
+
+  input[type="password"] {
+    margin-bottom: 20px;
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
+  }
+}

src/less/navbar.less 121(+121 -0)

diff --git a/src/less/navbar.less b/src/less/navbar.less
new file mode 100644
index 0000000..3826082
--- /dev/null
+++ b/src/less/navbar.less
@@ -0,0 +1,121 @@
+.navbar-logo {
+  float: left;
+  padding: 15px 15px;
+  font-size: 18px;
+  line-height: 20px;
+
+  a {
+    &:hover,
+    &:focus {
+      text-decoration: none;
+    }
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar > .container .navbar-logo {
+    margin-left: -15px;
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar-enviro {
+    width: auto;
+  }
+}
+
+.navbar-enviro {
+  margin: 30px 20px 0px 12px;
+     
+  .navbar-enviro-name {
+    color: #ff3601;
+    font-family: Helvetica, Arial, Sans-Serif;
+    font-size: 118.75%;
+    font-weight: bold;
+		line-height: 100%;
+  }
+       
+  .navbar-enviro-server {
+    color: #999;
+    font-family: Helvetica, Arial, Sans-Serif;
+    font-size: 75%;
+  }
+}
+
+// Restyle navbar-inverse for Azkaban navbar.
+.navbar-inverse {
+  background-color: #383838;
+  border-top: 5px solid #ff3601;
+  margin-bottom: 0;
+
+  .navbar-logo {
+    background: url('../../images/logo.png') top left no-repeat;
+    color: #ffffff;
+    font-size: 156.25%;
+    font-weight: bold;
+    margin: 15px 0.6% 15px 4.75%;
+    padding: 12px 0 11px 42px;
+
+    a {
+      color: #ffffff;
+      &:hover,
+      &:focus {
+        color: #ffffff;
+        background-color: transparent;
+      }
+    }
+  }
+
+  .navbar-nav {
+    font-family: Arial;
+    font-size: 81.25%;
+
+    > li {
+      padding: 25px 12px 25px 12px;
+      cursor: pointer;
+      &:hover {
+        background-color: rgba(0, 0, 0, 0.1);
+      }
+    }
+
+    > li > a {
+      padding: 0px;
+      color: #ccc;
+      &:focus,
+      &:hover {
+        color: #ccc;
+        background-color: transparent;
+      }
+    }
+
+    > .active {
+      background-color: rgba(0, 0, 0, 0.1);
+    }
+
+    > .active > a {
+      color: #fff;
+      font-weight: bold;
+      background-color: transparent;
+      border-bottom: 1px solid #ff3601;
+      &:hover {
+        color: #fff;
+        background-color: transparent;
+      }
+    }
+
+    > li.dropdown {
+      padding: 0;
+    }
+
+    > li.dropdown > a {
+      padding: 25px 12px 25px 12px;
+    }
+
+    > .open > a,
+    > .open > a:hover,
+    > .open > a:focus {
+      color: #ccc;
+      background-color: transparent;
+    }
+  }
+}
diff --git a/src/less/offcanvas.less b/src/less/offcanvas.less
new file mode 100644
index 0000000..541b3a3
--- /dev/null
+++ b/src/less/offcanvas.less
@@ -0,0 +1,32 @@
+@media screen and (max-width: 767px) {
+  .row-offcanvas {
+    position: relative;
+    -webkit-transition: all 0.25s ease-out;
+    -moz-transition: all 0.25s ease-out;
+    transition: all 0.25s ease-out;
+  }
+
+  .row-offcanvas-right {
+    &.active {
+      right: 50%; /* 6 columns */
+    }
+    .sidebar-offcanvas {
+      right: -50%; /* 6 columns */
+    }
+  }
+
+  .row-offcanvas-left {
+    &.active {
+      left: 50%; /* 6 columns */
+    }
+    .sidebar-offcanvas {
+      left: -50%; /* 6 columns */
+    }
+  }
+
+  .sidebar-offcanvas {
+    position: absolute;
+    top: 0;
+    width: 50%; /* 6 columns */
+  }
+}
diff --git a/src/less/project.less b/src/less/project.less
new file mode 100644
index 0000000..b3ae43f
--- /dev/null
+++ b/src/less/project.less
@@ -0,0 +1,72 @@
+.az-project-row {
+	cursor: pointer;
+}
+
+// Flow panel heading.
+.flow-expander {
+  cursor: pointer;
+}
+
+.expanded-flow-job-list {
+  .list-group-item {
+    .job-buttons {
+      visibility: hidden;
+    }
+
+    &:hover {
+      background-color: #f5f5f5;
+
+      .job-buttons {
+        visibility: visible;
+      }
+    }
+  }
+
+  .dependency {
+    background-color: #f0f0f0;
+  }
+
+  .dependent {
+    background-color: #fafafa;
+  }
+}
+
+// Permissions page table.
+.permission-table {
+  .tb-perm {
+    width: 41px;
+    margin: 0px;
+  }
+
+  .tb-admin {
+    width: 41px;
+    margin: 0px;
+  }
+
+  .tb-read {
+    width: 33px;
+    margin: 0px;
+  }
+
+  .tb-write {
+    width: 34px;
+    margin: 0px;
+  }
+
+  .tb-execute {
+    width: 51px;
+    margin: 0px;
+  }
+
+  .tb-schedule {
+    margin: 0px;
+    width: 60px;
+  }
+
+  .tb-action {
+    margin: 0px;
+    width: 70px;
+    min-width: 70px;
+    max-width: 70px;
+  }
+}

src/less/tables.less 107(+107 -0)

diff --git a/src/less/tables.less b/src/less/tables.less
new file mode 100644
index 0000000..d67061b
--- /dev/null
+++ b/src/less/tables.less
@@ -0,0 +1,107 @@
+table.table-properties {
+  table-layout: fixed;
+  word-wrap: break-word;
+}
+
+// Flow summary.
+.property-key {
+  width: 25%;
+  font-weight: bold;
+}
+
+.property-value-half {
+  width: 25%;
+}
+
+.editable {
+  .remove-btn {
+    visibility: hidden;
+  }
+
+  &:hover .remove-btn {
+    visibility: visible;
+  }
+}
+
+// Job table.
+#all-jobs {
+  .tb-name {
+    width: 70%;
+    border-bottom-width: 0;
+    border-bottom-style: none;
+  }
+
+  .tb-up-date {
+    width: 140px;
+    min-width: 130px;
+  }
+
+  .tb-owner {
+    width: 10%;
+    min-width: 95px;
+  }
+}
+
+// Properties table.
+.properties-table {
+  .all-jobs .tb-pname {
+  }
+
+  .all-jobs .tb-pvalue {
+  }
+}
+
+// Table of executions.
+.executions-table {
+  th {
+    &.date {
+      width: 160px;
+    }
+
+    &.execid {
+      width: 100px;
+    }
+
+    &.project {
+      width: 200px;
+    }
+
+    &.user {
+      width: 60px;
+    }
+
+    &.elapse {
+      width: 90px;
+    }
+
+    &.status {
+      width: 100px;
+    }
+
+    &.details {
+      width: 10px;
+    }
+
+    &.action {
+      width: 20px;
+    }
+
+    &.logs {
+      width: 30px;
+    }
+  }
+
+  td {
+    &.timeline {
+      width: 280px;
+      padding: 0px;
+      height: 100%;
+      vertical-align: bottom;
+      margin: 0px;
+    }
+
+    &.execId {
+      font-weight: bold;
+    }
+  }
+}
diff --git a/src/package/execserver/bin/azkaban-executor-start.sh b/src/package/execserver/bin/azkaban-executor-start.sh
index 29f4241..5245714 100755
--- a/src/package/execserver/bin/azkaban-executor-start.sh
+++ b/src/package/execserver/bin/azkaban-executor-start.sh
@@ -1,7 +1,9 @@
+#!/bin/bash
+
 azkaban_dir=$(dirname $0)/..
 
 if [[ -z "$tmpdir" ]]; then
-tmpdir=temp
+tmpdir=/tmp
 fi
 
 for file in $azkaban_dir/lib/*.jar;
@@ -19,6 +21,21 @@ do
   CLASSPATH=$CLASSPATH:$file
 done
 
+if [ "HADOOP_HOME" != "" ]; then
+	for file in $HADOOP_HOME/hadoop-core*.jar ;
+	do
+		CLASSPATH=$CLASSPATH:$file
+	done
+	CLASSPATH=$CLASSPATH:$HADOOP_HOME/conf
+    JAVA_LIB_PATH="-Djava.library.path=$HADOOP_HOME/lib/native/Linux-amd64-64"
+else
+	echo "Error: HADOOP_HOME is not set. Hadoop job types will not run properly."
+fi
+
+if [ "HIVE_HOME" != "" ]; then
+    CLASSPATH=$CLASSPATH:$HIVE_HOME/conf
+fi
+
 echo $azkaban_dir;
 echo $CLASSPATH;
 
@@ -27,11 +44,11 @@ echo "Starting AzkabanExecutorServer on port $executorport ..."
 serverpath=`pwd`
 
 if [ -z $AZKABAN_OPTS ]; then
-  AZKABAN_OPTS=-Xmx3G
+  AZKABAN_OPTS="-Xmx3G"
 fi
-AZKABAN_OPTS=$AZKABAN_OPTS -server -Dcom.sun.management.jmxremote -Djava.io.tmpdir=$tmpdir -Dexecutorport=$executorport -Dserverpath=$serverpath
+AZKABAN_OPTS="$AZKABAN_OPTS -server -Dcom.sun.management.jmxremote -Djava.io.tmpdir=$tmpdir -Dexecutorport=$executorport -Dserverpath=$serverpath"
 
-java $AZKABAN_OPTS -cp $CLASSPATH azkaban.webapp.AzkabanExecutorServer -conf $azkaban_dir/conf $@ &
+java $AZKABAN_OPTS $JAVA_LIB_PATH -cp $CLASSPATH azkaban.execapp.AzkabanExecutorServer -conf $azkaban_dir/conf $@ &
 
 echo $! > currentpid
 
diff --git a/src/package/execserver/bin/start-exec.sh b/src/package/execserver/bin/start-exec.sh
new file mode 100644
index 0000000..fbb7124
--- /dev/null
+++ b/src/package/execserver/bin/start-exec.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+base_dir=$(dirname $0)/..
+
+bin/azkaban-executor-start.sh $base_dir 2>&1>logs/executorServerLog__`date +%F+%T`.out &
+
diff --git a/src/package/execserver/conf/azkaban.properties b/src/package/execserver/conf/azkaban.properties
index f42ea60..b54b0f5 100644
--- a/src/package/execserver/conf/azkaban.properties
+++ b/src/package/execserver/conf/azkaban.properties
@@ -1,6 +1,9 @@
 #Azkaban
 default.timezone.id=America/Los_Angeles
 
+# Azkaban JobTypes Plugins
+azkaban.jobtype.plugin.dir=plugins/jobtypes
+
 #Loader for projects
 executor.global.properties=conf/global.properties
 azkaban.project.dir=projects
@@ -16,4 +19,4 @@ mysql.numconnections=100
 # Azkaban Executor settings
 executor.maxThreads=50
 executor.port=12321
-executor.flow.threads=30
\ No newline at end of file
+executor.flow.threads=30
diff --git a/src/package/soloserver/bin/azkaban-solo-start.sh b/src/package/soloserver/bin/azkaban-solo-start.sh
index 4c0d983..54ac8d9 100755
--- a/src/package/soloserver/bin/azkaban-solo-start.sh
+++ b/src/package/soloserver/bin/azkaban-solo-start.sh
@@ -1,7 +1,9 @@
+#!/bin/bash
+
 azkaban_dir=$(dirname $0)/..
 
 if [[ -z "$tmpdir" ]]; then
-tmpdir=temp
+tmpdir=/tmp
 fi
 
 for file in $azkaban_dir/lib/*.jar;
@@ -19,6 +21,21 @@ do
   CLASSPATH=$CLASSPATH:$file
 done
 
+if [ "HADOOP_HOME" != "" ]; then
+    for file in $HADOOP_HOME/hadoop-core*.jar ;
+    do
+        CLASSPATH=$CLASSPATH:$file
+    done
+    CLASSPATH=$CLASSPATH:$HADOOP_HOME/conf
+    JAVA_LIB_PATH="-Djava.library.path=$HADOOP_HOME/lib/native/Linux-amd64-64"
+else
+    echo "Error: HADOOP_HOME is not set. Hadoop job types will not run properly."
+fi
+
+if [ "HIVE_HOME" != "" ]; then
+    CLASSPATH=$CLASSPATH:$HIVE_HOME/conf
+fi
+
 echo $azkaban_dir;
 echo $CLASSPATH;
 
diff --git a/src/package/soloserver/conf/azkaban.properties b/src/package/soloserver/conf/azkaban.properties
index ac36e26..7524a14 100644
--- a/src/package/soloserver/conf/azkaban.properties
+++ b/src/package/soloserver/conf/azkaban.properties
@@ -25,6 +25,7 @@ velocity.dev.mode=false
 
 # Azkaban Jetty server properties. Ignored in tomcat
 jetty.use.ssl=false
+jetty.ssl.port=8043
 jetty.maxThreads=25
 jetty.port=8081
 
@@ -39,4 +40,4 @@ mail.host=
 job.failure.email=
 job.success.email=
 
-lockdown.create.projects=false
\ No newline at end of file
+lockdown.create.projects=false
diff --git a/src/package/soloserver/conf/azkaban-users.xml b/src/package/soloserver/conf/azkaban-users.xml
index b30da41..e19acc8 100644
--- a/src/package/soloserver/conf/azkaban-users.xml
+++ b/src/package/soloserver/conf/azkaban-users.xml
@@ -1,5 +1,7 @@
 <azkaban-users>
 	<user username="azkaban" password="azkaban" roles="admin" groups="azkaban" />
+	<user username="metrics" password="metrics" roles="metrics"/>
 	
 	<role name="admin" permissions="ADMIN" />
+	<role name="metrics" permissions="METRICS"/>
 </azkaban-users>
diff --git a/src/package/triggerserver/bin/azkaban-trigger-shutdown.sh b/src/package/triggerserver/bin/azkaban-trigger-shutdown.sh
new file mode 100755
index 0000000..3dda364
--- /dev/null
+++ b/src/package/triggerserver/bin/azkaban-trigger-shutdown.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+azkaban_dir=$(dirname $0)/..
+
+triggerport=`cat $azkaban_dir/conf/azkaban.properties | grep trigger.port | cut -d = -f 2`
+echo "Shutting down current running AzkabanTriggerServer at port $triggerport"
+
+proc=`cat $azkaban_dir/currentpid`
+
+kill $proc
+
+cat /dev/null > $azkaban_dir/currentpid
diff --git a/src/package/triggerserver/bin/azkaban-trigger-start.sh b/src/package/triggerserver/bin/azkaban-trigger-start.sh
new file mode 100755
index 0000000..64daa9b
--- /dev/null
+++ b/src/package/triggerserver/bin/azkaban-trigger-start.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+azkaban_dir=$(dirname $0)/..
+
+if [[ -z "$tmpdir" ]]; then
+tmpdir=temp
+fi
+
+for file in $azkaban_dir/lib/*.jar;
+do
+  CLASSPATH=$CLASSPATH:$file
+done
+
+for file in $azkaban_dir/extlib/*.jar;
+do
+  CLASSPATH=$CLASSPATH:$file
+done
+
+for file in $azkaban_dir/plugins/*/*.jar;
+do
+  CLASSPATH=$CLASSPATH:$file
+done
+
+echo $azkaban_dir;
+echo $CLASSPATH;
+
+triggerport=`cat $azkaban_dir/conf/azkaban.properties | grep trigger.port | cut -d = -f 2`
+echo "Starting AzkabanTriggerServer on port $triggerport ..."
+serverpath=`pwd`
+
+if [ -z $AZKABAN_OPTS ]; then
+  AZKABAN_OPTS=-Xmx3G
+fi
+AZKABAN_OPTS=$AZKABAN_OPTS -server -Dcom.sun.management.jmxremote -Djava.io.tmpdir=$tmpdir -Dtriggerport=$triggerport -Dserverpath=$serverpath
+
+java $AZKABAN_OPTS -cp $CLASSPATH azkaban.triggerapp.AzkabanTriggerServer -conf $azkaban_dir/conf $@ &
+
+echo $! > currentpid
+
diff --git a/src/package/triggerserver/conf/azkaban.private.properties b/src/package/triggerserver/conf/azkaban.private.properties
new file mode 100644
index 0000000..cce1792
--- /dev/null
+++ b/src/package/triggerserver/conf/azkaban.private.properties
@@ -0,0 +1 @@
+# Optional Properties that are hidden to the executions
\ No newline at end of file
diff --git a/src/package/triggerserver/conf/azkaban.properties b/src/package/triggerserver/conf/azkaban.properties
new file mode 100644
index 0000000..3504854
--- /dev/null
+++ b/src/package/triggerserver/conf/azkaban.properties
@@ -0,0 +1,18 @@
+#Azkaban
+default.timezone.id=America/Los_Angeles
+
+#Loader for projects
+azkaban.project.dir=projects
+
+database.type=mysql
+mysql.port=3306
+mysql.host=localhost
+mysql.database=azkaban2
+mysql.user=azkaban
+mysql.password=azkaban
+mysql.numconnections=100
+
+# Azkaban Executor settings
+trigger.server.maxThreads=50
+trigger.server.port=22321
+jetty.hostname=eat1-spadesaz01.grid.linkedin.com
diff --git a/src/package/triggerserver/conf/global.properties b/src/package/triggerserver/conf/global.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/package/triggerserver/conf/global.properties
diff --git a/src/package/webserver/bin/azkaban-web-start.sh b/src/package/webserver/bin/azkaban-web-start.sh
index bdd19e0..b0604fd 100755
--- a/src/package/webserver/bin/azkaban-web-start.sh
+++ b/src/package/webserver/bin/azkaban-web-start.sh
@@ -1,7 +1,9 @@
+#!/bin/bash
+
 azkaban_dir=$(dirname $0)/..
 
 if [[ -z "$tmpdir" ]]; then
-tmpdir=temp
+tmpdir=/tmp
 fi
 
 for file in $azkaban_dir/lib/*.jar;
@@ -19,6 +21,21 @@ do
   CLASSPATH=$CLASSPATH:$file
 done
 
+if [ "HADOOP_HOME" != "" ]; then
+    for file in $HADOOP_HOME/hadoop-core*.jar;
+    do
+        CLASSPATH=$CLASSPATH:$file
+    done
+    CLASSPATH=$CLASSPATH:$HADOOP_HOME/conf
+    JAVA_LIB_PATH="-Djava.library.path=$HADOOP_HOME/lib/native/Linux-amd64-64"
+else
+    echo "Error: HADOOP_HOME is not set. Hadoop job types will not run properly."
+fi
+
+if [ "HIVE_HOME" != "" ]; then
+    CLASSPATH=$CLASSPATH:$HIVE_HOME/conf
+fi
+
 echo $azkaban_dir;
 echo $CLASSPATH;
 
@@ -26,11 +43,11 @@ executorport=`cat $azkaban_dir/conf/azkaban.properties | grep executor.port | cu
 serverpath=`pwd`
 
 if [ -z $AZKABAN_OPTS ]; then
-  AZKABAN_OPTS=-Xmx3G
+  AZKABAN_OPTS="-Xmx4G"
 fi
-AZKABAN_OPTS=$AZKABAN_OPTS -server -Dcom.sun.management.jmxremote -Djava.io.tmpdir=$tmpdir -Dexecutorport=$executorport -Dserverpath=$serverpath
+AZKABAN_OPTS="$AZKABAN_OPTS -server -Dcom.sun.management.jmxremote -Djava.io.tmpdir=$tmpdir -Dexecutorport=$executorport -Dserverpath=$serverpath"
 
-java $AZKABAN_OPTS -cp $CLASSPATH azkaban.webapp.AzkabanWebServer -conf $azkaban_dir/conf $@ &
+java $AZKABAN_OPTS $JAVA_LIB_PATH -cp $CLASSPATH azkaban.webapp.AzkabanWebServer -conf $azkaban_dir/conf $@ &
 
 echo $! > currentpid
 
diff --git a/src/package/webserver/bin/start-web.sh b/src/package/webserver/bin/start-web.sh
new file mode 100644
index 0000000..b063aaa
--- /dev/null
+++ b/src/package/webserver/bin/start-web.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+base_dir=$(dirname $0)/..
+
+bin/azkaban-web-start.sh $base_dir 2>&1>logs/webServerLog_`date +%F+%T`.out &
diff --git a/src/package/webserver/conf/azkaban.properties b/src/package/webserver/conf/azkaban.properties
index c9aed8d..3ccb2f3 100644
--- a/src/package/webserver/conf/azkaban.properties
+++ b/src/package/webserver/conf/azkaban.properties
@@ -1,7 +1,8 @@
 #Azkaban Personalization Settings
-azkaban.name=Local
+azkaban.name=Test
 azkaban.label=My Local Azkaban
 azkaban.color=#FF3601
+azkaban.default.servlet.path=/index
 web.resource.dir=web/
 default.timezone.id=America/Los_Angeles
 
@@ -43,4 +44,6 @@ mail.host=
 job.failure.email=
 job.success.email=
 
-lockdown.create.projects=false
\ No newline at end of file
+lockdown.create.projects=false
+
+cache.directory=cache
diff --git a/src/package/webserver/conf/azkaban-users.xml b/src/package/webserver/conf/azkaban-users.xml
index b30da41..e19acc8 100644
--- a/src/package/webserver/conf/azkaban-users.xml
+++ b/src/package/webserver/conf/azkaban-users.xml
@@ -1,5 +1,7 @@
 <azkaban-users>
 	<user username="azkaban" password="azkaban" roles="admin" groups="azkaban" />
+	<user username="metrics" password="metrics" roles="metrics"/>
 	
 	<role name="admin" permissions="ADMIN" />
+	<role name="metrics" permissions="METRICS"/>
 </azkaban-users>
diff --git a/src/sql/create.project_properties.sql b/src/sql/create.project_properties.sql
index 9ed6267..7adf25e 100644
--- a/src/sql/create.project_properties.sql
+++ b/src/sql/create.project_properties.sql
@@ -1,7 +1,7 @@
 CREATE TABLE project_properties (
 	project_id INT NOT NULL,
 	version INT NOT NULL,
-	name VARCHAR(128),
+	name VARCHAR(255),
 	modified_time BIGINT NOT NULL,
 	encoding_type TINYINT,
 	property BLOB,
diff --git a/src/sql/create.triggers.sql b/src/sql/create.triggers.sql
new file mode 100644
index 0000000..523ad5a
--- /dev/null
+++ b/src/sql/create.triggers.sql
@@ -0,0 +1,8 @@
+CREATE TABLE triggers (
+	trigger_id INT NOT NULL AUTO_INCREMENT,
+	trigger_source VARCHAR(128),
+	modify_time BIGINT NOT NULL,
+	enc_type TINYINT,
+	data LONGBLOB,
+	PRIMARY KEY (trigger_id)
+);
diff --git a/src/sql/database.properties b/src/sql/database.properties
index 8b13789..b68be28 100644
--- a/src/sql/database.properties
+++ b/src/sql/database.properties
@@ -1 +1 @@
-
+version=
diff --git a/src/sql/update.execution_logs.2.1.sql b/src/sql/update.execution_logs.2.1.sql
index 5c2dc0b..1760a4b 100644
--- a/src/sql/update.execution_logs.2.1.sql
+++ b/src/sql/update.execution_logs.2.1.sql
@@ -2,6 +2,5 @@ ALTER TABLE execution_logs ADD COLUMN attempt INT DEFAULT 0;
 ALTER TABLE execution_logs ADD COLUMN upload_time BIGINT DEFAULT 1420099200000;
 UPDATE execution_logs SET upload_time=(UNIX_TIMESTAMP()*1000) WHERE upload_time=1420099200000;
 
-ALTER TABLE execution_logs DROP PRIMARY KEY;
 ALTER TABLE execution_logs ADD PRIMARY KEY(exec_id, name, attempt, start_byte);
-ALTER TABLE execution_logs ADD INDEX ex_log_attempt (exec_id, name, attempt)
+ALTER TABLE execution_logs ADD INDEX ex_log_attempt (exec_id, name, attempt);

src/tl/flowsummary.tl 112(+112 -0)

diff --git a/src/tl/flowsummary.tl b/src/tl/flowsummary.tl
new file mode 100644
index 0000000..10a0680
--- /dev/null
+++ b/src/tl/flowsummary.tl
@@ -0,0 +1,112 @@
+        <div class="col-lg-12">
+          <table class="table table-bordered table-condensed table-striped">
+            <tbody>
+              <tr>
+                <td class="property-key">Flow name</td>
+                <td class="property-value-half">{flowName}</td>
+                <td class="property-key">Project name</td>
+                <td class="property-value-half">{projectName}</td>
+              </tr>
+              <tr>
+                <td class="property-key">Run As</td>
+                <td class="property-value-half">{user}</td>
+                <td class="property-key">Job Types Used</td>
+                <td class="property-value-half">{#jobTypes}{.} {/jobTypes}</td>
+              </tr>
+            </tbody>
+          </table>
+					
+					<div class="panel panel-default">
+						<div class="panel-heading">
+							Scheduling
+							{?schedule}
+							<div class="pull-right">
+								<button type="button" id="removeSchedBtn" class="btn btn-xs btn-danger" onclick="removeSched({schedule.scheduleId})" >Remove Schedule</button>
+							</div>
+							{/schedule}
+						</div>
+						{?schedule}
+						<table class="table table-condensed table-bordered table-striped">
+							<tbody>
+								<tr>
+									<td class="property-key">Schedule ID</td>
+									<td class="property-value-half">{schedule.scheduleId}</td>
+									<td class="property-key">Submitted By</td>
+									<td class="property-value-half">{schedule.submitUser}</td>
+								</tr>
+								<tr>
+									<td class="property-key">First Scheduled to Run</td>
+									<td class="property-value-half">{schedule.firstSchedTime}</td>
+									<td class="property-key">Repeats Every</td>
+									<td class="property-value-half">{schedule.period}</td>
+								</tr>
+								<tr>
+									<td class="property-key">Next Execution Time</td>
+									<td class="property-value-half">{schedule.nextExecTime}</td>
+									<td class="property-key">SLA</td>
+									<td class="property-value-half">
+									{?schedule.slaOptions}
+										true 
+									{:else} 
+										false 
+									{/schedule.slaOptions}
+										<div class="pull-right">
+											<button type="button" id="addSlaBtn" class="btn btn-xs btn-primary" onclick="slaView.initFromSched({schedule.scheduleId}, '{flowName}')" >Set SLA</button>
+										</div>
+									</td>
+								</tr>
+							</tbody>
+						</table>
+						{:else}
+						<div class="panel-body">
+							<div class="alert alert-info">
+								<h4>No Schedule</h4>
+								<p>This flow has not been scheduled.</p>
+							</div>
+						</div>
+						{/schedule}
+					</div>
+
+          <div class="panel panel-default">
+            <div class="panel-heading">Last Run Stats</div>
+            {?lastRun}
+            <table class="table table-bordered table-condensed table-striped">
+              <tbody>
+								<tr>
+									<td class="property-key">Max Map Slots from Largest Job</td>
+									<td>{lastRun.maxMapSlots}</td>
+                </tr>
+                <tr>
+									<td class="property-key">Max Reduce Slots from Largest Job</td>
+									<td>{lastRun.maxReduceSlots}</td>
+								</tr>
+								<tr>
+									<td class="property-key">Total Map Slots from All Jobs</td>
+									<td>{lastRun.totalMapSlots}</td>
+                </tr>
+                <tr>
+									<td class="property-key">Total Reduce Slots from All Jobs</td>
+									<td>{lastRun.totalReduceSlots}</td>
+								</tr>
+								<tr>
+									<td class="property-key">Total Number of Jobs</td>
+									<td>{lastRun.numJobs}</td>
+                </tr>
+                <tr>
+									<td class="property-key">Longest Task Time</td>
+									<td>{lastRun.longestTaskTime}</td>
+								</tr>
+              </tbody>
+            </table>
+            {:else}
+            <div class="panel-body">
+              <div class="alert alert-info">
+                <h4>No last run stats available</h4>
+                <p>Last run stats requires at least one successful run of the flow.</p>
+              </div>
+            </div>
+            {/lastRun}
+          </div>
+        </div><!-- /.col-lg-12 -->
+
+

src/web/css/bootstrap.css 7098(+7098 -0)

diff --git a/src/web/css/bootstrap.css b/src/web/css/bootstrap.css
new file mode 100644
index 0000000..fb50e38
--- /dev/null
+++ b/src/web/css/bootstrap.css
@@ -0,0 +1,7098 @@
+/*!
+ * Bootstrap v3.0.2 by @fat and @mdo
+ * Copyright 2013 Twitter, Inc.
+ * Licensed under http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */
+
+/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+  display: block;
+}
+
+audio,
+canvas,
+video {
+  display: inline-block;
+}
+
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+
+[hidden],
+template {
+  display: none;
+}
+
+html {
+  font-family: sans-serif;
+  -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+}
+
+body {
+  margin: 0;
+}
+
+a {
+  background: transparent;
+}
+
+a:focus {
+  outline: thin dotted;
+}
+
+a:active,
+a:hover {
+  outline: 0;
+}
+
+h1 {
+  margin: 0.67em 0;
+  font-size: 2em;
+}
+
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+
+b,
+strong {
+  font-weight: bold;
+}
+
+dfn {
+  font-style: italic;
+}
+
+hr {
+  height: 0;
+  -moz-box-sizing: content-box;
+       box-sizing: content-box;
+}
+
+mark {
+  color: #000;
+  background: #ff0;
+}
+
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, serif;
+  font-size: 1em;
+}
+
+pre {
+  white-space: pre-wrap;
+}
+
+q {
+  quotes: "\201C" "\201D" "\2018" "\2019";
+}
+
+small {
+  font-size: 80%;
+}
+
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+img {
+  border: 0;
+}
+
+svg:not(:root) {
+  overflow: hidden;
+}
+
+figure {
+  margin: 0;
+}
+
+fieldset {
+  padding: 0.35em 0.625em 0.75em;
+  margin: 0 2px;
+  border: 1px solid #c0c0c0;
+}
+
+legend {
+  padding: 0;
+  border: 0;
+}
+
+button,
+input,
+select,
+textarea {
+  margin: 0;
+  font-family: inherit;
+  font-size: 100%;
+}
+
+button,
+input {
+  line-height: normal;
+}
+
+button,
+select {
+  text-transform: none;
+}
+
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  cursor: pointer;
+  -webkit-appearance: button;
+}
+
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+  padding: 0;
+  box-sizing: border-box;
+}
+
+input[type="search"] {
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+  -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+
+textarea {
+  overflow: auto;
+  vertical-align: top;
+}
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+@media print {
+  * {
+    color: #000 !important;
+    text-shadow: none !important;
+    background: transparent !important;
+    box-shadow: none !important;
+  }
+  a,
+  a:visited {
+    text-decoration: underline;
+  }
+  a[href]:after {
+    content: " (" attr(href) ")";
+  }
+  abbr[title]:after {
+    content: " (" attr(title) ")";
+  }
+  a[href^="javascript:"]:after,
+  a[href^="#"]:after {
+    content: "";
+  }
+  pre,
+  blockquote {
+    border: 1px solid #999;
+    page-break-inside: avoid;
+  }
+  thead {
+    display: table-header-group;
+  }
+  tr,
+  img {
+    page-break-inside: avoid;
+  }
+  img {
+    max-width: 100% !important;
+  }
+  @page  {
+    margin: 2cm .5cm;
+  }
+  p,
+  h2,
+  h3 {
+    orphans: 3;
+    widows: 3;
+  }
+  h2,
+  h3 {
+    page-break-after: avoid;
+  }
+  select {
+    background: #fff !important;
+  }
+  .navbar {
+    display: none;
+  }
+  .table td,
+  .table th {
+    background-color: #fff !important;
+  }
+  .btn > .caret,
+  .dropup > .btn > .caret {
+    border-top-color: #000 !important;
+  }
+  .label {
+    border: 1px solid #000;
+  }
+  .table {
+    border-collapse: collapse !important;
+  }
+  .table-bordered th,
+  .table-bordered td {
+    border: 1px solid #ddd !important;
+  }
+}
+
+*,
+*:before,
+*:after {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+html {
+  font-size: 62.5%;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #333333;
+  background-color: #ffffff;
+}
+
+input,
+button,
+select,
+textarea {
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+
+a {
+  color: #428bca;
+  text-decoration: none;
+}
+
+a:hover,
+a:focus {
+  color: #2a6496;
+  text-decoration: underline;
+}
+
+a:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+img {
+  vertical-align: middle;
+}
+
+.img-responsive {
+  display: block;
+  height: auto;
+  max-width: 100%;
+}
+
+.img-rounded {
+  border-radius: 6px;
+}
+
+.img-thumbnail {
+  display: inline-block;
+  height: auto;
+  max-width: 100%;
+  padding: 4px;
+  line-height: 1.428571429;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
+  -webkit-transition: all 0.2s ease-in-out;
+          transition: all 0.2s ease-in-out;
+}
+
+.img-circle {
+  border-radius: 50%;
+}
+
+hr {
+  margin-top: 20px;
+  margin-bottom: 20px;
+  border: 0;
+  border-top: 1px solid #eeeeee;
+}
+
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  border: 0;
+}
+
+p {
+  margin: 0 0 10px;
+}
+
+.lead {
+  margin-bottom: 20px;
+  font-size: 16px;
+  font-weight: 200;
+  line-height: 1.4;
+}
+
+@media (min-width: 768px) {
+  .lead {
+    font-size: 21px;
+  }
+}
+
+small,
+.small {
+  font-size: 85%;
+}
+
+cite {
+  font-style: normal;
+}
+
+.text-muted {
+  color: #999999;
+}
+
+.text-primary {
+  color: #428bca;
+}
+
+.text-primary:hover {
+  color: #3071a9;
+}
+
+.text-warning {
+  color: #c09853;
+}
+
+.text-warning:hover {
+  color: #a47e3c;
+}
+
+.text-danger {
+  color: #b94a48;
+}
+
+.text-danger:hover {
+  color: #953b39;
+}
+
+.text-success {
+  color: #468847;
+}
+
+.text-success:hover {
+  color: #356635;
+}
+
+.text-info {
+  color: #3a87ad;
+}
+
+.text-info:hover {
+  color: #2d6987;
+}
+
+.text-left {
+  text-align: left;
+}
+
+.text-right {
+  text-align: right;
+}
+
+.text-center {
+  text-align: center;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6 {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-weight: 500;
+  line-height: 1.1;
+  color: inherit;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small,
+.h1 small,
+.h2 small,
+.h3 small,
+.h4 small,
+.h5 small,
+.h6 small,
+h1 .small,
+h2 .small,
+h3 .small,
+h4 .small,
+h5 .small,
+h6 .small,
+.h1 .small,
+.h2 .small,
+.h3 .small,
+.h4 .small,
+.h5 .small,
+.h6 .small {
+  font-weight: normal;
+  line-height: 1;
+  color: #999999;
+}
+
+h1,
+h2,
+h3 {
+  margin-top: 20px;
+  margin-bottom: 10px;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h1 .small,
+h2 .small,
+h3 .small {
+  font-size: 65%;
+}
+
+h4,
+h5,
+h6 {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+
+h4 small,
+h5 small,
+h6 small,
+h4 .small,
+h5 .small,
+h6 .small {
+  font-size: 75%;
+}
+
+h1,
+.h1 {
+  font-size: 36px;
+}
+
+h2,
+.h2 {
+  font-size: 30px;
+}
+
+h3,
+.h3 {
+  font-size: 24px;
+}
+
+h4,
+.h4 {
+  font-size: 18px;
+}
+
+h5,
+.h5 {
+  font-size: 14px;
+}
+
+h6,
+.h6 {
+  font-size: 12px;
+}
+
+.page-header {
+  padding-bottom: 9px;
+  margin: 40px 0 20px;
+  border-bottom: 1px solid #eeeeee;
+}
+
+ul,
+ol {
+  margin-top: 0;
+  margin-bottom: 10px;
+}
+
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+  margin-bottom: 0;
+}
+
+.list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline > li {
+  display: inline-block;
+  padding-right: 5px;
+  padding-left: 5px;
+}
+
+.list-inline > li:first-child {
+  padding-left: 0;
+}
+
+dl {
+  margin-bottom: 20px;
+}
+
+dt,
+dd {
+  line-height: 1.428571429;
+}
+
+dt {
+  font-weight: bold;
+}
+
+dd {
+  margin-left: 0;
+}
+
+@media (min-width: 768px) {
+  .dl-horizontal dt {
+    float: left;
+    width: 160px;
+    overflow: hidden;
+    clear: left;
+    text-align: right;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .dl-horizontal dd {
+    margin-left: 180px;
+  }
+  .dl-horizontal dd:before,
+  .dl-horizontal dd:after {
+    display: table;
+    content: " ";
+  }
+  .dl-horizontal dd:after {
+    clear: both;
+  }
+  .dl-horizontal dd:before,
+  .dl-horizontal dd:after {
+    display: table;
+    content: " ";
+  }
+  .dl-horizontal dd:after {
+    clear: both;
+  }
+}
+
+abbr[title],
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted #999999;
+}
+
+abbr.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+
+blockquote {
+  padding: 10px 20px;
+  margin: 0 0 20px;
+  border-left: 5px solid #eeeeee;
+}
+
+blockquote p {
+  font-size: 17.5px;
+  font-weight: 300;
+  line-height: 1.25;
+}
+
+blockquote p:last-child {
+  margin-bottom: 0;
+}
+
+blockquote small {
+  display: block;
+  line-height: 1.428571429;
+  color: #999999;
+}
+
+blockquote small:before {
+  content: '\2014 \00A0';
+}
+
+blockquote.pull-right {
+  padding-right: 15px;
+  padding-left: 0;
+  border-right: 5px solid #eeeeee;
+  border-left: 0;
+}
+
+blockquote.pull-right p,
+blockquote.pull-right small,
+blockquote.pull-right .small {
+  text-align: right;
+}
+
+blockquote.pull-right small:before,
+blockquote.pull-right .small:before {
+  content: '';
+}
+
+blockquote.pull-right small:after,
+blockquote.pull-right .small:after {
+  content: '\00A0 \2014';
+}
+
+blockquote:before,
+blockquote:after {
+  content: "";
+}
+
+address {
+  margin-bottom: 20px;
+  font-style: normal;
+  line-height: 1.428571429;
+}
+
+code,
+kbd,
+pre,
+samp {
+  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+}
+
+code {
+  padding: 2px 4px;
+  font-size: 90%;
+  color: #c7254e;
+  white-space: nowrap;
+  background-color: #f9f2f4;
+  border-radius: 4px;
+}
+
+pre {
+  display: block;
+  padding: 9.5px;
+  margin: 0 0 10px;
+  font-size: 13px;
+  line-height: 1.428571429;
+  color: #333333;
+  word-break: break-all;
+  word-wrap: break-word;
+  background-color: #f5f5f5;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+}
+
+pre code {
+  padding: 0;
+  font-size: inherit;
+  color: inherit;
+  white-space: pre-wrap;
+  background-color: transparent;
+  border-radius: 0;
+}
+
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+
+.container {
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.container:before,
+.container:after {
+  display: table;
+  content: " ";
+}
+
+.container:after {
+  clear: both;
+}
+
+.container:before,
+.container:after {
+  display: table;
+  content: " ";
+}
+
+.container:after {
+  clear: both;
+}
+
+.row {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+.row:before,
+.row:after {
+  display: table;
+  content: " ";
+}
+
+.row:after {
+  clear: both;
+}
+
+.row:before,
+.row:after {
+  display: table;
+  content: " ";
+}
+
+.row:after {
+  clear: both;
+}
+
+.col-xs-1,
+.col-sm-1,
+.col-md-1,
+.col-lg-1,
+.col-xs-2,
+.col-sm-2,
+.col-md-2,
+.col-lg-2,
+.col-xs-3,
+.col-sm-3,
+.col-md-3,
+.col-lg-3,
+.col-xs-4,
+.col-sm-4,
+.col-md-4,
+.col-lg-4,
+.col-xs-5,
+.col-sm-5,
+.col-md-5,
+.col-lg-5,
+.col-xs-6,
+.col-sm-6,
+.col-md-6,
+.col-lg-6,
+.col-xs-7,
+.col-sm-7,
+.col-md-7,
+.col-lg-7,
+.col-xs-8,
+.col-sm-8,
+.col-md-8,
+.col-lg-8,
+.col-xs-9,
+.col-sm-9,
+.col-md-9,
+.col-lg-9,
+.col-xs-10,
+.col-sm-10,
+.col-md-10,
+.col-lg-10,
+.col-xs-11,
+.col-sm-11,
+.col-md-11,
+.col-lg-11,
+.col-xs-12,
+.col-sm-12,
+.col-md-12,
+.col-lg-12 {
+  position: relative;
+  min-height: 1px;
+  padding-right: 15px;
+  padding-left: 15px;
+}
+
+.col-xs-1,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9,
+.col-xs-10,
+.col-xs-11 {
+  float: left;
+}
+
+.col-xs-12 {
+  width: 100%;
+}
+
+.col-xs-11 {
+  width: 91.66666666666666%;
+}
+
+.col-xs-10 {
+  width: 83.33333333333334%;
+}
+
+.col-xs-9 {
+  width: 75%;
+}
+
+.col-xs-8 {
+  width: 66.66666666666666%;
+}
+
+.col-xs-7 {
+  width: 58.333333333333336%;
+}
+
+.col-xs-6 {
+  width: 50%;
+}
+
+.col-xs-5 {
+  width: 41.66666666666667%;
+}
+
+.col-xs-4 {
+  width: 33.33333333333333%;
+}
+
+.col-xs-3 {
+  width: 25%;
+}
+
+.col-xs-2 {
+  width: 16.666666666666664%;
+}
+
+.col-xs-1 {
+  width: 8.333333333333332%;
+}
+
+.col-xs-pull-12 {
+  right: 100%;
+}
+
+.col-xs-pull-11 {
+  right: 91.66666666666666%;
+}
+
+.col-xs-pull-10 {
+  right: 83.33333333333334%;
+}
+
+.col-xs-pull-9 {
+  right: 75%;
+}
+
+.col-xs-pull-8 {
+  right: 66.66666666666666%;
+}
+
+.col-xs-pull-7 {
+  right: 58.333333333333336%;
+}
+
+.col-xs-pull-6 {
+  right: 50%;
+}
+
+.col-xs-pull-5 {
+  right: 41.66666666666667%;
+}
+
+.col-xs-pull-4 {
+  right: 33.33333333333333%;
+}
+
+.col-xs-pull-3 {
+  right: 25%;
+}
+
+.col-xs-pull-2 {
+  right: 16.666666666666664%;
+}
+
+.col-xs-pull-1 {
+  right: 8.333333333333332%;
+}
+
+.col-xs-pull-0 {
+  right: 0;
+}
+
+.col-xs-push-12 {
+  left: 100%;
+}
+
+.col-xs-push-11 {
+  left: 91.66666666666666%;
+}
+
+.col-xs-push-10 {
+  left: 83.33333333333334%;
+}
+
+.col-xs-push-9 {
+  left: 75%;
+}
+
+.col-xs-push-8 {
+  left: 66.66666666666666%;
+}
+
+.col-xs-push-7 {
+  left: 58.333333333333336%;
+}
+
+.col-xs-push-6 {
+  left: 50%;
+}
+
+.col-xs-push-5 {
+  left: 41.66666666666667%;
+}
+
+.col-xs-push-4 {
+  left: 33.33333333333333%;
+}
+
+.col-xs-push-3 {
+  left: 25%;
+}
+
+.col-xs-push-2 {
+  left: 16.666666666666664%;
+}
+
+.col-xs-push-1 {
+  left: 8.333333333333332%;
+}
+
+.col-xs-push-0 {
+  left: 0;
+}
+
+.col-xs-offset-12 {
+  margin-left: 100%;
+}
+
+.col-xs-offset-11 {
+  margin-left: 91.66666666666666%;
+}
+
+.col-xs-offset-10 {
+  margin-left: 83.33333333333334%;
+}
+
+.col-xs-offset-9 {
+  margin-left: 75%;
+}
+
+.col-xs-offset-8 {
+  margin-left: 66.66666666666666%;
+}
+
+.col-xs-offset-7 {
+  margin-left: 58.333333333333336%;
+}
+
+.col-xs-offset-6 {
+  margin-left: 50%;
+}
+
+.col-xs-offset-5 {
+  margin-left: 41.66666666666667%;
+}
+
+.col-xs-offset-4 {
+  margin-left: 33.33333333333333%;
+}
+
+.col-xs-offset-3 {
+  margin-left: 25%;
+}
+
+.col-xs-offset-2 {
+  margin-left: 16.666666666666664%;
+}
+
+.col-xs-offset-1 {
+  margin-left: 8.333333333333332%;
+}
+
+.col-xs-offset-0 {
+  margin-left: 0;
+}
+
+@media (min-width: 768px) {
+  .container {
+    width: 750px;
+  }
+  .col-sm-1,
+  .col-sm-2,
+  .col-sm-3,
+  .col-sm-4,
+  .col-sm-5,
+  .col-sm-6,
+  .col-sm-7,
+  .col-sm-8,
+  .col-sm-9,
+  .col-sm-10,
+  .col-sm-11 {
+    float: left;
+  }
+  .col-sm-12 {
+    width: 100%;
+  }
+  .col-sm-11 {
+    width: 91.66666666666666%;
+  }
+  .col-sm-10 {
+    width: 83.33333333333334%;
+  }
+  .col-sm-9 {
+    width: 75%;
+  }
+  .col-sm-8 {
+    width: 66.66666666666666%;
+  }
+  .col-sm-7 {
+    width: 58.333333333333336%;
+  }
+  .col-sm-6 {
+    width: 50%;
+  }
+  .col-sm-5 {
+    width: 41.66666666666667%;
+  }
+  .col-sm-4 {
+    width: 33.33333333333333%;
+  }
+  .col-sm-3 {
+    width: 25%;
+  }
+  .col-sm-2 {
+    width: 16.666666666666664%;
+  }
+  .col-sm-1 {
+    width: 8.333333333333332%;
+  }
+  .col-sm-pull-12 {
+    right: 100%;
+  }
+  .col-sm-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-sm-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-sm-pull-9 {
+    right: 75%;
+  }
+  .col-sm-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-sm-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-sm-pull-6 {
+    right: 50%;
+  }
+  .col-sm-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-sm-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-sm-pull-3 {
+    right: 25%;
+  }
+  .col-sm-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-sm-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-sm-pull-0 {
+    right: 0;
+  }
+  .col-sm-push-12 {
+    left: 100%;
+  }
+  .col-sm-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-sm-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-sm-push-9 {
+    left: 75%;
+  }
+  .col-sm-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-sm-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-sm-push-6 {
+    left: 50%;
+  }
+  .col-sm-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-sm-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-sm-push-3 {
+    left: 25%;
+  }
+  .col-sm-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-sm-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-sm-push-0 {
+    left: 0;
+  }
+  .col-sm-offset-12 {
+    margin-left: 100%;
+  }
+  .col-sm-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+  .col-sm-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-sm-offset-9 {
+    margin-left: 75%;
+  }
+  .col-sm-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-sm-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-sm-offset-6 {
+    margin-left: 50%;
+  }
+  .col-sm-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-sm-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-sm-offset-3 {
+    margin-left: 25%;
+  }
+  .col-sm-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-sm-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+  .col-sm-offset-0 {
+    margin-left: 0;
+  }
+}
+
+@media (min-width: 992px) {
+  .container {
+    width: 970px;
+  }
+  .col-md-1,
+  .col-md-2,
+  .col-md-3,
+  .col-md-4,
+  .col-md-5,
+  .col-md-6,
+  .col-md-7,
+  .col-md-8,
+  .col-md-9,
+  .col-md-10,
+  .col-md-11 {
+    float: left;
+  }
+  .col-md-12 {
+    width: 100%;
+  }
+  .col-md-11 {
+    width: 91.66666666666666%;
+  }
+  .col-md-10 {
+    width: 83.33333333333334%;
+  }
+  .col-md-9 {
+    width: 75%;
+  }
+  .col-md-8 {
+    width: 66.66666666666666%;
+  }
+  .col-md-7 {
+    width: 58.333333333333336%;
+  }
+  .col-md-6 {
+    width: 50%;
+  }
+  .col-md-5 {
+    width: 41.66666666666667%;
+  }
+  .col-md-4 {
+    width: 33.33333333333333%;
+  }
+  .col-md-3 {
+    width: 25%;
+  }
+  .col-md-2 {
+    width: 16.666666666666664%;
+  }
+  .col-md-1 {
+    width: 8.333333333333332%;
+  }
+  .col-md-pull-12 {
+    right: 100%;
+  }
+  .col-md-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-md-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-md-pull-9 {
+    right: 75%;
+  }
+  .col-md-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-md-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-md-pull-6 {
+    right: 50%;
+  }
+  .col-md-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-md-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-md-pull-3 {
+    right: 25%;
+  }
+  .col-md-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-md-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-md-pull-0 {
+    right: 0;
+  }
+  .col-md-push-12 {
+    left: 100%;
+  }
+  .col-md-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-md-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-md-push-9 {
+    left: 75%;
+  }
+  .col-md-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-md-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-md-push-6 {
+    left: 50%;
+  }
+  .col-md-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-md-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-md-push-3 {
+    left: 25%;
+  }
+  .col-md-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-md-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-md-push-0 {
+    left: 0;
+  }
+  .col-md-offset-12 {
+    margin-left: 100%;
+  }
+  .col-md-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+  .col-md-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-md-offset-9 {
+    margin-left: 75%;
+  }
+  .col-md-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-md-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-md-offset-6 {
+    margin-left: 50%;
+  }
+  .col-md-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-md-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-md-offset-3 {
+    margin-left: 25%;
+  }
+  .col-md-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-md-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+  .col-md-offset-0 {
+    margin-left: 0;
+  }
+}
+
+@media (min-width: 1200px) {
+  .container {
+    width: 1170px;
+  }
+  .col-lg-1,
+  .col-lg-2,
+  .col-lg-3,
+  .col-lg-4,
+  .col-lg-5,
+  .col-lg-6,
+  .col-lg-7,
+  .col-lg-8,
+  .col-lg-9,
+  .col-lg-10,
+  .col-lg-11 {
+    float: left;
+  }
+  .col-lg-12 {
+    width: 100%;
+  }
+  .col-lg-11 {
+    width: 91.66666666666666%;
+  }
+  .col-lg-10 {
+    width: 83.33333333333334%;
+  }
+  .col-lg-9 {
+    width: 75%;
+  }
+  .col-lg-8 {
+    width: 66.66666666666666%;
+  }
+  .col-lg-7 {
+    width: 58.333333333333336%;
+  }
+  .col-lg-6 {
+    width: 50%;
+  }
+  .col-lg-5 {
+    width: 41.66666666666667%;
+  }
+  .col-lg-4 {
+    width: 33.33333333333333%;
+  }
+  .col-lg-3 {
+    width: 25%;
+  }
+  .col-lg-2 {
+    width: 16.666666666666664%;
+  }
+  .col-lg-1 {
+    width: 8.333333333333332%;
+  }
+  .col-lg-pull-12 {
+    right: 100%;
+  }
+  .col-lg-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-lg-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-lg-pull-9 {
+    right: 75%;
+  }
+  .col-lg-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-lg-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-lg-pull-6 {
+    right: 50%;
+  }
+  .col-lg-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-lg-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-lg-pull-3 {
+    right: 25%;
+  }
+  .col-lg-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-lg-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-lg-pull-0 {
+    right: 0;
+  }
+  .col-lg-push-12 {
+    left: 100%;
+  }
+  .col-lg-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-lg-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-lg-push-9 {
+    left: 75%;
+  }
+  .col-lg-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-lg-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-lg-push-6 {
+    left: 50%;
+  }
+  .col-lg-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-lg-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-lg-push-3 {
+    left: 25%;
+  }
+  .col-lg-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-lg-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-lg-push-0 {
+    left: 0;
+  }
+  .col-lg-offset-12 {
+    margin-left: 100%;
+  }
+  .col-lg-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+  .col-lg-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-lg-offset-9 {
+    margin-left: 75%;
+  }
+  .col-lg-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-lg-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-lg-offset-6 {
+    margin-left: 50%;
+  }
+  .col-lg-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-lg-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-lg-offset-3 {
+    margin-left: 25%;
+  }
+  .col-lg-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-lg-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+  .col-lg-offset-0 {
+    margin-left: 0;
+  }
+}
+
+table {
+  max-width: 100%;
+  background-color: transparent;
+}
+
+th {
+  text-align: left;
+}
+
+.table {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.table > thead > tr > th,
+.table > tbody > tr > th,
+.table > tfoot > tr > th,
+.table > thead > tr > td,
+.table > tbody > tr > td,
+.table > tfoot > tr > td {
+  padding: 8px;
+  line-height: 1.428571429;
+  vertical-align: top;
+  border-top: 1px solid #dddddd;
+}
+
+.table > thead > tr > th {
+  vertical-align: bottom;
+  border-bottom: 2px solid #dddddd;
+}
+
+.table > caption + thead > tr:first-child > th,
+.table > colgroup + thead > tr:first-child > th,
+.table > thead:first-child > tr:first-child > th,
+.table > caption + thead > tr:first-child > td,
+.table > colgroup + thead > tr:first-child > td,
+.table > thead:first-child > tr:first-child > td {
+  border-top: 0;
+}
+
+.table > tbody + tbody {
+  border-top: 2px solid #dddddd;
+}
+
+.table .table {
+  background-color: #ffffff;
+}
+
+.table-condensed > thead > tr > th,
+.table-condensed > tbody > tr > th,
+.table-condensed > tfoot > tr > th,
+.table-condensed > thead > tr > td,
+.table-condensed > tbody > tr > td,
+.table-condensed > tfoot > tr > td {
+  padding: 5px;
+}
+
+.table-bordered {
+  border: 1px solid #dddddd;
+}
+
+.table-bordered > thead > tr > th,
+.table-bordered > tbody > tr > th,
+.table-bordered > tfoot > tr > th,
+.table-bordered > thead > tr > td,
+.table-bordered > tbody > tr > td,
+.table-bordered > tfoot > tr > td {
+  border: 1px solid #dddddd;
+}
+
+.table-bordered > thead > tr > th,
+.table-bordered > thead > tr > td {
+  border-bottom-width: 2px;
+}
+
+.table-striped > tbody > tr:nth-child(odd) > td,
+.table-striped > tbody > tr:nth-child(odd) > th {
+  background-color: #f9f9f9;
+}
+
+.table-hover > tbody > tr:hover > td,
+.table-hover > tbody > tr:hover > th {
+  background-color: #f5f5f5;
+}
+
+table col[class*="col-"] {
+  display: table-column;
+  float: none;
+}
+
+table td[class*="col-"],
+table th[class*="col-"] {
+  display: table-cell;
+  float: none;
+}
+
+.table > thead > tr > td.active,
+.table > tbody > tr > td.active,
+.table > tfoot > tr > td.active,
+.table > thead > tr > th.active,
+.table > tbody > tr > th.active,
+.table > tfoot > tr > th.active,
+.table > thead > tr.active > td,
+.table > tbody > tr.active > td,
+.table > tfoot > tr.active > td,
+.table > thead > tr.active > th,
+.table > tbody > tr.active > th,
+.table > tfoot > tr.active > th {
+  background-color: #f5f5f5;
+}
+
+.table > thead > tr > td.success,
+.table > tbody > tr > td.success,
+.table > tfoot > tr > td.success,
+.table > thead > tr > th.success,
+.table > tbody > tr > th.success,
+.table > tfoot > tr > th.success,
+.table > thead > tr.success > td,
+.table > tbody > tr.success > td,
+.table > tfoot > tr.success > td,
+.table > thead > tr.success > th,
+.table > tbody > tr.success > th,
+.table > tfoot > tr.success > th {
+  background-color: #dff0d8;
+}
+
+.table-hover > tbody > tr > td.success:hover,
+.table-hover > tbody > tr > th.success:hover,
+.table-hover > tbody > tr.success:hover > td,
+.table-hover > tbody > tr.success:hover > th {
+  background-color: #d0e9c6;
+}
+
+.table > thead > tr > td.danger,
+.table > tbody > tr > td.danger,
+.table > tfoot > tr > td.danger,
+.table > thead > tr > th.danger,
+.table > tbody > tr > th.danger,
+.table > tfoot > tr > th.danger,
+.table > thead > tr.danger > td,
+.table > tbody > tr.danger > td,
+.table > tfoot > tr.danger > td,
+.table > thead > tr.danger > th,
+.table > tbody > tr.danger > th,
+.table > tfoot > tr.danger > th {
+  background-color: #f2dede;
+}
+
+.table-hover > tbody > tr > td.danger:hover,
+.table-hover > tbody > tr > th.danger:hover,
+.table-hover > tbody > tr.danger:hover > td,
+.table-hover > tbody > tr.danger:hover > th {
+  background-color: #ebcccc;
+}
+
+.table > thead > tr > td.warning,
+.table > tbody > tr > td.warning,
+.table > tfoot > tr > td.warning,
+.table > thead > tr > th.warning,
+.table > tbody > tr > th.warning,
+.table > tfoot > tr > th.warning,
+.table > thead > tr.warning > td,
+.table > tbody > tr.warning > td,
+.table > tfoot > tr.warning > td,
+.table > thead > tr.warning > th,
+.table > tbody > tr.warning > th,
+.table > tfoot > tr.warning > th {
+  background-color: #fcf8e3;
+}
+
+.table-hover > tbody > tr > td.warning:hover,
+.table-hover > tbody > tr > th.warning:hover,
+.table-hover > tbody > tr.warning:hover > td,
+.table-hover > tbody > tr.warning:hover > th {
+  background-color: #faf2cc;
+}
+
+@media (max-width: 767px) {
+  .table-responsive {
+    width: 100%;
+    margin-bottom: 15px;
+    overflow-x: scroll;
+    overflow-y: hidden;
+    border: 1px solid #dddddd;
+    -ms-overflow-style: -ms-autohiding-scrollbar;
+    -webkit-overflow-scrolling: touch;
+  }
+  .table-responsive > .table {
+    margin-bottom: 0;
+  }
+  .table-responsive > .table > thead > tr > th,
+  .table-responsive > .table > tbody > tr > th,
+  .table-responsive > .table > tfoot > tr > th,
+  .table-responsive > .table > thead > tr > td,
+  .table-responsive > .table > tbody > tr > td,
+  .table-responsive > .table > tfoot > tr > td {
+    white-space: nowrap;
+  }
+  .table-responsive > .table-bordered {
+    border: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:first-child,
+  .table-responsive > .table-bordered > tbody > tr > th:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+  .table-responsive > .table-bordered > thead > tr > td:first-child,
+  .table-responsive > .table-bordered > tbody > tr > td:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+    border-left: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:last-child,
+  .table-responsive > .table-bordered > tbody > tr > th:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+  .table-responsive > .table-bordered > thead > tr > td:last-child,
+  .table-responsive > .table-bordered > tbody > tr > td:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+    border-right: 0;
+  }
+  .table-responsive > .table-bordered > tbody > tr:last-child > th,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > th,
+  .table-responsive > .table-bordered > tbody > tr:last-child > td,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > td {
+    border-bottom: 0;
+  }
+}
+
+fieldset {
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 20px;
+  font-size: 21px;
+  line-height: inherit;
+  color: #333333;
+  border: 0;
+  border-bottom: 1px solid #e5e5e5;
+}
+
+label {
+  display: inline-block;
+  margin-bottom: 5px;
+  font-weight: bold;
+}
+
+input[type="search"] {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+  margin: 4px 0 0;
+  margin-top: 1px \9;
+  /* IE8-9 */
+
+  line-height: normal;
+}
+
+input[type="file"] {
+  display: block;
+}
+
+select[multiple],
+select[size] {
+  height: auto;
+}
+
+select optgroup {
+  font-family: inherit;
+  font-size: inherit;
+  font-style: inherit;
+}
+
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+input[type="number"]::-webkit-outer-spin-button,
+input[type="number"]::-webkit-inner-spin-button {
+  height: auto;
+}
+
+output {
+  display: block;
+  padding-top: 7px;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #555555;
+  vertical-align: middle;
+}
+
+.form-control {
+  display: block;
+  width: 100%;
+  height: 34px;
+  padding: 6px 12px;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #555555;
+  vertical-align: middle;
+  background-color: #ffffff;
+  background-image: none;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+          transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+}
+
+.form-control:focus {
+  border-color: #66afe9;
+  outline: 0;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
+
+.form-control:-moz-placeholder {
+  color: #999999;
+}
+
+.form-control::-moz-placeholder {
+  color: #999999;
+}
+
+.form-control:-ms-input-placeholder {
+  color: #999999;
+}
+
+.form-control::-webkit-input-placeholder {
+  color: #999999;
+}
+
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+  cursor: not-allowed;
+  background-color: #eeeeee;
+}
+
+textarea.form-control {
+  height: auto;
+}
+
+.form-group {
+  margin-bottom: 15px;
+}
+
+.radio,
+.checkbox {
+  display: block;
+  min-height: 20px;
+  padding-left: 20px;
+  margin-top: 10px;
+  margin-bottom: 10px;
+  vertical-align: middle;
+}
+
+.radio label,
+.checkbox label {
+  display: inline;
+  margin-bottom: 0;
+  font-weight: normal;
+  cursor: pointer;
+}
+
+.radio input[type="radio"],
+.radio-inline input[type="radio"],
+.checkbox input[type="checkbox"],
+.checkbox-inline input[type="checkbox"] {
+  float: left;
+  margin-left: -20px;
+}
+
+.radio + .radio,
+.checkbox + .checkbox {
+  margin-top: -5px;
+}
+
+.radio-inline,
+.checkbox-inline {
+  display: inline-block;
+  padding-left: 20px;
+  margin-bottom: 0;
+  font-weight: normal;
+  vertical-align: middle;
+  cursor: pointer;
+}
+
+.radio-inline + .radio-inline,
+.checkbox-inline + .checkbox-inline {
+  margin-top: 0;
+  margin-left: 10px;
+}
+
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+.radio[disabled],
+.radio-inline[disabled],
+.checkbox[disabled],
+.checkbox-inline[disabled],
+fieldset[disabled] input[type="radio"],
+fieldset[disabled] input[type="checkbox"],
+fieldset[disabled] .radio,
+fieldset[disabled] .radio-inline,
+fieldset[disabled] .checkbox,
+fieldset[disabled] .checkbox-inline {
+  cursor: not-allowed;
+}
+
+.input-sm {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+select.input-sm {
+  height: 30px;
+  line-height: 30px;
+}
+
+textarea.input-sm {
+  height: auto;
+}
+
+.input-lg {
+  height: 45px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+select.input-lg {
+  height: 45px;
+  line-height: 45px;
+}
+
+textarea.input-lg {
+  height: auto;
+}
+
+.has-warning .help-block,
+.has-warning .control-label,
+.has-warning .radio,
+.has-warning .checkbox,
+.has-warning .radio-inline,
+.has-warning .checkbox-inline {
+  color: #c09853;
+}
+
+.has-warning .form-control {
+  border-color: #c09853;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-warning .form-control:focus {
+  border-color: #a47e3c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+
+.has-warning .input-group-addon {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #c09853;
+}
+
+.has-error .help-block,
+.has-error .control-label,
+.has-error .radio,
+.has-error .checkbox,
+.has-error .radio-inline,
+.has-error .checkbox-inline {
+  color: #b94a48;
+}
+
+.has-error .form-control {
+  border-color: #b94a48;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-error .form-control:focus {
+  border-color: #953b39;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+
+.has-error .input-group-addon {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #b94a48;
+}
+
+.has-success .help-block,
+.has-success .control-label,
+.has-success .radio,
+.has-success .checkbox,
+.has-success .radio-inline,
+.has-success .checkbox-inline {
+  color: #468847;
+}
+
+.has-success .form-control {
+  border-color: #468847;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-success .form-control:focus {
+  border-color: #356635;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+
+.has-success .input-group-addon {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #468847;
+}
+
+.form-control-static {
+  margin-bottom: 0;
+}
+
+.help-block {
+  display: block;
+  margin-top: 5px;
+  margin-bottom: 10px;
+  color: #737373;
+}
+
+@media (min-width: 768px) {
+  .form-inline .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .form-inline .form-control {
+    display: inline-block;
+  }
+  .form-inline .radio,
+  .form-inline .checkbox {
+    display: inline-block;
+    padding-left: 0;
+    margin-top: 0;
+    margin-bottom: 0;
+  }
+  .form-inline .radio input[type="radio"],
+  .form-inline .checkbox input[type="checkbox"] {
+    float: none;
+    margin-left: 0;
+  }
+}
+
+.form-horizontal .control-label,
+.form-horizontal .radio,
+.form-horizontal .checkbox,
+.form-horizontal .radio-inline,
+.form-horizontal .checkbox-inline {
+  padding-top: 7px;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+.form-horizontal .form-group {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+  display: table;
+  content: " ";
+}
+
+.form-horizontal .form-group:after {
+  clear: both;
+}
+
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+  display: table;
+  content: " ";
+}
+
+.form-horizontal .form-group:after {
+  clear: both;
+}
+
+.form-horizontal .form-control-static {
+  padding-top: 7px;
+}
+
+@media (min-width: 768px) {
+  .form-horizontal .control-label {
+    text-align: right;
+  }
+}
+
+.btn {
+  display: inline-block;
+  padding: 6px 12px;
+  margin-bottom: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1.428571429;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: middle;
+  cursor: pointer;
+  background-image: none;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+       -o-user-select: none;
+          user-select: none;
+}
+
+.btn:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+.btn:hover,
+.btn:focus {
+  color: #333333;
+  text-decoration: none;
+}
+
+.btn:active,
+.btn.active {
+  background-image: none;
+  outline: 0;
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+  pointer-events: none;
+  cursor: not-allowed;
+  opacity: 0.65;
+  filter: alpha(opacity=65);
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-default {
+  color: #333333;
+  background-color: #ffffff;
+  border-color: #cccccc;
+}
+
+.btn-default:hover,
+.btn-default:focus,
+.btn-default:active,
+.btn-default.active,
+.open .dropdown-toggle.btn-default {
+  color: #333333;
+  background-color: #ebebeb;
+  border-color: #adadad;
+}
+
+.btn-default:active,
+.btn-default.active,
+.open .dropdown-toggle.btn-default {
+  background-image: none;
+}
+
+.btn-default.disabled,
+.btn-default[disabled],
+fieldset[disabled] .btn-default,
+.btn-default.disabled:hover,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default:hover,
+.btn-default.disabled:focus,
+.btn-default[disabled]:focus,
+fieldset[disabled] .btn-default:focus,
+.btn-default.disabled:active,
+.btn-default[disabled]:active,
+fieldset[disabled] .btn-default:active,
+.btn-default.disabled.active,
+.btn-default[disabled].active,
+fieldset[disabled] .btn-default.active {
+  background-color: #ffffff;
+  border-color: #cccccc;
+}
+
+.btn-primary {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #357ebd;
+}
+
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+  color: #ffffff;
+  background-color: #3276b1;
+  border-color: #285e8e;
+}
+
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+  background-image: none;
+}
+
+.btn-primary.disabled,
+.btn-primary[disabled],
+fieldset[disabled] .btn-primary,
+.btn-primary.disabled:hover,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary:hover,
+.btn-primary.disabled:focus,
+.btn-primary[disabled]:focus,
+fieldset[disabled] .btn-primary:focus,
+.btn-primary.disabled:active,
+.btn-primary[disabled]:active,
+fieldset[disabled] .btn-primary:active,
+.btn-primary.disabled.active,
+.btn-primary[disabled].active,
+fieldset[disabled] .btn-primary.active {
+  background-color: #428bca;
+  border-color: #357ebd;
+}
+
+.btn-warning {
+  color: #ffffff;
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning:active,
+.btn-warning.active,
+.open .dropdown-toggle.btn-warning {
+  color: #ffffff;
+  background-color: #ed9c28;
+  border-color: #d58512;
+}
+
+.btn-warning:active,
+.btn-warning.active,
+.open .dropdown-toggle.btn-warning {
+  background-image: none;
+}
+
+.btn-warning.disabled,
+.btn-warning[disabled],
+fieldset[disabled] .btn-warning,
+.btn-warning.disabled:hover,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning:hover,
+.btn-warning.disabled:focus,
+.btn-warning[disabled]:focus,
+fieldset[disabled] .btn-warning:focus,
+.btn-warning.disabled:active,
+.btn-warning[disabled]:active,
+fieldset[disabled] .btn-warning:active,
+.btn-warning.disabled.active,
+.btn-warning[disabled].active,
+fieldset[disabled] .btn-warning.active {
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+
+.btn-danger {
+  color: #ffffff;
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger:active,
+.btn-danger.active,
+.open .dropdown-toggle.btn-danger {
+  color: #ffffff;
+  background-color: #d2322d;
+  border-color: #ac2925;
+}
+
+.btn-danger:active,
+.btn-danger.active,
+.open .dropdown-toggle.btn-danger {
+  background-image: none;
+}
+
+.btn-danger.disabled,
+.btn-danger[disabled],
+fieldset[disabled] .btn-danger,
+.btn-danger.disabled:hover,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger:hover,
+.btn-danger.disabled:focus,
+.btn-danger[disabled]:focus,
+fieldset[disabled] .btn-danger:focus,
+.btn-danger.disabled:active,
+.btn-danger[disabled]:active,
+fieldset[disabled] .btn-danger:active,
+.btn-danger.disabled.active,
+.btn-danger[disabled].active,
+fieldset[disabled] .btn-danger.active {
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+
+.btn-success {
+  color: #ffffff;
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+
+.btn-success:hover,
+.btn-success:focus,
+.btn-success:active,
+.btn-success.active,
+.open .dropdown-toggle.btn-success {
+  color: #ffffff;
+  background-color: #47a447;
+  border-color: #398439;
+}
+
+.btn-success:active,
+.btn-success.active,
+.open .dropdown-toggle.btn-success {
+  background-image: none;
+}
+
+.btn-success.disabled,
+.btn-success[disabled],
+fieldset[disabled] .btn-success,
+.btn-success.disabled:hover,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success:hover,
+.btn-success.disabled:focus,
+.btn-success[disabled]:focus,
+fieldset[disabled] .btn-success:focus,
+.btn-success.disabled:active,
+.btn-success[disabled]:active,
+fieldset[disabled] .btn-success:active,
+.btn-success.disabled.active,
+.btn-success[disabled].active,
+fieldset[disabled] .btn-success.active {
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+
+.btn-info {
+  color: #ffffff;
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+
+.btn-info:hover,
+.btn-info:focus,
+.btn-info:active,
+.btn-info.active,
+.open .dropdown-toggle.btn-info {
+  color: #ffffff;
+  background-color: #39b3d7;
+  border-color: #269abc;
+}
+
+.btn-info:active,
+.btn-info.active,
+.open .dropdown-toggle.btn-info {
+  background-image: none;
+}
+
+.btn-info.disabled,
+.btn-info[disabled],
+fieldset[disabled] .btn-info,
+.btn-info.disabled:hover,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info:hover,
+.btn-info.disabled:focus,
+.btn-info[disabled]:focus,
+fieldset[disabled] .btn-info:focus,
+.btn-info.disabled:active,
+.btn-info[disabled]:active,
+fieldset[disabled] .btn-info:active,
+.btn-info.disabled.active,
+.btn-info[disabled].active,
+fieldset[disabled] .btn-info.active {
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+
+.btn-link {
+  font-weight: normal;
+  color: #428bca;
+  cursor: pointer;
+  border-radius: 0;
+}
+
+.btn-link,
+.btn-link:active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+  background-color: transparent;
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-link,
+.btn-link:hover,
+.btn-link:focus,
+.btn-link:active {
+  border-color: transparent;
+}
+
+.btn-link:hover,
+.btn-link:focus {
+  color: #2a6496;
+  text-decoration: underline;
+  background-color: transparent;
+}
+
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:hover,
+.btn-link[disabled]:focus,
+fieldset[disabled] .btn-link:focus {
+  color: #999999;
+  text-decoration: none;
+}
+
+.btn-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+.btn-sm,
+.btn-xs {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+.btn-xs {
+  padding: 1px 5px;
+}
+
+.btn-block {
+  display: block;
+  width: 100%;
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.btn-block + .btn-block {
+  margin-top: 5px;
+}
+
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+  width: 100%;
+}
+
+.fade {
+  opacity: 0;
+  -webkit-transition: opacity 0.15s linear;
+          transition: opacity 0.15s linear;
+}
+
+.fade.in {
+  opacity: 1;
+}
+
+.collapse {
+  display: none;
+}
+
+.collapse.in {
+  display: block;
+}
+
+.collapsing {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  -webkit-transition: height 0.35s ease;
+          transition: height 0.35s ease;
+}
+
+@font-face {
+  font-family: 'Glyphicons Halflings';
+  src: url('../../fonts/glyphicons-halflings-regular.eot');
+  src: url('../../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
+}
+
+.glyphicon {
+  position: relative;
+  top: 1px;
+  display: inline-block;
+  font-family: 'Glyphicons Halflings';
+  -webkit-font-smoothing: antialiased;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.glyphicon:empty {
+  width: 1em;
+}
+
+.glyphicon-asterisk:before {
+  content: "\2a";
+}
+
+.glyphicon-plus:before {
+  content: "\2b";
+}
+
+.glyphicon-euro:before {
+  content: "\20ac";
+}
+
+.glyphicon-minus:before {
+  content: "\2212";
+}
+
+.glyphicon-cloud:before {
+  content: "\2601";
+}
+
+.glyphicon-envelope:before {
+  content: "\2709";
+}
+
+.glyphicon-pencil:before {
+  content: "\270f";
+}
+
+.glyphicon-glass:before {
+  content: "\e001";
+}
+
+.glyphicon-music:before {
+  content: "\e002";
+}
+
+.glyphicon-search:before {
+  content: "\e003";
+}
+
+.glyphicon-heart:before {
+  content: "\e005";
+}
+
+.glyphicon-star:before {
+  content: "\e006";
+}
+
+.glyphicon-star-empty:before {
+  content: "\e007";
+}
+
+.glyphicon-user:before {
+  content: "\e008";
+}
+
+.glyphicon-film:before {
+  content: "\e009";
+}
+
+.glyphicon-th-large:before {
+  content: "\e010";
+}
+
+.glyphicon-th:before {
+  content: "\e011";
+}
+
+.glyphicon-th-list:before {
+  content: "\e012";
+}
+
+.glyphicon-ok:before {
+  content: "\e013";
+}
+
+.glyphicon-remove:before {
+  content: "\e014";
+}
+
+.glyphicon-zoom-in:before {
+  content: "\e015";
+}
+
+.glyphicon-zoom-out:before {
+  content: "\e016";
+}
+
+.glyphicon-off:before {
+  content: "\e017";
+}
+
+.glyphicon-signal:before {
+  content: "\e018";
+}
+
+.glyphicon-cog:before {
+  content: "\e019";
+}
+
+.glyphicon-trash:before {
+  content: "\e020";
+}
+
+.glyphicon-home:before {
+  content: "\e021";
+}
+
+.glyphicon-file:before {
+  content: "\e022";
+}
+
+.glyphicon-time:before {
+  content: "\e023";
+}
+
+.glyphicon-road:before {
+  content: "\e024";
+}
+
+.glyphicon-download-alt:before {
+  content: "\e025";
+}
+
+.glyphicon-download:before {
+  content: "\e026";
+}
+
+.glyphicon-upload:before {
+  content: "\e027";
+}
+
+.glyphicon-inbox:before {
+  content: "\e028";
+}
+
+.glyphicon-play-circle:before {
+  content: "\e029";
+}
+
+.glyphicon-repeat:before {
+  content: "\e030";
+}
+
+.glyphicon-refresh:before {
+  content: "\e031";
+}
+
+.glyphicon-list-alt:before {
+  content: "\e032";
+}
+
+.glyphicon-lock:before {
+  content: "\e033";
+}
+
+.glyphicon-flag:before {
+  content: "\e034";
+}
+
+.glyphicon-headphones:before {
+  content: "\e035";
+}
+
+.glyphicon-volume-off:before {
+  content: "\e036";
+}
+
+.glyphicon-volume-down:before {
+  content: "\e037";
+}
+
+.glyphicon-volume-up:before {
+  content: "\e038";
+}
+
+.glyphicon-qrcode:before {
+  content: "\e039";
+}
+
+.glyphicon-barcode:before {
+  content: "\e040";
+}
+
+.glyphicon-tag:before {
+  content: "\e041";
+}
+
+.glyphicon-tags:before {
+  content: "\e042";
+}
+
+.glyphicon-book:before {
+  content: "\e043";
+}
+
+.glyphicon-bookmark:before {
+  content: "\e044";
+}
+
+.glyphicon-print:before {
+  content: "\e045";
+}
+
+.glyphicon-camera:before {
+  content: "\e046";
+}
+
+.glyphicon-font:before {
+  content: "\e047";
+}
+
+.glyphicon-bold:before {
+  content: "\e048";
+}
+
+.glyphicon-italic:before {
+  content: "\e049";
+}
+
+.glyphicon-text-height:before {
+  content: "\e050";
+}
+
+.glyphicon-text-width:before {
+  content: "\e051";
+}
+
+.glyphicon-align-left:before {
+  content: "\e052";
+}
+
+.glyphicon-align-center:before {
+  content: "\e053";
+}
+
+.glyphicon-align-right:before {
+  content: "\e054";
+}
+
+.glyphicon-align-justify:before {
+  content: "\e055";
+}
+
+.glyphicon-list:before {
+  content: "\e056";
+}
+
+.glyphicon-indent-left:before {
+  content: "\e057";
+}
+
+.glyphicon-indent-right:before {
+  content: "\e058";
+}
+
+.glyphicon-facetime-video:before {
+  content: "\e059";
+}
+
+.glyphicon-picture:before {
+  content: "\e060";
+}
+
+.glyphicon-map-marker:before {
+  content: "\e062";
+}
+
+.glyphicon-adjust:before {
+  content: "\e063";
+}
+
+.glyphicon-tint:before {
+  content: "\e064";
+}
+
+.glyphicon-edit:before {
+  content: "\e065";
+}
+
+.glyphicon-share:before {
+  content: "\e066";
+}
+
+.glyphicon-check:before {
+  content: "\e067";
+}
+
+.glyphicon-move:before {
+  content: "\e068";
+}
+
+.glyphicon-step-backward:before {
+  content: "\e069";
+}
+
+.glyphicon-fast-backward:before {
+  content: "\e070";
+}
+
+.glyphicon-backward:before {
+  content: "\e071";
+}
+
+.glyphicon-play:before {
+  content: "\e072";
+}
+
+.glyphicon-pause:before {
+  content: "\e073";
+}
+
+.glyphicon-stop:before {
+  content: "\e074";
+}
+
+.glyphicon-forward:before {
+  content: "\e075";
+}
+
+.glyphicon-fast-forward:before {
+  content: "\e076";
+}
+
+.glyphicon-step-forward:before {
+  content: "\e077";
+}
+
+.glyphicon-eject:before {
+  content: "\e078";
+}
+
+.glyphicon-chevron-left:before {
+  content: "\e079";
+}
+
+.glyphicon-chevron-right:before {
+  content: "\e080";
+}
+
+.glyphicon-plus-sign:before {
+  content: "\e081";
+}
+
+.glyphicon-minus-sign:before {
+  content: "\e082";
+}
+
+.glyphicon-remove-sign:before {
+  content: "\e083";
+}
+
+.glyphicon-ok-sign:before {
+  content: "\e084";
+}
+
+.glyphicon-question-sign:before {
+  content: "\e085";
+}
+
+.glyphicon-info-sign:before {
+  content: "\e086";
+}
+
+.glyphicon-screenshot:before {
+  content: "\e087";
+}
+
+.glyphicon-remove-circle:before {
+  content: "\e088";
+}
+
+.glyphicon-ok-circle:before {
+  content: "\e089";
+}
+
+.glyphicon-ban-circle:before {
+  content: "\e090";
+}
+
+.glyphicon-arrow-left:before {
+  content: "\e091";
+}
+
+.glyphicon-arrow-right:before {
+  content: "\e092";
+}
+
+.glyphicon-arrow-up:before {
+  content: "\e093";
+}
+
+.glyphicon-arrow-down:before {
+  content: "\e094";
+}
+
+.glyphicon-share-alt:before {
+  content: "\e095";
+}
+
+.glyphicon-resize-full:before {
+  content: "\e096";
+}
+
+.glyphicon-resize-small:before {
+  content: "\e097";
+}
+
+.glyphicon-exclamation-sign:before {
+  content: "\e101";
+}
+
+.glyphicon-gift:before {
+  content: "\e102";
+}
+
+.glyphicon-leaf:before {
+  content: "\e103";
+}
+
+.glyphicon-fire:before {
+  content: "\e104";
+}
+
+.glyphicon-eye-open:before {
+  content: "\e105";
+}
+
+.glyphicon-eye-close:before {
+  content: "\e106";
+}
+
+.glyphicon-warning-sign:before {
+  content: "\e107";
+}
+
+.glyphicon-plane:before {
+  content: "\e108";
+}
+
+.glyphicon-calendar:before {
+  content: "\e109";
+}
+
+.glyphicon-random:before {
+  content: "\e110";
+}
+
+.glyphicon-comment:before {
+  content: "\e111";
+}
+
+.glyphicon-magnet:before {
+  content: "\e112";
+}
+
+.glyphicon-chevron-up:before {
+  content: "\e113";
+}
+
+.glyphicon-chevron-down:before {
+  content: "\e114";
+}
+
+.glyphicon-retweet:before {
+  content: "\e115";
+}
+
+.glyphicon-shopping-cart:before {
+  content: "\e116";
+}
+
+.glyphicon-folder-close:before {
+  content: "\e117";
+}
+
+.glyphicon-folder-open:before {
+  content: "\e118";
+}
+
+.glyphicon-resize-vertical:before {
+  content: "\e119";
+}
+
+.glyphicon-resize-horizontal:before {
+  content: "\e120";
+}
+
+.glyphicon-hdd:before {
+  content: "\e121";
+}
+
+.glyphicon-bullhorn:before {
+  content: "\e122";
+}
+
+.glyphicon-bell:before {
+  content: "\e123";
+}
+
+.glyphicon-certificate:before {
+  content: "\e124";
+}
+
+.glyphicon-thumbs-up:before {
+  content: "\e125";
+}
+
+.glyphicon-thumbs-down:before {
+  content: "\e126";
+}
+
+.glyphicon-hand-right:before {
+  content: "\e127";
+}
+
+.glyphicon-hand-left:before {
+  content: "\e128";
+}
+
+.glyphicon-hand-up:before {
+  content: "\e129";
+}
+
+.glyphicon-hand-down:before {
+  content: "\e130";
+}
+
+.glyphicon-circle-arrow-right:before {
+  content: "\e131";
+}
+
+.glyphicon-circle-arrow-left:before {
+  content: "\e132";
+}
+
+.glyphicon-circle-arrow-up:before {
+  content: "\e133";
+}
+
+.glyphicon-circle-arrow-down:before {
+  content: "\e134";
+}
+
+.glyphicon-globe:before {
+  content: "\e135";
+}
+
+.glyphicon-wrench:before {
+  content: "\e136";
+}
+
+.glyphicon-tasks:before {
+  content: "\e137";
+}
+
+.glyphicon-filter:before {
+  content: "\e138";
+}
+
+.glyphicon-briefcase:before {
+  content: "\e139";
+}
+
+.glyphicon-fullscreen:before {
+  content: "\e140";
+}
+
+.glyphicon-dashboard:before {
+  content: "\e141";
+}
+
+.glyphicon-paperclip:before {
+  content: "\e142";
+}
+
+.glyphicon-heart-empty:before {
+  content: "\e143";
+}
+
+.glyphicon-link:before {
+  content: "\e144";
+}
+
+.glyphicon-phone:before {
+  content: "\e145";
+}
+
+.glyphicon-pushpin:before {
+  content: "\e146";
+}
+
+.glyphicon-usd:before {
+  content: "\e148";
+}
+
+.glyphicon-gbp:before {
+  content: "\e149";
+}
+
+.glyphicon-sort:before {
+  content: "\e150";
+}
+
+.glyphicon-sort-by-alphabet:before {
+  content: "\e151";
+}
+
+.glyphicon-sort-by-alphabet-alt:before {
+  content: "\e152";
+}
+
+.glyphicon-sort-by-order:before {
+  content: "\e153";
+}
+
+.glyphicon-sort-by-order-alt:before {
+  content: "\e154";
+}
+
+.glyphicon-sort-by-attributes:before {
+  content: "\e155";
+}
+
+.glyphicon-sort-by-attributes-alt:before {
+  content: "\e156";
+}
+
+.glyphicon-unchecked:before {
+  content: "\e157";
+}
+
+.glyphicon-expand:before {
+  content: "\e158";
+}
+
+.glyphicon-collapse-down:before {
+  content: "\e159";
+}
+
+.glyphicon-collapse-up:before {
+  content: "\e160";
+}
+
+.glyphicon-log-in:before {
+  content: "\e161";
+}
+
+.glyphicon-flash:before {
+  content: "\e162";
+}
+
+.glyphicon-log-out:before {
+  content: "\e163";
+}
+
+.glyphicon-new-window:before {
+  content: "\e164";
+}
+
+.glyphicon-record:before {
+  content: "\e165";
+}
+
+.glyphicon-save:before {
+  content: "\e166";
+}
+
+.glyphicon-open:before {
+  content: "\e167";
+}
+
+.glyphicon-saved:before {
+  content: "\e168";
+}
+
+.glyphicon-import:before {
+  content: "\e169";
+}
+
+.glyphicon-export:before {
+  content: "\e170";
+}
+
+.glyphicon-send:before {
+  content: "\e171";
+}
+
+.glyphicon-floppy-disk:before {
+  content: "\e172";
+}
+
+.glyphicon-floppy-saved:before {
+  content: "\e173";
+}
+
+.glyphicon-floppy-remove:before {
+  content: "\e174";
+}
+
+.glyphicon-floppy-save:before {
+  content: "\e175";
+}
+
+.glyphicon-floppy-open:before {
+  content: "\e176";
+}
+
+.glyphicon-credit-card:before {
+  content: "\e177";
+}
+
+.glyphicon-transfer:before {
+  content: "\e178";
+}
+
+.glyphicon-cutlery:before {
+  content: "\e179";
+}
+
+.glyphicon-header:before {
+  content: "\e180";
+}
+
+.glyphicon-compressed:before {
+  content: "\e181";
+}
+
+.glyphicon-earphone:before {
+  content: "\e182";
+}
+
+.glyphicon-phone-alt:before {
+  content: "\e183";
+}
+
+.glyphicon-tower:before {
+  content: "\e184";
+}
+
+.glyphicon-stats:before {
+  content: "\e185";
+}
+
+.glyphicon-sd-video:before {
+  content: "\e186";
+}
+
+.glyphicon-hd-video:before {
+  content: "\e187";
+}
+
+.glyphicon-subtitles:before {
+  content: "\e188";
+}
+
+.glyphicon-sound-stereo:before {
+  content: "\e189";
+}
+
+.glyphicon-sound-dolby:before {
+  content: "\e190";
+}
+
+.glyphicon-sound-5-1:before {
+  content: "\e191";
+}
+
+.glyphicon-sound-6-1:before {
+  content: "\e192";
+}
+
+.glyphicon-sound-7-1:before {
+  content: "\e193";
+}
+
+.glyphicon-copyright-mark:before {
+  content: "\e194";
+}
+
+.glyphicon-registration-mark:before {
+  content: "\e195";
+}
+
+.glyphicon-cloud-download:before {
+  content: "\e197";
+}
+
+.glyphicon-cloud-upload:before {
+  content: "\e198";
+}
+
+.glyphicon-tree-conifer:before {
+  content: "\e199";
+}
+
+.glyphicon-tree-deciduous:before {
+  content: "\e200";
+}
+
+.caret {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  margin-left: 2px;
+  vertical-align: middle;
+  border-top: 4px solid #000000;
+  border-right: 4px solid transparent;
+  border-bottom: 0 dotted;
+  border-left: 4px solid transparent;
+}
+
+.dropdown {
+  position: relative;
+}
+
+.dropdown-toggle:focus {
+  outline: 0;
+}
+
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  display: none;
+  float: left;
+  min-width: 160px;
+  padding: 5px 0;
+  margin: 2px 0 0;
+  font-size: 14px;
+  list-style: none;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+  border-radius: 4px;
+  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+          box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+  background-clip: padding-box;
+}
+
+.dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.dropdown-menu .divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+
+.dropdown-menu > li > a {
+  display: block;
+  padding: 3px 20px;
+  clear: both;
+  font-weight: normal;
+  line-height: 1.428571429;
+  color: #333333;
+  white-space: nowrap;
+}
+
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  color: #262626;
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  color: #ffffff;
+  text-decoration: none;
+  background-color: #428bca;
+  outline: 0;
+}
+
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  color: #999999;
+}
+
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+  background-image: none;
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.open > .dropdown-menu {
+  display: block;
+}
+
+.open > a {
+  outline: 0;
+}
+
+.dropdown-header {
+  display: block;
+  padding: 3px 20px;
+  font-size: 12px;
+  line-height: 1.428571429;
+  color: #999999;
+}
+
+.dropdown-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 990;
+}
+
+.pull-right > .dropdown-menu {
+  right: 0;
+  left: auto;
+}
+
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+  border-top: 0 dotted;
+  border-bottom: 4px solid #000000;
+  content: "";
+}
+
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+  top: auto;
+  bottom: 100%;
+  margin-bottom: 1px;
+}
+
+@media (min-width: 768px) {
+  .navbar-right .dropdown-menu {
+    right: 0;
+    left: auto;
+  }
+}
+
+.btn-default .caret {
+  border-top-color: #333333;
+}
+
+.btn-primary .caret,
+.btn-success .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret {
+  border-top-color: #fff;
+}
+
+.dropup .btn-default .caret {
+  border-bottom-color: #333333;
+}
+
+.dropup .btn-primary .caret,
+.dropup .btn-success .caret,
+.dropup .btn-warning .caret,
+.dropup .btn-danger .caret,
+.dropup .btn-info .caret {
+  border-bottom-color: #fff;
+}
+
+.btn-group,
+.btn-group-vertical {
+  position: relative;
+  display: inline-block;
+  vertical-align: middle;
+}
+
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+  position: relative;
+  float: left;
+}
+
+.btn-group > .btn:hover,
+.btn-group-vertical > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus,
+.btn-group > .btn:active,
+.btn-group-vertical > .btn:active,
+.btn-group > .btn.active,
+.btn-group-vertical > .btn.active {
+  z-index: 2;
+}
+
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus {
+  outline: none;
+}
+
+.btn-group .btn + .btn,
+.btn-group .btn + .btn-group,
+.btn-group .btn-group + .btn,
+.btn-group .btn-group + .btn-group {
+  margin-left: -1px;
+}
+
+.btn-toolbar:before,
+.btn-toolbar:after {
+  display: table;
+  content: " ";
+}
+
+.btn-toolbar:after {
+  clear: both;
+}
+
+.btn-toolbar:before,
+.btn-toolbar:after {
+  display: table;
+  content: " ";
+}
+
+.btn-toolbar:after {
+  clear: both;
+}
+
+.btn-toolbar .btn-group {
+  float: left;
+}
+
+.btn-toolbar > .btn + .btn,
+.btn-toolbar > .btn-group + .btn,
+.btn-toolbar > .btn + .btn-group,
+.btn-toolbar > .btn-group + .btn-group {
+  margin-left: 5px;
+}
+
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+  border-radius: 0;
+}
+
+.btn-group > .btn:first-child {
+  margin-left: 0;
+}
+
+.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.btn-group > .btn:last-child:not(:first-child),
+.btn-group > .dropdown-toggle:not(:first-child) {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group > .btn-group {
+  float: left;
+}
+
+.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+
+.btn-group > .btn-group:first-child > .btn:last-child,
+.btn-group > .btn-group:first-child > .dropdown-toggle {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.btn-group > .btn-group:last-child > .btn:first-child {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+
+.btn-group-xs > .btn {
+  padding: 5px 10px;
+  padding: 1px 5px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+.btn-group-sm > .btn {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+.btn-group-lg > .btn {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+.btn-group > .btn + .dropdown-toggle {
+  padding-right: 8px;
+  padding-left: 8px;
+}
+
+.btn-group > .btn-lg + .dropdown-toggle {
+  padding-right: 12px;
+  padding-left: 12px;
+}
+
+.btn-group.open .dropdown-toggle {
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn-group.open .dropdown-toggle.btn-link {
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn .caret {
+  margin-left: 0;
+}
+
+.btn-lg .caret {
+  border-width: 5px 5px 0;
+  border-bottom-width: 0;
+}
+
+.dropup .btn-lg .caret {
+  border-width: 0 5px 5px;
+}
+
+.btn-group-vertical > .btn,
+.btn-group-vertical > .btn-group {
+  display: block;
+  float: none;
+  width: 100%;
+  max-width: 100%;
+}
+
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after {
+  display: table;
+  content: " ";
+}
+
+.btn-group-vertical > .btn-group:after {
+  clear: both;
+}
+
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after {
+  display: table;
+  content: " ";
+}
+
+.btn-group-vertical > .btn-group:after {
+  clear: both;
+}
+
+.btn-group-vertical > .btn-group > .btn {
+  float: none;
+}
+
+.btn-group-vertical > .btn + .btn,
+.btn-group-vertical > .btn + .btn-group,
+.btn-group-vertical > .btn-group + .btn,
+.btn-group-vertical > .btn-group + .btn-group {
+  margin-top: -1px;
+  margin-left: 0;
+}
+
+.btn-group-vertical > .btn:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+
+.btn-group-vertical > .btn:first-child:not(:last-child) {
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.btn-group-vertical > .btn:last-child:not(:first-child) {
+  border-top-right-radius: 0;
+  border-bottom-left-radius: 4px;
+  border-top-left-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:first-child > .btn:last-child,
+.btn-group-vertical > .btn-group:first-child > .dropdown-toggle {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:last-child > .btn:first-child {
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group-justified {
+  display: table;
+  width: 100%;
+  border-collapse: separate;
+  table-layout: fixed;
+}
+
+.btn-group-justified .btn {
+  display: table-cell;
+  float: none;
+  width: 1%;
+}
+
+[data-toggle="buttons"] > .btn > input[type="radio"],
+[data-toggle="buttons"] > .btn > input[type="checkbox"] {
+  display: none;
+}
+
+.input-group {
+  position: relative;
+  display: table;
+  border-collapse: separate;
+}
+
+.input-group.col {
+  float: none;
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.input-group .form-control {
+  width: 100%;
+  margin-bottom: 0;
+}
+
+.input-group-lg > .form-control,
+.input-group-lg > .input-group-addon,
+.input-group-lg > .input-group-btn > .btn {
+  height: 45px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+select.input-group-lg > .form-control,
+select.input-group-lg > .input-group-addon,
+select.input-group-lg > .input-group-btn > .btn {
+  height: 45px;
+  line-height: 45px;
+}
+
+textarea.input-group-lg > .form-control,
+textarea.input-group-lg > .input-group-addon,
+textarea.input-group-lg > .input-group-btn > .btn {
+  height: auto;
+}
+
+.input-group-sm > .form-control,
+.input-group-sm > .input-group-addon,
+.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+select.input-group-sm > .form-control,
+select.input-group-sm > .input-group-addon,
+select.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  line-height: 30px;
+}
+
+textarea.input-group-sm > .form-control,
+textarea.input-group-sm > .input-group-addon,
+textarea.input-group-sm > .input-group-btn > .btn {
+  height: auto;
+}
+
+.input-group-addon,
+.input-group-btn,
+.input-group .form-control {
+  display: table-cell;
+}
+
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child),
+.input-group .form-control:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+
+.input-group-addon,
+.input-group-btn {
+  width: 1%;
+  white-space: nowrap;
+  vertical-align: middle;
+}
+
+.input-group-addon {
+  padding: 6px 12px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1;
+  color: #555555;
+  text-align: center;
+  background-color: #eeeeee;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+}
+
+.input-group-addon.input-sm {
+  padding: 5px 10px;
+  font-size: 12px;
+  border-radius: 3px;
+}
+
+.input-group-addon.input-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  border-radius: 6px;
+}
+
+.input-group-addon input[type="radio"],
+.input-group-addon input[type="checkbox"] {
+  margin-top: 0;
+}
+
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .dropdown-toggle,
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.input-group-addon:first-child {
+  border-right: 0;
+}
+
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .dropdown-toggle,
+.input-group-btn:first-child > .btn:not(:first-child) {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.input-group-addon:last-child {
+  border-left: 0;
+}
+
+.input-group-btn {
+  position: relative;
+  white-space: nowrap;
+}
+
+.input-group-btn:first-child > .btn {
+  margin-right: -1px;
+}
+
+.input-group-btn:last-child > .btn {
+  margin-left: -1px;
+}
+
+.input-group-btn > .btn {
+  position: relative;
+}
+
+.input-group-btn > .btn + .btn {
+  margin-left: -4px;
+}
+
+.input-group-btn > .btn:hover,
+.input-group-btn > .btn:active {
+  z-index: 2;
+}
+
+.nav {
+  padding-left: 0;
+  margin-bottom: 0;
+  list-style: none;
+}
+
+.nav:before,
+.nav:after {
+  display: table;
+  content: " ";
+}
+
+.nav:after {
+  clear: both;
+}
+
+.nav:before,
+.nav:after {
+  display: table;
+  content: " ";
+}
+
+.nav:after {
+  clear: both;
+}
+
+.nav > li {
+  position: relative;
+  display: block;
+}
+
+.nav > li > a {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+}
+
+.nav > li > a:hover,
+.nav > li > a:focus {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+
+.nav > li.disabled > a {
+  color: #999999;
+}
+
+.nav > li.disabled > a:hover,
+.nav > li.disabled > a:focus {
+  color: #999999;
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+}
+
+.nav .open > a,
+.nav .open > a:hover,
+.nav .open > a:focus {
+  background-color: #eeeeee;
+  border-color: #428bca;
+}
+
+.nav .open > a .caret,
+.nav .open > a:hover .caret,
+.nav .open > a:focus .caret {
+  border-top-color: #2a6496;
+  border-bottom-color: #2a6496;
+}
+
+.nav .nav-divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+
+.nav > li > a > img {
+  max-width: none;
+}
+
+.nav-tabs {
+  border-bottom: 1px solid #dddddd;
+}
+
+.nav-tabs > li {
+  float: left;
+  margin-bottom: -1px;
+}
+
+.nav-tabs > li > a {
+  margin-right: 2px;
+  line-height: 1.428571429;
+  border: 1px solid transparent;
+  border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs > li > a:hover {
+  border-color: #eeeeee #eeeeee #dddddd;
+}
+
+.nav-tabs > li.active > a,
+.nav-tabs > li.active > a:hover,
+.nav-tabs > li.active > a:focus {
+  color: #555555;
+  cursor: default;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-bottom-color: transparent;
+}
+
+.nav-tabs.nav-justified {
+  width: 100%;
+  border-bottom: 0;
+}
+
+.nav-tabs.nav-justified > li {
+  float: none;
+}
+
+.nav-tabs.nav-justified > li > a {
+  margin-bottom: 5px;
+  text-align: center;
+}
+
+.nav-tabs.nav-justified > .dropdown .dropdown-menu {
+  top: auto;
+  left: auto;
+}
+
+@media (min-width: 768px) {
+  .nav-tabs.nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+  .nav-tabs.nav-justified > li > a {
+    margin-bottom: 0;
+  }
+}
+
+.nav-tabs.nav-justified > li > a {
+  margin-right: 0;
+  border-radius: 4px;
+}
+
+.nav-tabs.nav-justified > .active > a,
+.nav-tabs.nav-justified > .active > a:hover,
+.nav-tabs.nav-justified > .active > a:focus {
+  border: 1px solid #dddddd;
+}
+
+@media (min-width: 768px) {
+  .nav-tabs.nav-justified > li > a {
+    border-bottom: 1px solid #dddddd;
+    border-radius: 4px 4px 0 0;
+  }
+  .nav-tabs.nav-justified > .active > a,
+  .nav-tabs.nav-justified > .active > a:hover,
+  .nav-tabs.nav-justified > .active > a:focus {
+    border-bottom-color: #ffffff;
+  }
+}
+
+.nav-pills > li {
+  float: left;
+}
+
+.nav-pills > li > a {
+  border-radius: 4px;
+}
+
+.nav-pills > li + li {
+  margin-left: 2px;
+}
+
+.nav-pills > li.active > a,
+.nav-pills > li.active > a:hover,
+.nav-pills > li.active > a:focus {
+  color: #ffffff;
+  background-color: #428bca;
+}
+
+.nav-pills > li.active > a .caret,
+.nav-pills > li.active > a:hover .caret,
+.nav-pills > li.active > a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.nav-stacked > li {
+  float: none;
+}
+
+.nav-stacked > li + li {
+  margin-top: 2px;
+  margin-left: 0;
+}
+
+.nav-justified {
+  width: 100%;
+}
+
+.nav-justified > li {
+  float: none;
+}
+
+.nav-justified > li > a {
+  margin-bottom: 5px;
+  text-align: center;
+}
+
+.nav-justified > .dropdown .dropdown-menu {
+  top: auto;
+  left: auto;
+}
+
+@media (min-width: 768px) {
+  .nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+  .nav-justified > li > a {
+    margin-bottom: 0;
+  }
+}
+
+.nav-tabs-justified {
+  border-bottom: 0;
+}
+
+.nav-tabs-justified > li > a {
+  margin-right: 0;
+  border-radius: 4px;
+}
+
+.nav-tabs-justified > .active > a,
+.nav-tabs-justified > .active > a:hover,
+.nav-tabs-justified > .active > a:focus {
+  border: 1px solid #dddddd;
+}
+
+@media (min-width: 768px) {
+  .nav-tabs-justified > li > a {
+    border-bottom: 1px solid #dddddd;
+    border-radius: 4px 4px 0 0;
+  }
+  .nav-tabs-justified > .active > a,
+  .nav-tabs-justified > .active > a:hover,
+  .nav-tabs-justified > .active > a:focus {
+    border-bottom-color: #ffffff;
+  }
+}
+
+.tab-content > .tab-pane {
+  display: none;
+}
+
+.tab-content > .active {
+  display: block;
+}
+
+.nav .caret {
+  border-top-color: #428bca;
+  border-bottom-color: #428bca;
+}
+
+.nav a:hover .caret {
+  border-top-color: #2a6496;
+  border-bottom-color: #2a6496;
+}
+
+.nav-tabs .dropdown-menu {
+  margin-top: -1px;
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.navbar {
+  position: relative;
+  min-height: 50px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+}
+
+.navbar:before,
+.navbar:after {
+  display: table;
+  content: " ";
+}
+
+.navbar:after {
+  clear: both;
+}
+
+.navbar:before,
+.navbar:after {
+  display: table;
+  content: " ";
+}
+
+.navbar:after {
+  clear: both;
+}
+
+@media (min-width: 768px) {
+  .navbar {
+    border-radius: 4px;
+  }
+}
+
+.navbar-header:before,
+.navbar-header:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-header:after {
+  clear: both;
+}
+
+.navbar-header:before,
+.navbar-header:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-header:after {
+  clear: both;
+}
+
+@media (min-width: 768px) {
+  .navbar-header {
+    float: left;
+  }
+}
+
+.navbar-collapse {
+  max-height: 340px;
+  padding-right: 15px;
+  padding-left: 15px;
+  overflow-x: visible;
+  border-top: 1px solid transparent;
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  -webkit-overflow-scrolling: touch;
+}
+
+.navbar-collapse:before,
+.navbar-collapse:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-collapse:after {
+  clear: both;
+}
+
+.navbar-collapse:before,
+.navbar-collapse:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-collapse:after {
+  clear: both;
+}
+
+.navbar-collapse.in {
+  overflow-y: auto;
+}
+
+@media (min-width: 768px) {
+  .navbar-collapse {
+    width: auto;
+    border-top: 0;
+    box-shadow: none;
+  }
+  .navbar-collapse.collapse {
+    display: block !important;
+    height: auto !important;
+    padding-bottom: 0;
+    overflow: visible !important;
+  }
+  .navbar-collapse.in {
+    overflow-y: auto;
+  }
+  .navbar-collapse .navbar-nav.navbar-left:first-child {
+    margin-left: -15px;
+  }
+  .navbar-collapse .navbar-nav.navbar-right:last-child {
+    margin-right: -15px;
+  }
+  .navbar-collapse .navbar-text:last-child {
+    margin-right: 0;
+  }
+}
+
+.container > .navbar-header,
+.container > .navbar-collapse {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+@media (min-width: 768px) {
+  .container > .navbar-header,
+  .container > .navbar-collapse {
+    margin-right: 0;
+    margin-left: 0;
+  }
+}
+
+.navbar-static-top {
+  z-index: 1000;
+  border-width: 0 0 1px;
+}
+
+@media (min-width: 768px) {
+  .navbar-static-top {
+    border-radius: 0;
+  }
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+}
+
+@media (min-width: 768px) {
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    border-radius: 0;
+  }
+}
+
+.navbar-fixed-top {
+  top: 0;
+  border-width: 0 0 1px;
+}
+
+.navbar-fixed-bottom {
+  bottom: 0;
+  margin-bottom: 0;
+  border-width: 1px 0 0;
+}
+
+.navbar-brand {
+  float: left;
+  padding: 15px 15px;
+  font-size: 18px;
+  line-height: 20px;
+}
+
+.navbar-brand:hover,
+.navbar-brand:focus {
+  text-decoration: none;
+}
+
+@media (min-width: 768px) {
+  .navbar > .container .navbar-brand {
+    margin-left: -15px;
+  }
+}
+
+.navbar-toggle {
+  position: relative;
+  float: right;
+  padding: 9px 10px;
+  margin-top: 8px;
+  margin-right: 15px;
+  margin-bottom: 8px;
+  background-color: transparent;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+
+.navbar-toggle .icon-bar {
+  display: block;
+  width: 22px;
+  height: 2px;
+  border-radius: 1px;
+}
+
+.navbar-toggle .icon-bar + .icon-bar {
+  margin-top: 4px;
+}
+
+@media (min-width: 768px) {
+  .navbar-toggle {
+    display: none;
+  }
+}
+
+.navbar-nav {
+  margin: 7.5px -15px;
+}
+
+.navbar-nav > li > a {
+  padding-top: 10px;
+  padding-bottom: 10px;
+  line-height: 20px;
+}
+
+@media (max-width: 767px) {
+  .navbar-nav .open .dropdown-menu {
+    position: static;
+    float: none;
+    width: auto;
+    margin-top: 0;
+    background-color: transparent;
+    border: 0;
+    box-shadow: none;
+  }
+  .navbar-nav .open .dropdown-menu > li > a,
+  .navbar-nav .open .dropdown-menu .dropdown-header {
+    padding: 5px 15px 5px 25px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a {
+    line-height: 20px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-nav .open .dropdown-menu > li > a:focus {
+    background-image: none;
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar-nav {
+    float: left;
+    margin: 0;
+  }
+  .navbar-nav > li {
+    float: left;
+  }
+  .navbar-nav > li > a {
+    padding-top: 15px;
+    padding-bottom: 15px;
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar-left {
+    float: left !important;
+  }
+  .navbar-right {
+    float: right !important;
+  }
+}
+
+.navbar-form {
+  padding: 10px 15px;
+  margin-top: 8px;
+  margin-right: -15px;
+  margin-bottom: 8px;
+  margin-left: -15px;
+  border-top: 1px solid transparent;
+  border-bottom: 1px solid transparent;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+}
+
+@media (min-width: 768px) {
+  .navbar-form .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .navbar-form .form-control {
+    display: inline-block;
+  }
+  .navbar-form .radio,
+  .navbar-form .checkbox {
+    display: inline-block;
+    padding-left: 0;
+    margin-top: 0;
+    margin-bottom: 0;
+  }
+  .navbar-form .radio input[type="radio"],
+  .navbar-form .checkbox input[type="checkbox"] {
+    float: none;
+    margin-left: 0;
+  }
+}
+
+@media (max-width: 767px) {
+  .navbar-form .form-group {
+    margin-bottom: 5px;
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar-form {
+    width: auto;
+    padding-top: 0;
+    padding-bottom: 0;
+    margin-right: 0;
+    margin-left: 0;
+    border: 0;
+    -webkit-box-shadow: none;
+            box-shadow: none;
+  }
+}
+
+.navbar-nav > li > .dropdown-menu {
+  margin-top: 0;
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.navbar-nav.pull-right > li > .dropdown-menu,
+.navbar-nav > li > .dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.navbar-btn {
+  margin-top: 8px;
+  margin-bottom: 8px;
+}
+
+.navbar-text {
+  float: left;
+  margin-top: 15px;
+  margin-bottom: 15px;
+}
+
+@media (min-width: 768px) {
+  .navbar-text {
+    margin-right: 15px;
+    margin-left: 15px;
+  }
+}
+
+.navbar-default {
+  background-color: #f8f8f8;
+  border-color: #e7e7e7;
+}
+
+.navbar-default .navbar-brand {
+  color: #777777;
+}
+
+.navbar-default .navbar-brand:hover,
+.navbar-default .navbar-brand:focus {
+  color: #5e5e5e;
+  background-color: transparent;
+}
+
+.navbar-default .navbar-text {
+  color: #777777;
+}
+
+.navbar-default .navbar-nav > li > a {
+  color: #777777;
+}
+
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+  color: #333333;
+  background-color: transparent;
+}
+
+.navbar-default .navbar-nav > .active > a,
+.navbar-default .navbar-nav > .active > a:hover,
+.navbar-default .navbar-nav > .active > a:focus {
+  color: #555555;
+  background-color: #e7e7e7;
+}
+
+.navbar-default .navbar-nav > .disabled > a,
+.navbar-default .navbar-nav > .disabled > a:hover,
+.navbar-default .navbar-nav > .disabled > a:focus {
+  color: #cccccc;
+  background-color: transparent;
+}
+
+.navbar-default .navbar-toggle {
+  border-color: #dddddd;
+}
+
+.navbar-default .navbar-toggle:hover,
+.navbar-default .navbar-toggle:focus {
+  background-color: #dddddd;
+}
+
+.navbar-default .navbar-toggle .icon-bar {
+  background-color: #cccccc;
+}
+
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+  border-color: #e7e7e7;
+}
+
+.navbar-default .navbar-nav > .dropdown > a:hover .caret,
+.navbar-default .navbar-nav > .dropdown > a:focus .caret {
+  border-top-color: #333333;
+  border-bottom-color: #333333;
+}
+
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .open > a:hover,
+.navbar-default .navbar-nav > .open > a:focus {
+  color: #555555;
+  background-color: #e7e7e7;
+}
+
+.navbar-default .navbar-nav > .open > a .caret,
+.navbar-default .navbar-nav > .open > a:hover .caret,
+.navbar-default .navbar-nav > .open > a:focus .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+
+.navbar-default .navbar-nav > .dropdown > a .caret {
+  border-top-color: #777777;
+  border-bottom-color: #777777;
+}
+
+@media (max-width: 767px) {
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a {
+    color: #777777;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #333333;
+    background-color: transparent;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #555555;
+    background-color: #e7e7e7;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #cccccc;
+    background-color: transparent;
+  }
+}
+
+.navbar-default .navbar-link {
+  color: #777777;
+}
+
+.navbar-default .navbar-link:hover {
+  color: #333333;
+}
+
+.navbar-inverse {
+  background-color: #222222;
+  border-color: #080808;
+}
+
+.navbar-inverse .navbar-brand {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-brand:hover,
+.navbar-inverse .navbar-brand:focus {
+  color: #ffffff;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-text {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-nav > li > a {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-nav > li > a:hover,
+.navbar-inverse .navbar-nav > li > a:focus {
+  color: #ffffff;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-nav > .active > a,
+.navbar-inverse .navbar-nav > .active > a:hover,
+.navbar-inverse .navbar-nav > .active > a:focus {
+  color: #ffffff;
+  background-color: #080808;
+}
+
+.navbar-inverse .navbar-nav > .disabled > a,
+.navbar-inverse .navbar-nav > .disabled > a:hover,
+.navbar-inverse .navbar-nav > .disabled > a:focus {
+  color: #444444;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-toggle {
+  border-color: #333333;
+}
+
+.navbar-inverse .navbar-toggle:hover,
+.navbar-inverse .navbar-toggle:focus {
+  background-color: #333333;
+}
+
+.navbar-inverse .navbar-toggle .icon-bar {
+  background-color: #ffffff;
+}
+
+.navbar-inverse .navbar-collapse,
+.navbar-inverse .navbar-form {
+  border-color: #101010;
+}
+
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .open > a:hover,
+.navbar-inverse .navbar-nav > .open > a:focus {
+  color: #ffffff;
+  background-color: #080808;
+}
+
+.navbar-inverse .navbar-nav > .dropdown > a:hover .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .navbar-nav > .dropdown > a .caret {
+  border-top-color: #999999;
+  border-bottom-color: #999999;
+}
+
+.navbar-inverse .navbar-nav > .open > a .caret,
+.navbar-inverse .navbar-nav > .open > a:hover .caret,
+.navbar-inverse .navbar-nav > .open > a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+@media (max-width: 767px) {
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
+    border-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
+    color: #999999;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #ffffff;
+    background-color: transparent;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #ffffff;
+    background-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #444444;
+    background-color: transparent;
+  }
+}
+
+.navbar-inverse .navbar-link {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-link:hover {
+  color: #ffffff;
+}
+
+.breadcrumb {
+  padding: 8px 15px;
+  margin-bottom: 20px;
+  list-style: none;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+}
+
+.breadcrumb > li {
+  display: inline-block;
+}
+
+.breadcrumb > li + li:before {
+  padding: 0 5px;
+  color: #cccccc;
+  content: "/\00a0";
+}
+
+.breadcrumb > .active {
+  color: #999999;
+}
+
+.pagination {
+  display: inline-block;
+  padding-left: 0;
+  margin: 20px 0;
+  border-radius: 4px;
+}
+
+.pagination > li {
+  display: inline;
+}
+
+.pagination > li > a,
+.pagination > li > span {
+  position: relative;
+  float: left;
+  padding: 6px 12px;
+  margin-left: -1px;
+  line-height: 1.428571429;
+  text-decoration: none;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+}
+
+.pagination > li:first-child > a,
+.pagination > li:first-child > span {
+  margin-left: 0;
+  border-bottom-left-radius: 4px;
+  border-top-left-radius: 4px;
+}
+
+.pagination > li:last-child > a,
+.pagination > li:last-child > span {
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 4px;
+}
+
+.pagination > li > a:hover,
+.pagination > li > span:hover,
+.pagination > li > a:focus,
+.pagination > li > span:focus {
+  background-color: #eeeeee;
+}
+
+.pagination > .active > a,
+.pagination > .active > span,
+.pagination > .active > a:hover,
+.pagination > .active > span:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span:focus {
+  z-index: 2;
+  color: #ffffff;
+  cursor: default;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+.pagination > .disabled > span,
+.pagination > .disabled > span:hover,
+.pagination > .disabled > span:focus,
+.pagination > .disabled > a,
+.pagination > .disabled > a:hover,
+.pagination > .disabled > a:focus {
+  color: #999999;
+  cursor: not-allowed;
+  background-color: #ffffff;
+  border-color: #dddddd;
+}
+
+.pagination-lg > li > a,
+.pagination-lg > li > span {
+  padding: 10px 16px;
+  font-size: 18px;
+}
+
+.pagination-lg > li:first-child > a,
+.pagination-lg > li:first-child > span {
+  border-bottom-left-radius: 6px;
+  border-top-left-radius: 6px;
+}
+
+.pagination-lg > li:last-child > a,
+.pagination-lg > li:last-child > span {
+  border-top-right-radius: 6px;
+  border-bottom-right-radius: 6px;
+}
+
+.pagination-sm > li > a,
+.pagination-sm > li > span {
+  padding: 5px 10px;
+  font-size: 12px;
+}
+
+.pagination-sm > li:first-child > a,
+.pagination-sm > li:first-child > span {
+  border-bottom-left-radius: 3px;
+  border-top-left-radius: 3px;
+}
+
+.pagination-sm > li:last-child > a,
+.pagination-sm > li:last-child > span {
+  border-top-right-radius: 3px;
+  border-bottom-right-radius: 3px;
+}
+
+.pager {
+  padding-left: 0;
+  margin: 20px 0;
+  text-align: center;
+  list-style: none;
+}
+
+.pager:before,
+.pager:after {
+  display: table;
+  content: " ";
+}
+
+.pager:after {
+  clear: both;
+}
+
+.pager:before,
+.pager:after {
+  display: table;
+  content: " ";
+}
+
+.pager:after {
+  clear: both;
+}
+
+.pager li {
+  display: inline;
+}
+
+.pager li > a,
+.pager li > span {
+  display: inline-block;
+  padding: 5px 14px;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 15px;
+}
+
+.pager li > a:hover,
+.pager li > a:focus {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+
+.pager .next > a,
+.pager .next > span {
+  float: right;
+}
+
+.pager .previous > a,
+.pager .previous > span {
+  float: left;
+}
+
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+  color: #999999;
+  cursor: not-allowed;
+  background-color: #ffffff;
+}
+
+.label {
+  display: inline;
+  padding: .2em .6em .3em;
+  font-size: 75%;
+  font-weight: bold;
+  line-height: 1;
+  color: #ffffff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  border-radius: .25em;
+}
+
+.label[href]:hover,
+.label[href]:focus {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.label:empty {
+  display: none;
+}
+
+.label-default {
+  background-color: #999999;
+}
+
+.label-default[href]:hover,
+.label-default[href]:focus {
+  background-color: #808080;
+}
+
+.label-primary {
+  background-color: #428bca;
+}
+
+.label-primary[href]:hover,
+.label-primary[href]:focus {
+  background-color: #3071a9;
+}
+
+.label-success {
+  background-color: #5cb85c;
+}
+
+.label-success[href]:hover,
+.label-success[href]:focus {
+  background-color: #449d44;
+}
+
+.label-info {
+  background-color: #5bc0de;
+}
+
+.label-info[href]:hover,
+.label-info[href]:focus {
+  background-color: #31b0d5;
+}
+
+.label-warning {
+  background-color: #f0ad4e;
+}
+
+.label-warning[href]:hover,
+.label-warning[href]:focus {
+  background-color: #ec971f;
+}
+
+.label-danger {
+  background-color: #d9534f;
+}
+
+.label-danger[href]:hover,
+.label-danger[href]:focus {
+  background-color: #c9302c;
+}
+
+.badge {
+  display: inline-block;
+  min-width: 10px;
+  padding: 3px 7px;
+  font-size: 12px;
+  font-weight: bold;
+  line-height: 1;
+  color: #ffffff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  background-color: #999999;
+  border-radius: 10px;
+}
+
+.badge:empty {
+  display: none;
+}
+
+a.badge:hover,
+a.badge:focus {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.btn .badge {
+  position: relative;
+  top: -1px;
+}
+
+a.list-group-item.active > .badge,
+.nav-pills > .active > a > .badge {
+  color: #428bca;
+  background-color: #ffffff;
+}
+
+.nav-pills > li > a > .badge {
+  margin-left: 3px;
+}
+
+.jumbotron {
+  padding: 30px;
+  margin-bottom: 30px;
+  font-size: 21px;
+  font-weight: 200;
+  line-height: 2.1428571435;
+  color: inherit;
+  background-color: #eeeeee;
+}
+
+.jumbotron h1 {
+  line-height: 1;
+  color: inherit;
+}
+
+.jumbotron p {
+  line-height: 1.4;
+}
+
+.container .jumbotron {
+  border-radius: 6px;
+}
+
+@media screen and (min-width: 768px) {
+  .jumbotron {
+    padding-top: 48px;
+    padding-bottom: 48px;
+  }
+  .container .jumbotron {
+    padding-right: 60px;
+    padding-left: 60px;
+  }
+  .jumbotron h1 {
+    font-size: 63px;
+  }
+}
+
+.thumbnail {
+  display: inline-block;
+  display: block;
+  height: auto;
+  max-width: 100%;
+  padding: 4px;
+  margin-bottom: 20px;
+  line-height: 1.428571429;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
+  -webkit-transition: all 0.2s ease-in-out;
+          transition: all 0.2s ease-in-out;
+}
+
+.thumbnail > img {
+  display: block;
+  height: auto;
+  max-width: 100%;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+a.thumbnail:hover,
+a.thumbnail:focus,
+a.thumbnail.active {
+  border-color: #428bca;
+}
+
+.thumbnail .caption {
+  padding: 9px;
+  color: #333333;
+}
+
+.alert {
+  padding: 15px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+
+.alert h4 {
+  margin-top: 0;
+  color: inherit;
+}
+
+.alert .alert-link {
+  font-weight: bold;
+}
+
+.alert > p,
+.alert > ul {
+  margin-bottom: 0;
+}
+
+.alert > p + p {
+  margin-top: 5px;
+}
+
+.alert-dismissable {
+  padding-right: 35px;
+}
+
+.alert-dismissable .close {
+  position: relative;
+  top: -2px;
+  right: -21px;
+  color: inherit;
+}
+
+.alert-success {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.alert-success hr {
+  border-top-color: #c9e2b3;
+}
+
+.alert-success .alert-link {
+  color: #356635;
+}
+
+.alert-info {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+
+.alert-info hr {
+  border-top-color: #a6e1ec;
+}
+
+.alert-info .alert-link {
+  color: #2d6987;
+}
+
+.alert-warning {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #faebcc;
+}
+
+.alert-warning hr {
+  border-top-color: #f7e1b5;
+}
+
+.alert-warning .alert-link {
+  color: #a47e3c;
+}
+
+.alert-danger {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #ebccd1;
+}
+
+.alert-danger hr {
+  border-top-color: #e4b9c0;
+}
+
+.alert-danger .alert-link {
+  color: #953b39;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-moz-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-o-keyframes progress-bar-stripes {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 40px 0;
+  }
+}
+
+@keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+.progress {
+  height: 20px;
+  margin-bottom: 20px;
+  overflow: hidden;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.progress-bar {
+  float: left;
+  width: 0;
+  height: 100%;
+  font-size: 12px;
+  line-height: 20px;
+  color: #ffffff;
+  text-align: center;
+  background-color: #428bca;
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-transition: width 0.6s ease;
+          transition: width 0.6s ease;
+}
+
+.progress-striped .progress-bar {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-size: 40px 40px;
+}
+
+.progress.active .progress-bar {
+  -webkit-animation: progress-bar-stripes 2s linear infinite;
+          animation: progress-bar-stripes 2s linear infinite;
+}
+
+.progress-bar-success {
+  background-color: #5cb85c;
+}
+
+.progress-striped .progress-bar-success {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-info {
+  background-color: #5bc0de;
+}
+
+.progress-striped .progress-bar-info {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-warning {
+  background-color: #f0ad4e;
+}
+
+.progress-striped .progress-bar-warning {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-danger {
+  background-color: #d9534f;
+}
+
+.progress-striped .progress-bar-danger {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.media,
+.media-body {
+  overflow: hidden;
+  zoom: 1;
+}
+
+.media,
+.media .media {
+  margin-top: 15px;
+}
+
+.media:first-child {
+  margin-top: 0;
+}
+
+.media-object {
+  display: block;
+}
+
+.media-heading {
+  margin: 0 0 5px;
+}
+
+.media > .pull-left {
+  margin-right: 10px;
+}
+
+.media > .pull-right {
+  margin-left: 10px;
+}
+
+.media-list {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-group {
+  padding-left: 0;
+  margin-bottom: 20px;
+}
+
+.list-group-item {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+  margin-bottom: -1px;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+}
+
+.list-group-item:first-child {
+  border-top-right-radius: 4px;
+  border-top-left-radius: 4px;
+}
+
+.list-group-item:last-child {
+  margin-bottom: 0;
+  border-bottom-right-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+
+.list-group-item > .badge {
+  float: right;
+}
+
+.list-group-item > .badge + .badge {
+  margin-right: 5px;
+}
+
+a.list-group-item {
+  color: #555555;
+}
+
+a.list-group-item .list-group-item-heading {
+  color: #333333;
+}
+
+a.list-group-item:hover,
+a.list-group-item:focus {
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+
+a.list-group-item.active,
+a.list-group-item.active:hover,
+a.list-group-item.active:focus {
+  z-index: 2;
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+a.list-group-item.active .list-group-item-heading,
+a.list-group-item.active:hover .list-group-item-heading,
+a.list-group-item.active:focus .list-group-item-heading {
+  color: inherit;
+}
+
+a.list-group-item.active .list-group-item-text,
+a.list-group-item.active:hover .list-group-item-text,
+a.list-group-item.active:focus .list-group-item-text {
+  color: #e1edf7;
+}
+
+.list-group-item-heading {
+  margin-top: 0;
+  margin-bottom: 5px;
+}
+
+.list-group-item-text {
+  margin-bottom: 0;
+  line-height: 1.3;
+}
+
+.panel {
+  margin-bottom: 20px;
+  background-color: #ffffff;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.panel-body {
+  padding: 15px;
+}
+
+.panel-body:before,
+.panel-body:after {
+  display: table;
+  content: " ";
+}
+
+.panel-body:after {
+  clear: both;
+}
+
+.panel-body:before,
+.panel-body:after {
+  display: table;
+  content: " ";
+}
+
+.panel-body:after {
+  clear: both;
+}
+
+.panel > .list-group {
+  margin-bottom: 0;
+}
+
+.panel > .list-group .list-group-item {
+  border-width: 1px 0;
+}
+
+.panel > .list-group .list-group-item:first-child {
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.panel > .list-group .list-group-item:last-child {
+  border-bottom: 0;
+}
+
+.panel-heading + .list-group .list-group-item:first-child {
+  border-top-width: 0;
+}
+
+.panel > .table,
+.panel > .table-responsive {
+  margin-bottom: 0;
+}
+
+.panel > .panel-body + .table,
+.panel > .panel-body + .table-responsive {
+  border-top: 1px solid #dddddd;
+}
+
+.panel > .table-bordered,
+.panel > .table-responsive > .table-bordered {
+  border: 0;
+}
+
+.panel > .table-bordered > thead > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,
+.panel > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-bordered > thead > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,
+.panel > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-bordered > tfoot > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+  border-left: 0;
+}
+
+.panel > .table-bordered > thead > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,
+.panel > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-bordered > thead > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,
+.panel > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-bordered > tfoot > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+  border-right: 0;
+}
+
+.panel > .table-bordered > thead > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > thead > tr:last-child > th,
+.panel > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-bordered > tfoot > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th,
+.panel > .table-bordered > thead > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > thead > tr:last-child > td,
+.panel > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td {
+  border-bottom: 0;
+}
+
+.panel-heading {
+  padding: 10px 15px;
+  border-bottom: 1px solid transparent;
+  border-top-right-radius: 3px;
+  border-top-left-radius: 3px;
+}
+
+.panel-heading > .dropdown .dropdown-toggle {
+  color: inherit;
+}
+
+.panel-title {
+  margin-top: 0;
+  margin-bottom: 0;
+  font-size: 16px;
+}
+
+.panel-title > a {
+  color: inherit;
+}
+
+.panel-footer {
+  padding: 10px 15px;
+  background-color: #f5f5f5;
+  border-top: 1px solid #dddddd;
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+
+.panel-group .panel {
+  margin-bottom: 0;
+  overflow: hidden;
+  border-radius: 4px;
+}
+
+.panel-group .panel + .panel {
+  margin-top: 5px;
+}
+
+.panel-group .panel-heading {
+  border-bottom: 0;
+}
+
+.panel-group .panel-heading + .panel-collapse .panel-body {
+  border-top: 1px solid #dddddd;
+}
+
+.panel-group .panel-footer {
+  border-top: 0;
+}
+
+.panel-group .panel-footer + .panel-collapse .panel-body {
+  border-bottom: 1px solid #dddddd;
+}
+
+.panel-default {
+  border-color: #dddddd;
+}
+
+.panel-default > .panel-heading {
+  color: #333333;
+  background-color: #f5f5f5;
+  border-color: #dddddd;
+}
+
+.panel-default > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #dddddd;
+}
+
+.panel-default > .panel-heading > .dropdown .caret {
+  border-color: #333333 transparent;
+}
+
+.panel-default > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #dddddd;
+}
+
+.panel-primary {
+  border-color: #428bca;
+}
+
+.panel-primary > .panel-heading {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+.panel-primary > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #428bca;
+}
+
+.panel-primary > .panel-heading > .dropdown .caret {
+  border-color: #ffffff transparent;
+}
+
+.panel-primary > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #428bca;
+}
+
+.panel-success {
+  border-color: #d6e9c6;
+}
+
+.panel-success > .panel-heading {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.panel-success > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #d6e9c6;
+}
+
+.panel-success > .panel-heading > .dropdown .caret {
+  border-color: #468847 transparent;
+}
+
+.panel-success > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #d6e9c6;
+}
+
+.panel-warning {
+  border-color: #faebcc;
+}
+
+.panel-warning > .panel-heading {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #faebcc;
+}
+
+.panel-warning > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #faebcc;
+}
+
+.panel-warning > .panel-heading > .dropdown .caret {
+  border-color: #c09853 transparent;
+}
+
+.panel-warning > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #faebcc;
+}
+
+.panel-danger {
+  border-color: #ebccd1;
+}
+
+.panel-danger > .panel-heading {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #ebccd1;
+}
+
+.panel-danger > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #ebccd1;
+}
+
+.panel-danger > .panel-heading > .dropdown .caret {
+  border-color: #b94a48 transparent;
+}
+
+.panel-danger > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #ebccd1;
+}
+
+.panel-info {
+  border-color: #bce8f1;
+}
+
+.panel-info > .panel-heading {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+
+.panel-info > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #bce8f1;
+}
+
+.panel-info > .panel-heading > .dropdown .caret {
+  border-color: #3a87ad transparent;
+}
+
+.panel-info > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #bce8f1;
+}
+
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.well blockquote {
+  border-color: #ddd;
+  border-color: rgba(0, 0, 0, 0.15);
+}
+
+.well-lg {
+  padding: 24px;
+  border-radius: 6px;
+}
+
+.well-sm {
+  padding: 9px;
+  border-radius: 3px;
+}
+
+.close {
+  float: right;
+  font-size: 21px;
+  font-weight: bold;
+  line-height: 1;
+  color: #000000;
+  text-shadow: 0 1px 0 #ffffff;
+  opacity: 0.2;
+  filter: alpha(opacity=20);
+}
+
+.close:hover,
+.close:focus {
+  color: #000000;
+  text-decoration: none;
+  cursor: pointer;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+button.close {
+  padding: 0;
+  cursor: pointer;
+  background: transparent;
+  border: 0;
+  -webkit-appearance: none;
+}
+
+.modal-open {
+  overflow: hidden;
+}
+
+.modal {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1040;
+  display: none;
+  overflow: auto;
+  overflow-y: scroll;
+}
+
+.modal.fade .modal-dialog {
+  -webkit-transform: translate(0, -25%);
+      -ms-transform: translate(0, -25%);
+          transform: translate(0, -25%);
+  -webkit-transition: -webkit-transform 0.3s ease-out;
+     -moz-transition: -moz-transform 0.3s ease-out;
+       -o-transition: -o-transform 0.3s ease-out;
+          transition: transform 0.3s ease-out;
+}
+
+.modal.in .modal-dialog {
+  -webkit-transform: translate(0, 0);
+      -ms-transform: translate(0, 0);
+          transform: translate(0, 0);
+}
+
+.modal-dialog {
+  position: relative;
+  z-index: 1050;
+  width: auto;
+  padding: 10px;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.modal-content {
+  position: relative;
+  background-color: #ffffff;
+  border: 1px solid #999999;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 6px;
+  outline: none;
+  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+          box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+  background-clip: padding-box;
+}
+
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1030;
+  background-color: #000000;
+}
+
+.modal-backdrop.fade {
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+
+.modal-backdrop.in {
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+.modal-header {
+  min-height: 16.428571429px;
+  padding: 15px;
+  border-bottom: 1px solid #e5e5e5;
+}
+
+.modal-header .close {
+  margin-top: -2px;
+}
+
+.modal-title {
+  margin: 0;
+  line-height: 1.428571429;
+}
+
+.modal-body {
+  position: relative;
+  padding: 20px;
+}
+
+.modal-footer {
+  padding: 19px 20px 20px;
+  margin-top: 15px;
+  text-align: right;
+  border-top: 1px solid #e5e5e5;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: " ";
+}
+
+.modal-footer:after {
+  clear: both;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: " ";
+}
+
+.modal-footer:after {
+  clear: both;
+}
+
+.modal-footer .btn + .btn {
+  margin-bottom: 0;
+  margin-left: 5px;
+}
+
+.modal-footer .btn-group .btn + .btn {
+  margin-left: -1px;
+}
+
+.modal-footer .btn-block + .btn-block {
+  margin-left: 0;
+}
+
+@media screen and (min-width: 768px) {
+  .modal-dialog {
+    width: 600px;
+    padding-top: 30px;
+    padding-bottom: 30px;
+  }
+  .modal-content {
+    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+  }
+}
+
+.tooltip {
+  position: absolute;
+  z-index: 1030;
+  display: block;
+  font-size: 12px;
+  line-height: 1.4;
+  opacity: 0;
+  filter: alpha(opacity=0);
+  visibility: visible;
+}
+
+.tooltip.in {
+  opacity: 0.9;
+  filter: alpha(opacity=90);
+}
+
+.tooltip.top {
+  padding: 5px 0;
+  margin-top: -3px;
+}
+
+.tooltip.right {
+  padding: 0 5px;
+  margin-left: 3px;
+}
+
+.tooltip.bottom {
+  padding: 5px 0;
+  margin-top: 3px;
+}
+
+.tooltip.left {
+  padding: 0 5px;
+  margin-left: -3px;
+}
+
+.tooltip-inner {
+  max-width: 200px;
+  padding: 3px 8px;
+  color: #ffffff;
+  text-align: center;
+  text-decoration: none;
+  background-color: #000000;
+  border-radius: 4px;
+}
+
+.tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-top-color: #000000;
+  border-width: 5px 5px 0;
+}
+
+.tooltip.top-left .tooltip-arrow {
+  bottom: 0;
+  left: 5px;
+  border-top-color: #000000;
+  border-width: 5px 5px 0;
+}
+
+.tooltip.top-right .tooltip-arrow {
+  right: 5px;
+  bottom: 0;
+  border-top-color: #000000;
+  border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -5px;
+  border-right-color: #000000;
+  border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -5px;
+  border-left-color: #000000;
+  border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-bottom-color: #000000;
+  border-width: 0 5px 5px;
+}
+
+.tooltip.bottom-left .tooltip-arrow {
+  top: 0;
+  left: 5px;
+  border-bottom-color: #000000;
+  border-width: 0 5px 5px;
+}
+
+.tooltip.bottom-right .tooltip-arrow {
+  top: 0;
+  right: 5px;
+  border-bottom-color: #000000;
+  border-width: 0 5px 5px;
+}
+
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1010;
+  display: none;
+  max-width: 276px;
+  padding: 1px;
+  text-align: left;
+  white-space: normal;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+          box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  background-clip: padding-box;
+}
+
+.popover.top {
+  margin-top: -10px;
+}
+
+.popover.right {
+  margin-left: 10px;
+}
+
+.popover.bottom {
+  margin-top: 10px;
+}
+
+.popover.left {
+  margin-left: -10px;
+}
+
+.popover-title {
+  padding: 8px 14px;
+  margin: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 18px;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  border-radius: 5px 5px 0 0;
+}
+
+.popover-content {
+  padding: 9px 14px;
+}
+
+.popover .arrow,
+.popover .arrow:after {
+  position: absolute;
+  display: block;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.popover .arrow {
+  border-width: 11px;
+}
+
+.popover .arrow:after {
+  border-width: 10px;
+  content: "";
+}
+
+.popover.top .arrow {
+  bottom: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-top-color: #999999;
+  border-top-color: rgba(0, 0, 0, 0.25);
+  border-bottom-width: 0;
+}
+
+.popover.top .arrow:after {
+  bottom: 1px;
+  margin-left: -10px;
+  border-top-color: #ffffff;
+  border-bottom-width: 0;
+  content: " ";
+}
+
+.popover.right .arrow {
+  top: 50%;
+  left: -11px;
+  margin-top: -11px;
+  border-right-color: #999999;
+  border-right-color: rgba(0, 0, 0, 0.25);
+  border-left-width: 0;
+}
+
+.popover.right .arrow:after {
+  bottom: -10px;
+  left: 1px;
+  border-right-color: #ffffff;
+  border-left-width: 0;
+  content: " ";
+}
+
+.popover.bottom .arrow {
+  top: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-bottom-color: #999999;
+  border-bottom-color: rgba(0, 0, 0, 0.25);
+  border-top-width: 0;
+}
+
+.popover.bottom .arrow:after {
+  top: 1px;
+  margin-left: -10px;
+  border-bottom-color: #ffffff;
+  border-top-width: 0;
+  content: " ";
+}
+
+.popover.left .arrow {
+  top: 50%;
+  right: -11px;
+  margin-top: -11px;
+  border-left-color: #999999;
+  border-left-color: rgba(0, 0, 0, 0.25);
+  border-right-width: 0;
+}
+
+.popover.left .arrow:after {
+  right: 1px;
+  bottom: -10px;
+  border-left-color: #ffffff;
+  border-right-width: 0;
+  content: " ";
+}
+
+.carousel {
+  position: relative;
+}
+
+.carousel-inner {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+}
+
+.carousel-inner > .item {
+  position: relative;
+  display: none;
+  -webkit-transition: 0.6s ease-in-out left;
+          transition: 0.6s ease-in-out left;
+}
+
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+  display: block;
+  height: auto;
+  max-width: 100%;
+  line-height: 1;
+}
+
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  display: block;
+}
+
+.carousel-inner > .active {
+  left: 0;
+}
+
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+
+.carousel-inner > .next {
+  left: 100%;
+}
+
+.carousel-inner > .prev {
+  left: -100%;
+}
+
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+  left: 0;
+}
+
+.carousel-inner > .active.left {
+  left: -100%;
+}
+
+.carousel-inner > .active.right {
+  left: 100%;
+}
+
+.carousel-control {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 15%;
+  font-size: 20px;
+  color: #ffffff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+.carousel-control.left {
+  background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001)));
+  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0), color-stop(rgba(0, 0, 0, 0.0001) 100%));
+  background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
+  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
+}
+
+.carousel-control.right {
+  right: 0;
+  left: auto;
+  background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5)));
+  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0), color-stop(rgba(0, 0, 0, 0.5) 100%));
+  background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
+  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
+}
+
+.carousel-control:hover,
+.carousel-control:focus {
+  color: #ffffff;
+  text-decoration: none;
+  opacity: 0.9;
+  filter: alpha(opacity=90);
+}
+
+.carousel-control .icon-prev,
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .glyphicon-chevron-right {
+  position: absolute;
+  top: 50%;
+  z-index: 5;
+  display: inline-block;
+}
+
+.carousel-control .icon-prev,
+.carousel-control .glyphicon-chevron-left {
+  left: 50%;
+}
+
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-right {
+  right: 50%;
+}
+
+.carousel-control .icon-prev,
+.carousel-control .icon-next {
+  width: 20px;
+  height: 20px;
+  margin-top: -10px;
+  margin-left: -10px;
+  font-family: serif;
+}
+
+.carousel-control .icon-prev:before {
+  content: '\2039';
+}
+
+.carousel-control .icon-next:before {
+  content: '\203a';
+}
+
+.carousel-indicators {
+  position: absolute;
+  bottom: 10px;
+  left: 50%;
+  z-index: 15;
+  width: 60%;
+  padding-left: 0;
+  margin-left: -30%;
+  text-align: center;
+  list-style: none;
+}
+
+.carousel-indicators li {
+  display: inline-block;
+  width: 10px;
+  height: 10px;
+  margin: 1px;
+  text-indent: -999px;
+  cursor: pointer;
+  background-color: #000 \9;
+  background-color: rgba(0, 0, 0, 0);
+  border: 1px solid #ffffff;
+  border-radius: 10px;
+}
+
+.carousel-indicators .active {
+  width: 12px;
+  height: 12px;
+  margin: 0;
+  background-color: #ffffff;
+}
+
+.carousel-caption {
+  position: absolute;
+  right: 15%;
+  bottom: 20px;
+  left: 15%;
+  z-index: 10;
+  padding-top: 20px;
+  padding-bottom: 20px;
+  color: #ffffff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
+}
+
+.carousel-caption .btn {
+  text-shadow: none;
+}
+
+@media screen and (min-width: 768px) {
+  .carousel-control .glyphicons-chevron-left,
+  .carousel-control .glyphicons-chevron-right,
+  .carousel-control .icon-prev,
+  .carousel-control .icon-next {
+    width: 30px;
+    height: 30px;
+    margin-top: -15px;
+    margin-left: -15px;
+    font-size: 30px;
+  }
+  .carousel-caption {
+    right: 20%;
+    left: 20%;
+    padding-bottom: 30px;
+  }
+  .carousel-indicators {
+    bottom: 20px;
+  }
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: " ";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.center-block {
+  display: block;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.pull-right {
+  float: right !important;
+}
+
+.pull-left {
+  float: left !important;
+}
+
+.hide {
+  display: none !important;
+}
+
+.show {
+  display: block !important;
+}
+
+.invisible {
+  visibility: hidden;
+}
+
+.text-hide {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.hidden {
+  display: none !important;
+  visibility: hidden !important;
+}
+
+.affix {
+  position: fixed;
+}
+
+@-ms-viewport {
+  width: device-width;
+}
+
+.visible-xs,
+tr.visible-xs,
+th.visible-xs,
+td.visible-xs {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-xs {
+    display: block !important;
+  }
+  tr.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-xs,
+  td.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-xs.visible-sm {
+    display: block !important;
+  }
+  tr.visible-xs.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-sm,
+  td.visible-xs.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-xs.visible-md {
+    display: block !important;
+  }
+  tr.visible-xs.visible-md {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-md,
+  td.visible-xs.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-xs.visible-lg {
+    display: block !important;
+  }
+  tr.visible-xs.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-lg,
+  td.visible-xs.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.visible-sm,
+tr.visible-sm,
+th.visible-sm,
+td.visible-sm {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-sm.visible-xs {
+    display: block !important;
+  }
+  tr.visible-sm.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-xs,
+  td.visible-sm.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm {
+    display: block !important;
+  }
+  tr.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-sm,
+  td.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-sm.visible-md {
+    display: block !important;
+  }
+  tr.visible-sm.visible-md {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-md,
+  td.visible-sm.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-sm.visible-lg {
+    display: block !important;
+  }
+  tr.visible-sm.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-lg,
+  td.visible-sm.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.visible-md,
+tr.visible-md,
+th.visible-md,
+td.visible-md {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-md.visible-xs {
+    display: block !important;
+  }
+  tr.visible-md.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-md.visible-xs,
+  td.visible-md.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-md.visible-sm {
+    display: block !important;
+  }
+  tr.visible-md.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-md.visible-sm,
+  td.visible-md.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md {
+    display: block !important;
+  }
+  tr.visible-md {
+    display: table-row !important;
+  }
+  th.visible-md,
+  td.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-md.visible-lg {
+    display: block !important;
+  }
+  tr.visible-md.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-md.visible-lg,
+  td.visible-md.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.visible-lg,
+tr.visible-lg,
+th.visible-lg,
+td.visible-lg {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-lg.visible-xs {
+    display: block !important;
+  }
+  tr.visible-lg.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-xs,
+  td.visible-lg.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-lg.visible-sm {
+    display: block !important;
+  }
+  tr.visible-lg.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-sm,
+  td.visible-lg.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-lg.visible-md {
+    display: block !important;
+  }
+  tr.visible-lg.visible-md {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-md,
+  td.visible-lg.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-lg {
+    display: block !important;
+  }
+  tr.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-lg,
+  td.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.hidden-xs {
+  display: block !important;
+}
+
+tr.hidden-xs {
+  display: table-row !important;
+}
+
+th.hidden-xs,
+td.hidden-xs {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-xs,
+  tr.hidden-xs,
+  th.hidden-xs,
+  td.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-xs.hidden-sm,
+  tr.hidden-xs.hidden-sm,
+  th.hidden-xs.hidden-sm,
+  td.hidden-xs.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-xs.hidden-md,
+  tr.hidden-xs.hidden-md,
+  th.hidden-xs.hidden-md,
+  td.hidden-xs.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-xs.hidden-lg,
+  tr.hidden-xs.hidden-lg,
+  th.hidden-xs.hidden-lg,
+  td.hidden-xs.hidden-lg {
+    display: none !important;
+  }
+}
+
+.hidden-sm {
+  display: block !important;
+}
+
+tr.hidden-sm {
+  display: table-row !important;
+}
+
+th.hidden-sm,
+td.hidden-sm {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-sm.hidden-xs,
+  tr.hidden-sm.hidden-xs,
+  th.hidden-sm.hidden-xs,
+  td.hidden-sm.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-sm,
+  tr.hidden-sm,
+  th.hidden-sm,
+  td.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-sm.hidden-md,
+  tr.hidden-sm.hidden-md,
+  th.hidden-sm.hidden-md,
+  td.hidden-sm.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-sm.hidden-lg,
+  tr.hidden-sm.hidden-lg,
+  th.hidden-sm.hidden-lg,
+  td.hidden-sm.hidden-lg {
+    display: none !important;
+  }
+}
+
+.hidden-md {
+  display: block !important;
+}
+
+tr.hidden-md {
+  display: table-row !important;
+}
+
+th.hidden-md,
+td.hidden-md {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-md.hidden-xs,
+  tr.hidden-md.hidden-xs,
+  th.hidden-md.hidden-xs,
+  td.hidden-md.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-md.hidden-sm,
+  tr.hidden-md.hidden-sm,
+  th.hidden-md.hidden-sm,
+  td.hidden-md.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-md,
+  tr.hidden-md,
+  th.hidden-md,
+  td.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-md.hidden-lg,
+  tr.hidden-md.hidden-lg,
+  th.hidden-md.hidden-lg,
+  td.hidden-md.hidden-lg {
+    display: none !important;
+  }
+}
+
+.hidden-lg {
+  display: block !important;
+}
+
+tr.hidden-lg {
+  display: table-row !important;
+}
+
+th.hidden-lg,
+td.hidden-lg {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-lg.hidden-xs,
+  tr.hidden-lg.hidden-xs,
+  th.hidden-lg.hidden-xs,
+  td.hidden-lg.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-lg.hidden-sm,
+  tr.hidden-lg.hidden-sm,
+  th.hidden-lg.hidden-sm,
+  td.hidden-lg.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-lg.hidden-md,
+  tr.hidden-lg.hidden-md,
+  th.hidden-lg.hidden-md,
+  td.hidden-lg.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-lg,
+  tr.hidden-lg,
+  th.hidden-lg,
+  td.hidden-lg {
+    display: none !important;
+  }
+}
+
+.visible-print,
+tr.visible-print,
+th.visible-print,
+td.visible-print {
+  display: none !important;
+}
+
+@media print {
+  .visible-print {
+    display: block !important;
+  }
+  tr.visible-print {
+    display: table-row !important;
+  }
+  th.visible-print,
+  td.visible-print {
+    display: table-cell !important;
+  }
+  .hidden-print,
+  tr.hidden-print,
+  th.hidden-print,
+  td.hidden-print {
+    display: none !important;
+  }
+}
diff --git a/src/web/css/bootstrap-datetimepicker.css b/src/web/css/bootstrap-datetimepicker.css
new file mode 100644
index 0000000..e5eb7a6
--- /dev/null
+++ b/src/web/css/bootstrap-datetimepicker.css
@@ -0,0 +1,174 @@
+/**
+ * Build file for the dist version of datetimepicker.css
+ */
+/*!
+ * Datetimepicker for Bootstrap v3
+ * https://github.com/Eonasdan/bootstrap-datetimepicker/
+ * Copyright 2012 Stefan Petre
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+.bootstrap-datetimepicker-widget {
+  top: 0;
+  left: 0;
+  width: 250px;
+  padding: 4px;
+  margin-top: 1px;
+  z-index: 9999;
+  border-radius: 4px;
+  /*.dow {
+  border-top: 1px solid #ddd !important;
+  }*/
+}
+.bootstrap-datetimepicker-widget .btn {
+  padding: 6px;
+}
+.bootstrap-datetimepicker-widget:before {
+  content: '';
+  display: inline-block;
+  border-left: 7px solid transparent;
+  border-right: 7px solid transparent;
+  border-bottom: 7px solid #ccc;
+  border-bottom-color: rgba(0, 0, 0, 0.2);
+  position: absolute;
+  top: -7px;
+  left: 6px;
+}
+.bootstrap-datetimepicker-widget:after {
+  content: '';
+  display: inline-block;
+  border-left: 6px solid transparent;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid white;
+  position: absolute;
+  top: -6px;
+  left: 7px;
+}
+.bootstrap-datetimepicker-widget.pull-right:before {
+  left: auto;
+  right: 6px;
+}
+.bootstrap-datetimepicker-widget.pull-right:after {
+  left: auto;
+  right: 7px;
+}
+.bootstrap-datetimepicker-widget > ul {
+  list-style-type: none;
+  margin: 0;
+}
+.bootstrap-datetimepicker-widget .timepicker-hour,
+.bootstrap-datetimepicker-widget .timepicker-minute,
+.bootstrap-datetimepicker-widget .timepicker-second {
+  width: 100%;
+  font-weight: bold;
+  font-size: 1.2em;
+}
+.bootstrap-datetimepicker-widget table[data-hour-format="12"] .separator {
+  width: 4px;
+  padding: 0;
+  margin: 0;
+}
+.bootstrap-datetimepicker-widget .datepicker > div {
+  display: none;
+}
+.bootstrap-datetimepicker-widget .picker-switch {
+  text-align: center;
+}
+.bootstrap-datetimepicker-widget table {
+  width: 100%;
+  margin: 0;
+}
+.bootstrap-datetimepicker-widget td,
+.bootstrap-datetimepicker-widget th {
+  text-align: center;
+  width: 20px;
+  height: 20px;
+  border-radius: 4px;
+}
+.bootstrap-datetimepicker-widget td.day:hover,
+.bootstrap-datetimepicker-widget td.hour:hover,
+.bootstrap-datetimepicker-widget td.minute:hover,
+.bootstrap-datetimepicker-widget td.second:hover {
+  background: #eeeeee;
+  cursor: pointer;
+}
+.bootstrap-datetimepicker-widget td.old,
+.bootstrap-datetimepicker-widget td.new {
+  color: #999999;
+}
+.bootstrap-datetimepicker-widget td.active,
+.bootstrap-datetimepicker-widget td.active:hover {
+  background-color: #428bca;
+  color: #fff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.bootstrap-datetimepicker-widget td.disabled,
+.bootstrap-datetimepicker-widget td.disabled:hover {
+  background: none;
+  color: #999999;
+  cursor: not-allowed;
+}
+.bootstrap-datetimepicker-widget td span {
+  display: block;
+  width: 47px;
+  height: 54px;
+  line-height: 54px;
+  float: left;
+  margin: 2px;
+  cursor: pointer;
+  border-radius: 4px;
+}
+.bootstrap-datetimepicker-widget td span:hover {
+  background: #eeeeee;
+}
+.bootstrap-datetimepicker-widget td span.active {
+  background-color: #428bca;
+  color: #fff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.bootstrap-datetimepicker-widget td span.old {
+  color: #999999;
+}
+.bootstrap-datetimepicker-widget td span.disabled,
+.bootstrap-datetimepicker-widget td span.disabled:hover {
+  background: none;
+  color: #999999;
+  cursor: not-allowed;
+}
+.bootstrap-datetimepicker-widget th.switch {
+  width: 145px;
+}
+.bootstrap-datetimepicker-widget th.next,
+.bootstrap-datetimepicker-widget th.prev {
+  font-size: 21px;
+}
+.bootstrap-datetimepicker-widget th.disabled,
+.bootstrap-datetimepicker-widget th.disabled:hover {
+  background: none;
+  color: #999999;
+  cursor: not-allowed;
+}
+.bootstrap-datetimepicker-widget thead tr:first-child th {
+  cursor: pointer;
+}
+.bootstrap-datetimepicker-widget thead tr:first-child th:hover {
+  background: #eeeeee;
+}
+.input-group.date .input-group-addon span {
+  display: block;
+  cursor: pointer;
+  width: 16px;
+  height: 16px;
+}
+.bootstrap-datetimepicker-widget.left-oriented:before {
+  left: auto;
+  right: 6px;
+}
+.bootstrap-datetimepicker-widget.left-oriented:after {
+  left: auto;
+  right: 7px;
+}
+.bootstrap-datetimepicker-widget ul.list-unstyled li.in div.timepicker div.timepicker-picker table.table-condensed tbody > tr > td {
+  padding: 0px !important;
+}
diff --git a/src/web/fonts/glyphicons-halflings-regular.eot b/src/web/fonts/glyphicons-halflings-regular.eot
new file mode 100644
index 0000000..423bd5d
Binary files /dev/null and b/src/web/fonts/glyphicons-halflings-regular.eot differ
diff --git a/src/web/fonts/glyphicons-halflings-regular.svg b/src/web/fonts/glyphicons-halflings-regular.svg
new file mode 100644
index 0000000..4469488
--- /dev/null
+++ b/src/web/fonts/glyphicons-halflings-regular.svg
@@ -0,0 +1,229 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
+<font-face units-per-em="1200" ascent="960" descent="-240" />
+<missing-glyph horiz-adv-x="500" />
+<glyph />
+<glyph />
+<glyph unicode="&#xd;" />
+<glyph unicode=" " />
+<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
+<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
+<glyph unicode="&#xa0;" />
+<glyph unicode="&#x2000;" horiz-adv-x="652" />
+<glyph unicode="&#x2001;" horiz-adv-x="1304" />
+<glyph unicode="&#x2002;" horiz-adv-x="652" />
+<glyph unicode="&#x2003;" horiz-adv-x="1304" />
+<glyph unicode="&#x2004;" horiz-adv-x="434" />
+<glyph unicode="&#x2005;" horiz-adv-x="326" />
+<glyph unicode="&#x2006;" horiz-adv-x="217" />
+<glyph unicode="&#x2007;" horiz-adv-x="217" />
+<glyph unicode="&#x2008;" horiz-adv-x="163" />
+<glyph unicode="&#x2009;" horiz-adv-x="260" />
+<glyph unicode="&#x200a;" horiz-adv-x="72" />
+<glyph unicode="&#x202f;" horiz-adv-x="260" />
+<glyph unicode="&#x205f;" horiz-adv-x="326" />
+<glyph unicode="&#x20ac;" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
+<glyph unicode="&#x2212;" d="M200 400h900v300h-900v-300z" />
+<glyph unicode="&#x2601;" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" />
+<glyph unicode="&#x2709;" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
+<glyph unicode="&#x270f;" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
+<glyph unicode="&#xe000;" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="&#xe001;" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
+<glyph unicode="&#xe002;" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q17 -55 85.5 -75.5t147.5 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />
+<glyph unicode="&#xe003;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
+<glyph unicode="&#xe005;" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" />
+<glyph unicode="&#xe006;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" />
+<glyph unicode="&#xe007;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" />
+<glyph unicode="&#xe008;" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" />
+<glyph unicode="&#xe009;" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" />
+<glyph unicode="&#xe010;" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe011;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe012;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe013;" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
+<glyph unicode="&#xe014;" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
+<glyph unicode="&#xe015;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" />
+<glyph unicode="&#xe016;" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 299q-120 -77 -261 -77q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" />
+<glyph unicode="&#xe017;" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" />
+<glyph unicode="&#xe018;" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
+<glyph unicode="&#xe019;" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" />
+<glyph unicode="&#xe020;" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
+<glyph unicode="&#xe021;" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
+<glyph unicode="&#xe022;" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" />
+<glyph unicode="&#xe023;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" />
+<glyph unicode="&#xe024;" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
+<glyph unicode="&#xe025;" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
+<glyph unicode="&#xe026;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" />
+<glyph unicode="&#xe027;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" />
+<glyph unicode="&#xe028;" d="M0 25v475l200 700h800q199 -700 200 -700v-475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" />
+<glyph unicode="&#xe029;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" />
+<glyph unicode="&#xe030;" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" />
+<glyph unicode="&#xe031;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" />
+<glyph unicode="&#xe032;" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" />
+<glyph unicode="&#xe033;" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
+<glyph unicode="&#xe034;" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" />
+<glyph unicode="&#xe035;" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" />
+<glyph unicode="&#xe036;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" />
+<glyph unicode="&#xe037;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
+<glyph unicode="&#xe038;" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" />
+<glyph unicode="&#xe039;" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" />
+<glyph unicode="&#xe040;" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" />
+<glyph unicode="&#xe041;" d="M1 700v475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" />
+<glyph unicode="&#xe042;" d="M2 700v475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" />
+<glyph unicode="&#xe043;" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
+<glyph unicode="&#xe044;" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
+<glyph unicode="&#xe045;" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" />
+<glyph unicode="&#xe046;" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" />
+<glyph unicode="&#xe047;" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" />
+<glyph unicode="&#xe048;" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v70h471q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" />
+<glyph unicode="&#xe049;" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" />
+<glyph unicode="&#xe050;" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " />
+<glyph unicode="&#xe051;" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" />
+<glyph unicode="&#xe052;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
+<glyph unicode="&#xe053;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
+<glyph unicode="&#xe054;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" />
+<glyph unicode="&#xe055;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
+<glyph unicode="&#xe056;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
+<glyph unicode="&#xe057;" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" />
+<glyph unicode="&#xe058;" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" />
+<glyph unicode="&#xe059;" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" />
+<glyph unicode="&#xe060;" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " />
+<glyph unicode="&#xe062;" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" />
+<glyph unicode="&#xe063;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" />
+<glyph unicode="&#xe064;" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 138.5t-64 210.5zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" />
+<glyph unicode="&#xe065;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" />
+<glyph unicode="&#xe066;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" />
+<glyph unicode="&#xe067;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l566 567l-136 137l-430 -431l-147 147z" />
+<glyph unicode="&#xe068;" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
+<glyph unicode="&#xe069;" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe070;" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe071;" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
+<glyph unicode="&#xe072;" d="M200 0l900 550l-900 550v-1100z" />
+<glyph unicode="&#xe073;" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
+<glyph unicode="&#xe074;" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
+<glyph unicode="&#xe075;" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
+<glyph unicode="&#xe076;" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" />
+<glyph unicode="&#xe077;" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" />
+<glyph unicode="&#xe078;" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" />
+<glyph unicode="&#xe079;" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" />
+<glyph unicode="&#xe080;" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" />
+<glyph unicode="&#xe081;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" />
+<glyph unicode="&#xe082;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM300 500h600v200h-600v-200z" />
+<glyph unicode="&#xe083;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141z" />
+<glyph unicode="&#xe084;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" />
+<glyph unicode="&#xe085;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM363 700h144q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5q19 0 30 -10t11 -26 q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-105 0 -172 -56t-67 -183zM500 300h200v100h-200v-100z" />
+<glyph unicode="&#xe086;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" />
+<glyph unicode="&#xe087;" d="M0 500v200h194q15 60 36 104.5t55.5 86t88 69t126.5 40.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200 v-206q149 48 201 206h-201v200h200q-25 74 -76 127.5t-124 76.5v-204h-200v203q-75 -24 -130 -77.5t-79 -125.5h209v-200h-210z" />
+<glyph unicode="&#xe088;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" />
+<glyph unicode="&#xe089;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" />
+<glyph unicode="&#xe090;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" />
+<glyph unicode="&#xe091;" d="M0 547l600 453v-300h600v-300h-600v-301z" />
+<glyph unicode="&#xe092;" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
+<glyph unicode="&#xe093;" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
+<glyph unicode="&#xe094;" d="M104 600h296v600h300v-600h298l-449 -600z" />
+<glyph unicode="&#xe095;" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" />
+<glyph unicode="&#xe096;" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
+<glyph unicode="&#xe097;" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" />
+<glyph unicode="&#xe101;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-33 14.5h-207q-20 0 -32 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" />
+<glyph unicode="&#xe102;" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111v6t-1 15t-3 18l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6h-111v-100z M100 0h400v400h-400v-400zM200 900q-3 0 14 48t35 96l18 47l214 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" />
+<glyph unicode="&#xe103;" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" />
+<glyph unicode="&#xe104;" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" />
+<glyph unicode="&#xe105;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" />
+<glyph unicode="&#xe106;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" />
+<glyph unicode="&#xe107;" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 33 -48 36t-48 -29l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" />
+<glyph unicode="&#xe108;" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -21 -13 -29t-32 1l-94 78h-222l-94 -78q-19 -9 -32 -1t-13 29v64 q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" />
+<glyph unicode="&#xe109;" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" />
+<glyph unicode="&#xe110;" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" />
+<glyph unicode="&#xe111;" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" />
+<glyph unicode="&#xe112;" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" />
+<glyph unicode="&#xe113;" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" />
+<glyph unicode="&#xe114;" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" />
+<glyph unicode="&#xe115;" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" />
+<glyph unicode="&#xe116;" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" />
+<glyph unicode="&#xe117;" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" />
+<glyph unicode="&#xe118;" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" />
+<glyph unicode="&#xe119;" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
+<glyph unicode="&#xe120;" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
+<glyph unicode="&#xe121;" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" />
+<glyph unicode="&#xe122;" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM99 500v250v5q0 13 0.5 18.5t2.5 13t8 10.5t15 3h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35q-56 337 -56 351z M1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" />
+<glyph unicode="&#xe123;" d="M74 350q0 21 13.5 35.5t33.5 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-22 -9 -63 -23t-167.5 -37 t-251.5 -23t-245.5 20.5t-178.5 41.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" />
+<glyph unicode="&#xe124;" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" />
+<glyph unicode="&#xe125;" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q123 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 212l100 213h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" />
+<glyph unicode="&#xe126;" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q123 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" />
+<glyph unicode="&#xe127;" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" />
+<glyph unicode="&#xe128;" d="M-101 651q0 72 54 110t139 37h302l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 16.5 -10.5t26 -26t16.5 -36.5v-526q0 -13 -85.5 -93.5t-93.5 -80.5h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l106 89v502l-342 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM999 201v600h200v-600h-200z" />
+<glyph unicode="&#xe129;" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6v7.5v7v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" />
+<glyph unicode="&#xe130;" d="M1 585q-15 -31 7 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85l-1 -302q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM76 565l237 339h503l89 -100v-294l-340 -130 q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" />
+<glyph unicode="&#xe131;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 500h300l-2 -194l402 294l-402 298v-197h-298v-201z" />
+<glyph unicode="&#xe132;" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l400 -294v194h302v201h-300v197z" />
+<glyph unicode="&#xe133;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" />
+<glyph unicode="&#xe134;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" />
+<glyph unicode="&#xe135;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -34 5.5 -93t7.5 -87q0 -9 17 -44t16 -60q12 0 23 -5.5 t23 -15t20 -13.5q20 -10 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55.5t-20 -57.5q12 -21 22.5 -34.5t28 -27t36.5 -17.5q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q101 -2 221 111q31 30 47 48t34 49t21 62q-14 9 -37.5 9.5t-35.5 7.5q-14 7 -49 15t-52 19 q-9 0 -39.5 -0.5t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q8 16 22 22q6 -1 26 -1.5t33.5 -4.5t19.5 -13q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5 t5.5 57.5q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 41 1 44q31 -13 58.5 -14.5t39.5 3.5l11 4q6 36 -17 53.5t-64 28.5t-56 23 q-19 -3 -37 0q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -46 0t-45 -3q-20 -6 -51.5 -25.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79zM518 915q3 12 16 30.5t16 25.5q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -18 8 -42.5t16.5 -44 t9.5 -23.5q-6 1 -39 5t-53.5 10t-36.5 16z" />
+<glyph unicode="&#xe136;" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" />
+<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" />
+<glyph unicode="&#xe138;" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
+<glyph unicode="&#xe139;" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" />
+<glyph unicode="&#xe140;" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" />
+<glyph unicode="&#xe141;" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM513 609q0 32 21 56.5t52 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-16 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5q-37 0 -62.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" />
+<glyph unicode="&#xe142;" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -79.5 -17t-67.5 -51l-388 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23q38 0 53 -36 q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60l517 511 q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" />
+<glyph unicode="&#xe143;" d="M79 784q0 131 99 229.5t230 98.5q144 0 242 -129q103 129 245 129q130 0 227 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100l-84.5 84.5t-68 74t-60 78t-33.5 70.5t-15 78z M250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-106 48.5q-73 0 -131 -83l-118 -171l-114 174q-51 80 -124 80q-59 0 -108.5 -49.5t-49.5 -118.5z" />
+<glyph unicode="&#xe144;" d="M57 353q0 -94 66 -160l141 -141q66 -66 159 -66q95 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-12 12 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141l19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -18q46 -46 77 -99l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" />
+<glyph unicode="&#xe145;" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" />
+<glyph unicode="&#xe146;" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" />
+<glyph unicode="&#xe148;" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335l-27 7q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5v-307l64 -14 q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5zM700 237 q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" />
+<glyph unicode="&#xe149;" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -11 2.5 -24.5t5.5 -24t9.5 -26.5t10.5 -25t14 -27.5t14 -25.5 t15.5 -27t13.5 -24h242v-100h-197q8 -50 -2.5 -115t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q32 1 102 -16t104 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10 t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5t-30 142.5h-221z" />
+<glyph unicode="&#xe150;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
+<glyph unicode="&#xe151;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" />
+<glyph unicode="&#xe152;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" />
+<glyph unicode="&#xe153;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
+<glyph unicode="&#xe154;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
+<glyph unicode="&#xe155;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
+<glyph unicode="&#xe156;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
+<glyph unicode="&#xe157;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" />
+<glyph unicode="&#xe158;" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" />
+<glyph unicode="&#xe159;" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" />
+<glyph unicode="&#xe160;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" />
+<glyph unicode="&#xe161;" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" />
+<glyph unicode="&#xe162;" d="M216 519q10 -19 32 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8l9 -1q13 0 26 16l538 630q15 19 6 36q-8 18 -32 16h-300q1 4 78 219.5t79 227.5q2 17 -6 27l-8 8h-9q-16 0 -25 -15q-4 -5 -98.5 -111.5t-228 -257t-209.5 -238.5q-17 -19 -7 -40z" />
+<glyph unicode="&#xe163;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " />
+<glyph unicode="&#xe164;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" />
+<glyph unicode="&#xe165;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" />
+<glyph unicode="&#xe166;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" />
+<glyph unicode="&#xe167;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" />
+<glyph unicode="&#xe168;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" />
+<glyph unicode="&#xe169;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 401h700v699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" />
+<glyph unicode="&#xe170;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l248 -237v700h-699zM900 150h100v50h-100v-50z" />
+<glyph unicode="&#xe171;" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" />
+<glyph unicode="&#xe172;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" />
+<glyph unicode="&#xe173;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" />
+<glyph unicode="&#xe174;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" />
+<glyph unicode="&#xe175;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" />
+<glyph unicode="&#xe176;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" />
+<glyph unicode="&#xe177;" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" />
+<glyph unicode="&#xe178;" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" />
+<glyph unicode="&#xe179;" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -117q-25 -16 -43.5 -50.5t-18.5 -65.5v-359z" />
+<glyph unicode="&#xe180;" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" />
+<glyph unicode="&#xe181;" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" />
+<glyph unicode="&#xe182;" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q16 17 13 40.5t-22 37.5l-192 136q-19 14 -45 12t-42 -19l-119 -118q-143 103 -267 227q-126 126 -227 268l118 118q17 17 20 41.5 t-11 44.5l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" />
+<glyph unicode="&#xe183;" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -15 -35.5t-35 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" />
+<glyph unicode="&#xe184;" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" />
+<glyph unicode="&#xe185;" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" />
+<glyph unicode="&#xe186;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
+<glyph unicode="&#xe187;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
+<glyph unicode="&#xe188;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" />
+<glyph unicode="&#xe189;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" />
+<glyph unicode="&#xe190;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" />
+<glyph unicode="&#xe191;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
+<glyph unicode="&#xe192;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
+<glyph unicode="&#xe193;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" />
+<glyph unicode="&#xe194;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" />
+<glyph unicode="&#xe195;" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" />
+<glyph unicode="&#xe197;" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300 h200l-300 -300z" />
+<glyph unicode="&#xe198;" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104t60.5 178q0 121 -85 207.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" />
+<glyph unicode="&#xe199;" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" />
+<glyph unicode="&#xe200;" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -12t1 -11q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" />
+</font>
+</defs></svg> 
\ No newline at end of file
diff --git a/src/web/fonts/glyphicons-halflings-regular.ttf b/src/web/fonts/glyphicons-halflings-regular.ttf
new file mode 100644
index 0000000..a498ef4
Binary files /dev/null and b/src/web/fonts/glyphicons-halflings-regular.ttf differ
diff --git a/src/web/fonts/glyphicons-halflings-regular.woff b/src/web/fonts/glyphicons-halflings-regular.woff
new file mode 100644
index 0000000..d83c539
Binary files /dev/null and b/src/web/fonts/glyphicons-halflings-regular.woff differ
diff --git a/src/web/js/azkaban.admin.setup.js b/src/web/js/azkaban.admin.setup.js
index 80a0f94..632f5d0 100644
--- a/src/web/js/azkaban.admin.setup.js
+++ b/src/web/js/azkaban.admin.setup.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var dbUploadPanel;
@@ -114,4 +130,4 @@ $(function() {
 			window.location="/?usersetup";
 		}
 	});
-});
\ No newline at end of file
+});
diff --git a/src/web/js/azkaban.ajax.utils.js b/src/web/js/azkaban.ajax.utils.js
index f35b424..342e2af 100644
--- a/src/web/js/azkaban.ajax.utils.js
+++ b/src/web/js/azkaban.ajax.utils.js
@@ -1,140 +1,159 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 function ajaxCall(requestURL, data, callback) {
-	$.get(
-		requestURL,
-		data,
-		function(data) {
-			if (data.error == "session") {
-				// We need to relogin.
-				var errorDialog = document.getElementById("invalid-session");
-				if (errorDialog) {
-					  $(errorDialog).modal({
-					      closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-					      position: ["20%",],
-					      containerId: 'confirm-container',
-					      containerCss: {
-					        'height': '220px',
-					        'width': '565px'
-					      },
-					      onClose: function (dialog) {
-					      	window.location.reload();
-					      }
-					    });
-				}
-			}
-			else {
-				callback.call(this,data);
+	var successHandler = function(data) {
+		if (data.error == "session") {
+			// We need to relogin.
+			var errorDialog = document.getElementById("invalid-session");
+			if (errorDialog) {
+				$(errorDialog).modal({
+					closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
+					position: ["20%",],
+					containerId: 'confirm-container',
+					containerCss: {
+						'height': '220px',
+						'width': '565px'
+					},
+					onClose: function (dialog) {
+						window.location.reload();
+					}
+				});
 			}
-		},
-		"json"
-	);
+		}
+		else {
+			callback.call(this,data);
+		}
+	};
+	$.get(requestURL, data, successHandler, "json");
 }
 
 function executeFlow(executingData) {
 	executeURL = contextURL + "/executor";
-	$.get(
-		executeURL,
-		executingData,
-		function(data) {
-			if (data.error) {
-				messageDialogView.show("Error Executing Flow", data.error);
-			}
-			else {
-				messageDialogView.show("Flow submitted", data.message,
-					function() {
-						var redirectURL = contextURL + "/executor?execid=" + data.execid;
-						window.location.href = redirectURL;
-					}
-				);
-			}
-		},
-		"json"
-	);
+	var successHandler = function(data) {
+		if (data.error) {
+			flowExecuteDialogView.hideExecutionOptionPanel();
+			messageDialogView.show("Error Executing Flow", data.error);
+		}
+		else {
+			flowExecuteDialogView.hideExecutionOptionPanel();
+			messageDialogView.show("Flow submitted", data.message,
+				function() {
+					var redirectURL = contextURL + "/executor?execid=" + data.execid;
+					window.location.href = redirectURL;
+				}
+			);
+		}
+	};
+
+	$.get(executeURL, executingData, successHandler, "json");
 }
 
 function fetchFlowInfo(model, projectName, flowId, execId) {
-  	var fetchData = {"project": projectName, "ajax":"flowInfo", "flow":flowId};
-  	if (execId) {
-  		fetchData.execid = execId;
-  	}
-  	
-  	var executeURL = contextURL + "/executor";
-  	$.ajax({
-  		url: executeURL,
-  		data: fetchData,
-  		success: function(data) {
-			if (data.error) {
-				alert(data.error);
-			}
-			else {
-				model.set({
-					"successEmails": data.successEmails, 
-					"failureEmails": data.failureEmails,
-					"failureAction": data.failureAction,
-					"notifyFailure": {"first": data.notifyFailureFirst, "last":data.notifyFailureLast},
-					"flowParams": data.flowParam,
-					"isRunning": data.running,
-					"nodeStatus": data.nodeStatus,
-					"concurrentOption": data.concurrentOptions,
-					"pipelineLevel": data.pipelineLevel,
-					"pipelineExecution": data.pipelineExecution,
-					"queueLevel":data.queueLevel
-				});
-			}
-			
-			model.trigger("change:flowinfo");
-		},
+	var fetchData = {"project": projectName, "ajax":"flowInfo", "flow":flowId};
+	if (execId) {
+		fetchData.execid = execId;
+	}
+	
+	var executeURL = contextURL + "/executor";
+	var successHandler = function(data) {
+		if (data.error) {
+			alert(data.error);
+		}
+		else {
+			model.set({
+				"successEmails": data.successEmails, 
+				"failureEmails": data.failureEmails,
+				"failureAction": data.failureAction,
+				"notifyFailure": {
+					"first": data.notifyFailureFirst, 
+					"last": data.notifyFailureLast
+				},
+				"flowParams": data.flowParam,
+				"isRunning": data.running,
+				"nodeStatus": data.nodeStatus,
+				"concurrentOption": data.concurrentOptions,
+				"pipelineLevel": data.pipelineLevel,
+				"pipelineExecution": data.pipelineExecution,
+				"queueLevel":data.queueLevel
+			});
+		}
+		model.trigger("change:flowinfo");
+	};
+
+	$.ajax({
+		url: executeURL,
+		data: fetchData,
+		success: successHandler,
 		dataType: "json",
 		async: false
-  	});
+	});
 }
 
 function fetchFlow(model, projectName, flowId, sync) {
 	// Just in case people don't set sync
 	sync = sync ? true : false;
-
-  	var managerUrl = contextURL + "/manager";
-  	var fetchData = {
-  		"ajax" : "fetchflowgraph",
-  		"project" : projectName,
-  		"flow" : flowId
-  	};
+	var managerUrl = contextURL + "/manager";
+	var fetchData = {
+		"ajax" : "fetchflowgraph",
+		"project" : projectName,
+		"flow" : flowId
+	};
+	var successHandler = function(data) {
+		if (data.error) {
+			alert(data.error);
+		}
+		else {
+			var disabled = data.disabled ? data.disabled : {};
+			model.set({
+				flowId: data.flowId, 
+				data: data, 
+				disabled: disabled
+			});
+			
+			var nodeMap = {};
+			for (var i = 0; i < data.nodes.length; ++i) {
+				var node = data.nodes[i];
+				nodeMap[node.id] = node;
+			}
+			
+			for (var i = 0; i < data.edges.length; ++i) {
+				 var edge = data.edges[i];
+				 
+				 if (!nodeMap[edge.target].in) {
+					nodeMap[edge.target].in = {};
+				 }
+				 var targetInMap = nodeMap[edge.target].in;
+				 targetInMap[edge.from] = nodeMap[edge.from];
+				 
+				 if (!nodeMap[edge.from].out) {
+					nodeMap[edge.from].out = {};
+				 }
+				 var sourceOutMap = nodeMap[edge.from].out;
+				 sourceOutMap[edge.target] = nodeMap[edge.target];
+			}
+			
+			model.set({nodeMap: nodeMap});
+		}
+	};
 
 	$.ajax({
 		url: managerUrl,
 		data: fetchData,
-		success: function(data) {
-			if (data.error) {
-				alert(data.error);
-			}
-			else {
-				var disabled = data.disabled ? data.disabled : {};
-				model.set({flowId: data.flowId, data:data, disabled: disabled});
-				
-				var nodeMap = {};
-				for (var i = 0; i < data.nodes.length; ++i) {
-					var node = data.nodes[i];
-					nodeMap[node.id] = node;
-				}
-				
-				for (var i = 0; i < data.edges.length; ++i) {
-					 var edge = data.edges[i];
-					 
-					 if (!nodeMap[edge.target].in) {
-					 	nodeMap[edge.target].in = {};
-					 }
-					 var targetInMap = nodeMap[edge.target].in;
-					 targetInMap[edge.from] = nodeMap[edge.from];
-					 
-					 if (!nodeMap[edge.from].out) {
-					 	nodeMap[edge.from].out = {};
-					 }
-					 var sourceOutMap = nodeMap[edge.from].out;
-					 sourceOutMap[edge.target] = nodeMap[edge.target];
-				}
-				
-				model.set({nodeMap: nodeMap});
-			}
-		},
+		success: successHandler,
 		dataType: "json",
 		async: !sync
 	});
@@ -148,34 +167,39 @@ function flowExecutingStatus(projectName, flowId) {
 	var requestURL = contextURL + "/executor";
 	
 	var executionIds;
-	$.ajax( {
+	var successHandler = function(data) {
+		if (data.error == "session") {
+			// We need to relogin.
+			var errorDialog = document.getElementById("invalid-session");
+			if (errorDialog) {
+				$(errorDialog).modal({
+					closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
+					position: ["20%",],
+					containerId: 'confirm-container',
+					containerCss: {
+						'height': '220px',
+						'width': '565px'
+					},
+					onClose: function (dialog) {
+						window.location.reload();
+					}
+				});
+			}
+		}
+		else {
+			executionIds = data.execIds;
+		}
+	};
+	$.ajax({
 		url: requestURL,
 		async: false,
-		data: {"ajax":"getRunning", "project":projectName, "flow":flowId},
+		data: {
+			"ajax": "getRunning", 
+			"project": projectName, 
+			"flow": flowId
+		},
 		error: function(data) {},
-		success: function(data) {
-			if (data.error == "session") {
-				// We need to relogin.
-				var errorDialog = document.getElementById("invalid-session");
-				if (errorDialog) {
-					  $(errorDialog).modal({
-					      closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-					      position: ["20%",],
-					      containerId: 'confirm-container',
-					      containerCss: {
-					        'height': '220px',
-					        'width': '565px'
-					      },
-					      onClose: function (dialog) {
-					      	window.location.reload();
-					      }
-					    });
-				}
-			}
-			else {
-				executionIds = data.execIds;
-			}
-		}
+		success: successHandler
 	});
 	
 	return executionIds;
diff --git a/src/web/js/azkaban.common.utils.js b/src/web/js/azkaban.common.utils.js
new file mode 100644
index 0000000..147bb94
--- /dev/null
+++ b/src/web/js/azkaban.common.utils.js
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+function addClass(el, name) {
+	if (!hasClass(el, name)) { 
+		var classes = el.getAttribute("class");
+		classes += classes ? ' ' + name : '' +name;
+		el.setAttribute("class", classes);
+	}
+}
+
+function removeClass(el, name) {
+	if (hasClass(el, name)) {
+		var classes = el.getAttribute("class");
+		el.setAttribute("class", classes.replace(new RegExp('(\\s|^)'+name+'(\\s|$)'),' ').replace(/^\s+|\s+$/g, ''));
+	}
+}
+
+function hasClass(el, name) {
+	var classes = el.getAttribute("class");
+	if (classes == null) {
+		return false;
+	}
+	return new RegExp('(\\s|^)'+name+'(\\s|$)').test(classes);
+}
diff --git a/src/web/js/azkaban.context.menu.js b/src/web/js/azkaban.context.menu.js
index 9bf1ef0..0e92dd2 100644
--- a/src/web/js/azkaban.context.menu.js
+++ b/src/web/js/azkaban.context.menu.js
@@ -1,16 +1,34 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 azkaban.ContextMenuView = Backbone.View.extend({
-	events :  {
+	events: {
 	},
-	initialize : function(settings) {
+	
+  initialize: function(settings) {
 		var div = this.el;
 		$('body').click(function(e) {
 			$(".contextMenu").remove();
 		});
 		$('body').bind("contextmenu", function(e) {$(".contextMenu").remove()});
 	},
-	show : function(evt, menu) {
+	
+  show: function(evt, menu) {
 		console.log("Show context menu");
 		$(".contextMenu").remove();
 		var x = evt.pageX;
@@ -20,14 +38,17 @@ azkaban.ContextMenuView = Backbone.View.extend({
 		$(contextMenu).css({top: y, left: x});
 		$(this.el).after(contextMenu);
 	},
-	hide : function(evt) {
+	
+  hide: function(evt) {
 		console.log("Hide context menu");
 		$(".contextMenu").remove();
 	},
-	handleClick: function(evt) {
+	
+  handleClick: function(evt) {
 		console.log("handling click");
 	},
-	setupMenu: function(menu) {
+	
+  setupMenu: function(menu) {
 		var contextMenu = document.createElement("div");
 		$(contextMenu).addClass("contextMenu");
 		var ul = document.createElement("ul");
@@ -37,51 +58,51 @@ azkaban.ContextMenuView = Backbone.View.extend({
 			var menuItem = document.createElement("li");
 			if (menu[i].break) {
 				$(menuItem).addClass("break");
+			  $(ul).append(menuItem);
+        continue;
 			}
-			else {
-				var title = menu[i].title;
-				var callback = menu[i].callback;
-				$(menuItem).addClass("menuitem");
-				$(menuItem).text(title);
-				menuItem.callback = callback;
-				$(menuItem).click(function() { 
-					$(contextMenu).hide(); 
-					this.callback.call();});
-					
-				if (menu[i].submenu) {
-					var expandSymbol = document.createElement("div");
-					$(expandSymbol).addClass("expandSymbol");
-					$(menuItem).append(expandSymbol);
-					
-					var subMenu = this.setupMenu(menu[i].submenu);
-					$(subMenu).addClass("subMenu");
-					subMenu.parent = contextMenu;
-					menuItem.subMenu = subMenu;
-					$(subMenu).hide();
-					$(this.el).after(subMenu);
-					
-					$(menuItem).mouseenter(function() {
-						$(".subMenu").hide();
-						var menuItem = this;
-						menuItem.selected = true;
-						setTimeout(function() {
-							if (menuItem.selected) {
-								var offset = $(menuItem).offset();
-								var left = offset.left;
-								var top = offset.top;
-								var width = $(menuItem).width();
-								var subMenu = menuItem.subMenu;
-								
-								var newLeft = left + width - 5;
-								$(subMenu).css({left: newLeft, top: top});
-								$(subMenu).show();
-							}
-						}, 500);
-					});
-					$(menuItem).mouseleave(function() {this.selected = false;});
-				}
-			}
-
+      var title = menu[i].title;
+      var callback = menu[i].callback;
+      $(menuItem).addClass("menuitem");
+      $(menuItem).text(title);
+      menuItem.callback = callback;
+      $(menuItem).click(function() { 
+        $(contextMenu).hide(); 
+        this.callback.call();
+      });
+        
+      if (menu[i].submenu) {
+        var expandSymbol = document.createElement("div");
+        $(expandSymbol).addClass("expandSymbol");
+        $(menuItem).append(expandSymbol);
+        
+        var subMenu = this.setupMenu(menu[i].submenu);
+        $(subMenu).addClass("subMenu");
+        subMenu.parent = contextMenu;
+        menuItem.subMenu = subMenu;
+        $(subMenu).hide();
+        $(this.el).after(subMenu);
+        
+        $(menuItem).mouseenter(function() {
+          $(".subMenu").hide();
+          var menuItem = this;
+          menuItem.selected = true;
+          setTimeout(function() {
+            if (menuItem.selected) {
+              var offset = $(menuItem).offset();
+              var left = offset.left;
+              var top = offset.top;
+              var width = $(menuItem).width();
+              var subMenu = menuItem.subMenu;
+              
+              var newLeft = left + width - 5;
+              $(subMenu).css({left: newLeft, top: top});
+              $(subMenu).show();
+            }
+          }, 500);
+        });
+        $(menuItem).mouseleave(function() {this.selected = false;});
+      }
 			$(ul).append(menuItem);
 		}
 
@@ -93,4 +114,4 @@ var contextMenuView;
 $(function() {
 	contextMenuView = new azkaban.ContextMenuView({el:$('#contextMenu')});
 	contextMenuView.hide();
-});
\ No newline at end of file
+});
diff --git a/src/web/js/azkaban.date.utils.js b/src/web/js/azkaban.date.utils.js
index 78dbd90..a65ec50 100644
--- a/src/web/js/azkaban.date.utils.js
+++ b/src/web/js/azkaban.date.utils.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 var getDuration = function(startMs, endMs) {
 	if (startMs) {
 		if (endMs == null || endMs < startMs) {
@@ -51,7 +67,8 @@ var getDateFormat = function(date) {
 	var minutes = getTwoDigitStr(date.getMinutes());
 	var second = getTwoDigitStr(date.getSeconds());
 
-	var datestring = year + "-" + month + "-" + day + "  " + hours + ":" + minutes + " " + second + "s";
+	var datestring = year + "-" + month + "-" + day + "  " + hours + ":" + 
+			minutes + " " + second + "s";
 	return datestring;
 }
 
@@ -70,4 +87,4 @@ var getTwoDigitStr = function(value) {
 	}
 	
 	return value;
-}
\ No newline at end of file
+}
diff --git a/src/web/js/azkaban.executions.view.js b/src/web/js/azkaban.executions.view.js
new file mode 100644
index 0000000..58b75c4
--- /dev/null
+++ b/src/web/js/azkaban.executions.view.js
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+$.namespace('azkaban');
+
+var executionsTabView;
+azkaban.ExecutionsTabView = Backbone.View.extend({
+  events: {
+    'click #currently-running-view-link': 'handleCurrentlyRunningViewLinkClick',
+    'click #recently-finished-view-link': 'handleRecentlyFinishedViewLinkClick'
+  },
+
+  initialize: function(settings) {
+    var selectedView = settings.selectedView;
+    if (selectedView == 'recently-finished') {
+      this.handleRecentlyFinishedViewLinkClick();
+    }
+    else {
+      this.handleCurrentlyRunningViewLinkClick();
+    }
+  },
+
+  render: function() {
+  },
+
+  handleCurrentlyRunningViewLinkClick: function() {
+    $('#recently-finished-view-link').removeClass('active');
+    $('#recently-finished-view').hide();
+    $('#currently-running-view-link').addClass('active');
+    $('#currently-running-view').show();
+  },
+
+  handleRecentlyFinishedViewLinkClick: function() {
+    $('#currently-running-view-link').removeClass('active');
+    $('#currently-running-view').hide();
+    $('#recently-finished-view-link').addClass('active');
+    $('#recently-finished-view').show();
+  }
+});
+
+$(function() {
+  executionsTabView = new azkaban.ExecutionsTabView({el: $('#header-tabs')});
+  if (window.location.hash) {
+    var hash = window.location.hash;
+    if (hash == '#recently-finished') {
+      executionsTabView.handleRecentlyFinishedLinkClick();
+    }
+  }
+});
diff --git a/src/web/js/azkaban.exflow.view.js b/src/web/js/azkaban.exflow.view.js
index bde19da..4ac616b 100644
--- a/src/web/js/azkaban.exflow.view.js
+++ b/src/web/js/azkaban.exflow.view.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var handleJobMenuClick = function(action, el, pos) {
@@ -12,12 +28,12 @@ var handleJobMenuClick = function(action, el, pos) {
 }
 
 var statusView;
-azkaban.StatusView= Backbone.View.extend({
-	initialize : function(settings) {
+azkaban.StatusView = Backbone.View.extend({
+	initialize: function(settings) {
 		this.model.bind('change:graph', this.render, this);
 		this.model.bind('change:update', this.statusUpdate, this);
 	},
-	render : function(evt) {
+	render: function(evt) {
 		var data = this.model.get("data");
 		
 		var user = data.submitUser;
@@ -25,7 +41,8 @@ azkaban.StatusView= Backbone.View.extend({
 		
 		this.statusUpdate(evt);
 	},
-	statusUpdate : function(evt) {
+	
+	statusUpdate: function(evt) {
 		var data = this.model.get("data");
 		
 		statusItem = $("#flowStatus");
@@ -67,202 +84,189 @@ azkaban.StatusView= Backbone.View.extend({
 });
 
 var flowTabView;
-azkaban.FlowTabView= Backbone.View.extend({
-  events : {
-  	"click #graphViewLink" : "handleGraphLinkClick",
-  	"click #jobslistViewLink" : "handleJobslistLinkClick",
-  	"click #flowLogViewLink" : "handleLogLinkClick",
-  	"click #cancelbtn" : "handleCancelClick",
-  	"click #executebtn" : "handleRestartClick",
-  	"click #pausebtn" : "handlePauseClick",
-  	"click #resumebtn" : "handleResumeClick",
-  	"click #retrybtn" : "handleRetryClick"
-  },
-  initialize : function(settings) {
-  	$("#cancelbtn").hide();
-  	$("#executebtn").hide();
-  	$("#pausebtn").hide();
-  	$("#resumebtn").hide();
-  	$("#retrybtn").hide();
-  
- 	this.model.bind('change:graph', this.handleFlowStatusChange, this);
-	this.model.bind('change:update', this.handleFlowStatusChange, this);
-	
-  	var selectedView = settings.selectedView;
-  	if (selectedView == "jobslist") {
-  		this.handleJobslistLinkClick();
-  	}
-  	else {
-  		this.handleGraphLinkClick();
-  	}
-  },
-  render: function() {
-  	console.log("render graph");
-  },
-  handleGraphLinkClick: function(){
-  	$("#jobslistViewLink").removeClass("selected");
-  	$("#graphViewLink").addClass("selected");
-  	$("#flowLogViewLink").removeClass("selected");
-  	
-  	$("#jobListView").hide();
-  	$("#graphView").show();
-  	$("#flowLogView").hide();
-  },
-  handleJobslistLinkClick: function() {
-  	$("#graphViewLink").removeClass("selected");
-  	$("#jobslistViewLink").addClass("selected");
-  	$("#flowLogViewLink").removeClass("selected");
-  	
-  	$("#graphView").hide();
-  	$("#jobListView").show();
-  	$("#flowLogView").hide();
-  },
-  handleLogLinkClick: function() {
-  	$("#graphViewLink").removeClass("selected");
-  	$("#jobslistViewLink").removeClass("selected");
-  	$("#flowLogViewLink").addClass("selected");
-  	
-  	$("#graphView").hide();
-  	$("#jobListView").hide();
-  	$("#flowLogView").show();
-  },
-  handleFlowStatusChange: function() {
-  	var data = this.model.get("data");
-  	$("#cancelbtn").hide();
-  	$("#executebtn").hide();
-  	$("#pausebtn").hide();
-  	$("#resumebtn").hide();
-  	$("#retrybtn").hide();
-
-  	if(data.status=="SUCCEEDED") {
-  	  	$("#executebtn").show();
-  	}
-  	else if (data.status=="FAILED") {
-  		$("#executebtn").show();
-  	}
-  	else if (data.status=="FAILED_FINISHING") {
-  		$("#cancelbtn").show();
-  		$("#executebtn").hide();
-  		$("#retrybtn").show();
-  	}
-  	else if (data.status=="RUNNING") {
-  		$("#cancelbtn").show();
-  		$("#pausebtn").show();
-  	}
-  	else if (data.status=="PAUSED") {
-  		$("#cancelbtn").show();
-  		$("#resumebtn").show();
-  	}
-  	else if (data.status=="WAITING") {
-  		$("#cancelbtn").show();
-  	}
-  	else if (data.status=="KILLED") {
-  		$("#executebtn").show();
-  	}
-  },
-  handleCancelClick : function(evt) {
-    var requestURL = contextURL + "/executor";
-	ajaxCall(
-		requestURL,
-		{"execid": execId, "ajax":"cancelFlow"},
-		function(data) {
-          console.log("cancel clicked");
-          if (data.error) {
-          	showDialog("Error", data.error);
-          }
-          else {
-            showDialog("Cancelled", "Flow has been cancelled.");
-
-            setTimeout(function() {updateStatus();}, 1100);
-          }
-      	}
-      );
-  },
-  handleRetryClick : function(evt) {
-      var graphData = graphModel.get("data");
-
-      var requestURL = contextURL + "/executor";
-	  ajaxCall(
-		requestURL,
-		{"execid": execId, "ajax":"retryFailedJobs"},
-		function(data) {
-          console.log("cancel clicked");
-          if (data.error) {
-          	showDialog("Error", data.error);
-          }
-          else {
-            showDialog("Retry", "Flow has been retried.");
-            setTimeout(function() {updateStatus();}, 1100);
-          }
-      	}
-      );
-  },
-  handleRestartClick : function(evt) {
-  	var data = graphModel.get("data");
-  	var nodes = data.nodes;
-  
-    var executingData = {
-  		project: projectName,
-  		ajax: "executeFlow",
-  		flow: flowId,
-  		execid: execId
-	};
-
-  	flowExecuteDialogView.show(executingData);
-  },
-  handlePauseClick : function(evt) {
-  	  var requestURL = contextURL + "/executor";
-		ajaxCall(
-	      requestURL,
-	      {"execid": execId, "ajax":"pauseFlow"},
-	      function(data) {
-	          console.log("pause clicked");
-	          if (data.error) {
-	          	showDialog("Error", data.error);
-	          }
-	          else {
-	            showDialog("Paused", "Flow has been paused.");
-	            
-            	setTimeout(function() {updateStatus();}, 1100);
-	          }
-	      }
-      );
-  },
-  handleResumeClick : function(evt) {
-     var requestURL = contextURL + "/executor";
-     ajaxCall(
-          requestURL,
-	      {"execid": execId, "ajax":"resumeFlow"},
-	      function(data) {
-	          console.log("pause clicked");
-	          if (data.error) {
-	          	showDialog("Error", data.error);
-	          }
-	          else {
-	          	showDialog("Resumed", "Flow has been resumed.");
-            	setTimeout(function() {updateStatus();}, 1100);
-	          }
-	      }
-	  );
-  }
+azkaban.FlowTabView = Backbone.View.extend({
+	events: {
+		"click #graphViewLink": "handleGraphLinkClick",
+		"click #jobslistViewLink": "handleJobslistLinkClick",
+		"click #flowLogViewLink": "handleLogLinkClick",
+		"click #cancelbtn": "handleCancelClick",
+		"click #executebtn": "handleRestartClick",
+		"click #pausebtn": "handlePauseClick",
+		"click #resumebtn": "handleResumeClick",
+		"click #retrybtn": "handleRetryClick"
+	},
+	
+	initialize: function(settings) {
+		$("#cancelbtn").hide();
+		$("#executebtn").hide();
+		$("#pausebtn").hide();
+		$("#resumebtn").hide();
+		$("#retrybtn").hide();
+	
+		this.model.bind('change:graph', this.handleFlowStatusChange, this);
+		this.model.bind('change:update', this.handleFlowStatusChange, this);
+	
+		var selectedView = settings.selectedView;
+		if (selectedView == "jobslist") {
+			this.handleJobslistLinkClick();
+		}
+		else {
+			this.handleGraphLinkClick();
+		}
+	},
+	
+	render: function() {
+		console.log("render graph");
+	},
+	
+	handleGraphLinkClick: function(){
+		$("#jobslistViewLink").removeClass("active");
+		$("#graphViewLink").addClass("active");
+		$("#flowLogViewLink").removeClass("active");
+		
+		$("#jobListView").hide();
+		$("#graphView").show();
+		$("#flowLogView").hide();
+	},
+	
+	handleJobslistLinkClick: function() {
+		$("#graphViewLink").removeClass("active");
+		$("#jobslistViewLink").addClass("active");
+		$("#flowLogViewLink").removeClass("active");
+		
+		$("#graphView").hide();
+		$("#jobListView").show();
+		$("#flowLogView").hide();
+	},
+	
+	handleLogLinkClick: function() {
+		$("#graphViewLink").removeClass("active");
+		$("#jobslistViewLink").removeClass("active");
+		$("#flowLogViewLink").addClass("active");
+		
+		$("#graphView").hide();
+		$("#jobListView").hide();
+		$("#flowLogView").show();
+	},
+	
+	handleFlowStatusChange: function() {
+		var data = this.model.get("data");
+		$("#cancelbtn").hide();
+		$("#executebtn").hide();
+		$("#pausebtn").hide();
+		$("#resumebtn").hide();
+		$("#retrybtn").hide();
+
+		if (data.status == "SUCCEEDED") {
+      $("#executebtn").show();
+		}
+		else if (data.status == "FAILED") {
+			$("#executebtn").show();
+		}
+		else if (data.status == "FAILED_FINISHING") {
+			$("#cancelbtn").show();
+			$("#executebtn").hide();
+			$("#retrybtn").show();
+		}
+		else if (data.status == "RUNNING") {
+			$("#cancelbtn").show();
+			$("#pausebtn").show();
+		}
+		else if (data.status == "PAUSED") {
+			$("#cancelbtn").show();
+			$("#resumebtn").show();
+		}
+		else if (data.status == "WAITING") {
+			$("#cancelbtn").show();
+		}
+		else if (data.status == "KILLED") {
+			$("#executebtn").show();
+		}
+	},
+	
+	handleCancelClick: function(evt) {
+		var requestURL = contextURL + "/executor";
+		var requestData = {"execid": execId, "ajax": "cancelFlow"};
+		var successHandler = function(data) {
+			console.log("cancel clicked");
+			if (data.error) {
+				showDialog("Error", data.error);
+			}
+			else {
+				showDialog("Cancelled", "Flow has been cancelled.");
+				setTimeout(function() {updateStatus();}, 1100);
+			}
+		};
+		ajaxCall(requestURL, requestData, successHandler);
+	},
+	
+	handleRetryClick: function(evt) {
+		var graphData = graphModel.get("data");
+		var requestURL = contextURL + "/executor";
+		var requestData = {"execid": execId, "ajax": "retryFailedJobs"};
+		var successHandler = function(data) {
+			console.log("cancel clicked");
+			if (data.error) {
+				showDialog("Error", data.error);
+			}
+			else {
+				showDialog("Retry", "Flow has been retried.");
+				setTimeout(function() {updateStatus();}, 1100);
+			}
+		};
+		ajaxCall(requestURL, requestData, successHandler);
+	},
+	
+	handleRestartClick: function(evt) {
+    console.log("handleRestartClick");
+		var data = graphModel.get("data");
+		var nodes = data.nodes;
+		var executingData = {
+			project: projectName,
+			ajax: "executeFlow",
+			flow: flowId,
+			execid: execId
+		};
+		flowExecuteDialogView.show(executingData);
+	},
+	
+	handlePauseClick: function(evt) {
+		var requestURL = contextURL + "/executor";
+		var requestData = {"execid": execId, "ajax":"pauseFlow"};
+		var successHandler = function(data) {
+			console.log("pause clicked");
+			if (data.error) {
+				showDialog("Error", data.error);
+			}
+			else {
+				showDialog("Paused", "Flow has been paused.");
+				setTimeout(function() {updateStatus();}, 1100);
+			}
+		};
+		ajaxCall(requestURL, requestData, successHandler);
+	},
+	
+	handleResumeClick: function(evt) {
+		var requestURL = contextURL + "/executor";
+		var requestData = {"execid": execId, "ajax":"resumeFlow"};
+		var successHandler = function(data) {
+			console.log("pause clicked");
+			if (data.error) {
+				showDialog("Error", data.error);
+			}
+			else {
+				showDialog("Resumed", "Flow has been resumed.");
+				setTimeout(function() {updateStatus();}, 1100);
+			}
+		};
+		ajaxCall(requestURL, requestData, successHandler);
+	}
 });
 
 var showDialog = function(title, message) {
-  $('#messageTitle').text(title);
-
-  $('#messageBox').text(message);
-
-  $('#messageDialog').modal({
-      closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-      position: ["20%",],
-      containerId: 'confirm-container',
-      containerCss: {
-        'height': '220px',
-        'width': '565px'
-      },
-      onShow: function (dialog) {
-      }
-    });
+	$('#messageTitle').text(title);
+	$('#messageBox').text(message);
+	$('#messageDialog').modal();
 }
 
 var jobListView;
@@ -271,19 +275,22 @@ var mainSvgGraphView;
 var executionListView;
 azkaban.ExecutionListView = Backbone.View.extend({
 	events: {
-//		"click .progressBox" : "handleProgressBoxClick"
+		//"click .flow-progress-bar": "handleProgressBoxClick"
 	},
+	
 	initialize: function(settings) {
 		this.model.bind('change:graph', this.renderJobs, this);
 		this.model.bind('change:update', this.updateJobs, this);
 	},
+	
 	renderJobs: function(evt) {
 		var data = this.model.get("data");
 		var lastTime = data.endTime == -1 ? (new Date()).getTime() : data.endTime;
 		this.updateJobRow(data.nodes);
 		this.updateProgressBar(data);
 	},
-/*	handleProgressBoxClick: function(evt) {
+
+	/*handleProgressBoxClick: function(evt) {
 		var target = evt.currentTarget;
 		var job = target.job;
 		var attempt = target.attempt;
@@ -301,6 +308,7 @@ azkaban.ExecutionListView = Backbone.View.extend({
 	
 		contextMenuView.show(evt, menu);
 	},*/
+	
 	updateJobs: function(evt) {
 		var data = this.model.get("update");
 		var lastTime = data.endTime == -1 ? (new Date()).getTime() : data.endTime;
@@ -308,84 +316,87 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		this.updateJobRow(data.nodes);
 		this.updateProgressBar(this.model.get("data"));
 	},
+	
 	updateJobRow: function(nodes) {
 		var executingBody = $("#executableBody");
 		nodes.sort(function(a,b) { return a.startTime - b.startTime; });
 		
 		for (var i = 0; i < nodes.length; ++i) {
 			var node = nodes[i];
-			if (node.startTime > -1) {
-				var nodeId = node.id.replace(".", "\\\\.");
-				var row = document.getElementById(nodeId + "-row");
-				if (!row) {
-					this.addNodeRow(node);
-				}
-				
-				var div = $("#" + nodeId + "-status-div");
-				div.text(statusStringMap[node.status]);
-				$(div).attr("class", "status " + node.status);
-				
-				var startdate = new Date(node.startTime);
-				$("#" + nodeId + "-start").text(getDateFormat(startdate));
-				
-				var endTime = node.endTime;
-				if (node.endTime == -1) {
-					$("#" + nodeId + "-end").text("-");
-					endTime = node.startTime + 1;
-				}
-				else {
-					var enddate = new Date(node.endTime);
-					$("#" + nodeId + "-end").text(getDateFormat(enddate));
-				}
-				
-				var progressBar = $("#" + nodeId + "-progressbar");
-				if (!progressBar.hasClass(node.status)) {
-					for (var j = 0; j < statusList.length; ++j) {
-						var status = statusList[j];
-						progressBar.removeClass(status);
-					}
-					progressBar.addClass(node.status);
-				}
-				
-				// Create past attempts
-				if (node.pastAttempts) {
-					for (var a = 0; a < node.pastAttempts.length; ++a) {
-						var attemptBarId = nodeId + "-progressbar-" + a;
-						var attempt = node.pastAttempts[a];
-						if ($("#" + attemptBarId).length == 0) {
-							var attemptBox = document.createElement("div");
-							$(attemptBox).attr("id", attemptBarId);
-							$(attemptBox).addClass("progressBox");
-							$(attemptBox).addClass("attempt");
-							$(attemptBox).addClass(attempt.status);
-							$(attemptBox).css("float","left");
-							$(attemptBox).bind("contextmenu", attemptRightClick);
-							$(progressBar).before(attemptBox);
-							attemptBox.job = nodeId;
-							attemptBox.attempt = a;
-						}
-					}
-				}
-				
-				if (node.endTime == -1) {
-//					$("#" + node.id + "-elapse").text("0 sec");
-					$("#" + nodeId + "-elapse").text(getDuration(node.startTime, (new Date()).getTime()));					
-				}
-				else {
-					$("#" + nodeId + "-elapse").text(getDuration(node.startTime, node.endTime));
-				}
-			}
+			if (node.startTime < 0) {
+        continue;
+      }
+      var nodeId = node.id.replace(".", "\\\\.");
+      var row = document.getElementById(nodeId + "-row");
+      if (!row) {
+        this.addNodeRow(node);
+      }
+      
+      var div = $("#" + nodeId + "-status-div");
+      div.text(statusStringMap[node.status]);
+      $(div).attr("class", "status " + node.status);
+      
+      var startdate = new Date(node.startTime);
+      $("#" + nodeId + "-start").text(getDateFormat(startdate));
+      
+      var endTime = node.endTime;
+      if (node.endTime == -1) {
+        $("#" + nodeId + "-end").text("-");
+        endTime = node.startTime + 1;
+      }
+      else {
+        var enddate = new Date(node.endTime);
+        $("#" + nodeId + "-end").text(getDateFormat(enddate));
+      }
+      
+      var progressBar = $("#" + nodeId + "-progressbar");
+      if (!progressBar.hasClass(node.status)) {
+        for (var j = 0; j < statusList.length; ++j) {
+          var status = statusList[j];
+          progressBar.removeClass(status);
+        }
+        progressBar.addClass(node.status);
+      }
+      
+      // Create past attempts
+      if (node.pastAttempts) {
+        for (var a = 0; a < node.pastAttempts.length; ++a) {
+          var attemptBarId = nodeId + "-progressbar-" + a;
+          var attempt = node.pastAttempts[a];
+          if ($("#" + attemptBarId).length == 0) {
+            var attemptBox = document.createElement("div");
+            $(attemptBox).attr("id", attemptBarId);
+            $(attemptBox).addClass("flow-progress-bar");
+            $(attemptBox).addClass("attempt");
+            $(attemptBox).addClass(attempt.status);
+            $(attemptBox).css("float","left");
+            $(attemptBox).bind("contextmenu", attemptRightClick);
+            $(progressBar).before(attemptBox);
+            attemptBox.job = nodeId;
+            attemptBox.attempt = a;
+          }
+        }
+      }
+      
+      if (node.endTime == -1) {
+        //$("#" + node.id + "-elapse").text("0 sec");
+        $("#" + nodeId + "-elapse").text(getDuration(node.startTime, (new Date()).getTime()));					
+      }
+      else {
+        $("#" + nodeId + "-elapse").text(getDuration(node.startTime, node.endTime));
+      }
 		}
 	},
+	
 	updateProgressBar: function(data) {
-		if(data.startTime == -1) {
+		if (data.startTime == -1) {
 			return;
 		}
 		
 		var flowLastTime = data.endTime == -1 ? (new Date()).getTime() : data.endTime;
 		var flowStartTime = data.startTime;
 
-		var outerWidth = $(".outerProgress").css("width");
+		var outerWidth = $(".flow-progress").css("width");
 		if (outerWidth) {
 			if (outerWidth.substring(outerWidth.length - 2, outerWidth.length) == "px") {
 				outerWidth = outerWidth.substring(0, outerWidth.length - 2);
@@ -408,24 +419,24 @@ azkaban.ExecutionListView = Backbone.View.extend({
 			
 			// Add all the attempts
 			if (node.pastAttempts) {
-				var logURL = contextURL + "/executor?execid=" + execId + "&job=" + node.id + "&attempt=" +  node.pastAttempts.length;
+				var logURL = contextURL + "/executor?execid=" + execId + "&job=" + node.id + "&attempt=" +	node.pastAttempts.length;
 				var aId = node.id + "-log-link";
 				$("#" + aId).attr("href", logURL);
 				elem.attempt = node.pastAttempts.length;
 				
 				// Calculate the node attempt bars
-				for(var p = 0; p < node.pastAttempts.length; ++p) {
+				for (var p = 0; p < node.pastAttempts.length; ++p) {
 					var pastAttempt = node.pastAttempts[p];
 					var pastAttemptBox = $("#" + nodeId + "-progressbar-" + p);
 					
 					var left = (pastAttempt.startTime - flowStartTime)*factor;
-					var width =  Math.max((pastAttempt.endTime - pastAttempt.startTime)*factor, 3);
+					var width =	Math.max((pastAttempt.endTime - pastAttempt.startTime)*factor, 3);
 					
 					var margin = left - offsetLeft;
 					$(pastAttemptBox).css("margin-left", left - offsetLeft);
 					$(pastAttemptBox).css("width", width);
 					
-					$(pastAttemptBox).attr("title", "attempt:" + p + "  start:" + getHourMinSec(new Date(pastAttempt.startTime)) + "  end:" + getHourMinSec(new Date(pastAttempt.endTime)));
+					$(pastAttemptBox).attr("title", "attempt:" + p + "	start:" + getHourMinSec(new Date(pastAttempt.startTime)) + "	end:" + getHourMinSec(new Date(pastAttempt.endTime)));
 					offsetLeft += width + margin;
 				}
 			}
@@ -438,9 +449,10 @@ azkaban.ExecutionListView = Backbone.View.extend({
 			
 			elem.css("margin-left", left)
 			elem.css("width", width);
-			elem.attr("title", "attempt:" + elem.attempt + "  start:" + getHourMinSec(new Date(node.startTime)) + "  end:" + getHourMinSec(new Date(node.endTime)));
+			elem.attr("title", "attempt:" + elem.attempt + "	start:" + getHourMinSec(new Date(node.startTime)) + "	end:" + getHourMinSec(new Date(node.endTime)));
 		}
 	},
+	
 	addNodeRow: function(node) {
 		var executingBody = $("#executableBody");
 		var tr = document.createElement("tr");
@@ -450,7 +462,7 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		var tdEnd = document.createElement("td");
 		var tdElapse = document.createElement("td");
 		var tdStatus = document.createElement("td");
-		var tdLog = document.createElement("td");
+		var tdDetails = document.createElement("td");
 		
 		$(tr).append(tdName);
 		$(tr).append(tdTimeline);
@@ -458,7 +470,7 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		$(tr).append(tdEnd);
 		$(tr).append(tdElapse);
 		$(tr).append(tdStatus);
-		$(tr).append(tdLog);
+		$(tr).append(tdDetails);
 		$(tr).attr("id", node.id + "-row");
 		$(tdTimeline).attr("id", node.id + "-timeline");
 		$(tdStart).attr("id", node.id + "-start");
@@ -468,12 +480,12 @@ azkaban.ExecutionListView = Backbone.View.extend({
 
 		var outerProgressBar = document.createElement("div");
 		$(outerProgressBar).attr("id", node.id + "-outerprogressbar");
-		$(outerProgressBar).addClass("outerProgress");
+		$(outerProgressBar).addClass("flow-progress");
 
 		var progressBox = document.createElement("div");
 		progressBox.job = node.id;
 		$(progressBox).attr("id", node.id + "-progressbar");
-		$(progressBox).addClass("progressBox");
+		$(progressBox).addClass("flow-progress-bar");
 		$(outerProgressBar).append(progressBox);
 		$(tdTimeline).append(outerProgressBar);
 		$(tdTimeline).addClass("timeline");
@@ -497,10 +509,10 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		var a = document.createElement("a");
 		$(a).attr("href", logURL);
 		$(a).attr("id", node.id + "-log-link");
-		$(a).text("Log");
-		$(tdLog).addClass("logLink");
-		$(tdLog).append(a);
-
+		$(a).text("Details");
+		$(tdDetails).addClass("details");
+		$(tdDetails).append(a);
+		
 		executingBody.append(tr);
 	}
 });
@@ -520,31 +532,36 @@ azkaban.FlowLogView = Backbone.View.extend({
 		var model = this.model;
 		console.log("fetchLogs offset is " + offset)
 
-		$.ajax({async:false, 
-			url:requestURL,
-			data:{"execid": execId, "ajax":"fetchExecFlowLogs", "offset": offset, "length": 50000},
-			success:
-				function(data) {
-					console.log("fetchLogs");
-					if (data.error) {
-						console.log(data.error);
+		$.ajax({
+			async: false, 
+			url: requestURL,
+			data: {
+				"execid": execId, 
+				"ajax": "fetchExecFlowLogs", 
+				"offset": offset, 
+				"length": 50000
+			},
+			success: function(data) {
+				console.log("fetchLogs");
+				if (data.error) {
+					console.log(data.error);
+				}
+				else {
+					var log = $("#logSection").text();
+					if (!log) {
+						log = data.data;
 					}
 					else {
-						var log = $("#logSection").text();
-						if (!log) {
-							log = data.data;
-						}
-						else {
-							log += data.data;
-						}
-	
-						var newOffset = data.offset + data.length;
-	
-						$("#logSection").text(log);
-						model.set({"offset": newOffset, "log": log});
-						$(".logViewer").scrollTop(9999);
+						log += data.data;
 					}
+
+					var newOffset = data.offset + data.length;
+
+					$("#logSection").text(log);
+					model.set({"offset": newOffset, "log": log});
+					$(".logViewer").scrollTop(9999);
 				}
+			}
 		});
 	}
 });
@@ -560,48 +577,57 @@ var updateStatus = function() {
 	var oldData = graphModel.get("data");
 	var nodeMap = graphModel.get("nodeMap");
 	
-	ajaxCall(
-	      requestURL,
-	      {"execid": execId, "ajax":"fetchexecflowupdate", "lastUpdateTime": updateTime},
-	      function(data) {
-	          console.log("data updated");
-	          updateTime = data.updateTime;
-	          oldData.submitTime = data.submitTime;
-	          oldData.startTime = data.startTime;
-	          oldData.endTime = data.endTime;
-	          oldData.status = data.status;
-	          
-	          for (var i = 0; i < data.nodes.length; ++i) {
-	          	var node = data.nodes[i];
-	          	var oldNode = nodeMap[node.id];
-	          	oldNode.startTime = node.startTime;
-	          	oldNode.updateTime = node.updateTime;
-	          	oldNode.endTime = node.endTime;
-	          	oldNode.status = node.status;
-	          	oldNode.attempt = node.attempt;
-	          	if (oldNode.attempt > 0) {
-	          		oldNode.pastAttempts = node.pastAttempts;
-	          	}
-	          }
-
-	          graphModel.set({"update": data});
-	          graphModel.trigger("change:update");
-	      });
+	var requestData = {
+		"execid": execId, 
+		"ajax": "fetchexecflowupdate", 
+		"lastUpdateTime": updateTime
+	};
+
+	var successHandler = function(data) {
+		console.log("data updated");
+		updateTime = data.updateTime;
+		oldData.submitTime = data.submitTime;
+		oldData.startTime = data.startTime;
+		oldData.endTime = data.endTime;
+		oldData.status = data.status;
+		
+		for (var i = 0; i < data.nodes.length; ++i) {
+			var node = data.nodes[i];
+			var oldNode = nodeMap[node.id];
+			oldNode.startTime = node.startTime;
+			oldNode.updateTime = node.updateTime;
+			oldNode.endTime = node.endTime;
+			oldNode.status = node.status;
+			oldNode.attempt = node.attempt;
+			if (oldNode.attempt > 0) {
+				oldNode.pastAttempts = node.pastAttempts;
+			}
+		}
+
+		graphModel.set({"update": data});
+		graphModel.trigger("change:update");
+	};
+	ajaxCall(requestURL, requestData, successHandler);
 }
 
 var updateTime = -1;
 var updaterFunction = function() {
 	var oldData = graphModel.get("data");
-	var keepRunning = oldData.status != "SUCCEEDED" && oldData.status != "FAILED" && oldData.status != "KILLED";
+	var keepRunning = 
+			oldData.status != "SUCCEEDED" && 
+			oldData.status != "FAILED" && 
+			oldData.status != "KILLED";
 
 	if (keepRunning) {
 		updateStatus();
 
 		var data = graphModel.get("data");
-		if (data.status == "UNKNOWN" || data.status == "WAITING" || data.status == "PREPARING") {
+		if (data.status == "UNKNOWN" || 
+				data.status == "WAITING" || 
+				data.status == "PREPARING") {
 			setTimeout(function() {updaterFunction();}, 1000);
 		}
-		else if (data.status != "SUCCEEDED" && data.status != "FAILED" ) {
+		else if (data.status != "SUCCEEDED" && data.status != "FAILED") {
 			// 5 sec updates
 			setTimeout(function() {updaterFunction();}, 5000);
 		}
@@ -616,7 +642,10 @@ var updaterFunction = function() {
 
 var logUpdaterFunction = function() {
 	var oldData = graphModel.get("data");
-	var keepRunning = oldData.status != "SUCCEEDED" && oldData.status != "FAILED" && oldData.status != "KILLED";
+	var keepRunning = 
+			oldData.status != "SUCCEEDED" && 
+			oldData.status != "FAILED" && 
+			oldData.status != "KILLED";
 	if (keepRunning) {
 		// update every 30 seconds for the logs until finished
 		flowLogView.handleUpdate();
@@ -631,10 +660,12 @@ var exNodeClickCallback = function(event) {
 	console.log("Node clicked callback");
 	var jobId = event.currentTarget.jobid;
 	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
+	var visualizerURL = contextURL + "/pigvisualizer?execid=" + execId + "&jobid=" + jobId;
 
 	var menu = [	
-			{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
-			{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}}
+		{title: "Open Job...", callback: function() {window.location.href = requestURL;}},
+		{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
+		{title: "Visualize Job...", callback: function() {window.location.href = visualizerURL;}}
 	];
 
 	contextMenuView.show(event, menu);
@@ -644,10 +675,12 @@ var exJobClickCallback = function(event) {
 	console.log("Node clicked callback");
 	var jobId = event.currentTarget.jobid;
 	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
+	var visualizerURL = contextURL + "/pigvisualizer?execid=" + execId + "&jobid=" + jobId;
 
 	var menu = [	
-			{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
-			{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}}
+		{title: "Open Job...", callback: function() {window.location.href = requestURL;}},
+		{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
+		{title: "Visualize Job...", callback: function() {window.location.href = visualizerURL;}}
 	];
 
 	contextMenuView.show(event, menu);
@@ -680,8 +713,8 @@ var attemptRightClick = function(event) {
 	var requestURL = contextURL + "/executor?project=" + projectName + "&execid=" + execId + "&job=" + job + "&attempt=" + attempt;
 
 	var menu = [	
-			{title: "Open Attempt Log...", callback: function() {window.location.href=requestURL;}},
-			{title: "Open Attempt Log in New Window...", callback: function() {window.open(requestURL);}}
+		{title: "Open Attempt Log...", callback: function() {window.location.href=requestURL;}},
+		{title: "Open Attempt Log in New Window...", callback: function() {window.open(requestURL);}}
 	];
 
 	contextMenuView.show(event, menu);
@@ -694,66 +727,92 @@ $(function() {
 	graphModel = new azkaban.GraphModel();
 	logModel = new azkaban.LogModel();
 	
-	flowTabView = new azkaban.FlowTabView({el:$( '#headertabs'), model: graphModel});
-	mainSvgGraphView = new azkaban.SvgGraphView({el:$('#svgDiv'), model: graphModel, rightClick:  { "node": exNodeClickCallback, "edge": exEdgeClickCallback, "graph": exGraphClickCallback }});
-	jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel, contextMenuCallback: exJobClickCallback});
-	flowLogView = new azkaban.FlowLogView({el:$('#flowLogView'), model: logModel});
-	statusView = new azkaban.StatusView({el:$('#flow-status'), model: graphModel});
+	flowTabView = new azkaban.FlowTabView({
+		el: $('#headertabs'), 
+		model: graphModel
+	});
+	
+  mainSvgGraphView = new azkaban.SvgGraphView({
+		el: $('#svgDiv'), 
+		model: graphModel, 
+		rightClick:	{ 
+			"node": exNodeClickCallback, 
+			"edge": exEdgeClickCallback, 
+			"graph": exGraphClickCallback 
+		}
+	});
+	
+  jobsListView = new azkaban.JobListView({
+		el: $('#jobList'), 
+		model: graphModel, 
+		contextMenuCallback: exJobClickCallback
+	});
+	
+  flowLogView = new azkaban.FlowLogView({
+		el: $('#flowLogView'), 
+		model: logModel
+	});
+	
+  statusView = new azkaban.StatusView({
+		el: $('#flow-status'), 
+		model: graphModel
+	});
 	
-	executionListView = new azkaban.ExecutionListView({el: $('#jobListView'), model:graphModel});
+	executionListView = new azkaban.ExecutionListView({
+		el: $('#jobListView'), 
+		model: graphModel
+	});
 	
 	var requestURL = contextURL + "/executor";
-
-	ajaxCall(
-	      requestURL,
-	      {"execid": execId, "ajax":"fetchexecflow"},
-	      function(data) {
-	          console.log("data fetched");
-	          graphModel.set({data: data});
-	          graphModel.set({disabled: {}});
-	          graphModel.trigger("change:graph");
-	          
-	          updateTime = Math.max(updateTime, data.submitTime);
-	          updateTime = Math.max(updateTime, data.startTime);
-	          updateTime = Math.max(updateTime, data.endTime);
-	          
-	          var nodeMap = {};
-	          for (var i = 0; i < data.nodes.length; ++i) {
-	             var node = data.nodes[i];
-	             nodeMap[node.id] = node;
-	             updateTime = Math.max(updateTime, node.startTime);
-	             updateTime = Math.max(updateTime, node.endTime);
-	          }
-	          for (var i = 0; i < data.edges.length; ++i) {
-	          	 var edge = data.edges[i];
-	          	 
-	          	 if (!nodeMap[edge.target].in) {
-	          	 	nodeMap[edge.target].in = {};
-	          	 }
-	          	 var targetInMap = nodeMap[edge.target].in;
-	          	 targetInMap[edge.from] = nodeMap[edge.from];
-	          	 
-	          	 if (!nodeMap[edge.from].out) {
-	          	 	nodeMap[edge.from].out = {};
-	          	 }
-	          	 var sourceOutMap = nodeMap[edge.from].out;
-	          	 sourceOutMap[edge.target] = nodeMap[edge.target];
-	          }
-	          
-	          graphModel.set({nodeMap: nodeMap});
-	          
-	          if (window.location.hash) {
-					var hash = window.location.hash;
-					if (hash == "#jobslist") {
-						flowTabView.handleJobslistLinkClick();
-					}
-					else if (hash == "#log") {
-						flowTabView.handleLogLinkClick();
-					}
-			 }
-	          
-	      	 updaterFunction();
-	      	 logUpdaterFunction();
-	      }
-	    );
+	var requestData = {"execid": execId, "ajax":"fetchexecflow"};
+	var successHandler = function(data) {
+		console.log("data fetched");
+		graphModel.set({data: data});
+		graphModel.set({disabled: {}});
+		graphModel.trigger("change:graph");
+		
+		updateTime = Math.max(updateTime, data.submitTime);
+		updateTime = Math.max(updateTime, data.startTime);
+		updateTime = Math.max(updateTime, data.endTime);
+		
+		var nodeMap = {};
+		for (var i = 0; i < data.nodes.length; ++i) {
+			var node = data.nodes[i];
+			nodeMap[node.id] = node;
+			updateTime = Math.max(updateTime, node.startTime);
+			updateTime = Math.max(updateTime, node.endTime);
+		}
+		for (var i = 0; i < data.edges.length; ++i) {
+			var edge = data.edges[i];
+			 
+			if (!nodeMap[edge.target].in) {
+				nodeMap[edge.target].in = {};
+			}
+			var targetInMap = nodeMap[edge.target].in;
+			targetInMap[edge.from] = nodeMap[edge.from];
+			 
+			if (!nodeMap[edge.from].out) {
+				nodeMap[edge.from].out = {};
+			}
+			var sourceOutMap = nodeMap[edge.from].out;
+			sourceOutMap[edge.target] = nodeMap[edge.target];
+		}
+		
+		graphModel.set({nodeMap: nodeMap});
+		if (window.location.hash) {
+			var hash = window.location.hash;
+			if (hash == "#jobslist") {
+				flowTabView.handleJobslistLinkClick();
+			}
+			else if (hash == "#log") {
+				flowTabView.handleLogLinkClick();
+			}
+		}
+		else {
+			flowTabView.handleGraphLinkClick();
+		}
+		updaterFunction();
+		logUpdaterFunction();
+	};
+	ajaxCall(requestURL, requestData, successHandler);
 });
diff --git a/src/web/js/azkaban.flow.execute.view.js b/src/web/js/azkaban.flow.execute.view.js
index c8aed58..2fc3b4b 100644
--- a/src/web/js/azkaban.flow.execute.view.js
+++ b/src/web/js/azkaban.flow.execute.view.js
@@ -1,8 +1,23 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 function recurseAllAncestors(nodes, disabledMap, id, disable) {
 	var node = nodes[id];
-	
 	if (node.in) {
 		for (var key in node.in) {
 			disabledMap[key] = disable;
@@ -13,7 +28,6 @@ function recurseAllAncestors(nodes, disabledMap, id, disable) {
 
 function recurseAllDescendents(nodes, disabledMap, id, disable) {
 	var node = nodes[id];
-	
 	if (node.out) {
 		for (var key in node.out) {
 			disabledMap[key] = disable;
@@ -23,267 +37,282 @@ function recurseAllDescendents(nodes, disabledMap, id, disable) {
 }
 
 var flowExecuteDialogView;
-azkaban.FlowExecuteDialogView= Backbone.View.extend({
-  events : {
-  	"click .closeExecPanel": "hideExecutionOptionPanel",
-  	"click #schedule-btn" : "scheduleClick",
-  	"click #execute-btn" : "handleExecuteFlow"
-  },
-  initialize : function(settings) {
-  	  this.model.bind('change:flowinfo', this.changeFlowInfo, this);
-  	  
-  	  $("#overrideSuccessEmails").click(function(evt) {
-		 if($(this).is(':checked')){
-		 	$('#successEmails').attr('disabled',null);
-		 }
-		 else {
-		 	$('#successEmails').attr('disabled',"disabled");
-		 }
-  	  });
-  	  
-  	   $("#overrideFailureEmails").click(function(evt) {
-		 if($(this).is(':checked')){
-		 	$('#failureEmails').attr('disabled',null);
-		 }
-		 else {
-		 	$('#failureEmails').attr('disabled',"disabled");
-		 }
-  	  });
-  },
-  render: function() {
-  },
-  getExecutionOptionData: function() {
-	var failureAction = $('#failureAction').val();
-	var failureEmails = $('#failureEmails').val();
-	var successEmails = $('#successEmails').val();
-	var notifyFailureFirst = $('#notifyFailureFirst').is(':checked');
-	var notifyFailureLast = $('#notifyFailureLast').is(':checked');
-	var failureEmailsOverride = $("#overrideFailureEmails").is(':checked');
-	var successEmailsOverride = $("#overrideSuccessEmails").is(':checked');
-	
-	var flowOverride = {};
-	var editRows = $(".editRow");
-	for (var i = 0; i < editRows.length; ++i) {
-		var row = editRows[i];
-		var td = $(row).find('td');
-		var key = $(td[0]).text();
-		var val = $(td[1]).text();
+azkaban.FlowExecuteDialogView = Backbone.View.extend({
+	events: {
+		"click .closeExecPanel": "hideExecutionOptionPanel",
+		"click #schedule-btn": "scheduleClick",
+		"click #execute-btn": "handleExecuteFlow"
+	},
+	
+	initialize: function(settings) {
+		this.model.bind('change:flowinfo', this.changeFlowInfo, this);
+		$("#overrideSuccessEmails").click(function(evt) {
+			if ($(this).is(':checked')) {
+				$('#successEmails').attr('disabled', null);
+			}
+			else {
+				$('#successEmails').attr('disabled', "disabled");
+			}
+		});
+				
+		$("#overrideFailureEmails").click(function(evt) {
+			if ($(this).is(':checked')) {
+				$('#failureEmails').attr('disabled', null);
+			}
+			else {
+				$('#failureEmails').attr('disabled', "disabled");
+			}
+		});
+	},
+	
+	render: function() {
+	},
+	
+	getExecutionOptionData: function() {
+		var failureAction = $('#failureAction').val();
+		var failureEmails = $('#failureEmails').val();
+		var successEmails = $('#successEmails').val();
+		var notifyFailureFirst = $('#notifyFailureFirst').is(':checked');
+		var notifyFailureLast = $('#notifyFailureLast').is(':checked');
+		var failureEmailsOverride = $("#overrideFailureEmails").is(':checked');
+		var successEmailsOverride = $("#overrideSuccessEmails").is(':checked');
+		
+		var flowOverride = {};
+		var editRows = $(".editRow");
+		for (var i = 0; i < editRows.length; ++i) {
+			var row = editRows[i];
+			var td = $(row).find('td');
+			var key = $(td[0]).text();
+			var val = $(td[1]).text();
+			
+			if (key && key.length > 0) {
+				flowOverride[key] = val;
+			}
+		}
 		
-		if (key && key.length > 0) {
-			flowOverride[key] = val;
+		var disabled = "";
+		var disabledMap = this.model.get('disabled');
+		for (var dis in disabledMap) {
+			if (disabledMap[dis]) {
+				disabled += dis + ",";
+			}
 		}
-	}
-	
-	var disabled = "";
-	var disabledMap = this.model.get('disabled');
-	for (var dis in disabledMap) {
-		if (disabledMap[dis]) {
-			disabled += dis + ",";
+		
+		var executingData = {
+			projectId: projectId,
+			project: this.projectName,
+			ajax: "executeFlow",
+			flow: this.flowId,
+			disabled: disabled,
+			failureEmailsOverride:failureEmailsOverride,
+			successEmailsOverride:successEmailsOverride,
+			failureAction: failureAction,
+			failureEmails: failureEmails,
+			successEmails: successEmails,
+			notifyFailureFirst: notifyFailureFirst,
+			notifyFailureLast: notifyFailureLast,
+			flowOverride: flowOverride
+		};
+		
+		// Set concurrency option, default is skip
+
+		var concurrentOption = $('input[name=concurrent]:checked').val();
+		executingData.concurrentOption = concurrentOption;
+		if (concurrentOption == "pipeline") {
+			var pipelineLevel = $("#pipelineLevel").val();
+			executingData.pipelineLevel = pipelineLevel;
 		}
-	}
-	
-	var executingData = {
-		projectId: projectId,
-		project: this.projectName,
-		ajax: "executeFlow",
-		flow: this.flowId,
-		disabled: disabled,
-		failureEmailsOverride:failureEmailsOverride,
-		successEmailsOverride:successEmailsOverride,
-		failureAction: failureAction,
-		failureEmails: failureEmails,
-		successEmails: successEmails,
-		notifyFailureFirst: notifyFailureFirst,
-		notifyFailureLast: notifyFailureLast,
-		flowOverride: flowOverride
-	};
-	
-	// Set concurrency option, default is skip
-
-	var concurrentOption = $('input[name=concurrent]:checked').val();
-	executingData.concurrentOption = concurrentOption;
-	if (concurrentOption == "pipeline") {
-		var pipelineLevel = $("#pipelineLevel").val();
-		executingData.pipelineLevel = pipelineLevel;
-	}
-	else if (concurrentOption == "queue") {
-		executingData.queueLevel = $("#queueLevel").val();
-	}
+		else if (concurrentOption == "queue") {
+			executingData.queueLevel = $("#queueLevel").val();
+		}
+		
+		return executingData;
+	},
 	
-	return executingData;
-  },
-  changeFlowInfo: function() {
-  	var successEmails = this.model.get("successEmails");
-  	var failureEmails = this.model.get("failureEmails");
-  	var failureActions = this.model.get("failureAction");
-  	var notifyFailure = this.model.get("notifyFailure");
-  	var flowParams = this.model.get("flowParams");
-  	var isRunning = this.model.get("isRunning");
-  	var concurrentOption = this.model.get("concurrentOption");
-  	var pipelineLevel = this.model.get("pipelineLevel");
-  	var pipelineExecutionId = this.model.get("pipelineExecution");
-  	var queueLevel = this.model.get("queueLevel");
-  	var nodeStatus = this.model.get("nodeStatus");
-  	var overrideSuccessEmails = this.model.get("overrideSuccessEmails");
-  	var overrideFailureEmails = this.model.get("overrideFailureEmails");
-  	
-	if (overrideSuccessEmails) {
-		$('#overrideSuccessEmails').attr('checked', true);
-	}
-	else {
-		$('#successEmails').attr('disabled','disabled');
-	}
-	if (overrideFailureEmails) {
-		$('#overrideFailureEmails').attr('checked', true);
-	}
-	else {
-		$('#failureEmails').attr('disabled','disabled');
-	}
-  	
-  	if (successEmails) {
-  		$('#successEmails').val(successEmails.join());
-  	}
-  	if (failureEmails) {
-  		$('#failureEmails').val(failureEmails.join());
-  	}
-  	if (failureActions) {
+	changeFlowInfo: function() {
+		var successEmails = this.model.get("successEmails");
+		var failureEmails = this.model.get("failureEmails");
+		var failureActions = this.model.get("failureAction");
+		var notifyFailure = this.model.get("notifyFailure");
+		var flowParams = this.model.get("flowParams");
+		var isRunning = this.model.get("isRunning");
+		var concurrentOption = this.model.get("concurrentOption");
+		var pipelineLevel = this.model.get("pipelineLevel");
+		var pipelineExecutionId = this.model.get("pipelineExecution");
+		var queueLevel = this.model.get("queueLevel");
+		var nodeStatus = this.model.get("nodeStatus");
+		var overrideSuccessEmails = this.model.get("overrideSuccessEmails");
+		var overrideFailureEmails = this.model.get("overrideFailureEmails");
+		
+		if (overrideSuccessEmails) {
+			$('#overrideSuccessEmails').attr('checked', true);
+		}
+		else {
+			$('#successEmails').attr('disabled','disabled');
+		}
+		if (overrideFailureEmails) {
+			$('#overrideFailureEmails').attr('checked', true);
+		}
+		else {
+			$('#failureEmails').attr('disabled','disabled');
+		}
+		
+		if (successEmails) {
+			$('#successEmails').val(successEmails.join());
+		}
+		if (failureEmails) {
+			$('#failureEmails').val(failureEmails.join());
+		}
+		if (failureActions) {
 		$('#failureAction').val(failureActions);
-  	}
-  	
-  	if (notifyFailure.first) {
+		}
+		
+		if (notifyFailure.first) {
 		$('#notifyFailureFirst').attr('checked', true);
-  	}
-  	if (notifyFailure.last) {
-  		$('#notifyFailureLast').attr('checked', true);
-  	}
-  	
-  	if (concurrentOption) {
-  		$('input[value='+concurrentOption+'][name="concurrent"]').attr('checked', true);
-  	}
-  	if (pipelineLevel) {
-  		$('#pipelineLevel').val(pipelineLevel);
-  	}
-  	if (queueLevel) {
-  		$('#queueLevel').val(queueLevel);
-  	}
-  	
-  	if (nodeStatus) {
-  		var nodeMap = this.model.get("nodeMap");
-  
-  		var disabled = {};
-  		for (var key in nodeStatus) {
-  			var status = nodeStatus[key];
-  			
-  			var node = nodeMap[key];
-  			if (node) {
-  				node.status = status;
-  				
-				if (node.status == "DISABLED" || node.status == "SKIPPED") {
-					node.status = "READY";
-					disabled[node.id] = true;
-				}
-				if (node.status == "SUCCEEDED" || node.status=="RUNNING") {
-					disabled[node.id] = true;
+		}
+		if (notifyFailure.last) {
+			$('#notifyFailureLast').attr('checked', true);
+		}
+		
+		if (concurrentOption) {
+			$('input[value='+concurrentOption+'][name="concurrent"]').attr('checked', true);
+		}
+		if (pipelineLevel) {
+			$('#pipelineLevel').val(pipelineLevel);
+		}
+		if (queueLevel) {
+			$('#queueLevel').val(queueLevel);
+		}
+		
+		if (nodeStatus) {
+			var nodeMap = this.model.get("nodeMap");
+			var disabled = {};
+			for (var key in nodeStatus) {
+				var status = nodeStatus[key];
+				
+				var node = nodeMap[key];
+				if (node) {
+					node.status = status;
+					if (node.status == "DISABLED" || node.status == "SKIPPED") {
+						node.status = "READY";
+						disabled[node.id] = true;
+					}
+					if (node.status == "SUCCEEDED" || node.status=="RUNNING") {
+						disabled[node.id] = true;
+					}
 				}
-  			}
-  		}
-  		this.model.set({"disabled":disabled});
-  	}
-  	
-	if (flowParams) {
-		for (var key in flowParams) {
-			editTableView.handleAddRow({paramkey: key, paramvalue: flowParams[key]});
+			}
+			this.model.set({"disabled":disabled});
 		}
-	}
-  },
-  show: function(data) {
-  	var projectName = data.project;
-  	var flowId = data.flow;
-  	var jobId = data.job;
-  	
-  	// ExecId is optional
-  	var execId = data.execid;
-  
-  	var loadedId = executableGraphModel.get("flowId");
-  
-  	this.loadGraph(projectName, flowId);
-  	this.loadFlowInfo(projectName, flowId, execId);
-
-  	this.projectName = projectName;
-  	this.flowId = flowId;
-	if (jobId) {
-		this.showExecuteJob(projectName, flowId, jobId, data.withDep);
-	}
-	else {
-		this.showExecuteFlow(projectName, flowId);
-	}
-  },
-  showExecuteFlow: function(projectName, flowId) {
-	$("#execute-flow-panel-title").text("Execute Flow " + flowId);
-	this.showExecutionOptionPanel();
-
-	// Triggers a render
-	this.model.trigger("change:graph");
-  },
-  showExecuteJob: function(projectName, flowId, jobId, withDep) {
-	sideMenuDialogView.menuSelect($("#flowOption"));
-	$("#execute-flow-panel-title").text("Execute Flow " + flowId);
-	
-	var nodes = this.model.get("nodeMap");
-	var disabled = this.model.get("disabled");
-	
-	// Disable all, then re-enable those you want.
-	for(var key in nodes) {
-		disabled[key] = true;
-	}
+		
+		if (flowParams) {
+			for (var key in flowParams) {
+				editTableView.handleAddRow({
+					paramkey: key, 
+					paramvalue: flowParams[key]
+				});
+			}
+		}
+	},
 	
-	var jobNode = nodes[jobId];
-	disabled[jobId] = false;
+	show: function(data) {
+		var projectName = data.project;
+		var flowId = data.flow;
+		var jobId = data.job;
+		
+		// ExecId is optional
+		var execId = data.execid;
 	
-	if (withDep) {
-		recurseAllAncestors(nodes, disabled, jobId, false);
-	}
+		var loadedId = executableGraphModel.get("flowId");
+	
+		this.loadGraph(projectName, flowId);
+		this.loadFlowInfo(projectName, flowId, execId);
+
+		this.projectName = projectName;
+		this.flowId = flowId;
+		if (jobId) {
+			this.showExecuteJob(projectName, flowId, jobId, data.withDep);
+		}
+		else {
+			this.showExecuteFlow(projectName, flowId);
+		}
+	},
+	
+	showExecuteFlow: function(projectName, flowId) {
+		$("#execute-flow-panel-title").text("Execute Flow " + flowId);
+		this.showExecutionOptionPanel();
+
+		// Triggers a render
+		this.model.trigger("change:graph");
+	},
+	
+	showExecuteJob: function(projectName, flowId, jobId, withDep) {
+		sideMenuDialogView.menuSelect($("#flow-option"));
+		$("#execute-flow-panel-title").text("Execute Flow " + flowId);
+		
+		var nodes = this.model.get("nodeMap");
+		var disabled = this.model.get("disabled");
+		
+		// Disable all, then re-enable those you want.
+		for (var key in nodes) {
+			disabled[key] = true;
+		}
+		
+		var jobNode = nodes[jobId];
+		disabled[jobId] = false;
+		
+		if (withDep) {
+			recurseAllAncestors(nodes, disabled, jobId, false);
+		}
 
-	this.showExecutionOptionPanel();
-	this.model.trigger("change:graph");
-  },
-  showExecutionOptionPanel: function() {
-  	sideMenuDialogView.menuSelect($("#flowOption"));
-  	$('#modalBackground').show();
-  	$('#execute-flow-panel').show();
-  },
-  hideExecutionOptionPanel: function() {
-  	$('#modalBackground').hide();
-  	$('#execute-flow-panel').hide();
-  },
-  scheduleClick: function() {
-  	schedulePanelView.showSchedulePanel();
-  },
-  loadFlowInfo: function(projectName, flowId, execId) {
-    console.log("Loading flow " + flowId);
-	fetchFlowInfo(this.model, projectName, flowId, execId);
-  },
-  loadGraph: function(projectName, flowId) {
-  	console.log("Loading flow " + flowId);
-	fetchFlow(this.model, projectName, flowId, true);
-  },
-  handleExecuteFlow: function(evt) {
-  	  	var executeURL = contextURL + "/executor";
+		this.showExecutionOptionPanel();
+		this.model.trigger("change:graph");
+	},
+	
+	showExecutionOptionPanel: function() {
+		sideMenuDialogView.menuSelect($("#flow-option"));
+		$('#execute-flow-panel').modal();
+	},
+	
+	hideExecutionOptionPanel: function() {
+		$('#execute-flow-panel').modal("hide");
+	},
+	
+	scheduleClick: function() {
+		console.log("click schedule button.");
+		this.hideExecutionOptionPanel();
+		schedulePanelView.showSchedulePanel();
+	},
+	
+	loadFlowInfo: function(projectName, flowId, execId) {
+		console.log("Loading flow " + flowId);
+		fetchFlowInfo(this.model, projectName, flowId, execId);
+	},
+	
+	loadGraph: function(projectName, flowId) {
+		console.log("Loading flow " + flowId);
+		fetchFlow(this.model, projectName, flowId, true);
+	},
+	
+	handleExecuteFlow: function(evt) {
+		console.log("click schedule button.");
+		var executeURL = contextURL + "/executor";
 		var executingData = this.getExecutionOptionData();
 		executeFlow(executingData);
-  }
+	}
 });
 
 var editTableView;
 azkaban.EditTableView = Backbone.View.extend({
-	events : {
+	events: {
 		"click table .addRow": "handleAddRow",
 		"click table .editable": "handleEditColumn",
 		"click table .removeIcon": "handleRemoveColumn"
 	},
-	initialize: function(setting) {
 
+	initialize: function(setting) {
 	},
+	
 	handleAddRow: function(data) {
 		var name = "";
 		if (data.paramkey) {
@@ -295,42 +324,50 @@ azkaban.EditTableView = Backbone.View.extend({
 			value = data.paramvalue;
 		}
 	
-	  	var tr = document.createElement("tr");
-	  	var tdName = document.createElement("td");
-	    var tdValue = document.createElement("td");
-	    
-	    var icon = document.createElement("span");
-	    $(icon).addClass("removeIcon");
-	    var nameData = document.createElement("span");
-	    $(nameData).addClass("spanValue");
-	    $(nameData).text(name);
-	    var valueData = document.createElement("span");
-	    $(valueData).addClass("spanValue");
-	    $(valueData).text(value);
-	    	    
-		$(tdName).append(icon);
+		var tr = document.createElement("tr");
+		var tdName = document.createElement("td");
+    $(tdName).addClass('property-key');
+		var tdValue = document.createElement("td");
+		
+		var remove = document.createElement("div");
+    $(remove).addClass("pull-right").addClass('remove-btn');
+    var removeBtn = document.createElement("button");
+    $(removeBtn).attr('type', 'button');
+    $(removeBtn).addClass('btn').addClass('btn-xs').addClass('btn-danger');
+    $(removeBtn).text('Delete');
+    $(remove).append(removeBtn);
+
+		var nameData = document.createElement("span");
+		$(nameData).addClass("spanValue");
+		$(nameData).text(name);
+		var valueData = document.createElement("span");
+		$(valueData).addClass("spanValue");
+		$(valueData).text(value);
+						
 		$(tdName).append(nameData);
-		$(tdName).addClass("name");
 		$(tdName).addClass("editable");
 		
 		$(tdValue).append(valueData);
-	    $(tdValue).addClass("editable");
+    $(tdValue).append(remove);
+		$(tdValue).addClass("editable").addClass('value');
 		
 		$(tr).addClass("editRow");
-	  	$(tr).append(tdName);
-	  	$(tr).append(tdValue);
-	   
-	  	$(tr).insertBefore(".addRow");
-	  	return tr;
-	  },
-	  handleEditColumn : function(evt) {
-	  	var curTarget = evt.currentTarget;
+		$(tr).append(tdName);
+		$(tr).append(tdValue);
+	 
+		$(tr).insertBefore(".addRow");
+		return tr;
+	},
+	
+	handleEditColumn: function(evt) {
+		var curTarget = evt.currentTarget;
 	
 		var text = $(curTarget).children(".spanValue").text();
 		$(curTarget).empty();
 					
 		var input = document.createElement("input");
 		$(input).attr("type", "text");
+    $(input).addClass('form-control').addClass('input-sm');
 		$(input).css("width", "100%");
 		$(input).val(text);
 		$(curTarget).addClass("editing");
@@ -343,105 +380,95 @@ azkaban.EditTableView = Backbone.View.extend({
 		});
 		
 		$(input).keypress(function(evt) {
-		    if(evt.which == 13) {
-		        obj.closeEditingTarget(evt);
-		    }
+			if (evt.which == 13) {
+				obj.closeEditingTarget(evt);
+			}
 		});
-
-	  },
-	  handleRemoveColumn : function(evt) {
-	  	var curTarget = evt.currentTarget;
-	  	// Should be the table
-	  	var row = curTarget.parentElement.parentElement;
+	},
+	
+	handleRemoveColumn: function(evt) {
+		var curTarget = evt.currentTarget;
+		// Should be the table
+		var row = curTarget.parentElement.parentElement;
 		$(row).remove();
-	  },
-	  closeEditingTarget: function(evt) {
-  		var input = evt.currentTarget;
-  		var text = $(input).val();
-  		var parent = $(input).parent();
-  		$(parent).empty();
-
-	    var valueData = document.createElement("span");
-	    $(valueData).addClass("spanValue");
-	    $(valueData).text(text);
-
-		if($(parent).hasClass("name")) {
-			var icon = document.createElement("span");
-			$(icon).addClass("removeIcon");
-			$(parent).append(icon);
-		}
-	    
-	    $(parent).removeClass("editing");
-	    $(parent).append(valueData);
-	  }
+	},
+	
+	closeEditingTarget: function(evt) {
+		var input = evt.currentTarget;
+		var text = $(input).val();
+		var parent = $(input).parent();
+		$(parent).empty();
+
+		var valueData = document.createElement("span");
+		$(valueData).addClass("spanValue");
+		$(valueData).text(text);
+
+		if ($(parent).hasClass("value")) {
+      var remove = document.createElement("div");
+      $(remove).addClass("pull-right").addClass('remove-btn');
+      var removeBtn = document.createElement("button");
+      $(removeBtn).attr('type', 'button');
+      $(removeBtn).addClass('btn').addClass('btn-xs').addClass('btn-danger');
+      $(removeBtn).text('Delete');
+      $(remove).append(removeBtn);
+			$(parent).append(remove);
+		}
+		
+		$(parent).removeClass("editing");
+		$(parent).append(valueData);
+	}
 });
 
 var sideMenuDialogView;
-azkaban.SideMenuDialogView= Backbone.View.extend({
-	events : {
-		"click .menuHeader" : "menuClick"
-  	},
-  	initialize : function(settings) {
-  		var children = $(this.el).children();
-  		var currentParent;
-  		var parents = [];
-  		var realChildren = [];
-  		for (var i = 0; i < children.length; ++i ) {
-  			var child = children[i];
-  			if ((i % 2) == 0) {
-  				currentParent = child;
-  				$(child).addClass("menuHeader");
-  				parents.push(child);
-  			}
-  			else {
-  				$(child).addClass("menuContent");
-  				$(child).hide();
-  				currentParent.child = child;
-  				realChildren.push(child);
-  			}
-  		}
-  		
-  		this.menuSelect($("#flowOption"));
-  		
-  		this.parents = parents;
-  		this.children = realChildren;
-  	},
-  	menuClick : function(evt) {
-  		this.menuSelect(evt.currentTarget);
-  	},
-  	menuSelect : function(target) {
-  		if ($(target).hasClass("selected")) {
-  			return;
-  		}
-  		
-  		$(".sidePanel").each(function() {
-  			$(this).hide();
-  		});
-  		
-  		$(".menuHeader").each(function() {
-  			$(this.child).slideUp("fast");
-  			$(this).removeClass("selected");
-  		});
-  		
-  		$(".sidePanel").each(function() {
-  			$(this).hide();
-  		});
-  		
-  		$(target).addClass("selected");
-  		$(target.child).slideDown("fast");
-  		var panelName = $(target).attr("viewpanel");
-  		$("#" + panelName).show();
-  	}
+azkaban.SideMenuDialogView = Backbone.View.extend({
+	events: {
+		"click .menu-header": "menuClick"
+	},
+	
+	initialize: function(settings) {
+		var children = $(this.el).children();
+		for (var i = 0; i < children.length; ++i ) {
+			var child = children[i];
+			$(child).addClass("menu-header");
+			var caption = $(child).find(".menu-caption");
+			$(caption).hide();
+		}
+		this.menuSelect($("#flow-option"));
+	},
+	
+	menuClick: function(evt) {
+		this.menuSelect(evt.currentTarget);
+	},
+	
+	menuSelect: function(target) {
+		if ($(target).hasClass("active")) {
+			return;
+		}
+		
+		$(".side-panel").each(function() {
+			$(this).hide();
+		});
+		
+		$(".menu-header").each(function() {
+			$(this).find(".menu-caption").slideUp("fast");
+			$(this).removeClass("active");
+		});
+		
+		$(target).addClass("active");
+		$(target).find(".menu-caption").slideDown("fast");
+		var panelName = $(target).attr("viewpanel");
+		$("#" + panelName).show();
+	}
 });
 
 var handleJobMenuClick = function(action, el, pos) {
 	var jobid = el[0].jobid;
 	
-	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowName + "&job=" + jobid;
+	var requesgURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowName + "&job=" + jobid;
 	if (action == "open") {
 		window.location.href = requestURL;
 	}
-	else if(action == "openwindow") {
+	else if (action == "openwindow") {
 		window.open(requestURL);
 	}
 }
@@ -482,7 +509,7 @@ var touchParents = function(jobid, disable) {
 
 	if (inNodes) {
 		for (var key in inNodes) {
-		  disabled[key] = disable;
+			disabled[key] = disable;
 		}
 	}
 	
@@ -497,7 +524,7 @@ var touchChildren = function(jobid, disable) {
 
 	if (outNodes) {
 		for (var key in outNodes) {
-		  disabledMap[key] = disable;
+			disabledMap[key] = disable;
 		}
 	}
 	
@@ -531,24 +558,23 @@ var nodeClickCallback = function(event) {
 	var flowId = executableGraphModel.get("flowId");
 	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
 
-	var menu = [	{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
-			{break: 1},
-			{title: "Enable", callback: function() {touchNode(jobId, false);}, submenu: [
-									{title: "Parents", callback: function(){touchParents(jobId, false);}},
-									{title: "Ancestors", callback: function(){touchAncestors(jobId, false);}},
-									{title: "Children", callback: function(){touchChildren(jobId, false);}},
-									{title: "Descendents", callback: function(){touchDescendents(jobId, false);}},
-									{title: "Enable All", callback: function(){enableAll();}}
-								]
-			},
-			{title: "Disable", callback: function() {touchNode(jobId, true)}, submenu: [
-									{title: "Parents", callback: function(){touchParents(jobId, true);}},
-									{title: "Ancestors", callback: function(){touchAncestors(jobId, true);}},
-									{title: "Children", callback: function(){touchChildren(jobId, true);}},
-									{title: "Descendents", callback: function(){touchDescendents(jobId, true);}},
-									{title: "Disable All", callback: function(){disableAll();}}
-								]
-			}
+	var menu = [
+		{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
+		{break: 1},
+		{title: "Enable", callback: function() {touchNode(jobId, false);}, submenu: [
+			{title: "Parents", callback: function(){touchParents(jobId, false);}},
+			{title: "Ancestors", callback: function(){touchAncestors(jobId, false);}},
+			{title: "Children", callback: function(){touchChildren(jobId, false);}},
+			{title: "Descendents", callback: function(){touchDescendents(jobId, false);}},
+			{title: "Enable All", callback: function(){enableAll();}}
+		]},
+		{title: "Disable", callback: function() {touchNode(jobId, true)}, submenu: [
+			{title: "Parents", callback: function(){touchParents(jobId, true);}},
+			{title: "Ancestors", callback: function(){touchAncestors(jobId, true);}},
+			{title: "Children", callback: function(){touchChildren(jobId, true);}},
+			{title: "Descendents", callback: function(){touchDescendents(jobId, true);}},
+			{title: "Disable All", callback: function(){disableAll();}}
+		]}
 	];
 
 	contextMenuView.show(event, menu);
@@ -563,7 +589,8 @@ var graphClickCallback = function(event) {
 	var flowId = executableGraphModel.get("flowId");
 	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId;
 	
-	var menu = [	{title: "Open Flow in New Window...", callback: function() {window.open(requestURL);}},
+	var menu = [
+		{title: "Open Flow in New Window...", callback: function() {window.open(requestURL);}},
 		{break: 1},
 		{title: "Enable All", callback: function() {enableAll();}},
 		{title: "Disable All", callback: function() {disableAll();}},
@@ -577,11 +604,30 @@ var graphClickCallback = function(event) {
 var contextMenuView;
 $(function() {
 	executableGraphModel = new azkaban.GraphModel();
-	flowExecuteDialogView = new azkaban.FlowExecuteDialogView({el:$('#execute-flow-panel'), model: executableGraphModel});
-	svgGraphView = new azkaban.SvgGraphView({el:$('#svgDivCustom'), model: executableGraphModel, topGId:"topG", graphMargin: 10, rightClick: { "node": nodeClickCallback, "edge": edgeClickCallback, "graph": graphClickCallback }});
+	flowExecuteDialogView = new azkaban.FlowExecuteDialogView({
+		el: $('#execute-flow-panel'), 
+		model: executableGraphModel
+	});
+	svgGraphView = new azkaban.SvgGraphView({
+		el: $('#svg-div-custom'), 
+		model: executableGraphModel, 
+		topGId: "topG", 
+		graphMargin: 10, 
+		rightClick: { 
+			"node": nodeClickCallback, 
+			"edge": edgeClickCallback, 
+			"graph": graphClickCallback 
+		}
+	});
 	
-	sideMenuDialogView = new azkaban.SideMenuDialogView({el:$('#graphOptions')});
-	editTableView = new azkaban.EditTableView({el:$('#editTable')});
-
-	contextMenuView = new azkaban.ContextMenuView({el:$('#contextMenu')});
+	sideMenuDialogView = new azkaban.SideMenuDialogView({
+		el: $('#graph-options')
+	});
+	editTableView = new azkaban.EditTableView({
+		el: $('#editTable')
+	});
+
+	contextMenuView = new azkaban.ContextMenuView({
+		el: $('#contextMenu')
+	});
 });
diff --git a/src/web/js/azkaban.flow.graph.view.js b/src/web/js/azkaban.flow.graph.view.js
index 19f5117..6ad2100 100644
--- a/src/web/js/azkaban.flow.graph.view.js
+++ b/src/web/js/azkaban.flow.graph.view.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 var svgGraphView;
 azkaban.SvgGraphView = Backbone.View.extend({
 	events: {
@@ -104,9 +120,9 @@ azkaban.SvgGraphView = Backbone.View.extend({
 				this.nodes[id].disabled = true;
 				addClass(g, "disabled");
 			}
-		    else {
-		    	this.nodes[id].disabled = false;
-		    	removeClass(g, "disabled");
+			else {
+				this.nodes[id].disabled = false;
+				removeClass(g, "disabled");
 			}
 		}
 	},
@@ -140,7 +156,12 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			var x = node.x - offset;
 			var y = node.y - offset;
 			
-			$(this.svgGraph).svgNavigate("transformToBox", {x: x, y: y, width: widthHeight, height: widthHeight});
+			$(this.svgGraph).svgNavigate("transformToBox", {
+        x: x, 
+        y: y, 
+        width: widthHeight, 
+        height: widthHeight
+      });
 		}
 	},
 	handleStatusUpdate: function(evt) {
@@ -210,7 +231,6 @@ azkaban.SvgGraphView = Backbone.View.extend({
 
 		var xOffset = 10;
 		var yOffset = 10;
-
 		
 		var nodeG = document.createElementNS(svgns, "g");
 		nodeG.setAttributeNS(null, "class", "jobnode");
@@ -234,7 +254,12 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		text.setAttributeNS(null, "y", 15);
 		text.setAttributeNS(null, "height", 10); 
 				
-		this.addBounds(bounds, {minX:node.x - xOffset, minY: node.y - yOffset, maxX: node.x + xOffset, maxY: node.y + yOffset});
+		this.addBounds(bounds, {
+			minX: node.x - xOffset, 
+			minY: node.y - yOffset, 
+			maxX: node.x + xOffset, 
+			maxY: node.y + yOffset
+		});
 		
 		var backRect = document.createElementNS(svgns, 'rect');
 		backRect.setAttributeNS(null, "x", 0);
@@ -274,8 +299,14 @@ azkaban.SvgGraphView = Backbone.View.extend({
 	},
 	resetPanZoom : function(duration) {
 		var bounds = this.graphBounds;
-		var param = {x: bounds.minX, y: bounds.minY, width: (bounds.maxX - bounds.minX), height: (bounds.maxY - bounds.minY), duration: duration };
+		var param = {
+      x: bounds.minX, 
+      y: bounds.minY, 
+      width: (bounds.maxX - bounds.minX), 
+      height: (bounds.maxY - bounds.minY), 
+      duration: duration
+    };
 
 		$(this.svgGraph).svgNavigate("transformToBox", param);
 	}
-});
\ No newline at end of file
+});
diff --git a/src/web/js/azkaban.flow.job.view.js b/src/web/js/azkaban.flow.job.view.js
index fa14b58..fe8cf6d 100644
--- a/src/web/js/azkaban.flow.job.view.js
+++ b/src/web/js/azkaban.flow.job.view.js
@@ -1,94 +1,114 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 azkaban.JobListView = Backbone.View.extend({
 	events: {
 		"keyup input": "filterJobs",
-		"click li": "handleJobClick",
-		"click .resetPanZoomBtn" : "handleResetPanZoom",
-		"contextmenu li" : "handleContextMenuClick"
+		"click .job": "handleJobClick",
+		"click .resetPanZoomBtn": "handleResetPanZoom",
+		"contextmenu li": "handleContextMenuClick"
 	},
+	
 	initialize: function(settings) {
 		this.model.bind('change:selected', this.handleSelectionChange, this);
 		this.model.bind('change:disabled', this.handleDisabledChange, this);
 		this.model.bind('change:graph', this.render, this);
 		this.model.bind('change:update', this.handleStatusUpdate, this);
 		
-		this.filterInput = $(this.el).find(".filter");
-		this.list = $(this.el).find(".list");
+		this.filterInput = $(this.el).find("#filter");
+		this.list = $(this.el).find("#list");
 		this.contextMenu = settings.contextMenuCallback;
 		this.listNodes = {};
 	},
+	
 	filterJobs: function(self) {
 		var filter = this.filterInput.val();
-		
 		if (filter && filter.trim() != "") {
 			filter = filter.trim();
-			
 			if (filter == "") {
 				if (this.filter) {
-					this.jobs.children().each(
-						function(){
-							var a = $(this).find("a");
-        					$(a).html(this.jobid);
-        					$(this).show();
-						}
-					);
+					this.jobs.children().each(function(){
+						var a = $(this).find("a");
+						$(a).html(this.jobid);
+						$(this).show();
+					});
 				}
-				
 				this.filter = null;
 				return;
 			}
 		}
 		else {
 			if (this.filter) {
-				this.jobs.children().each(
-					function(){
-						var a = $(this).find("a");
-    					$(a).html(this.jobid);
-    					$(this).show();
-					}
-				);
+				this.jobs.children().each(function(){
+					var a = $(this).find("a");
+					$(a).html(this.jobid);
+					$(this).show();
+				});
 			}
 				
 			this.filter = null;
 			return;
 		}
 		
-		this.jobs.children().each(
-			function(){
-        		var jobid = this.jobid;
-        		var index = jobid.indexOf(filter);
-        		if (index != -1) {
-        			var a = $(this).find("a");
-        			
-        			var endIndex = index + filter.length;
-        			var newHTML = jobid.substring(0, index) + "<span>" + jobid.substring(index, endIndex) + "</span>" + jobid.substring(endIndex, jobid.length);
-        			
-        			$(a).html(newHTML);
-        			$(this).show();
-        		}
-        		else {
-        			$(this).hide();
-        		}
-    	});
-    	
-    	this.filter = filter;
+		this.jobs.children().each(function() {
+			var jobid = this.jobid;
+			var index = jobid.indexOf(filter);
+			if (index != -1) {
+				var a = $(this).find("a");
+				var endIndex = index + filter.length;
+				var newHTML = jobid.substring(0, index) + "<span>" + 
+						jobid.substring(index, endIndex) + "</span>" + 
+						jobid.substring(endIndex, jobid.length);
+				
+				$(a).html(newHTML);
+				$(this).show();
+			}
+			else {
+				$(this).hide();
+			}
+		});
+			
+		this.filter = filter;
 	},
+	
 	handleStatusUpdate: function(evt) {
 		var updateData = this.model.get("update");
 		if (updateData.nodes) {
 			for (var i = 0; i < updateData.nodes.length; ++i) {
 				var updateNode = updateData.nodes[i];
-				$(this.listNodes[updateNode.id]).removeClass();
-				$(this.listNodes[updateNode.id]).addClass(updateNode.status);
+				var job = this.listNodes[updateNode.id];
+				$(job).removeClass();
+				$(job).addClass("list-group-item");
+				$(job).addClass(updateNode.status);
 			}
 		}
 	},
+	
 	assignInitialStatus: function(evt) {
 		var data = this.model.get("data");
 		for (var i = 0; i < data.nodes.length; ++i) {
 			var updateNode = data.nodes[i];
-			$(this.listNodes[updateNode.id]).addClass(updateNode.status);
+			var job = this.listNodes[updateNode.id];
+      if (!$(job).hasClass("list-group-item")) {
+        $(job).addClass("list-group-item");
+      }
+			$(job).addClass(updateNode.status);
 		}
 	},
+	
 	render: function(self) {
 		var data = this.model.get("data");
 		var nodes = data.nodes;
@@ -101,7 +121,7 @@ azkaban.JobListView = Backbone.View.extend({
 		};
 	
 		var nodeArray = nodes.slice(0);
-		nodeArray.sort(function(a,b){ 
+		nodeArray.sort(function(a, b) {
 			var diff = a.y - b.y;
 			if (diff == 0) {
 				return a.x - b.x;
@@ -111,40 +131,36 @@ azkaban.JobListView = Backbone.View.extend({
 			}
 		});
 		
-		var ul = document.createElement("ul");
-		$(ul).attr("class", "jobs");
-		this.jobs = $(ul);
-		
+		var list = this.list;
+		this.jobs = $(list);
 		for (var i = 0; i < nodeArray.length; ++i) {
-			var li = document.createElement("li");
-			li.jobid=nodeArray[i].id;
-			
-			var iconDiv = document.createElement("div");
-			$(iconDiv).addClass("icon");
-			li.appendChild(iconDiv);
-			
 			var a = document.createElement("a");
-			$(a).text(nodeArray[i].id);
-			li.appendChild(a);
-			ul.appendChild(li);
-			li.jobid=nodeArray[i].id;
-			
-			this.listNodes[nodeArray[i].id] = li;
+			$(a).addClass('list-group-item').addClass('job');
+      $(a).attr('href', '#');
+      
+      var iconDiv = document.createElement('div');
+      $(iconDiv).addClass('icon');
+      $(a).append(iconDiv);
+			$(a).append(nodeArray[i].id);
+			$(list).append(a);
+			a.jobid = nodeArray[i].id;
+			this.listNodes[nodeArray[i].id] = a;
 		}
 		
-		this.list.append(ul);
 		this.assignInitialStatus(self);
 		this.handleDisabledChange(self);
 	},
+	
 	handleContextMenuClick: function(evt) {
 		if (this.contextMenu) {
 			this.contextMenu(evt);
 			return false;
 		}
 	},
-	handleJobClick : function(evt) {
+	
+	handleJobClick: function(evt) {
 		var jobid = evt.currentTarget.jobid;
-		if(!evt.currentTarget.jobid) {
+		if (!evt.currentTarget.jobid) {
 			return;
 		}
 		
@@ -161,6 +177,7 @@ azkaban.JobListView = Backbone.View.extend({
 			this.model.set({"selected": jobid});
 		}
 	},
+	
 	handleDisabledChange: function(evt) {
 		var disabledMap = this.model.get("disabled");
 		var nodes = this.model.get("nodes");
@@ -174,6 +191,7 @@ azkaban.JobListView = Backbone.View.extend({
 			}
 		}
 	},
+	
 	handleSelectionChange: function(evt) {
 		if (!this.model.hasChanged("selected")) {
 			return;
@@ -183,13 +201,14 @@ azkaban.JobListView = Backbone.View.extend({
 		var current = this.model.get("selected");
 		
 		if (previous) {
-			$(this.listNodes[previous]).removeClass("selected");
+			$(this.listNodes[previous]).removeClass("active");
 		}
 		
 		if (current) {
-			$(this.listNodes[current]).addClass("selected");
+			$(this.listNodes[current]).addClass("active");
 		}
 	},
+	
 	handleResetPanZoom: function(evt) {
 		this.model.trigger("resetPanZoom");
 	}
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index 9f3536b..2e33ddb 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var statusStringMap = {
@@ -15,92 +31,86 @@ var statusStringMap = {
 
 var handleJobMenuClick = function(action, el, pos) {
 	var jobid = el[0].jobid;
-	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobid;
+	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + 
+			flowId + "&job=" + jobid;
 	if (action == "open") {
 		window.location.href = requestURL;
 	}
-	else if(action == "openwindow") {
+	else if (action == "openwindow") {
 		window.open(requestURL);
 	}
 }
 
-function hasClass(el, name) 
-{
-	var classes = el.getAttribute("class");
-	if (classes == null) {
-		return false;
-	}
-   return new RegExp('(\\s|^)'+name+'(\\s|$)').test(classes);
-}
-
-function addClass(el, name)
-{
-   if (!hasClass(el, name)) { 
-   		var classes = el.getAttribute("class");
-   		classes += classes ? ' ' + name : '' +name;
-   		el.setAttribute("class", classes);
-   }
-}
-
-function removeClass(el, name)
-{
-   if (hasClass(el, name)) {
-      var classes = el.getAttribute("class");
-      el.setAttribute("class", classes.replace(new RegExp('(\\s|^)'+name+'(\\s|$)'),' ').replace(/^\s+|\s+$/g, ''));
-   }
-}
-
 var flowTabView;
-azkaban.FlowTabView= Backbone.View.extend({
-  events : {
-  	"click #graphViewLink" : "handleGraphLinkClick",
-  	"click #executionsViewLink" : "handleExecutionLinkClick"
-  },
-  initialize : function(settings) {
-  	var selectedView = settings.selectedView;
-  	if (selectedView == "executions") {
-  		this.handleExecutionLinkClick();
-  	}
-  	else {
-  		this.handleGraphLinkClick();
-  	}
+azkaban.FlowTabView = Backbone.View.extend({
+	events: {
+		"click #graphViewLink": "handleGraphLinkClick",
+		"click #executionsViewLink": "handleExecutionLinkClick",
+		"click #summaryViewLink": "handleSummaryLinkClick"
+	},
+	
+	initialize: function(settings) {
+		var selectedView = settings.selectedView;
+		if (selectedView == "executions") {
+			this.handleExecutionLinkClick();
+		}
+		else {
+			this.handleGraphLinkClick();
+		}
+	},
+	
+	render: function() {
+		console.log("render graph");
+	},
+	
+	handleGraphLinkClick: function(){
+		$("#executionsViewLink").removeClass("active");
+		$("#graphViewLink").addClass("active");
+		$('#summaryViewLink').removeClass('active');
+		
+		$("#executionsView").hide();
+		$("#graphView").show();
+		$('#summaryView').hide();
+	},
+	
+	handleExecutionLinkClick: function() {
+		$("#graphViewLink").removeClass("active");
+		$("#executionsViewLink").addClass("active");
+		$('#summaryViewLink').removeClass('active');
+		
+		$("#graphView").hide();
+		$("#executionsView").show();
+		$('#summaryView').hide();
+		executionModel.trigger("change:view");
+	},
 
-  },
-  render: function() {
-  	console.log("render graph");
-  },
-  handleGraphLinkClick: function(){
-  	$("#executionsViewLink").removeClass("selected");
-  	$("#graphViewLink").addClass("selected");
-  	
-  	$("#executionsView").hide();
-  	$("#graphView").show();
-  },
-  handleExecutionLinkClick: function() {
-  	$("#graphViewLink").removeClass("selected");
-  	$("#executionsViewLink").addClass("selected");
-  	
-  	 $("#graphView").hide();
-  	 $("#executionsView").show();
-  	 executionModel.trigger("change:view");
-  }
+  handleSummaryLinkClick: function() {
+		$('#graphViewLink').removeClass('active');
+		$('#executionsViewLink').removeClass('active');
+		$('#summaryViewLink').addClass('active');
+
+		$('#graphView').hide();
+		$('#executionsView').hide();
+		$('#summaryView').show();
+	},
 });
 
 var jobListView;
-
 var svgGraphView;
-
 var executionsView;
+
 azkaban.ExecutionsView = Backbone.View.extend({
 	events: {
 		"click #pageSelection li": "handleChangePageSelection"
 	},
+	
 	initialize: function(settings) {
 		this.model.bind('change:view', this.handleChangeView, this);
 		this.model.bind('render', this.render, this);
 		this.model.set({page: 1, pageSize: 16});
 		this.model.bind('change:page', this.handlePageChange, this);
 	},
+	
 	render: function(evt) {
 		console.log("render");
 		// Render page selections
@@ -166,11 +176,12 @@ azkaban.ExecutionsView = Backbone.View.extend({
 		
 		this.renderPagination(evt);
 	},
+	
 	renderPagination: function(evt) {
 		var total = this.model.get("total");
 		total = total? total : 1;
 		var pageSize = this.model.get("pageSize");
-		var numPages = Math.ceil(total/pageSize);
+		var numPages = Math.ceil(total / pageSize);
 		
 		this.model.set({"numPages": numPages});
 		var page = this.model.get("page");
@@ -239,50 +250,127 @@ azkaban.ExecutionsView = Backbone.View.extend({
 			a.attr("href", "#page" + tpage);
 		}
 	},
+	
 	handleChangePageSelection: function(evt) {
 		if ($(evt.currentTarget).hasClass("disabled")) {
 			return;
 		}
 		var page = evt.currentTarget.page;
-		
 		this.model.set({"page": page});
 	},
+	
 	handleChangeView: function(evt) {
 		if (this.init) {
 			return;
 		}
-		
 		console.log("init");
 		this.handlePageChange(evt);
 		this.init = true;
 	},
+	
 	handlePageChange: function(evt) {
 		var page = this.model.get("page") - 1;
 		var pageSize = this.model.get("pageSize");
 		var requestURL = contextURL + "/manager";
 		
 		var model = this.model;
-		$.get(
-			requestURL,
-			{"project": projectName, "flow":flowId, "ajax": "fetchFlowExecutions", "start":page * pageSize, "length": pageSize},
-			function(data) {
-				model.set({"executions": data.executions, "total": data.total});
-				model.trigger("render");
-			},
-			"json"
-		);
-		
+		var requestData = {
+			"project": projectName, 
+			"flow": flowId, 
+			"ajax": "fetchFlowExecutions", 
+			"start": page * pageSize, 
+			"length": pageSize
+		};
+		var successHandler = function(data) {
+			model.set({
+				"executions": data.executions, 
+				"total": data.total
+			});
+			model.trigger("render");
+		};
+		$.get(requestURL, requestData, successHandler, "json");
 	}
 });
 
+var summaryView;
+azkaban.SummaryView = Backbone.View.extend({
+	events: {
+	},
+	
+	initialize: function(settings) {
+		this.model.bind('change:view', this.handleChangeView, this);
+		this.model.bind('render', this.render, this);
+		
+		this.fetchDetails();
+    this.fetchSchedule();
+		this.fetchLastRun();
+		this.model.trigger('render');
+	},
+
+  fetchDetails: function() {
+    var requestURL = contextURL + "/manager";
+    var requestData = {
+      'ajax': 'fetchflowdetails',
+      'project': projectName,
+      'flow': flowId
+    };
+		var model = this.model;
+    var successHandler = function(data) {
+      console.log(data);
+      model.set({
+        'jobTypes': data.jobTypes
+      });
+      model.trigger('render');
+    };
+    $.get(requestURL, requestData, successHandler, 'json');
+  },
+
+	fetchSchedule: function() {
+		var requestURL = contextURL + "/schedule"
+		var requestData = {
+			'ajax': 'fetchSchedule',
+			'projectId': projectId,
+			'flowId': flowId
+		};
+		var model = this.model;
+		var successHandler = function(data) {
+      model.set({'schedule': data.schedule});
+      model.trigger('render');
+		};
+		$.get(requestURL, requestData, successHandler, 'json');
+	},
+
+	fetchLastRun: function() {
+
+	},
+
+	handleChangeView: function(evt) {
+	},
+
+	render: function(evt) {
+		var data = {
+      projectName: projectName,
+			flowName: flowId,
+      jobTypes: this.model.get('jobTypes'),
+			general: this.model.get('general'),
+			schedule: this.model.get('schedule'),
+			lastRun: this.model.get('lastRun')
+		};
+		dust.render("flowsummary", data, function(err, out) {
+			$('#summary-view-content').html(out);
+		});
+	},
+});
+
 var exNodeClickCallback = function(event) {
 	console.log("Node clicked callback");
 	var jobId = event.currentTarget.jobid;
-	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
+	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + 
+			flowId + "&job=" + jobId;
 
 	var menu = [	
-			{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
-			{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}}
+		{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
+		{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}}
 	];
 
 	contextMenuView.show(event, menu);
@@ -291,11 +379,12 @@ var exNodeClickCallback = function(event) {
 var exJobClickCallback = function(event) {
 	console.log("Node clicked callback");
 	var jobId = event.currentTarget.jobid;
-	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
+	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + 
+			flowId + "&job=" + jobId;
 
 	var menu = [	
-			{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
-			{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}}
+		{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
+		{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}}
 	];
 
 	contextMenuView.show(event, menu);
@@ -324,93 +413,123 @@ azkaban.GraphModel = Backbone.Model.extend({});
 
 var executionModel;
 azkaban.ExecutionModel = Backbone.Model.extend({});
+
+var summaryModel;
+azkaban.SummaryModel = Backbone.Model.extend({});
+
 var mainSvgGraphView;
 
 $(function() {
 	var selected;
 	// Execution model has to be created before the window switches the tabs.
 	executionModel = new azkaban.ExecutionModel();
-	executionsView = new azkaban.ExecutionsView({el:$('#executionsView'), model: executionModel});
+	executionsView = new azkaban.ExecutionsView({
+		el: $('#executionsView'), 
+		model: executionModel
+	});
 	
-	flowTabView = new azkaban.FlowTabView({el:$( '#headertabs'), selectedView: selected });
+  summaryModel = new azkaban.SummaryModel();
+	summaryView = new azkaban.SummaryView({
+		el: $('#summaryView'),
+		model: summaryModel
+	});
+	
+  flowTabView = new azkaban.FlowTabView({
+		el: $('#headertabs'), 
+		selectedView: selected 
+	});
 
 	graphModel = new azkaban.GraphModel();
-	mainSvgGraphView = new azkaban.SvgGraphView({el:$('#svgDiv'), model: graphModel, rightClick:  { "node": exNodeClickCallback, "edge": exEdgeClickCallback, "graph": exGraphClickCallback }});
-	jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel, contextMenuCallback: exJobClickCallback});
+	mainSvgGraphView = new azkaban.SvgGraphView({
+		el: $('#svgDiv'), 
+		model: graphModel, 
+		rightClick: { 
+			"node": exNodeClickCallback, 
+			"edge": exEdgeClickCallback, 
+			"graph": exGraphClickCallback 
+		}
+	});
+	
+  jobsListView = new azkaban.JobListView({
+		el: $('#jobList'), 
+		model: graphModel, 
+		contextMenuCallback: exJobClickCallback
+	});
 	
 	var requestURL = contextURL + "/manager";
 
 	// Set up the Flow options view. Create a new one every time :p
-	 $('#executebtn').click( function() {
-	  	var data = graphModel.get("data");
-	  	var nodes = data.nodes;
-	  
-	    var executingData = {
-	  		project: projectName,
-	  		ajax: "executeFlow",
-	  		flow: flowId
+	$('#executebtn').click(function() {
+		var data = graphModel.get("data");
+		var nodes = data.nodes;
+		var executingData = {
+			project: projectName,
+			ajax: "executeFlow",
+			flow: flowId
 		};
+
+		flowExecuteDialogView.show(executingData);
+	});
+
+	var requestData = {
+		"project": projectName, 
+		"ajax": "fetchflowgraph", 
+		"flow": flowId
+	};
+	var successHandler = function(data) {
+		// Create the nodes
+		var nodes = {};
+		for (var i = 0; i < data.nodes.length; ++i) {
+			var node = data.nodes[i];
+			nodes[node.id] = node;
+		}
+		for (var i = 0; i < data.edges.length; ++i) {
+			var edge = data.edges[i];
+			var fromNode = nodes[edge.from];
+			var toNode = nodes[edge.target];
+			
+			if (!fromNode.outNodes) {
+				fromNode.outNodes = {};
+			}
+			fromNode.outNodes[toNode.id] = toNode;
+			
+			if (!toNode.inNodes) {
+				toNode.inNodes = {};
+			}
+			toNode.inNodes[fromNode.id] = fromNode;
+		}
 	
-	  	flowExecuteDialogView.show(executingData);
-	 });
-
-	$.get(
-	      requestURL,
-	      {"project": projectName, "ajax":"fetchflowgraph", "flow":flowId},
-	      function(data) {
-	      	  // Create the nodes
-	      	  var nodes = {};
-	      	  for (var i=0; i < data.nodes.length; ++i) {
-	      	  	var node = data.nodes[i];
-	      	  	nodes[node.id] = node;
-	      	  }
-	      	  for (var i=0; i < data.edges.length; ++i) {
-	      	  	var edge = data.edges[i];
-	      	  	var fromNode = nodes[edge.from];
-	      	  	var toNode = nodes[edge.target];
-	      	  	
-	      	  	if (!fromNode.outNodes) {
-	      	  		fromNode.outNodes = {};
-	      	  	}
-	      	  	fromNode.outNodes[toNode.id] = toNode;
-	      	  	
-	      	  	if (!toNode.inNodes) {
-	      	  		toNode.inNodes = {};
-	      	  	}
-	      	  	toNode.inNodes[fromNode.id] = fromNode;
-	      	  }
-	      
-	          console.log("data fetched");
-	          graphModel.set({data: data});
-	          graphModel.set({nodes: nodes});
-	          graphModel.set({disabled: {}});
-	          graphModel.trigger("change:graph");
-	          
-	          // Handle the hash changes here so the graph finishes rendering first.
-	          if (window.location.hash) {
-				var hash = window.location.hash;
-				
-				if (hash == "#executions") {
+		console.log("data fetched");
+		graphModel.set({data: data});
+		graphModel.set({nodes: nodes});
+		graphModel.set({disabled: {}});
+		graphModel.trigger("change:graph");
+		
+		// Handle the hash changes here so the graph finishes rendering first.
+		if (window.location.hash) {
+			var hash = window.location.hash;
+			if (hash == "#executions") {
+				flowTabView.handleExecutionLinkClick();
+			}
+			if (hash == "#summary") {
+				flowTabView.handleSummaryLinkClick();
+			}
+			else if (hash == "#graph") {
+				// Redundant, but we may want to change the default. 
+				selected = "graph";
+			}
+			else {
+				if ("#page" == hash.substring(0, "#page".length)) {
+					var page = hash.substring("#page".length, hash.length);
+					console.log("page " + page);
 					flowTabView.handleExecutionLinkClick();
-				}
-				else if (hash == "#graph") {
-					// Redundant, but we may want to change the default. 
-					selected = "graph";
+					executionModel.set({"page": parseInt(page)});
 				}
 				else {
-					if ("#page" == hash.substring(0, "#page".length)) {
-						var page = hash.substring("#page".length, hash.length);
-						console.log("page " + page);
-						flowTabView.handleExecutionLinkClick();
-						executionModel.set({"page": parseInt(page)});
-					}
-					else {
-						selected = "graph";
-					}
+					selected = "graph";
 				}
 			}
-	      },
-	      "json"
-	    );
-
+		}
+	};
+	$.get(requestURL, requestData, successHandler, "json");
 });
diff --git a/src/web/js/azkaban.history.view.js b/src/web/js/azkaban.history.view.js
index 01bb55d..a381e74 100644
--- a/src/web/js/azkaban.history.view.js
+++ b/src/web/js/azkaban.history.view.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var advFilterView;
@@ -5,87 +21,59 @@ azkaban.AdvFilterView = Backbone.View.extend({
 	events: {
 		"click #filter-btn": "handleAdvFilter"
 	},
+	
 	initialize: function(settings) {
-		$( "#datetimebegin" ).datetimepicker({
-			dateFormat: "mm/dd/yy",
-			separator: '-',
-			timeFormat: "HH:mm"
-		});
-		$( "#datetimeend" ).datetimepicker({
-			dateFormat: "mm/dd/yy",
-			separator: '-',
-			timeFormat: "HH:mm"
-		});
-		$("#errorMsg").hide();
+		$('#datetimebegin').datetimepicker();
+		$('#datetimeend').datetimepicker();
+		$('#datetimebegin').on('change.dp', function(e) {
+      $('#datetimeend').data('DateTimePicker').setStartDate(e.date);
+    });
+		$('#datetimeend').on('change.dp', function(e) {
+      $('#datetimebegin').data('DateTimePicker').setEndDate(e.date);
+    });
+		$('#adv-filter-error-msg').hide();
 	},
+	
 	handleAdvFilter: function(evt) {
 		console.log("handleAdv");
 		var projcontain = $('#projcontain').val();
 		var flowcontain = $('#flowcontain').val();
 		var usercontain = $('#usercontain').val();
 		var status = $('#status').val();
-		var begin  = $('#datetimebegin').val();
-		var end    = $('#datetimeend').val();
+		var begin	= $('#datetimebegin').val();
+		var end		= $('#datetimeend').val();
 		
 		console.log("filtering history");
 
 		var historyURL = contextURL + "/history"
 		var redirectURL = contextURL + "/schedule"	
-		
 
 		var requestURL = historyURL + "?advfilter=true" + "&projcontain=" + projcontain + "&flowcontain=" + flowcontain + "&usercontain=" + usercontain + "&status=" + status + "&begin=" + begin + "&end=" + end ; 
 		window.location = requestURL;
-		
-//		$.get(
-//			historyURL,
-//			{"action": "advfilter", "projre": projre, "flowre": flowre, "userre": userre},
-//			function(data) {
-//				if (data.action == "redirect") {
-//                    window.location = data.redirect;
-//                }
-//			},
-//			"json"
-//		)
-//		$.ajax({
-//        	async: "false",
-//        	url: "history",
-//        	dataType: "json",
-//        	type: "POST",
-//        	data: {
-//        	action:"advfilter",
-//        	projre:projre,
-//        	flowre:flowre,
-//        	userre:userre
-//        	},
-//        	success: function(data) {
-//        		if (data.redirect) {
-//               		window.location = data.redirect;
-//            	}
-//        	}
-//        })
+
+		/*
+		var requestData = {
+			"action": "advfilter", 
+			"projre": projre, 
+			"flowre": flowre, 
+			"userre": userre
+		};
+		var successHandler = function(data) {
+			if (data.action == "redirect") {
+				window.location = data.redirect;
+			}
+		};
+		$.get(historyURL, requestData, successHandler, "json");
+	*/
 	},
-	render: function(){
+
+	render: function() {
 	}
 });
 
 $(function() {
-
 	filterView = new azkaban.AdvFilterView({el: $('#adv-filter')});
-
-	 $('#adv-filter-btn').click( function() {
-		$('#adv-filter').modal({
-        closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-          position: ["20%",],
-          containerId: 'confirm-container',
-          containerCss: {
-            'height': '220px',
-            'width': '500px'
-          },
-          onShow: function (dialog) {
-            var modal = this;
-            $("#errorMsg").hide();
-          }
-        });
-    });
-
-});
\ No newline at end of file
+	$('#adv-filter-btn').click( function() {
+		$('#adv-filter').modal();
+	});
+});
diff --git a/src/web/js/azkaban.historyday.view.js b/src/web/js/azkaban.historyday.view.js
index 312ae40..fba1c6c 100644
--- a/src/web/js/azkaban.historyday.view.js
+++ b/src/web/js/azkaban.historyday.view.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var dayDataModel;
diff --git a/src/web/js/azkaban.jmx.view.js b/src/web/js/azkaban.jmx.view.js
index a38633b..9aa63ea 100644
--- a/src/web/js/azkaban.jmx.view.js
+++ b/src/web/js/azkaban.jmx.view.js
@@ -1,37 +1,54 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var jmxTableView;
-azkaban.JMXTableView= Backbone.View.extend({
-  events : {
-    "click .querybtn": "queryJMX",
-    "click .collapse": "collapseRow"
-  },
-  initialize : function(settings) {
+azkaban.JMXTableView = Backbone.View.extend({
+	events: {
+		"click .query-btn": "queryJMX",
+		"click .collapse-btn": "collapseRow"
+	},
 
-  },
-  queryJMX : function(evt) {
-	var target = evt.currentTarget;
-	var id = target.id;
-	
-	var childID = id + "-child";
-	var tbody = id + "-tbody";
+	initialize: function(settings) {
+	},
 	
-	var requestURL = contextURL + "/jmx";
-	var canonicalName=$(target).attr("domain") + ":name=" + $(target).attr("name");
+	queryJMX: function(evt) {
+		var target = evt.currentTarget;
+		var id = target.id;
+		
+		var childID = id + "-child";
+		var tbody = id + "-tbody";
+		
+		var requestURL = contextURL + "/jmx";
+		var canonicalName=$(target).attr("domain") + ":name=" + $(target).attr("name");
 
-	var data = {"ajax":"getAllMBeanAttributes", "mBean":canonicalName};
-	if ($(target).attr("hostPort")) {
-		data.ajax = "getAllExecutorAttributes";
-		data.hostPort = $(target).attr("hostPort");
-	}
-	$.get(
-		requestURL,
-		data,
-		function(data) {
+		var data = {
+			"ajax": "getAllMBeanAttributes", 
+			"mBean": canonicalName
+		};
+		if ($(target).attr("hostPort")) {
+			data.ajax = "getAllExecutorAttributes";
+			data.hostPort = $(target).attr("hostPort");
+		}
+		var successHandler = function(data) {
 			var table = $('#' + tbody);
 			$(table).empty();
 			
-			for(var key in data.attributes) {
+			for (var key in data.attributes) {
 				var value = data.attributes[key];
 				
 				var tr = document.createElement("tr");
@@ -48,28 +65,31 @@ azkaban.JMXTableView= Backbone.View.extend({
 			}
 			
 			var child = $("#" + childID);
-	    	$(child).fadeIn();
-		}
-	);
-  },
-  queryRemote : function(evt) {
-	var target = evt.currentTarget;
-	var id = target.id;
+			$(child).fadeIn();
+		};
+		$.get(requestURL, data, successHandler);
+	},
 	
-	var childID = id + "-child";
-	var tbody = id + "-tbody";
-	
-	var requestURL = contextURL + "/jmx";
-	var canonicalName=$(target).attr("domain") + ":name=" + $(target).attr("name");
-	var hostPort = $(target).attr("hostport");
-	$.get(
-		requestURL,
-		{"ajax":"getAllExecutorAttributes", "mBean":canonicalName, "hostPort": hostPort},
-		function(data) {
+	queryRemote: function(evt) {
+		var target = evt.currentTarget;
+		var id = target.id;
+		
+		var childID = id + "-child";
+		var tbody = id + "-tbody";
+		
+		var requestURL = contextURL + "/jmx";
+		var canonicalName = $(target).attr("domain") + ":name=" + $(target).attr("name");
+		var hostPort = $(target).attr("hostport");
+		var requestData = {
+			"ajax": "getAllExecutorAttributes", 
+			"mBean": canonicalName, 
+			"hostPort": hostPort
+		};
+		var successHandler = function(data) {
 			var table = $('#' + tbody);
 			$(table).empty();
 			
-			for(var key in data.attributes) {
+			for (var key in data.attributes) {
 				var value = data.attributes[key];
 				
 				var tr = document.createElement("tr");
@@ -86,20 +106,22 @@ azkaban.JMXTableView= Backbone.View.extend({
 			}
 			
 			var child = $("#" + childID);
-	    	$(child).fadeIn();
-		}
-	);
-  },
-  collapseRow: function(evt) {
-  	$(evt.currentTarget).parent().parent().fadeOut();
-  },
-  render: function() {
-  }
+				$(child).fadeIn();
+		};
+		$.get(requestURL, requestData, successHandler);
+	},
+	
+	collapseRow: function(evt) {
+		$(evt.currentTarget).parent().parent().fadeOut();
+	},
+
+	render: function() {
+	}
 });
 
 var remoteTables = new Array();
 $(function() {
-	jmxTableView = new azkaban.JMXTableView({el:$('#all-jobs')});
+	jmxTableView = new azkaban.JMXTableView({el:$('#all-jmx')});
 	
 	$(".remoteJMX").each(function(item) {
 		var newTableView = new azkaban.JMXTableView({el:$(this)});
diff --git a/src/web/js/azkaban.job.status.utils.js b/src/web/js/azkaban.job.status.utils.js
index 7d4b45d..ee03ae6 100644
--- a/src/web/js/azkaban.job.status.utils.js
+++ b/src/web/js/azkaban.job.status.utils.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 var statusList = ["FAILED", "FAILED_FINISHING", "SUCCEEDED", "RUNNING", "WAITING", "KILLED", "DISABLED", "READY", "UNKNOWN", "PAUSED", "SKIPPED"];
 var statusStringMap = {
 	"SKIPPED": "Skipped",
@@ -13,29 +29,3 @@ var statusStringMap = {
 	"UNKNOWN": "Unknown",
 	"PAUSED": "Paused"
 };
-
-function hasClass(el, name) 
-{
-	var classes = el.getAttribute("class");
-	if (classes == null) {
-		return false;
-	}
-   return new RegExp('(\\s|^)'+name+'(\\s|$)').test(classes);
-}
-
-function addClass(el, name)
-{
-   if (!hasClass(el, name)) { 
-   		var classes = el.getAttribute("class");
-   		classes += classes ? ' ' + name : '' +name;
-   		el.setAttribute("class", classes);
-   }
-}
-
-function removeClass(el, name)
-{
-   if (hasClass(el, name)) {
-      var classes = el.getAttribute("class");
-      el.setAttribute("class", classes.replace(new RegExp('(\\s|^)'+name+'(\\s|$)'),' ').replace(/^\s+|\s+$/g, ''));
-   }
-}
\ No newline at end of file
diff --git a/src/web/js/azkaban.jobdetails.view.js b/src/web/js/azkaban.jobdetails.view.js
new file mode 100644
index 0000000..3f2b7a2
--- /dev/null
+++ b/src/web/js/azkaban.jobdetails.view.js
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+$.namespace('azkaban');
+
+var logModel;
+azkaban.LogModel = Backbone.Model.extend({});
+
+var jobLogView;
+azkaban.JobLogView = Backbone.View.extend({
+	events: {
+		"click #updateLogBtn" : "handleUpdate"
+	},
+	initialize: function(settings) {
+		this.model.set({"offset": 0});
+		this.handleUpdate();
+	},
+	handleUpdate: function(evt) {
+		var requestURL = contextURL + "/executor"; 
+		var model = this.model;
+		var finished = false;
+
+		var date = new Date();
+		var startTime = date.getTime();
+		
+		while (!finished) {
+			var offset = this.model.get("offset");
+			var requestData = {
+				"execid": execId, 
+				"jobId": jobId, 
+				"ajax":"fetchExecJobLogs", 
+				"offset": offset, 
+				"length": 50000, 
+				"attempt": attempt
+			};
+
+			var successHandler = function(data) {
+				console.log("fetchLogs");
+				if (data.error) {
+					console.log(data.error);
+					finished = true;
+				}
+				else if (data.length == 0) {
+					finished = true;
+				}
+				else {
+					var date = new Date();
+					var endTime = date.getTime();
+					if ((endTime - startTime) > 10000) {
+						finished = true;
+						showDialog("Alert","The log is taking a long time to finish loading. Azkaban has stopped loading them. Please click Refresh to restart the load.");
+					} 
+
+					var re = /(https?:\/\/(([-\w\.]+)+(:\d+)?(\/([\w/_\.]*(\?\S+)?)?)?))/g;
+					var log = $("#logSection").text();
+					if (!log) {
+						log = data.data;
+					}
+					else {
+						log += data.data;
+					}
+
+					var newOffset = data.offset + data.length;
+					$("#logSection").text(log);
+					log = $("#logSection").html();
+					log = log.replace(re, "<a href=\"$1\" title=\"\">$1</a>");
+					$("#logSection").html(log);
+
+					model.set({"offset": newOffset, "log": log});
+					$(".logViewer").scrollTop(9999);
+				}
+			}
+
+			$.ajax({
+				url: requestURL,
+				type: "get",
+				async: false,
+				data: requestData,
+				dataType: "json",
+				error: function(data) {
+					console.log(data);
+					finished = true;
+				},
+				success: successHandler
+			});
+		}
+	}
+});
+
+var summaryModel;
+azkaban.SummaryModel = Backbone.Model.extend({});
+
+var jobSummaryView;
+azkaban.JobSummaryView = Backbone.View.extend({
+	events: {
+		"click #updateSummaryBtn" : "handleUpdate"
+	},
+	initialize: function(settings) {
+		this.handleUpdate();
+	},
+	handleUpdate: function(evt) {
+		var requestURL = contextURL + "/executor"; 
+		var model = this.model;
+		var self = this;
+
+		var requestData = {
+			"execid": execId, 
+			"jobId": jobId, 
+			"ajax":"fetchExecJobSummary", 
+			"attempt": attempt
+		};
+
+		$.ajax({
+			url: requestURL,
+			dataType: "json",
+			data: requestData,
+			error: function(data) {
+				console.log(data);
+			},
+			success: function(data) {
+				console.log("fetchSummary");
+				if (data.error) {
+					console.log(data.error);
+				}
+				else {
+					self.renderCommandTable(data.commandProperties);
+					self.renderJobTable(data.summaryTableHeaders, data.summaryTableData, "summary");
+					self.renderJobTable(data.statTableHeaders, data.statTableData, "stats");
+					self.renderHiveTable(data.hiveQueries, data.hiveQueryJobs);
+				}
+			}
+		});
+	},
+	renderCommandTable: function(commandProperties) {
+		if (commandProperties) {
+			var commandTable = $("#commandTable");
+			
+			for (var i = 0; i < commandProperties.length; i++) {
+				var prop = commandProperties[i];
+				var tr = document.createElement("tr");
+				var name = document.createElement("td");
+				var value = document.createElement("td");
+				$(name).html("<b>" + prop.first + "</b>");
+				$(value).html(prop.second);
+				$(tr).append(name);
+				$(tr).append(value);
+				commandTable.append(tr);
+			}
+		}
+	},
+	renderJobTable: function(headers, data, prefix) {
+		if (headers) {
+			// Add table headers
+			var header = $("#" + prefix + "Header");
+			var tr = document.createElement("tr");
+			var i;
+			for (i = 0; i < headers.length; i++) {
+				var th = document.createElement("th");
+				$(th).text(headers[i]);
+				$(tr).append(th);
+			}
+			header.append(tr);
+			
+			// Add table body
+			var body = $("#" + prefix + "Body");
+			for (i = 0; i < data.length; i++) {
+				tr = document.createElement("tr");
+				var row = data[i];
+				for (var j = 0; j < row.length; j++) {
+					var td = document.createElement("td");
+					if (j == 0) {
+						// first column is a link to job details page 
+						$(td).html(row[j]);
+					} else {
+						$(td).text(row[j]);
+					}
+					$(tr).append(td);
+				}
+				body.append(tr);
+			}
+		} else {
+			$("#job" + prefix).hide();
+		}
+	},
+	renderHiveTable: function(queries, queryJobs) {
+		if (queries) {
+			// Set up table column headers
+			var header = $("#hiveTableHeader");
+			var tr = document.createElement("tr");
+			var headers = ["Query","Job","Map","Reduce","HDFS Read","HDFS Write"];
+			var i;
+			
+			for (i = 0; i < headers.length; i++) {
+				var th = document.createElement("th");
+				$(th).text(headers[i]);
+				$(tr).append(th);
+			}
+			header.append(tr);
+			
+			// Construct table body
+			var body = $("#hiveTableBody");
+			for (i = 0; i < queries.length; i++) {
+				// new query
+				tr = document.createElement("tr");
+				var td = document.createElement("td");
+				$(td).html("<b>" + queries[i] + "</b>");
+				$(tr).append(td);
+				
+				var jobs = queryJobs[i];
+				if (jobs != null) {
+					// add first job for this query
+					var jobValues = jobs[0];
+					var j;
+					for (j = 0; j < jobValues.length; j++) {
+						td = document.createElement("td");
+						$(td).html(jobValues[j]);
+						$(tr).append(td);
+					}
+					body.append(tr);
+					
+					// add remaining jobs for this query
+					for (j = 1; j < jobs.length; j++) {
+						jobValues = jobs[j];
+						tr = document.createElement("tr");
+						
+						// add empty cell for query column
+						td = document.createElement("td");
+						$(td).html("&nbsp;");
+						$(tr).append(td);
+						
+						// add job values
+						for (var k = 0; k < jobValues.length; k++) {
+							td = document.createElement("td");
+							$(td).html(jobValues[k]);
+							$(tr).append(td);
+						}
+						body.append(tr);
+					}
+					
+				} else {
+					body.append(tr);
+				}
+			}
+		} else {
+			$("#hiveTable").hide();
+		}
+	}
+});
+
+var jobTabView;
+azkaban.JobTabView = Backbone.View.extend({
+	events: {
+		'click #jobSummaryViewLink': 'handleJobSummaryViewLinkClick',
+		'click #jobLogViewLink': 'handleJobLogViewLinkClick'
+	},
+
+	initialize: function(settings) {
+		var selectedView = settings.selectedView;
+		if (selectedView == 'joblog') {
+			this.handleJobLogViewLinkClick();
+		}
+		else {
+			this.handleJobSummaryViewLinkClick();
+		}
+	},
+
+	render: function() {
+	},
+
+	handleJobLogViewLinkClick: function() {
+		$('#jobSummaryViewLink').removeClass('active');
+		$('#jobSummaryView').hide();
+		$('#jobLogViewLink').addClass('active');
+		$('#jobLogView').show();
+	},
+	
+	handleJobSummaryViewLinkClick: function() {
+		$('#jobSummaryViewLink').addClass('active');
+		$('#jobSummaryView').show();
+		$('#jobLogViewLink').removeClass('active');
+		$('#jobLogView').hide();
+	},
+});
+
+var showDialog = function(title, message) {
+  $('#messageTitle').text(title);
+  $('#messageBox').text(message);
+  $('#messageDialog').modal({
+		closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
+		position: ["20%",],
+		containerId: 'confirm-container',
+		containerCss: {
+			'height': '220px',
+			'width': '565px'
+		},
+		onShow: function (dialog) {
+		}
+	});
+}
+
+$(function() {
+	var selected;
+	logModel = new azkaban.LogModel();
+	jobLogView = new azkaban.JobLogView({
+		el: $('#jobLogView'), 
+		model: logModel
+	});
+
+	summaryModel = new azkaban.SummaryModel();
+	jobSummaryView = new azkaban.JobSummaryView({
+		el: $('#jobSummaryView'), 
+		model: summaryModel
+	});
+
+	jobTabView = new azkaban.JobTabView({
+		el: $('#headertabs')
+	});
+
+	if (window.location.hash) {
+		var hash = window.location.hash;
+		if (hash == '#joblog') {
+			jobTabView.handleJobLogViewLinkClick();
+		}
+		else if (hash == '#jobsummary') {
+			jobTabView.handleJobSummaryViewLinkClick();
+		}
+	}
+});
diff --git a/src/web/js/azkaban.jobedit.view.js b/src/web/js/azkaban.jobedit.view.js
index 64fbbc8..ba037b0 100644
--- a/src/web/js/azkaban.jobedit.view.js
+++ b/src/web/js/azkaban.jobedit.view.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var jobEditView;
@@ -6,26 +22,28 @@ azkaban.JobEditView = Backbone.View.extend({
 		"click" : "closeEditingTarget",
 		"click #set-btn": "handleSet",	
 		"click #cancel-btn": "handleCancel",
-		"click .modal-close": "handleCancel",
+		"click #close-btn": "handleCancel",
 		"click #addRow": "handleAddRow",
 		"click table .editable": "handleEditColumn",
-		"click table .removeIcon": "handleRemoveColumn"
+		"click table .remove-btn": "handleRemoveColumn"
 	},
+	
 	initialize: function(setting) {
 		this.projectURL = contextURL + "manager"
 		this.generalParams = {}
 		this.overrideParams = {}
 	},
+	
 	handleCancel: function(evt) {
-		$('#jobEditModalBackground').hide();
 		$('#job-edit-pane').hide();
 		var tbl = document.getElementById("generalProps").tBodies[0];
 		var rows = tbl.rows;
 		var len = rows.length;
-		for(var i=0; i < len-1; i++) {
+		for (var i = 0; i < len-1; i++) {
 			tbl.deleteRow(0);
 		}
 	},
+	
 	show: function(projectName, flowName, jobName) {
 		this.projectName = projectName;
 		this.flowName = flowName;
@@ -33,67 +51,63 @@ azkaban.JobEditView = Backbone.View.extend({
 		
 		var projectURL = this.projectURL
 		
-		
-		$('#jobEditModalBackground').show();
-		$('#job-edit-pane').show();
+		$('#job-edit-pane').modal();
 		
 		var handleAddRow = this.handleAddRow;
 		
-//		var overrideParams;
-//		var generalParams;
-//		this.overrideParams = overrideParams;
-//		this.generalParams = generalParams;
-		var fetchJobInfo = {"project": this.projectName, "ajax":"fetchJobInfo", "flowName":this.flowName, "jobName":this.jobName};
-		
+		/*var overrideParams;
+		var generalParams;
+		this.overrideParams = overrideParams;
+		this.generalParams = generalParams;*/
+		var fetchJobInfo = {
+			"project": this.projectName, 
+			"ajax": "fetchJobInfo", 
+			"flowName": this.flowName, 
+			"jobName": this.jobName
+		};
 		var mythis = this;
-		
-		$.get(
-				projectURL,
-				fetchJobInfo,
-				function(data) {
-					if (data.error) {
-						alert(data.error);
-					}
-					else {
-						document.getElementById('jobName').innerHTML = data.jobName;				
-						document.getElementById('jobType').innerHTML = data.jobType;
-						var generalParams = data.generalParams;
-						var overrideParams = data.overrideParams;
-						
-//						for(var key in generalParams) {
-//							var row = handleAddRow();
-//							var td = $(row).find('span');
-//							$(td[1]).text(key);
-//							$(td[2]).text(generalParams[key]);
-//						}
-						
-						mythis.overrideParams = overrideParams;
-						mythis.generalParams = generalParams;
-						
-						for(var okey in overrideParams) {
-							if(okey != 'type' && okey != 'dependencies') {
-								var row = handleAddRow();
-								var td = $(row).find('span');
-								$(td[1]).text(okey);
-								$(td[2]).text(overrideParams[okey]);
-							}
-						}
-						
-					}
-				},
-				"json"
-			);
+		var fetchJobSuccessHandler = function(data) {
+			if (data.error) {
+				alert(data.error);
+				return;
+			}
+			document.getElementById('jobName').innerHTML = data.jobName;				
+			document.getElementById('jobType').innerHTML = data.jobType;
+			var generalParams = data.generalParams;
+			var overrideParams = data.overrideParams;
+					
+			/*for (var key in generalParams) {
+				var row = handleAddRow();
+				var td = $(row).find('span');
+				$(td[1]).text(key);
+				$(td[2]).text(generalParams[key]);
+			}*/
+					
+			mythis.overrideParams = overrideParams;
+			mythis.generalParams = generalParams;
+			
+			for (var okey in overrideParams) {
+				if (okey != 'type' && okey != 'dependencies') {
+					var row = handleAddRow();
+					var td = $(row).find('span');
+					$(td[0]).text(okey);
+					$(td[1]).text(overrideParams[okey]);
+				}
+			}
+		};
 
+		$.get(projectURL, fetchJobInfo, fetchJobSuccessHandler, "json");
 	},
+
 	handleSet: function(evt) {
 		this.closeEditingTarget(evt);
 		var jobOverride = {};
-	  	var editRows = $(".editRow");
+		var editRows = $(".editRow");
 		for (var i = 0; i < editRows.length; ++i) {
 			var row = editRows[i];
 			var td = $(row).find('span');
-			var key = $(td[1]).text();
-			var val = $(td[2]).text();
+			var key = $(td[0]).text();
+			var val = $(td[1]).text();
 
 			if (key && key.length > 0) {
 				jobOverride[key] = val;
@@ -104,7 +118,7 @@ azkaban.JobEditView = Backbone.View.extend({
 		var generalParams = this.generalParams
 		
 		jobOverride['type'] = overrideParams['type']
-		if('dependencies' in overrideParams) {
+		if ('dependencies' in overrideParams) {
 			jobOverride['dependencies'] = overrideParams['dependencies']
 		}
 		
@@ -122,54 +136,57 @@ azkaban.JobEditView = Backbone.View.extend({
 
 		var projectURL = this.projectURL
 		var redirectURL = projectURL+'?project='+project+'&flow='+flowName+'&job='+jobName;
+		var jobOverrideSuccessHandler = function(data) {
+			if (data.error) {
+				alert(data.error);
+			}
+			else {
+				window.location = redirectURL;
+			}
+		};
 		
-		$.get(
-			projectURL,
-			jobOverrideData,
-			function(data) {
-				if (data.error) {
-					alert(data.error);
-				}
-				else {
-					window.location = redirectURL;
-				}
-			},
-			"json"
-		);
+		$.get(projectURL, jobOverrideData, jobOverrideSuccessHandler, "json");
 	},
+	
 	handleAddRow: function(evt) {
 		var tr = document.createElement("tr");
-	  	var tdName = document.createElement("td");
-	    var tdValue = document.createElement("td");
+		var tdName = document.createElement("td");
+    $(tdName).addClass('property-key');
+		var tdValue = document.createElement("td");
 
-	    var icon = document.createElement("span");
-	    $(icon).addClass("removeIcon");
-	    var nameData = document.createElement("span");
-	    $(nameData).addClass("spanValue");
-	    var valueData = document.createElement("span");
-	    $(valueData).addClass("spanValue");
+		var remove = document.createElement("div");
+    $(remove).addClass("pull-right").addClass('remove-btn');
+    var removeBtn = document.createElement("button");
+    $(removeBtn).attr('type', 'button');
+    $(removeBtn).addClass('btn').addClass('btn-xs').addClass('btn-danger');
+    $(removeBtn).text('Delete');
+    $(remove).append(removeBtn);
+
+		var nameData = document.createElement("span");
+		$(nameData).addClass("spanValue");
+		var valueData = document.createElement("span");
+		$(valueData).addClass("spanValue");
 
-		$(tdName).append(icon);
 		$(tdName).append(nameData);
-		$(tdName).addClass("name");
 		$(tdName).addClass("editable");
 		nameData.myparent = tdName;
 
 		$(tdValue).append(valueData);
-	    $(tdValue).addClass("editable");
+    $(tdValue).append(remove);
+		$(tdValue).addClass("editable");
+		$(tdValue).addClass("value");
 		valueData.myparent = tdValue;
 		
 		$(tr).addClass("editRow");
-	  	$(tr).append(tdName);
-	  	$(tr).append(tdValue);
-
-	  	$(tr).insertBefore("#addRow");
-	  	return tr;
+		$(tr).append(tdName);
+		$(tr).append(tdValue);
 
+		$(tr).insertBefore("#addRow");
+		return tr;
 	},
-	handleEditColumn : function(evt) {
-		var curTarget = evt.currentTarget;
 	
+	handleEditColumn: function(evt) {
+		var curTarget = evt.currentTarget;
 		if (this.editingTarget != curTarget) {
 			this.closeEditingTarget(evt);
 		
@@ -178,7 +195,7 @@ azkaban.JobEditView = Backbone.View.extend({
 						
 			var input = document.createElement("input");
 			$(input).attr("type", "text");
-			$(input).css("width", "100%");
+      $(input).addClass("form-control").addClass("input-sm");
 			$(input).val(text);
 			
 			$(curTarget).addClass("editing");
@@ -186,52 +203,58 @@ azkaban.JobEditView = Backbone.View.extend({
 			$(input).focus();
 			var obj = this;
 			$(input).keypress(function(evt) {
-		    	if(evt.which == 13) {
-			        obj.closeEditingTarget(evt);
-			    }
+				if (evt.which == 13) {
+					obj.closeEditingTarget(evt);
+				}
 			});
-			
 			this.editingTarget = curTarget;
 		}
 
 		evt.preventDefault();
 		evt.stopPropagation();
 	},
-	handleRemoveColumn : function(evt) {
+	
+	handleRemoveColumn: function(evt) {
 		var curTarget = evt.currentTarget;
 		// Should be the table
 		var row = curTarget.parentElement.parentElement;
 		$(row).remove();
 	},
+	
 	closeEditingTarget: function(evt) {
-		if (this.editingTarget != null && this.editingTarget != evt.target && this.editingTarget != evt.target.myparent ) {
-	  		var input = $(this.editingTarget).children("input")[0];
-	  		var text = $(input).val();
-	  		$(input).remove();
-
-		    var valueData = document.createElement("span");
-		    $(valueData).addClass("spanValue");
-		    $(valueData).text(text);
-
-	  		if ($(this.editingTarget).hasClass("name")) {
-		  		var icon = document.createElement("span");
-		    	$(icon).addClass("removeIcon");
-		    	$(this.editingTarget).append(icon);
-		    }
-
-		    $(this.editingTarget).removeClass("editing");
-		    $(this.editingTarget).append(valueData);
-		    valueData.myparent=this.editingTarget;
-		    this.editingTarget = null;
-	  	}
+		if (this.editingTarget == null ||
+				this.editingTarget == evt.target ||
+				this.editingTarget == evt.target.myparent) {
+			return;
+		}
+		var input = $(this.editingTarget).children("input")[0];
+		var text = $(input).val();
+		$(input).remove();
+
+		var valueData = document.createElement("span");
+		$(valueData).addClass("spanValue");
+		$(valueData).text(text);
+
+		if ($(this.editingTarget).hasClass("value")) {
+      var remove = document.createElement("div");
+      $(remove).addClass("pull-right").addClass('remove-btn');
+      var removeBtn = document.createElement("button");
+      $(removeBtn).attr('type', 'button');
+      $(removeBtn).addClass('btn').addClass('btn-xs').addClass('btn-danger');
+      $(removeBtn).text('Delete');
+      $(remove).append(removeBtn);
+			$(this.editingTarget).append(remove);
+		}
+
+		$(this.editingTarget).removeClass("editing");
+		$(this.editingTarget).append(valueData);
+		valueData.myparent = this.editingTarget;
+		this.editingTarget = null;
 	}
 });
 
 $(function() {
-
-
-	jobEditView = new azkaban.JobEditView({el:$('#job-edit-pane')});
-	
-	 
-	
-});
\ No newline at end of file
+	jobEditView = new azkaban.JobEditView({
+		el: $('#job-edit-pane')
+	});
+});
diff --git a/src/web/js/azkaban.jobhistory.view.js b/src/web/js/azkaban.jobhistory.view.js
index a690aca..9607258 100644
--- a/src/web/js/azkaban.jobhistory.view.js
+++ b/src/web/js/azkaban.jobhistory.view.js
@@ -1,28 +1,51 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var jobHistoryView;
 azkaban.JobHistoryView = Backbone.View.extend({
 	events: {
 	},
+	
 	initialize: function(settings) {
 		this.render();
 	},
-	render : function(self) {
+	
+	render: function(self) {
 		var data = this.model.get("data");
 	
-		var margin = {top: 20, right: 20, bottom: 30, left: 70},
-	    width = $(this.el).width() - margin.left - margin.right,
-	    height = 300 - margin.top - margin.bottom;
+		var margin = {
+			top: 20, 
+			right: 20, 
+			bottom: 30, 
+			left: 70
+		};
+	  var width = $(this.el).width() - margin.left - margin.right;
+	  var height = 300 - margin.top - margin.bottom;
 	    
-	    var x = d3.time.scale()
+		var x = d3.time.scale()
 		    .range([0, width]);
 		
 		var y = d3.scale.linear()
 		    .range([height, 0]);
 	    
-	    var xAxis = d3.svg.axis()
-		    .scale(x)
-		    .orient("bottom");
+		var xAxis = d3.svg.axis()
+				.scale(x)
+				.orient("bottom");
 
 		var yAxis = d3.svg.axis()
 		    .scale(y)
@@ -43,49 +66,56 @@ azkaban.JobHistoryView = Backbone.View.extend({
 		    .append("g")
 		    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
 		  
-		  var xextent = d3.extent(data, function(d) { return d.startTime; });
-		  var diff = (xextent[1] - xextent[0])*0.05;
-		  
-		  xextent[0] -= diff;
-		  xextent[1] += diff;
-		  x.domain(xextent);
-		  
-		  var yextent = d3.extent(data, function(d) { return d.endTime - d.startTime; });
-		  var upperYbound = yextent[1]*1.25;
-		  y.domain([0, upperYbound]);
+		var xextent = d3.extent(data, function(d) {
+			return d.startTime;
+		});
+		var diff = (xextent[1] - xextent[0])*0.05;
 		
-		  svg.append("g")
-		      .attr("class", "x axis")
-		      .attr("transform", "translate(0," + height + ")")
-		      .call(xAxis);
+		xextent[0] -= diff;
+		xextent[1] += diff;
+		x.domain(xextent);
 		
-		  svg.append("g")
-		      .attr("class", "y axis")
-		      .call(yAxis)
-		      .append("text")
-		      .attr("transform", "rotate(-90)")
-		      .attr("y", 6)
-		      .attr("dy", ".71em")
-		      .style("text-anchor", "end")
-		      .text("Duration");
+		var yextent = d3.extent(data, function(d) {
+			return d.endTime - d.startTime;
+		});
+		var upperYbound = yextent[1]*1.25;
+		y.domain([0, upperYbound]);
+	
+		svg.append("g")
+				.attr("class", "x axis")
+				.attr("transform", "translate(0," + height + ")")
+				.call(xAxis);
+	
+		svg.append("g")
+				.attr("class", "y axis")
+				.call(yAxis)
+				.append("text")
+				.attr("transform", "rotate(-90)")
+				.attr("y", 6)
+				.attr("dy", ".71em")
+				.style("text-anchor", "end")
+				.text("Duration");
+	
+		svg.append("path")
+				.datum(data)
+				.attr("class", "line")
+				.attr("d", line);
+				
+		var node = svg.selectAll("g.node")
+				.data(data)
+				.attr("class", "node")
+				.enter().append("g")
+				.attr("transform",  function(d) {
+			return "translate(" + x(d.startTime) + "," + y(d.endTime-d.startTime) + ")";
+		});
 		
-		  svg.append("path")
-		      .datum(data)
-		      .attr("class", "line")
-		      .attr("d", line);
-		      
-		  var node = svg.selectAll("g.node")
-		  				.data(data)
-		  				.attr("class", "node")
-		  				.enter().append("g")
-		  				.attr("transform",  function(d) { return "translate(" + x(d.startTime) + "," + y(d.endTime-d.startTime) + ")";});
-
-		  
-		  node.append("circle")
-		  	  .attr("r", 5)
-		  	  .attr("class", function(d) {return d.status;})
-		  	  .append("svg:title")
-		  		.text(function(d) { return d.execId + ":" + d.flowId + " ran in " + getDuration(d.startTime, d.endTime)});
+		node.append("circle")
+				.attr("r", 5)
+				.attr("class", function(d) {return d.status;})
+				.append("svg:title")
+				.text(function(d) {
+			return d.execId + ":" + d.flowId + " ran in " + getDuration(d.startTime, d.endTime);
+		});
 	}
 });
 
@@ -96,6 +126,11 @@ $(function() {
 	var selected;
 	var series = dataSeries;
 	dataModel = new azkaban.DataModel();
-	dataModel.set({"data":series});
-	jobDurationView = new azkaban.JobHistoryView({el:$('#timeGraph'), model: dataModel});
+	dataModel.set({
+		"data": series
+	});
+	jobDurationView = new azkaban.JobHistoryView({
+		el: $('#timeGraph'), 
+		model: dataModel
+	});
 });
diff --git a/src/web/js/azkaban.layout.js b/src/web/js/azkaban.layout.js
index 53e7692..6f6c2c5 100644
--- a/src/web/js/azkaban.layout.js
+++ b/src/web/js/azkaban.layout.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 var maxTextSize = 32;
 var reductionSize = 26;
 var degreeRatio = 1/8;
@@ -25,9 +41,16 @@ function layoutGraph(nodes, edges) {
 		//}
 		
 		var width = nodes[i].label.length * 10;
-		var node = { id: nodes[i].id, node: nodes[i], level: nodes[i].level, in:[], out:[], width: width, x:0 };
+		var node = {
+			id: nodes[i].id, 
+			node: nodes[i], 
+			level: nodes[i].level, 
+			in: [], 
+			out: [], 
+			width: width, 
+			x: 0 
+		};
 		nodeMap[nodes[i].id] = node;
-
 		maxLayer = Math.max(node.level, maxLayer);
 		if(!layers[node.level]) {
 			layers[node.level] = [];
@@ -54,7 +77,15 @@ function layoutGraph(nodes, edges) {
 		var guides = [];
 		
 		for (var j = srcNode.level + 1; j < destNode.level; ++j) {
-			var dummyNode = {level: j, in: [], x: lastNode.x, out: [], realSrc: srcNode, realDest: destNode, width: 10};
+			var dummyNode = {
+				level: j, 
+				in: [], 
+				x: lastNode.x, 
+				out: [], 
+				realSrc: srcNode, 
+				realDest: destNode, 
+				width: 10
+			};
 			layers[j].push(dummyNode);
 			dummyNode.in.push(lastNode);
 			lastNode.out.push(dummyNode);
@@ -98,7 +129,6 @@ function layoutGraph(nodes, edges) {
 		sort(layers[i]);
 		spreadLayerSmart(layers[i]);
 	}
-	
 
 	// Space it vertically
 	spaceVertically(layers, maxLayer);
@@ -118,7 +148,6 @@ function layoutGraph(nodes, edges) {
 		var dest = edges[i].target;
 		
 		var edgeId = src + ">>" + dest;
-
 		if (edgeDummies[edgeId] && edgeDummies[edgeId].length > 0) {
 			var prevX = nodeMap[src].x;
 			var destX = nodeMap[dest].x; 
@@ -130,7 +159,6 @@ function layoutGraph(nodes, edges) {
 				guides.push(point);
 				
 				var nextX = j == dummies.length - 1 ? destX: dummies[j + 1].x; 
-
 				if (point.x != prevX && point.x != nextX) {
 					// Add gap
 					if ((point.x > prevX) == (point.x > nextX)) {
@@ -150,7 +178,13 @@ function layoutGraph(nodes, edges) {
 
 function spreadLayerSmart(layer) {
 	var ranges = [];
-	ranges.push({start: 0, end:0, width: layer[0].width, x: layer[0].x, index: 0});
+	ranges.push({
+		start: 0, 
+		end: 0, 
+		width: layer[0].width, 
+		x: layer[0].x, 
+		index: 0
+	});
 	var largestRangeIndex = -1;
 	
 	var totalX = layer[0].x;
@@ -164,11 +198,17 @@ function spreadLayerSmart(layer) {
 		if (delta == 0) {
 			prevRange.end = i;
 			prevRange.width += layer[i].width;
-			totalWidth+=layer[i].width;
+			totalWidth += layer[i].width;
 		}
 		else {
-			totalWidth+=Math.max(layer[i].width, delta);
-			ranges.push({start: i, end: i, width: layer[i].width, x: layer[i].x, index: ranges.length});
+			totalWidth += Math.max(layer[i].width, delta);
+			ranges.push({
+				start: i, 
+				end: i, 
+				width: layer[i].width, 
+				x: layer[i].x, 
+				index: ranges.length
+			});
 		}
 		
 		totalX += layer[i].x;
@@ -204,34 +244,31 @@ function spreadLayerSmart(layer) {
 		endIndex = e + 1;
 	}
 	
-	for (var i=startIndex; i >= 0; --i) {
+	for (var i = startIndex; i >= 0; --i) {
 		var range = ranges[i];
 		var crossPointS = range.x + range.width/2;
 		var crossPointE = ranges[i + 1].x - ranges[i + 1].width/2;
-		
 		if (crossPointE < crossPointS) {
 			range.x -= crossPointS - crossPointE;
 		}
 	}
 	
-	for (var i=endIndex; i < ranges.length; ++i) {
+	for (var i = endIndex; i < ranges.length; ++i) {
 		var range = ranges[i];
 		var crossPointE = range.x - range.width/2;
 		var crossPointS = ranges[i - 1].x + ranges[i - 1].width/2;
-		
 		if (crossPointE < crossPointS) {
 			range.x += crossPointS - crossPointE;
 		}
 	}
 	
-	for (var i=0; i < ranges.length; ++i) {
+	for (var i = 0; i < ranges.length; ++i) {
 		var range = ranges[i];
 		if (range.start == range.end) {
 			layer[range.start].x = range.x;
 		}
 		else {
 			var start = range.x - range.width/2;
-			
 			for (var j=range.start;j <=range.end; ++j) {
 				layer[j].x = start + layer[j].width/2;
 				start += layer[j].width;
@@ -240,23 +277,21 @@ function spreadLayerSmart(layer) {
 	}
 }
 
-
 function spaceVertically(layers, maxLayer) {
 	var startY = 0;
 	var startLayer = layers[0];
-	for (var i=0; i < startLayer.length; ++i) {
+	for (var i = 0; i < startLayer.length; ++i) {
 		startLayer[i].y = startY;
 	}
 	
 	var minHeight = 50;
-	for (var a=1; a <= maxLayer; ++a) {
+	for (var a = 1; a <= maxLayer; ++a) {
 		var maxDelta = 0;
 		var layer = layers[a];
-		for (var i=0; i < layer.length; ++i) {
-			for (var j=0; j < layer[i].in.length; ++j) {
+		for (var i = 0; i < layer.length; ++i) {
+			for (var j = 0; j < layer[i].in.length; ++j) {
 				var upper = layer[i].in[j];
 				var delta = Math.abs(upper.x - layer[i].x);
-				
 				maxDelta = Math.max(maxDelta, delta);
 			}
 		}
@@ -266,11 +301,10 @@ function spaceVertically(layers, maxLayer) {
 		
 		calcHeight = Math.min(calcHeight, maxHeight); 
 		startY += Math.max(calcHeight, minHeight);
-		for (var i=0; i < layer.length; ++i) {
-			layer[i].y=startY;
+		for (var i = 0; i < layer.length; ++i) {
+			layer[i].y = startY;
 		}
 	}
-
 }
 
 function uncrossWithIn(layer) {
@@ -285,7 +319,6 @@ function findAverage(nodes) {
 	for (var i = 0; i < nodes.length; ++i) {
 		sum += nodes[i].x;
 	}
-	
 	return sum/nodes.length;
 }
 
diff --git a/src/web/js/azkaban.login.js b/src/web/js/azkaban.login.js
index 31851c4..1ac077f 100644
--- a/src/web/js/azkaban.login.js
+++ b/src/web/js/azkaban.login.js
@@ -1,46 +1,69 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 
 $.namespace('azkaban');
 
 var loginView;
-azkaban.LoginView= Backbone.View.extend({
-  events : {
-    "click #loginSubmit": "handleLogin",
-    'keypress input': 'handleKeyPress'
-  },
-  initialize : function(settings) {
-	 $('#errorMsg').hide();
-  },
-  handleLogin : function(evt) {
-	 console.log("Logging in.");
-	 var username = $("#username").val();
-	 var password = $("#password").val();
+azkaban.LoginView = Backbone.View.extend({
+	events: {
+		"click #login-submit": "handleLogin",
+		'keypress input': 'handleKeyPress'
+	},
+
+	initialize: function(settings) {
+		$('#error-msg').hide();
+	},
+	
+	handleLogin: function(evt) {
+		console.log("Logging in.");
+		var username = $("#username").val();
+		var password = $("#password").val();
 	 
-     $.ajax({
-     	async: "false",
-     	url: contextURL,
-     	dataType: "json",
-     	type: "POST",
-     	data: {action:"login", username:username, password:password},
-     	success: function(data) {
-     		if (data.error) {
-     			$('#errorMsg').text(data.error);
-     			$('#errorMsg').slideDown('fast');
-     		}
-     		else {
-     			document.location.reload();
-     		}
-     	}
-     });
-  },
-  handleKeyPress : function(evt) {
-	  if (evt.charCode == 13 || evt.keyCode == 13) {
-	  	this.handleLogin();
-  	  }
-  },
-  render: function() {
-  }
+		$.ajax({
+			async: "false",
+			url: contextURL,
+			dataType: "json",
+			type: "POST",
+			data: {
+				action: "login", 
+				username: username, 
+				password: password
+			},
+			success: function(data) {
+				if (data.error) {
+					$('#error-msg').text(data.error);
+					$('#error-msg').slideDown('fast');
+				}
+				else {
+					document.location.reload();
+				}
+			}
+		});
+	},
+	
+	handleKeyPress: function(evt) {
+		if (evt.charCode == 13 || evt.keyCode == 13) {
+			this.handleLogin();
+		}
+	},
+	
+	render: function() {
+	}
 });
 
 $(function() {
-	loginView = new azkaban.LoginView({el:$('#loginForm')});
+	loginView = new azkaban.LoginView({el: $('#login-form')});
 });
diff --git a/src/web/js/azkaban.main.view.js b/src/web/js/azkaban.main.view.js
index 5c9193a..56fba73 100644
--- a/src/web/js/azkaban.main.view.js
+++ b/src/web/js/azkaban.main.view.js
@@ -1,189 +1,215 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var projectTableView;
-azkaban.ProjectTableView= Backbone.View.extend({
-  events : {
-    "click .project-expand": "expandProject"
-  },
-  initialize : function(settings) {
-
-  },
-  expandProject : function(evt) {
-    if (evt.target.tagName!="SPAN") {
-    	return;
-    }
-    
-    var target = evt.currentTarget;
-    var targetId = target.id;
-    var requestURL = contextURL + "/manager";
-    
-    var targetExpanded = $('#' + targetId + '-child');
-    var targetTBody = $('#' + targetId + '-tbody');
-    
-    var createFlowListFunction = this.createFlowListTable;
-    
-    if (target.loading) {
-    	console.log("Still loading.");
-    }
-    else if (target.loaded) {
-    	if($(targetExpanded).is(':visible')) {
-    		$(target).addClass('expand').removeClass('collapse');
-    		$(targetExpanded).fadeOut("fast");
-    	}
-    	else {
-    	    $(target).addClass('collapse').removeClass('expand');
-    		$(targetExpanded).fadeIn();
-    	}
-    }
-    else {
-	    // projectId is available
-	    $(target).addClass('wait').removeClass('collapse').removeClass('expand');
-	    target.loading = true;
-	    
-	    $.get(
-	      requestURL,
-	      {"project": targetId, "ajax":"fetchprojectflows"},
-	      function(data) {
-	        console.log("Success");
-	        target.loaded = true;
-	        target.loading = false;
-	        
-	        createFlowListFunction(data, targetTBody);
-	        
-			$(target).addClass('collapse').removeClass('wait');
-	    	$(targetExpanded).fadeIn("fast");
-	      },
-	      "json"
-	    );
-    }
-  },
-  render: function() {
-  },
-  createFlowListTable : function(data, innerTable) {
-  	var flows = data.flows;
-  	flows.sort(function(a,b){return a.flowId.localeCompare(b.flowId);});
-  	
-  	var requestURL = contextURL + "/manager?project=" + data.project + "&flow=";
-  	for (var i = 0; i < flows.length; ++i) {
-  		var id = flows[i].flowId;
-  		
-  		var tr = document.createElement("tr");
-		var idtd = document.createElement("td");
-		$(idtd).addClass("tb-name");
-  		
-  		var ida = document.createElement("a");
-		ida.project = data.project;
-		$(ida).text(id);
-		$(ida).attr("href", requestURL + id);
+azkaban.ProjectTableView = Backbone.View.extend({
+	events: {
+		"click .project-expand": "expandProject"
+	},
+	
+	initialize: function(settings) {
+	},
+	
+	expandProject: function(evt) {
+		if (evt.target.tagName == "A") {
+			return;
+		}
+		
+		var target = evt.currentTarget;
+		var targetId = target.id;
+		var requestURL = contextURL + "/manager";
 		
-		$(idtd).append(ida);
-		$(tr).append(idtd);
-		$(innerTable).append(tr);
-  	}
-  }
+		var targetExpanded = $('#' + targetId + '-child');
+		var targetTBody = $('#' + targetId + '-tbody');
+		var createFlowListFunction = this.createFlowListTable;
+		
+		if (target.loading) {
+			console.log("Still loading.");
+		}
+		else if (target.loaded) {
+			if ($(targetExpanded).is(':visible')) {
+				$(target).addClass('expanded').removeClass('collapsed');
+				var expander = $(target).children('.az-expander')[0];
+				$(expander).removeClass('state-icon-collapse');
+				$(expander).addClass('state-icon-expand');
+				$(targetExpanded).fadeOut("fast");
+			}
+			else {
+				$(target).addClass('collapsed').removeClass('expanded');
+				var expander = $(target).children('.az-expander')[0];
+				$(expander).removeClass('state-icon-expand');
+				$(expander).addClass('state-icon-collapse');
+				$(targetExpanded).fadeIn();
+			}
+		}
+		else {
+			// projectId is available
+			$(target).addClass('wait').removeClass('collapsed').removeClass('expanded');
+			target.loading = true;
+
+			var request = {
+				"project": targetId, 
+				"ajax": "fetchprojectflows"
+			};
+
+			var successHandler = function(data) {
+				console.log("Success");
+				target.loaded = true;
+				target.loading = false;
+				
+				createFlowListFunction(data, targetTBody);
+				
+				$(target).addClass('collapsed').removeClass('wait');
+				var expander = $(target).children('.az-expander')[0];
+				$(expander).removeClass('state-icon-expand');
+				$(expander).addClass('state-icon-collapse');
+				$(targetExpanded).fadeIn("fast");
+			};
+			
+			$.get(requestURL, request, successHandler, "json");
+		}
+	},
+
+	render: function() {
+	},
+	
+	createFlowListTable: function(data, innerTable) {
+		var flows = data.flows;
+		flows.sort(function(a,b) {
+			return a.flowId.localeCompare(b.flowId);
+		});
+		var requestURL = contextURL + "/manager?project=" + data.project + "&flow=";
+		for (var i = 0; i < flows.length; ++i) {
+			var id = flows[i].flowId;
+			var tr = document.createElement("tr");
+			var idtd = document.createElement("td");
+			$(idtd).addClass("tb-name");
+			
+			var ida = document.createElement("a");
+			ida.project = data.project;
+			$(ida).text(id);
+			$(ida).attr("href", requestURL + id);
+			
+			$(idtd).append(ida);
+			$(tr).append(idtd);
+			$(innerTable).append(tr);
+		}
+	}
 });
 
 var projectHeaderView;
-azkaban.ProjectHeaderView= Backbone.View.extend({
-  events : {
-    "click #create-project-btn":"handleCreateProjectJob"
-  },
-  initialize : function(settings) {
-    if (settings.errorMsg && settings.errorMsg != "null") {
-      // Chrome bug in displaying placeholder text. Need to hide the box.
-      $('#searchtextbox').hide();
-      $('.messaging').addClass("error");
-      $('.messaging').removeClass("success");
-      $('.messaging').html(settings.errorMsg);
-    }
-    else if (settings.successMsg && settings.successMsg != "null") {
-      $('#searchtextbox').hide();
-      $('.messaging').addClass("success");
-      $('.messaging').removeClass("error");
-      $('#message').html(settings.successMsg);
-    }
-    else {
-      $('#searchtextbox').show();
-      $('.messaging').removeClass("success");
-      $('.messaging').removeClass("error");
-    }
-    
-    $('#messageClose').click(function() {
-      $('#searchtextbox').show();
-      
-      $('.messaging').slideUp('fast', function() {
-        $('.messaging').removeClass("success");
-        $('.messaging').removeClass("error");
-      });
-    });
-  },
-  handleCreateProjectJob : function(evt) {
-    console.log("click create project");
-      $('#create-project').modal({
-          closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-          position: ["20%",],
-          containerId: 'confirm-container',
-          containerCss: {
-            'height': '220px',
-            'width': '565px'
-          },
-          onShow: function (dialog) {
-            var modal = this;
-            $("#errorMsg").hide();
-          }
-        });
-  },
-  render: function() {
-  }
+azkaban.ProjectHeaderView = Backbone.View.extend({
+	events: {
+		"click #create-project-btn": "handleCreateProjectJob"
+	},
+
+	initialize: function(settings) {
+		console.log("project header view initialize.");
+		if (settings.errorMsg && settings.errorMsg != "null") {
+			$('#messaging').addClass("alert-danger");
+			$('#messaging').removeClass("alert-success");
+			$('#messaging-message').html(settings.errorMsg);
+		}
+		else if (settings.successMsg && settings.successMsg != "null") {
+			$('#messaging').addClass("alert-success");
+			$('#messaging').removeClass("alert-danger");
+			$('#messaging-message').html(settings.successMsg);
+		}
+		else {
+			$('#messaging').removeClass("alert-success");
+			$('#messaging').removeClass("alert-danger");
+		}
+	},
+
+	handleCreateProjectJob: function(evt) {
+		$('#create-project-modal').modal();
+	},
+	
+	render: function() {
+	}
 });
 
 var createProjectView;
-azkaban.CreateProjectView= Backbone.View.extend({
-  events : {
-    "click #create-btn": "handleCreateProject"
-  },
-  initialize : function(settings) {
-    $("#errorMsg").hide();
-  },
-  handleCreateProject : function(evt) {
-	 // First make sure we can upload
-	 var projectName = $('#path').val();
-	 var description = $('#description').val();
-
-     console.log("Creating");
-     $.ajax({
-     	async: "false",
-     	url: "manager",
-     	dataType: "json",
-     	type: "POST",
-     	data: {action:"create", name:projectName, description:description},
-     	success: function(data) {
-     		if (data.status == "success") {
-     			if (data.action == "redirect") {
-     				window.location = data.path;
-     			}
-     		}
-     		else {
-     			if (data.action == "login") {
- 					window.location = "";
-     			}
-     			else {
-	     			$("#errorMsg").text("ERROR: " + data.message);
-	    			$("#errorMsg").slideDown("fast");
-    			}
-     		}
-     	}
-     });
+azkaban.CreateProjectView = Backbone.View.extend({
+	events: {
+		"click #create-btn": "handleCreateProject"
+	},
+	
+	initialize: function(settings) {
+		$("#modal-error-msg").hide();
+	},
+	
+	handleCreateProject: function(evt) {
+		// First make sure we can upload
+		var projectName = $('#path').val();
+		var description = $('#description').val();
+		console.log("Creating");
+		$.ajax({
+			async: "false",
+			url: "manager",
+			dataType: "json",
+			type: "POST",
+			data: {
+				action: "create", 
+				name: projectName, 
+				description: description
+			},
+			success: function(data) {
+				if (data.status == "success") {
+					if (data.action == "redirect") {
+						window.location = data.path;
+					}
+				}
+				else {
+					if (data.action == "login") {
+						window.location = "";
+					}
+					else {
+						$("#modal-error-msg").text("ERROR: " + data.message);
+						$("#modal-error-msg").slideDown("fast");
+					}
+				}
+			}
+		});
+	},
 
-  },
-  render: function() {
-  }
+	render: function() {
+	}
 });
 
 var tableSorterView;
 $(function() {
-	projectHeaderView = new azkaban.ProjectHeaderView({el:$( '#all-jobs-content'), successMsg: successMessage, errorMsg: errorMessage });
-	projectTableView = new azkaban.ProjectTableView({el:$('#all-jobs')});
-	tableSorterView = new azkaban.TableSorter({el:$('#all-jobs'), initialSort: $('.tb-name')});
-	uploadView = new azkaban.CreateProjectView({el:$('#create-project')});
+	projectHeaderView = new azkaban.ProjectHeaderView({
+		el: $('#create-project'), 
+		successMsg: successMessage, 
+		errorMsg: errorMessage 
+	});
+	
+	projectTableView = new azkaban.ProjectTableView({
+		el: $('#all-jobs')
+	});
+	
+	tableSorterView = new azkaban.TableSorter({
+		el: $('#all-jobs'), 
+		initialSort: $('.tb-name')
+	});
+	
+	uploadView = new azkaban.CreateProjectView({
+		el: $('#create-project-modal')
+	});
 });
diff --git a/src/web/js/azkaban.message.dialog.view.js b/src/web/js/azkaban.message.dialog.view.js
index cc67db2..cea0852 100644
--- a/src/web/js/azkaban.message.dialog.view.js
+++ b/src/web/js/azkaban.message.dialog.view.js
@@ -1,38 +1,44 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var messageDialogView;
-
 azkaban.MessageDialogView = Backbone.View.extend({
-  events : {
-  },
-  initialize : function(settings) {
+	events: {
+	},
 
-  },
-  show: function(title, message, callback) {
-  	$("#azkabanMessageDialogTitle").text(title);
-  	$("#azkabanMessageDialogText").text(message);
-  	this.callback = callback;
-  	
-      $(this.el).modal({
-          position: ["20%",],
-          closeClass: "continueclass",
-          containerId: 'confirm-container',
-          containerCss: {
-            'height': '220px',
-            'width': '565px'
-          },
-          onShow: function (dialog) {
-          },
-          onClose: function() {
-          	if (callback) {
-          		callback.call();
-          	}
-          }
-     });
-  }
+	initialize: function(settings) {
+	},
+	
+	show: function(title, message, callback) {
+		$("#azkaban-message-dialog-title").text(title);
+		$("#azkaban-message-dialog-text").text(message);
+		this.callback = callback;
+		$(this.el).on('hidden.bs.modal', function() {
+			if (callback) {
+				callback.call();
+			}
+		});
+		$(this.el).modal();
+	}
 });
 
-
 $(function() {
-	messageDialogView = new azkaban.MessageDialogView({el: $('#azkabanMessageDialog')});
+	messageDialogView = new azkaban.MessageDialogView({
+		el: $('#azkaban-message-dialog')
+	});
 });
diff --git a/src/web/js/azkaban.permission.view.js b/src/web/js/azkaban.permission.view.js
index 67c826b..fbd332c 100644
--- a/src/web/js/azkaban.permission.view.js
+++ b/src/web/js/azkaban.permission.view.js
@@ -1,36 +1,58 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var permissionTableView;
 var groupPermissionTableView;
 
-azkaban.PermissionTableView= Backbone.View.extend({
-  events : {
-	"click button": "handleChangePermission"
-  },
-  initialize : function(settings) {
-  	this.group = settings.group;
-  	this.proxy = settings.proxy;
-  },
-  render: function() {
-  },
-  handleChangePermission: function(evt) {
-  	  var currentTarget = evt.currentTarget;
-  	  changePermissionView.display(currentTarget.id, false, this.group, this.proxy);
-  }
+azkaban.PermissionTableView = Backbone.View.extend({
+	events : {
+		"click button": "handleChangePermission"
+	},
+	
+	initialize : function(settings) {
+		this.group = settings.group;
+		this.proxy = settings.proxy;
+	},
+	
+	render: function() {
+	},
+	
+	handleChangePermission: function(evt) {
+		var currentTarget = evt.currentTarget;
+		changePermissionView.display(currentTarget.id, false, this.group, this.proxy);
+	}
 });
 
 var proxyTableView;
 azkaban.ProxyTableView= Backbone.View.extend({
-  events : {
-	"click button": "handleRemoveProxy"
-  },
-  initialize : function(settings) {
-  },
-  render: function() {
-  },
-  handleRemoveProxy: function(evt) {
-	removeProxyView.display($(evt.currentTarget).attr("name"));
-  }
+	events : {
+		"click button": "handleRemoveProxy"
+	},
+	
+	initialize : function(settings) {
+	},
+	
+	render: function() {
+	},
+	
+	handleRemoveProxy: function(evt) {
+		removeProxyView.display($(evt.currentTarget).attr("name"));
+	}
 });
 
 var removeProxyView;
@@ -38,46 +60,36 @@ azkaban.RemoveProxyView = Backbone.View.extend({
 	events: {
 		"click #remove-proxy-btn": "handleRemoveProxy"
 	},
+	
 	initialize : function(settings) {
-		$('#removeProxyErrorMsg').hide();
+		$('#remove-proxy-error-msg').hide();
 	},
+	
 	display: function(proxyName) {
 		this.el.proxyName = proxyName;
 		$("#proxyRemoveMsg").text("Removing proxy user '" + proxyName + "'");
-	  	 $(this.el).modal({
-	          closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-	          position: ["20%",],
-	          containerId: 'confirm-container',
-	          containerCss: {
-	            'height': '220px',
-	            'width': '565px'
-	          },
-	          onShow: function (dialog) {
-	            var modal = this;
-	            $("#removeProxyErrorMsg").hide();
-	          }
-        });
+		$(this.el).modal();
 	},
 	handleRemoveProxy: function() {
-	  	var requestURL = contextURL + "/manager";
+		var requestURL = contextURL + "/manager";
 		var proxyName = this.el.proxyName;
+		var requestData = {
+			"project": projectName, 
+			"name": proxyName, 
+			"ajax": "removeProxyUser"
+		};
+		var successHandler = function(data) {
+			console.log("Output");
+			if (data.error) {
+				$("#removeProxyErrorMsg").text(data.error);
+				$("#removeProxyErrorMsg").show();
+				return;
+			}
+			var replaceURL = requestURL + "?project=" + projectName +"&permissions";
+			window.location.replace(replaceURL);
+		};
 
-	  	$.get(
-	  	      requestURL,
-	  	      {"project": projectName, "name": proxyName, "ajax":"removeProxyUser"},
-	  	      function(data) {
-	  	      	  console.log("Output");
-	  	      	  if (data.error) {
-	  	      	  	$("#removeProxyErrorMsg").text(data.error);
-	  	      	  	$("#removeProxyErrorMsg").show();
-	  	      	  	return;
-	  	      	  }
-	  	      	  
-	  	      	  var replaceURL = requestURL + "?project=" + projectName +"&permissions";
-	  	          window.location.replace(replaceURL);
-	  	      },
-	  	      "json"
-	  	    );
+		$.get(requestURL, requestData, successHandler, "json");
 	}
 });
 
@@ -86,233 +98,241 @@ azkaban.AddProxyView = Backbone.View.extend({
 	events: {
 		"click #add-proxy-btn": "handleAddProxy"
 	},
+	
 	initialize : function(settings) {
-		$('#proxyErrorMsg').hide();
+		$('#add-proxy-error-msg').hide();
 	},
+	
 	display: function() {
-	  	 $(this.el).modal({
-	          closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-	          position: ["20%",],
-	          containerId: 'confirm-container',
-	          containerCss: {
-	            'height': '220px',
-	            'width': '565px'
-	          },
-	          onShow: function (dialog) {
-	            var modal = this;
-	            $("#errorMsg").hide();
-	          }
-        });
+		$(this.el).modal();
 	},
+	
 	handleAddProxy: function() {
-	  	var requestURL = contextURL + "/manager";
-	  	var name = $('#proxy-user-box').val();
-		
-	  	$.get(
-	  	      requestURL,
-	  	      {"project": projectName, "name": name, "ajax":"addProxyUser"},
-	  	      function(data) {
-	  	      	  console.log("Output");
-	  	      	  if (data.error) {
-	  	      	  	$("#proxyErrorMsg").text(data.error);
-	  	      	  	$("#proxyErrorMsg").show();
-	  	      	  	return;
-	  	      	  }
-	  	      	  
-	  	      	  var replaceURL = requestURL + "?project=" + projectName +"&permissions";
-	  	          window.location.replace(replaceURL);
-	  	      },
-	  	      "json"
-	  	    );
+		var requestURL = contextURL + "/manager";
+		var name = $('#proxy-user-box').val();
+		var requestData = {
+			"project": projectName, 
+			"name": name, 
+			"ajax":"addProxyUser"
+		};
+	
+		var successHandler = function(data) {
+			console.log("Output");
+			if (data.error) {
+				$("#proxyErrorMsg").text(data.error);
+				$("#proxyErrorMsg").show();
+				return;
+			}
+			
+			var replaceURL = requestURL + "?project=" + projectName +"&permissions";
+			window.location.replace(replaceURL);
+		};
+		$.get(requestURL, requestData, successHandler, "json");
 	}
 });
 
 var changePermissionView;
 azkaban.ChangePermissionView= Backbone.View.extend({
-  events : {
-  	"click input[type=checkbox]": "handleCheckboxClick",
-  	"click #change-btn": "handleChangePermissions"
-  },
-  initialize : function(settings) {
-  	$('#errorMsg').hide();
-  },
-  display: function(userid, newPerm, group, proxy) {
-  	// 6 is the length of the prefix "group-"
-  	this.userid = group ? userid.substring(6, userid.length) : userid;
-  	if(group == true) {
-  		this.userid = userid.substring(6, userid.length)
-  	} else if (proxy == true) {
-  		this.userid = userid.substring(6, userid.length)
-  	} else {
-  		this.userid = userid
-  	}
-  	
-  	this.permission = {};
-	$('#user-box').val(this.userid);
-	this.newPerm = newPerm;
-	this.group = group;
+	events: {
+		"click input[type=checkbox]": "handleCheckboxClick",
+		"click #change-btn": "handleChangePermissions"
+	},
 	
-	var prefix = userid;
-	var adminInput = $("#" + prefix + "-admin-checkbox");
-	var readInput = $("#" + prefix + "-read-checkbox");
-	var writeInput = $("#" + prefix + "-write-checkbox");
-	var executeInput = $("#" + prefix + "-execute-checkbox");
-	var scheduleInput = $("#" + prefix + "-schedule-checkbox");
+	initialize: function(settings) {
+		$('#change-permission-error-msg').hide();
+	},
 	
-	if (newPerm) {
-		if (group) {
-			$('#change-title').text("Add New Group Permissions");
-		}
-		else if(proxy){
-			$('#change-title').text("Add New Proxy User Permissions");
+	display: function(userid, newPerm, group, proxy) {
+		// 6 is the length of the prefix "group-"
+		this.userid = group ? userid.substring(6, userid.length) : userid;
+		if(group == true) {
+			this.userid = userid.substring(6, userid.length)
+		} else if (proxy == true) {
+			this.userid = userid.substring(6, userid.length)
+		} else {
+			this.userid = userid
 		}
-		else{
-			$('#change-title').text("Add New User Permissions");
-		}
-		$('#user-box').attr("disabled", null);
 		
-		// default
-		this.permission.admin = false;
-		this.permission.read = true;
-		this.permission.write = false;
-		this.permission.execute = false;
-		this.permission.schedule = false;
-	}
-	else {
-		if (group) {
-			$('#change-title').text("Change Group Permissions");
+		this.permission = {};
+		$('#user-box').val(this.userid);
+		this.newPerm = newPerm;
+		this.group = group;
+		
+		var prefix = userid;
+		var adminInput = $("#" + prefix + "-admin-checkbox");
+		var readInput = $("#" + prefix + "-read-checkbox");
+		var writeInput = $("#" + prefix + "-write-checkbox");
+		var executeInput = $("#" + prefix + "-execute-checkbox");
+		var scheduleInput = $("#" + prefix + "-schedule-checkbox");
+		
+		if (newPerm) {
+			if (group) {
+				$('#change-title').text("Add New Group Permissions");
+			}
+			else if(proxy){
+				$('#change-title').text("Add New Proxy User Permissions");
+			}
+			else{
+				$('#change-title').text("Add New User Permissions");
+			}
+			$('#user-box').attr("disabled", null);
+			
+			// default
+			this.permission.admin = false;
+			this.permission.read = true;
+			this.permission.write = false;
+			this.permission.execute = false;
+			this.permission.schedule = false;
 		}
 		else {
-			$('#change-title').text("Change User Permissions");
+			if (group) {
+				$('#change-title').text("Change Group Permissions");
+			}
+			else {
+				$('#change-title').text("Change User Permissions");
+			}
+			
+			$('#user-box').attr("disabled", "disabled");
+			
+			this.permission.admin = $(adminInput).is(":checked");
+			this.permission.read = $(readInput).is(":checked");
+			this.permission.write = $(writeInput).is(":checked");
+			this.permission.execute = $(executeInput).is(":checked");
+			this.permission.schedule = $(scheduleInput).is(":checked");
 		}
 		
-		$('#user-box').attr("disabled", "disabled");
+		this.changeCheckbox();
 		
-		this.permission.admin = $(adminInput).is(":checked");
-		this.permission.read = $(readInput).is(":checked");
-		this.permission.write = $(writeInput).is(":checked");
-		this.permission.execute = $(executeInput).is(":checked");
-		this.permission.schedule = $(scheduleInput).is(":checked");
-	}
+		changePermissionView.render();
+		$('#change-permission').modal();
+	},
 	
-	this.changeCheckbox();
+	render: function() {
+	},
 	
-	changePermissionView.render();
-  	 $('#change-permission').modal({
-          closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-          position: ["20%",],
-          containerId: 'confirm-container',
-          containerCss: {
-            'height': '220px',
-            'width': '565px'
-          },
-          onShow: function (dialog) {
-            var modal = this;
-            $("#errorMsg").hide();
-          }
-        });
-  },
-  render: function() {
-  },
-  handleCheckboxClick : function(evt) {
-  	console.log("click");
-  	var targetName = evt.currentTarget.name;
-  	if(targetName == "proxy") {
-  		this.doProxy = evt.currentTarget.checked;
-  	}
-  	else {
-  		this.permission[targetName] = evt.currentTarget.checked;
-  	}
-  	this.changeCheckbox(evt);
-  },
-  changeCheckbox : function(evt) {
-    var perm = this.permission;
+	handleCheckboxClick: function(evt) {
+		console.log("click");
+		var targetName = evt.currentTarget.name;
+		if(targetName == "proxy") {
+			this.doProxy = evt.currentTarget.checked;
+		}
+		else {
+			this.permission[targetName] = evt.currentTarget.checked;
+		}
+		this.changeCheckbox(evt);
+	},
+	
+	changeCheckbox: function(evt) {
+		var perm = this.permission;
 
-  	if (perm.admin) {
-  		$("#admin-change").attr("checked", true);
-  		$("#read-change").attr("checked", true);
-  		$("#read-change").attr("disabled", "disabled");
-  		
-  		$("#write-change").attr("checked", true);
-  		$("#write-change").attr("disabled", "disabled");
+		if (perm.admin) {
+			$("#admin-change").attr("checked", true);
+			$("#read-change").attr("checked", true);
+			$("#read-change").attr("disabled", "disabled");
+			
+			$("#write-change").attr("checked", true);
+			$("#write-change").attr("disabled", "disabled");
 
-  		$("#execute-change").attr("checked", true);
-  		$("#execute-change").attr("disabled", "disabled"); 
-  		
-  		$("#schedule-change").attr("checked", true);
-  		$("#schedule-change").attr("disabled", "disabled");
-  	}
-  	else {
-  		$("#admin-change").attr("checked", false);
-  		
-  		$("#read-change").attr("checked", perm.read);
-  		$("#read-change").attr("disabled", null);
-  		  		
-  		$("#write-change").attr("checked", perm.write);
-  		$("#write-change").attr("disabled", null);
-  		
-  		$("#execute-change").attr("checked", perm.execute);
-  		$("#execute-change").attr("disabled", null);
-  		
-  		$("#schedule-change").attr("checked", perm.schedule);
+			$("#execute-change").attr("checked", true);
+			$("#execute-change").attr("disabled", "disabled"); 
+			
+			$("#schedule-change").attr("checked", true);
+			$("#schedule-change").attr("disabled", "disabled");
+		}
+		else {
+			$("#admin-change").attr("checked", false);
+			
+			$("#read-change").attr("checked", perm.read);
+			$("#read-change").attr("disabled", null);
+						
+			$("#write-change").attr("checked", perm.write);
+			$("#write-change").attr("disabled", null);
+			
+			$("#execute-change").attr("checked", perm.execute);
+			$("#execute-change").attr("disabled", null);
+			
+			$("#schedule-change").attr("checked", perm.schedule);
 		$("#schedule-change").attr("disabled", null);
-  	}
-  	
-  	$("#change-btn").removeClass("btn-disabled");
-  	$("#change-btn").attr("disabled", null);
-  	
-  	if (perm.admin || perm.read || perm.write || perm.execute || perm.schedule) {
-  		$("#change-btn").text("Commit");
-  	}
-  	else {
-  		if(	this.newPerm) {
-  			$("#change-btn").disabled = true;
-  			$("#change-btn").addClass("btn-disabled");
-  		}
-  		else {
-  			$("#change-btn").text("Remove");
-  		}
-  	}
-  },
-  handleChangePermissions : function(evt) {
-  	var requestURL = contextURL + "/manager";
-  	var name = $('#user-box').val();
-	var command = this.newPerm ? "addPermission" : "changePermission";
-	var group = this.group;
-	
-	var permission = {};
-	permission.admin = $("#admin-change").is(":checked");
-	permission.read = $("#read-change").is(":checked");
-	permission.write = $("#write-change").is(":checked");
-	permission.execute = $("#execute-change").is(":checked");
-	permission.schedule = $("#schedule-change").is(":checked");
+		}
+		
+		$("#change-btn").removeClass("btn-disabled");
+		$("#change-btn").attr("disabled", null);
+		
+		if (perm.admin || perm.read || perm.write || perm.execute || perm.schedule) {
+			$("#change-btn").text("Commit");
+		}
+		else {
+			if(	this.newPerm) {
+				$("#change-btn").disabled = true;
+				$("#change-btn").addClass("btn-disabled");
+			}
+			else {
+				$("#change-btn").text("Remove");
+			}
+		}
+	},
 	
-  	$.get(
-	      requestURL,
-	      {"project": projectName, "name": name, "ajax":command, "permissions": this.permission, "group": group},
-	      function(data) {
-	      	  console.log("Output");
-	      	  if (data.error) {
-	      	  	$("#errorMsg").text(data.error);
-	      	  	$("#errorMsg").show();
-	      	  	return;
-	      	  }
-	      	  
-	      	  var replaceURL = requestURL + "?project=" + projectName +"&permissions";
-	          window.location.replace(replaceURL);
-	      },
-	      "json"
-	    );
-  }
+	handleChangePermissions : function(evt) {
+		var requestURL = contextURL + "/manager";
+		var name = $('#user-box').val();
+		var command = this.newPerm ? "addPermission" : "changePermission";
+		var group = this.group;
+		
+		var permission = {};
+		permission.admin = $("#admin-change").is(":checked");
+		permission.read = $("#read-change").is(":checked");
+		permission.write = $("#write-change").is(":checked");
+		permission.execute = $("#execute-change").is(":checked");
+		permission.schedule = $("#schedule-change").is(":checked");
+
+		var requestData = {
+			"project": projectName, 
+			"name": name, 
+			"ajax": command, 
+			"permissions": this.permission, 
+			"group": group
+		};
+		var successHandler = function(data) {
+			console.log("Output");
+			if (data.error) {
+				$("#errorMsg").text(data.error);
+				$("#errorMsg").show();
+				return;
+			}
+			
+			var replaceURL = requestURL + "?project=" + projectName +"&permissions";
+			window.location.replace(replaceURL);
+		};
+
+		$.get(requestURL, requestData, successHandler, "json");
+	}
 });
 
 $(function() {
-	permissionTableView = new azkaban.PermissionTableView({el:$('#permissions-table'), group: false, proxy: false});
-	groupPermissionTableView = new azkaban.PermissionTableView({el:$('#group-permissions-table'), group: true, proxy: false});
-	proxyTableView = new azkaban.ProxyTableView({el:$('#proxy-user-table'), group: false, proxy: true});
-	changePermissionView = new azkaban.ChangePermissionView({el:$('#change-permission')});
-	addProxyView = new azkaban.AddProxyView({el:$('#add-proxy')});
-	removeProxyView = new azkaban.RemoveProxyView({el:$('#remove-proxy')});
+	permissionTableView = new azkaban.PermissionTableView({
+		el: $('#permissions-table'), 
+		group: false, 
+		proxy: false
+	});
+	groupPermissionTableView = new azkaban.PermissionTableView({
+		el: $('#group-permissions-table'), 
+		group: true, 
+		proxy: false
+	});
+	proxyTableView = new azkaban.ProxyTableView({
+		el: $('#proxy-user-table'), 
+		group: false, 
+		proxy: true
+	});
+	changePermissionView = new azkaban.ChangePermissionView({
+		el: $('#change-permission')
+	});
+	addProxyView = new azkaban.AddProxyView({
+		el: $('#add-proxy')
+	});
+	removeProxyView = new azkaban.RemoveProxyView({
+		el: $('#remove-proxy')
+	});
 	$('#addUser').bind('click', function() {
 		changePermissionView.display("", true, false, false);
 	});
@@ -324,5 +344,4 @@ $(function() {
 	$('#addProxyUser').bind('click', function() {
 		addProxyView.display();
 	});
-	
 });
diff --git a/src/web/js/azkaban.project.view.js b/src/web/js/azkaban.project.view.js
index 533a85f..ef01a0e 100644
--- a/src/web/js/azkaban.project.view.js
+++ b/src/web/js/azkaban.project.view.js
@@ -1,348 +1,232 @@
-$.namespace('azkaban');
-
-var projectView;
-azkaban.ProjectView= Backbone.View.extend({
-  events : {
-      "click #project-upload-btn":"handleUploadProjectJob",
-      "click #project-delete-btn": "handleDeleteProject"
-  },
-  initialize : function(settings) {
-  },
-  handleUploadProjectJob : function(evt) {
-      console.log("click upload project");
-      $('#upload-project').modal({
-          closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-          position: ["20%",],
-          containerId: 'confirm-container',
-          containerCss: {
-            'height': '220px',
-            'width': '565px'
-          },
-          onShow: function (dialog) {
-            var modal = this;
-            $("#errorMsg").hide();
-          }
-        });
-  },
-  handleDeleteProject : function(evt) {
-	$('#delete-project').modal({
-        closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-        position: ["20%",],
-        containerId: 'confirm-container',
-        containerCss: {
-          'height': '240px',
-          'width': '640px'
-        },
-        onShow: function (dialog) {
-          var modal = this;
-          $("#errorMsg").hide();
-        }
-    });
-  },
-  render: function() {
-  }
-});
-
-var uploadProjectView;
-azkaban.UploadProjectView= Backbone.View.extend({
-  events : {
-    "click #upload-btn": "handleCreateProject"
-  },
-  initialize : function(settings) {
-    $("#errorMsg").hide();
-  },
-  handleCreateProject : function(evt) {
-    $("#upload-form").submit();
-  },
-  render: function() {
-  }
-});
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 
-var deleteProjectView;
-azkaban.DeleteProjectView= Backbone.View.extend({
-  events : {
-    "click #delete-btn": "handleDeleteProject"
-  },
-  initialize : function(settings) {
-  },
-  handleDeleteProject : function(evt) {
-  	$("#delete-form").submit();
-  },
-  render: function() {
-  }
-});
+$.namespace('azkaban');
 
 var flowTableView;
-azkaban.FlowTableView= Backbone.View.extend({
-  events : {
-    "click .jobfolder": "expandFlowProject",
-    "mouseover .expandedFlow a": "highlight",
-    "mouseout .expandedFlow a": "unhighlight",
-    "click .runJob": "runJob",
-    "click .runWithDep": "runWithDep",
-    "click .executeFlow": "executeFlow",
-    "click .viewFlow": "viewFlow",
-    "click .viewJob": "viewJob"
-  },
-  initialize : function(settings) {
-  },
-  expandFlowProject : function(evt) {
-    if (evt.target.tagName!="SPAN") {
-    	return;
-    }
-    
-    var target = evt.currentTarget;
-    var targetId = target.id;
-    var requestURL = contextURL + "/manager";
+azkaban.FlowTableView = Backbone.View.extend({
+	events : {
+		"click .flow-expander": "expandFlowProject",
+		"mouseover .expanded-flow-job-list li": "highlight",
+		"mouseout .expanded-flow-job-list li": "unhighlight",
+		"click .runJob": "runJob",
+		"click .runWithDep": "runWithDep",
+		"click .execute-flow": "executeFlow",
+		"click .viewFlow": "viewFlow",
+		"click .viewJob": "viewJob"
+	},
 
-    var targetExpanded = $('#' + targetId + '-child');
-    var targetTBody = $('#' + targetId + '-tbody');
-    
-    var createJobListFunction = this.createJobListTable;
-    
-    if (target.loading) {
-    	console.log("Still loading.");
-    }
-    else if (target.loaded) {
-    	if($(targetExpanded).is(':visible')) {
-    		$(target).addClass('expand').removeClass('collapse');
-    		$(targetExpanded).fadeOut("fast");
-    	}
-    	else {
-    	    $(target).addClass('collapse').removeClass('expand');
-    		$(targetExpanded).fadeIn();
-    	}
-    }
-    else {
-	    // projectName is available
-	    $(target).addClass('wait').removeClass('collapse').removeClass('expand');
-	    target.loading = true;
-	    
-	    $.get(
-	      requestURL,
-	      {"project": projectName, "ajax":"fetchflowjobs", "flow":targetId},
-	      function(data) {
-	        console.log("Success");
-	        target.loaded = true;
-	        target.loading = false;
-	        
-	        createJobListFunction(data, targetTBody);
-	        
-			$(target).addClass('collapse').removeClass('wait');
-	    	$(targetExpanded).fadeIn("fast");
-	      },
-	      "json"
-	    );
-    }
-  },
-  createJobListTable : function(data, innerTable) {
-  	var nodes = data.nodes;
-  	var flowId = data.flowId;
-  	var project = data.project;
-  	var requestURL = contextURL + "/manager?project=" + project + "&flow=" + flowId + "&job=";
-  	for (var i = 0; i < nodes.length; i++) {
-		var job = nodes[i];
-		var name = job.id;
-		var level = job.level;
-		var nodeId = flowId + "-" + name;
-		
-		var tr = document.createElement("tr");
-		$(tr).addClass("jobrow");
-		var idtd = document.createElement("td");
-		$(idtd).addClass("tb-name");
-		$(idtd).addClass("tb-job-name");
-		idtd.flowId=flowId;
-		idtd.projectName=project;
-		idtd.jobName=name;
+	initialize: function(settings) {
+	},
+
+	expandFlowProject: function(evt) {
+		if (evt.target.tagName == "A" || evt.target.tagName == "BUTTON") {
+			return;
+		}
 		
-		var ida = document.createElement("a");
-		ida.dependents = job.dependents;
-		ida.dependencies = job.dependencies;
-		ida.flowid = flowId;
-		$(ida).text(name);
-		$(ida).addClass("jobLink");
-		$(ida).attr("id", nodeId);
-		$(ida).css("margin-left", level * 20);
-		$(ida).attr("href", requestURL + name);
+		var target = evt.currentTarget;
+		var targetId = target.id;
+		var requestURL = contextURL + "/manager";
+
+		var targetExpanded = $('#' + targetId + '-child');
+		var targetTBody = $('#' + targetId + '-tbody');
 		
-		$(idtd).append(ida);
-		$(tr).append(idtd);
-		$(innerTable).append(tr);
+		var createJobListFunction = this.createJobListTable;
+		if (target.loading) {
+			console.log("Still loading.");
+		}
+		else if (target.loaded) {
+			$(targetExpanded).collapse('toggle');
+		}
+		else {
+			// projectName is available
+			target.loading = true;
+			var requestData = {
+				"project": projectName, 
+				"ajax": "fetchflowjobs", 
+				"flow": targetId
+			};
+			var successHandler = function(data) {
+				console.log("Success");
+				target.loaded = true;
+				target.loading = false;
+				createJobListFunction(data, targetTBody);
+				$(targetExpanded).collapse('show');
+			};
+			$.get(requestURL, requestData, successHandler, "json");
+		}
+	},
+	
+	createJobListTable: function(data, innerTable) {
+		var nodes = data.nodes;
+		var flowId = data.flowId;
+		var project = data.project;
+		var requestURL = contextURL + "/manager?project=" + project + "&flow=" + flowId + "&job=";
+		for (var i = 0; i < nodes.length; i++) {
+			var job = nodes[i];
+			var name = job.id;
+			var level = job.level;
+			var nodeId = flowId + "-" + name;
+	
+      var li = document.createElement('li');
+      $(li).addClass("list-group-item");
+			$(li).attr("id", nodeId);
+			li.flowId = flowId;
+			li.dependents = job.dependents;
+			li.dependencies = job.dependencies;
+			li.projectName = project;
+			li.jobName = name;
 
-		if (execAccess) {
-			var hoverMenuDiv = document.createElement("div");
-			$(hoverMenuDiv).addClass("job-hover-menu");
+			if (execAccess) {
+				var hoverMenuDiv = document.createElement('div');
+				$(hoverMenuDiv).addClass('pull-right');
+        $(hoverMenuDiv).addClass('job-buttons');
+				
+				var divRunJob = document.createElement('button');
+        $(divRunJob).attr('type', 'button');
+				$(divRunJob).addClass("btn");
+				$(divRunJob).addClass("btn-success");
+				$(divRunJob).addClass("btn-xs");
+				$(divRunJob).addClass("runJob");
+				$(divRunJob).text("Run Job");
+				divRunJob.jobName = name;
+				divRunJob.flowId = flowId;
+				$(hoverMenuDiv).append(divRunJob);
+				
+				var divRunWithDep = document.createElement("button");
+        $(divRunWithDep).attr('type', 'button');
+				$(divRunWithDep).addClass("btn");
+				$(divRunWithDep).addClass("btn-success");
+				$(divRunWithDep).addClass("btn-xs");
+				$(divRunWithDep).addClass("runWithDep");
+				$(divRunWithDep).text("Run With Dependencies");
+				divRunWithDep.jobName = name;
+				divRunWithDep.flowId = flowId;
+				$(hoverMenuDiv).append(divRunWithDep);
+				
+				$(li).append(hoverMenuDiv);
+			}
 			
-			var divRunJob = document.createElement("div");
-			$(divRunJob).addClass("btn1");
-			$(divRunJob).addClass("runJob");
-			$(divRunJob).text("Run Job");
-			divRunJob.jobName = name;
-			divRunJob.flowId = flowId;
-			$(hoverMenuDiv).append(divRunJob);
-			
-			var divRunWithDep = document.createElement("div");
-			$(divRunWithDep).addClass("btn1");
-			$(divRunWithDep).addClass("runWithDep");
-			$(divRunWithDep).text("Run With Dependencies");
-			divRunWithDep.jobName = name;
-			divRunWithDep.flowId = flowId;
-			$(hoverMenuDiv).append(divRunWithDep);
-			
-			$(idtd).append(hoverMenuDiv);
-		}		
-  	}
-  },
-  unhighlight: function(evt) {
-  	var currentTarget = evt.currentTarget;
- 	$(".dependent").removeClass("dependent");
-	$(".dependency").removeClass("dependency");
+      var ida = document.createElement("a");
+			$(ida).css("margin-left", level * 20);
+      $(ida).attr("href", requestURL + name);
+      $(ida).text(name);
 
-  },
-  highlight: function(evt) {
- 	var currentTarget = evt.currentTarget;
- 	$(".dependent").removeClass("dependent");
-	$(".dependency").removeClass("dependency");
+      $(li).append(ida);
+			$(innerTable).append(li);
+		}
+	},
 	
- 	if ($(currentTarget).hasClass("jobLink")) {
-		this.highlightJob(currentTarget);
-	}
+	unhighlight: function(evt) {
+		var currentTarget = evt.currentTarget;
+		$(".dependent").removeClass("dependent");
+		$(".dependency").removeClass("dependency");
+	},
 
-  },
-  highlightJob: function(currentTarget) {
-   	var dependents = currentTarget.dependents;
- 	var dependencies = currentTarget.dependencies;
- 	var flowid = currentTarget.flowid;
- 	
- 	if (dependents) {
-	 	for (var i = 0; i < dependents.length; ++i) {
-	 		var depId = flowid + "-" + dependents[i];
-	 		$("#"+depId).toggleClass("dependent");
-	 	}
- 	}
- 	
- 	if (dependencies) {
-	 	for (var i = 0; i < dependencies.length; ++i) {
-	 		var depId = flowid + "-" + dependencies[i];
-	 		$("#"+depId).toggleClass("dependency");
-	 	}
-  	}
-  },
-  viewFlow: function(evt) {
-  	console.log("View Flow");
-  	var flowId = evt.currentTarget.flowId;
+	highlight: function(evt) {
+		var currentTarget = evt.currentTarget;
+		$(".dependent").removeClass("dependent");
+		$(".dependency").removeClass("dependency");
+    this.highlightJob(currentTarget);
+	},
 
-  	location.href = contextURL + "/manager?project=" + projectName + "&flow=" + flowId;
-  },
-  viewJob: function(evt) {
-  	console.log("View Job");
-  	var flowId = evt.currentTarget.flowId;
-  	var jobId = evt.currentTarget.jobId;
-  	
-  	location.href = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
-  },
-  runJob: function(evt) {
-  	console.log("Run Job");
-  	var jobId = evt.currentTarget.jobName;
-  	var flowId = evt.currentTarget.flowId;
-  	
-  	var executingData = {
-  		project: projectName,
-  		ajax: "executeFlow",
-  		flow: flowId,
-  		job: jobId
-	};
-  	
-  	this.executeFlowDialog(executingData);
-  },
-  runWithDep: function(evt) {
-    var jobId = evt.currentTarget.jobName;
-  	var flowId = evt.currentTarget.flowId;
-    console.log("Run With Dep");
-    
-    var executingData = {
-  		project: projectName,
-  		ajax: "executeFlow",
-  		flow: flowId,
-  		job: jobId,
-  		withDep: true
-	};
-    
-    this.executeFlowDialog(executingData);
-  },
-  executeFlow: function(evt) {
-    console.log("Execute Flow");
-   	var flowId = $(evt.currentTarget).attr('flowid');
-    
-    var executingData = {
-  		project: projectName,
-  		ajax: "executeFlow",
-  		flow: flowId
-	};
-    
-    this.executeFlowDialog(executingData);
-  },
-  executeFlowDialog: function(executingData) {
-  	flowExecuteDialogView.show(executingData);
-  },
-  render: function() {
-  }
-});
+	highlightJob: function(currentTarget) {
+		var dependents = currentTarget.dependents;
+		var dependencies = currentTarget.dependencies;
+		var flowid = currentTarget.flowId;
+		
+		if (dependents) {
+			for (var i = 0; i < dependents.length; ++i) {
+				var depId = flowid + "-" + dependents[i];
+				$("#"+depId).toggleClass("dependent");
+			}
+		}
+		
+		if (dependencies) {
+			for (var i = 0; i < dependencies.length; ++i) {
+				var depId = flowid + "-" + dependencies[i];
+				$("#"+depId).toggleClass("dependency");
+			}
+		}
+	},
+
+	viewFlow: function(evt) {
+		console.log("View Flow");
+		var flowId = evt.currentTarget.flowId;
+		location.href = contextURL + "/manager?project=" + projectName + "&flow=" + flowId;
+	},
+
+	viewJob: function(evt) {
+		console.log("View Job");
+		var flowId = evt.currentTarget.flowId;
+		var jobId = evt.currentTarget.jobId;
+		location.href = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
+	},
+
+	runJob: function(evt) {
+		console.log("Run Job");
+		var jobId = evt.currentTarget.jobName;
+		var flowId = evt.currentTarget.flowId;
+		
+		var executingData = {
+			project: projectName,
+			ajax: "executeFlow",
+			flow: flowId,
+			job: jobId
+		};
+		
+		this.executeFlowDialog(executingData);
+	},
 
-var projectSummary;
-azkaban.ProjectSummaryView= Backbone.View.extend({
-  events : {
-      "click #edit": "handleDescriptionEdit"
-  },
-  initialize : function(settings) {
-  },
-  handleDescriptionEdit : function(evt) {
-      console.log("Edit description");
-      var editText = $("#edit").text();
-      var descriptionTD = $('#pdescription');
-      
-      if (editText != "Edit Description") {
-          var requestURL = contextURL + "/manager";
-          var newText = $("#descEdit").val();
+	runWithDep: function(evt) {
+		var jobId = evt.currentTarget.jobName;
+		var flowId = evt.currentTarget.flowId;
+		console.log("Run With Dep");
+		
+		var executingData = {
+			project: projectName,
+			ajax: "executeFlow",
+			flow: flowId,
+			job: jobId,
+			withDep: true
+		};
+		this.executeFlowDialog(executingData);
+	},
 
-          $.get(
-		      requestURL,
-		      {"project": projectName, "ajax":"changeDescription", "description":newText},
-		      function(data) {
-				if (data.error) {
-					alert(data.error);
-				}
-		      },
-		      "json"
-	    );
-          
-          $(descriptionTD).remove("#descEdit");
-          $(descriptionTD).text(newText);
-          
-          $("#edit").text("Edit Description");
-      }
-      else {
-	      var text = $(descriptionTD).text();
-	      var edit = document.createElement("textarea");
-	      
-	      $(edit).addClass("editTextArea");
-	      $(edit).attr("id", "descEdit");
-	      $(edit).val(text);
-	      $(descriptionTD).text("");
-	      $(descriptionTD).append(edit);
-	      
-	      $("#edit").text("Commit");
-      }
-  },
-  render: function() {
-  }
+	executeFlow: function(evt) {
+		console.log("Execute Flow");
+		var flowId = $(evt.currentTarget).attr('flowid');
+		
+		var executingData = {
+			project: projectName,
+			ajax: "executeFlow",
+			flow: flowId
+		};
+		
+		this.executeFlowDialog(executingData);
+	},
+
+	executeFlowDialog: function(executingData) {
+		flowExecuteDialogView.show(executingData);
+	},
+
+	render: function() {
+	}
 });
 
 $(function() {
-	projectView = new azkaban.ProjectView({el:$('#all-jobs-content')});
-	uploadView = new azkaban.UploadProjectView({el:$('#upload-project')});
 	flowTableView = new azkaban.FlowTableView({el:$('#flow-tabs')});
-	projectSummary = new azkaban.ProjectSummaryView({el:$('#project-summary')});
-	deleteProjectView = new azkaban.DeleteProjectView({el: $('#delete-project')});
-	// Setting up the project tabs
 });
diff --git a/src/web/js/azkaban.projectlog.view.js b/src/web/js/azkaban.projectlog.view.js
index c3006cf..0653146 100644
--- a/src/web/js/azkaban.projectlog.view.js
+++ b/src/web/js/azkaban.projectlog.view.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 var logModel;
@@ -19,74 +35,78 @@ var typeMapping = {
 var projectLogView;
 azkaban.ProjectLogView = Backbone.View.extend({
 	events: {
-		"click #updateLogBtn" : "handleUpdate"
+		"click #updateLogBtn": "handleUpdate"
 	},
-	initialize: function(settings) {
+	
+  initialize: function(settings) {
 		this.model.set({"current": 0});
 		this.handleUpdate();
 	},
-	handleUpdate: function(evt) {
+	
+  handleUpdate: function(evt) {
 		var current = this.model.get("current");
 		var requestURL = contextURL + "/manager"; 
 		var model = this.model;
+    var requestData = {
+      "project": projectName, 
+      "ajax": "fetchProjectLogs", 
+      "size": 1000, 
+      "skip": 0 
+    };
 
-		$.get(
-			requestURL,
-			{"project": projectName, "ajax":"fetchProjectLogs", "size": 1000, "skip": 0 },
-			function(data) {
+		var successHandler = function(data) {
 			console.log("fetchLogs");
-	          	if (data.error) {
-	          		showDialog("Error", data.error);
-	          	}
-	          	else {
-	          		// Get the columns to map to the values.
-	          		var columns = data.columns;
-	          		var columnMap = {};
-	          		for (var i =0; i < columns.length; ++i) {
-	          			columnMap[columns[i]] = i;
-	          		}
-	          		var logSection = $("#logTable");
-	          		$(logSection).empty();
-	          		var logData = data.logData;
-	          		for (var i = 0; i < logData.length; ++i) {
-	          			var event = logData[i];
-	          			var user = event[columnMap['user']];
-	          			var time = event[columnMap['time']];
-	          			var type = event[columnMap['type']];
-	          			var message = event[columnMap['message']];
-	          			
-	          			var containerEvent = document.createElement("tr");
-	          			$(containerEvent).addClass("projectEvent");
-	          			
-	          			var containerTime = document.createElement("td");
-	          			$(containerTime).addClass("time");
-	          			$(containerTime).text(getDateFormat(new Date(time)));
-	          			
-	          			var containerUser = document.createElement("td");
-	          			$(containerUser).addClass("user");
-	          			$(containerUser).text(user);
-	          			
-	          			var containerType = document.createElement("td");
-	          			$(containerType).addClass("type");
-	          			$(containerType).addClass(type);
-	          			$(containerType).text(typeMapping[type] ? typeMapping[type] : type);
-	          		
-	          			var containerMessage = document.createElement("td");
-	          			$(containerMessage).addClass("message");
-	          			$(containerMessage).text(message);
-	          			
-	          			$(containerEvent).append(containerTime);
-	          			$(containerEvent).append(containerUser);
-	          			$(containerEvent).append(containerType);
-	          			$(containerEvent).append(containerMessage);
-	          			
-	          			$(logSection).append(containerEvent);
-	          		}
-	          		
-	          		model.set({"log": data});
-	          	}
-	         }
-		);
+      if (data.error) {
+        showDialog("Error", data.error);
+        return;
+      }
+      // Get the columns to map to the values.
+      var columns = data.columns;
+      var columnMap = {};
+      for (var i =0; i < columns.length; ++i) {
+        columnMap[columns[i]] = i;
+      }
+      var logSection = $("#logTable").find("tbody")[0];
+      $(logSection).empty();
+      var logData = data.logData;
+      for (var i = 0; i < logData.length; ++i) {
+        var event = logData[i];
+        var user = event[columnMap['user']];
+        var time = event[columnMap['time']];
+        var type = event[columnMap['type']];
+        var message = event[columnMap['message']];
+        
+        var containerEvent = document.createElement("tr");
+        $(containerEvent).addClass("projectEvent");
+        
+        var containerTime = document.createElement("td");
+        $(containerTime).addClass("time");
+        $(containerTime).text(getDateFormat(new Date(time)));
+        
+        var containerUser = document.createElement("td");
+        $(containerUser).addClass("user");
+        $(containerUser).text(user);
+        
+        var containerType = document.createElement("td");
+        $(containerType).addClass("type");
+        $(containerType).addClass(type);
+        $(containerType).text(typeMapping[type] ? typeMapping[type] : type);
+      
+        var containerMessage = document.createElement("td");
+        $(containerMessage).addClass("message");
+        $(containerMessage).text(message);
+        
+        $(containerEvent).append(containerTime);
+        $(containerEvent).append(containerUser);
+        $(containerEvent).append(containerType);
+        $(containerEvent).append(containerMessage);
+        
+        $(logSection).append(containerEvent);
+      }
+      
+      model.set({"log": data});
+    };
+		$.get(requestURL, requestData, successHandler);
 	}
 });
 
@@ -96,16 +116,16 @@ var showDialog = function(title, message) {
   $('#messageBox').text(message);
 
   $('#messageDialog').modal({
-      closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-      position: ["20%",],
-      containerId: 'confirm-container',
-      containerCss: {
-        'height': '220px',
-        'width': '565px'
-      },
-      onShow: function (dialog) {
-      }
-    });
+	  closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
+	  position: ["20%",],
+	  containerId: 'confirm-container',
+	  containerCss: {
+		'height': '220px',
+		'width': '565px'
+	  },
+	  onShow: function (dialog) {
+	  }
+	});
 }
 
 
diff --git a/src/web/js/azkaban.projectmodals.view.js b/src/web/js/azkaban.projectmodals.view.js
new file mode 100644
index 0000000..4f9b5f4
--- /dev/null
+++ b/src/web/js/azkaban.projectmodals.view.js
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+$.namespace('azkaban');
+
+var projectView;
+azkaban.ProjectView = Backbone.View.extend({
+	events: {
+		"click #project-upload-btn": "handleUploadProjectJob",
+		"click #project-delete-btn": "handleDeleteProject"
+	},
+
+	initialize: function(settings) {
+	},
+
+	handleUploadProjectJob: function(evt) {
+		console.log("click upload project");
+		$('#upload-project-modal').modal();
+	},
+
+	handleDeleteProject: function(evt) {
+		console.log("click delete project");
+		$('#delete-project-modal').modal();
+	},
+	
+	render: function() {
+	}
+});
+
+var uploadProjectView;
+azkaban.UploadProjectView = Backbone.View.extend({
+	events: {
+		"click #upload-project-btn": "handleCreateProject"
+	},
+
+	initialize: function(settings) {
+		console.log("Hide upload project modal error msg");
+		$("#upload-project-modal-error-msg").hide();
+	},
+	
+	handleCreateProject: function(evt) {
+		console.log("Upload project button.");
+		$("#upload-project-form").submit();
+	},
+	
+	render: function() {
+	}
+});
+
+var deleteProjectView;
+azkaban.DeleteProjectView = Backbone.View.extend({
+	events: {
+		"click #delete-btn": "handleDeleteProject"
+	},
+	
+	initialize: function(settings) {
+	},
+	
+	handleDeleteProject: function(evt) {
+		$("#delete-form").submit();
+	},
+
+	render: function() {
+	}
+});
+
+var projectDescription;
+azkaban.ProjectDescriptionView = Backbone.View.extend({
+	events: {
+		"click #project-description": "handleDescriptionEdit",
+    "click #project-description-btn": "handleDescriptionSave"
+	},
+
+	initialize: function(settings) {
+    console.log("project description initialize");
+	},
+	
+	handleDescriptionEdit: function(evt) {
+		console.log("Edit description");
+    var description = null;
+    if ($('#project-description').hasClass('editable-placeholder')) {
+      description = '';
+      $('#project-description').removeClass('editable-placeholder');
+    }
+    else {
+      description = $('#project-description').text();
+    }
+    $('#project-description-edit').attr("value", description);
+    $('#project-description').hide();
+    $('#project-description-form').show();
+	},
+
+  handleDescriptionSave: function(evt) {
+    var newText = $('#project-description-edit').val();
+    if ($('#project-description-edit').hasClass('has-error')) {
+      $('#project-description-edit').removeClass('has-error');
+    }
+		var requestURL = contextURL + "/manager";
+    var requestData = {
+      "project": projectName, 
+      "ajax":"changeDescription", 
+      "description": newText
+    };
+    var successHandler = function(data) {
+      if (data.error) {
+        $('#project-description-edit').addClass('has-error');
+        alert(data.error);
+        return;
+      }
+      $('#project-description-form').hide();
+      if (newText != '') {
+        $('#project-description').text(newText);
+      }
+      else {
+        $('#project-description').text('Add project description.');
+        $('#project-description').addClass('editable-placeholder');
+      }
+      $('#project-description').show();
+    };
+    $.get(requestURL, requestData, successHandler, "json");
+  },
+
+	render: function() {
+	}
+});
+
+$(function() {
+	projectView = new azkaban.ProjectView({
+    el: $('#project-options')
+  });
+	uploadView = new azkaban.UploadProjectView({
+    el: $('#upload-project-modal')
+  });
+	deleteProjectView = new azkaban.DeleteProjectView({
+    el: $('#delete-project-modal')
+  });
+	projectDescription = new azkaban.ProjectDescriptionView({
+    el: $('#project-page-header')
+  });
+});
diff --git a/src/web/js/azkaban.schedule.options.view.js b/src/web/js/azkaban.schedule.options.view.js
index 5b9b760..b51af58 100644
--- a/src/web/js/azkaban.schedule.options.view.js
+++ b/src/web/js/azkaban.schedule.options.view.js
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -583,4 +583,4 @@ azkaban.AdvancedScheduleView = Backbone.View.extend({
 			flowData.trigger("change:disabled");
 		}
 	}
-});
\ No newline at end of file
+});
diff --git a/src/web/js/azkaban.schedule.panel.view.js b/src/web/js/azkaban.schedule.panel.view.js
index af81c0c..aa73f31 100644
--- a/src/web/js/azkaban.schedule.panel.view.js
+++ b/src/web/js/azkaban.schedule.panel.view.js
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 LinkedIn, Inc
+ * Copyright 2012 LinkedIn Corp.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -17,73 +17,77 @@
 $.namespace('azkaban');
 
 var schedulePanelView;
-azkaban.SchedulePanelView= Backbone.View.extend({
-  events : {
-  	"click .closeSchedule": "hideSchedulePanel",
-  	"click #schedule-button": "scheduleFlow"
-  },
-  initialize : function(settings) {
- 	$("#datepicker").css("backgroundColor", "transparent");
-	$( "#datepicker" ).datepicker();
-	$( "#datepicker" ).datepicker('setDate', new Date());
-	$( "#datepicker" ).datepicker("hide");
-  },
-  render: function() {
-  },
-  showSchedulePanel: function() {
-  	$('#scheduleModalBackground').show();
-  	$('#schedule-panel').show();
-  },
-  hideSchedulePanel: function() {
-  	$('#scheduleModalBackground').hide();
-  	$('#schedule-panel').hide();
-  },
-  scheduleFlow: function() {
-	var hourVal = $('#hour').val();
-	var minutesVal = $('#minutes').val();
-	var ampmVal = $('#am_pm').val();
-	var timezoneVal = $('#timezone').val();
-	var dateVal = $('#datepicker').val();
-	var is_recurringVal = $('#is_recurring').val();
-	var periodVal = $('#period').val();
-	var periodUnits = $('#period_units').val();
-  
-  	var scheduleURL = contextURL + "/schedule"
-  	
-  	var scheduleData = flowExecuteDialogView.getExecutionOptionData();
-  	 console.log("Creating schedule for "+projectName+"."+scheduleData.flow);
-	var scheduleTime = $('#hour').val() + "," + $('#minutes').val() + "," + $('#am_pm').val() + "," + $('#timezone').val();
-	var scheduleDate = $('#datepicker').val();
-	var is_recurring = document.getElementById('is_recurring').checked ? 'on' : 'off'; 
-	var period = $('#period').val() + $('#period_units').val();
+azkaban.SchedulePanelView = Backbone.View.extend({
+	events: {
+		"click #schedule-button": "scheduleFlow"
+	},
+
+	initialize: function(settings) {
+		$("#timepicker").datetimepicker({pickDate: false});
+		$("#datepicker").datetimepicker({pickTime: false});
+	},
+
+	render: function() {
+	},
+	
+	showSchedulePanel: function() {
+		$('#schedule-modal').modal();
+	},
 	
-	scheduleData.ajax = "scheduleFlow";
-	scheduleData.projectName = projectName;
-	scheduleData.scheduleTime = scheduleTime;
-	scheduleData.scheduleDate = scheduleDate;
-	scheduleData.is_recurring = is_recurring;
-	scheduleData.period = period;
+	hideSchedulePanel: function() {
+		$('#schedule-modal').modal("hide");
+	},
+	
+	scheduleFlow: function() {
+    var timeVal = $('#timepicker').val();
+		var timezoneVal = $('#timezone').val();
 
-	$.post(
-			scheduleURL,
-			scheduleData,
-			function(data) {
-				if (data.error) {
-					messageDialogView.show("Error Scheduling Flow", data.message);
-				}
-				else {
-					messageDialogView.show("Flow Scheduled", data.message,
-					function() {
-						window.location.href = scheduleURL;
-					}
-					);
-				}
-			},
-			"json"
-	);
-  }
+		var dateVal = $('#datepicker').val();
+		
+    var is_recurringVal = $('#is_recurring').val();
+		var periodVal = $('#period').val();
+		var periodUnits = $('#period_units').val();
+	
+		var scheduleURL = contextURL + "/schedule"
+		var scheduleData = flowExecuteDialogView.getExecutionOptionData();
+
+		console.log("Creating schedule for " + projectName + "." + 
+				scheduleData.flow);
+
+    var scheduleTime = moment(timeVal, 'h/mm A').format('h,mm,A,') + timezoneVal;
+    console.log(scheduleTime);
+		
+		var scheduleDate = $('#datepicker').val();
+		var is_recurring = document.getElementById('is_recurring').checked 
+				? 'on' : 'off'; 
+		var period = $('#period').val() + $('#period_units').val();
+		
+		scheduleData.ajax = "scheduleFlow";
+		scheduleData.projectName = projectName;
+		scheduleData.scheduleTime = scheduleTime;
+		scheduleData.scheduleDate = scheduleDate;
+		scheduleData.is_recurring = is_recurring;
+		scheduleData.period = period;
+			
+		var successHandler = function(data) {
+			if (data.error) {
+				schedulePanelView.hideSchedulePanel();
+				messageDialogView.show("Error Scheduling Flow", data.message);
+			}
+			else {
+				schedulePanelView.hideSchedulePanel();
+				messageDialogView.show("Flow Scheduled", data.message, function() {
+          window.location.href = scheduleURL;
+        });
+			}
+		};
+
+		$.post(scheduleURL, scheduleData, successHandler, "json");
+	}
 });
 
 $(function() {
-	schedulePanelView =  new azkaban.SchedulePanelView({el:$('#scheduleModalBackground')});
-});
\ No newline at end of file
+	schedulePanelView =	new azkaban.SchedulePanelView({
+		el: $('#schedule-modal')
+	});
+});
diff --git a/src/web/js/azkaban.schedule.svg.js b/src/web/js/azkaban.schedule.svg.js
index e9e583c..a49a8a9 100644
--- a/src/web/js/azkaban.schedule.svg.js
+++ b/src/web/js/azkaban.schedule.svg.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 $(function() {
@@ -432,8 +448,6 @@ $(function() {
 					{
 						var items = data.items;
 
-						console.log(data);
-
 						//Sort items by day
 						for(var i = 0; i < items.length; i++)
 						{
@@ -518,4 +532,4 @@ $(function() {
 		if(mins > 0) text = text + " " + (mins == 1 ? "1 minute" : mins.toString() + " minutes");
 		return text;
 	}
-});
\ No newline at end of file
+});
diff --git a/src/web/js/azkaban.scheduled.view.js b/src/web/js/azkaban.scheduled.view.js
index f507dc1..b182c27 100644
--- a/src/web/js/azkaban.scheduled.view.js
+++ b/src/web/js/azkaban.scheduled.view.js
@@ -1,219 +1,226 @@
-$.namespace('azkaban');
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 
+$.namespace('azkaban');
 
 function removeSched(scheduleId) {
 	var scheduleURL = contextURL + "/schedule"
 	var redirectURL = contextURL + "/schedule"
-	$.post(
-			scheduleURL,
-			{"action":"removeSched", "scheduleId":scheduleId},
-			function(data) {
-				if (data.error) {
-//                 alert(data.error)
-					$('#errorMsg').text(data.error);
-				}
-				else {
-// 		 alert("Schedule "+schedId+" removed!")
-					window.location = redirectURL;
-				}
-			},
-			"json"
-	)
+	var requestData = {
+		"action": "removeSched", 
+		"scheduleId":scheduleId
+	};
+	var successHandler = function(data) {
+		if (data.error) {
+			$('#errorMsg').text(data.error);
+		}
+		else {
+			window.location = redirectURL;
+		}
+	};
+	$.post(scheduleURL, requestData, successHandler, "json");
 }
 
 function removeSla(scheduleId) {
 	var scheduleURL = contextURL + "/schedule"
 	var redirectURL = contextURL + "/schedule"
-	$.post(
-			scheduleURL,
-			{"action":"removeSla", "scheduleId":scheduleId},
-			function(data) {
-				if (data.error) {
-//                 alert(data.error)
-					$('#errorMsg').text(data.error)
-				}
-				else {
-// 		 alert("Schedule "+schedId+" removed!")
-					window.location = redirectURL
-				}
-			},
-			"json"
-	)
+	var requestData = {
+		"action": "removeSla", 
+		"scheduleId": scheduleId
+	};
+	var successHandler = function(data) {
+		if (data.error) {
+			$('#errorMsg').text(data.error)
+		}
+		else {
+			window.location = redirectURL
+		}
+	};
+	$.post(scheduleURL, requestData, successHandler, "json");
 }
 
+var slaView;
 azkaban.ChangeSlaView = Backbone.View.extend({
-	events : {
-		"click" : "closeEditingTarget",
+	events: {
+		"click": "closeEditingTarget",
 		"click #set-sla-btn": "handleSetSla",	
 		"click #remove-sla-btn": "handleRemoveSla",
-		"click #sla-cancel-btn": "handleSlaCancel",
-		"click .modal-close": "handleSlaCancel",
 		"click #addRow": "handleAddRow"
 	},
+	
 	initialize: function(setting) {
-
+		$('#sla-options').on('hidden.bs.modal', function() {
+			slaView.handleSlaCancel();
+		});
 	},
-	handleSlaCancel: function(evt) {
+	
+	handleSlaCancel: function() {
 		console.log("Clicked cancel button");
 		var scheduleURL = contextURL + "/schedule";
-
-		$('#slaModalBackground').hide();
-		$('#sla-options').hide();
-		
 		var tFlowRules = document.getElementById("flowRulesTbl").tBodies[0];
 		var rows = tFlowRules.rows;
 		var rowLength = rows.length
-		for(var i = 0; i < rowLength-1; i++) {
+		for (var i = 0; i < rowLength-1; i++) {
 			tFlowRules.deleteRow(0);
 		}
-		
 	},
+	
 	initFromSched: function(scheduleId, flowName) {
 		this.scheduleId = scheduleId;
-		
 		var scheduleURL = contextURL + "/schedule"
 		this.scheduleURL = scheduleURL;
+		
 		var indexToName = {};
 		var nameToIndex = {};
 		var indexToText = {};
 		this.indexToName = indexToName;
 		this.nameToIndex = nameToIndex;
 		this.indexToText = indexToText;
+		
 		var ruleBoxOptions = ["SUCCESS", "FINISH"];
 		this.ruleBoxOptions = ruleBoxOptions;
 		
-		var fetchScheduleData = {"scheduleId": this.scheduleId, "ajax":"slaInfo"};
-		
-		$.get(
-				this.scheduleURL,
-				fetchScheduleData,
-				function(data) {
-					if (data.error) {
-						alert(data.error);
-					}
-					else {
-						if (data.slaEmails) {
-							$('#slaEmails').val(data.slaEmails.join());
+		var fetchScheduleData = {
+			"scheduleId": this.scheduleId, 
+			"ajax": "slaInfo"
+		};
+	
+		var successHandler = function(data) {
+			if (data.error) {
+				alert(data.error);
+				return;
+			}
+			if (data.slaEmails) {
+				$('#slaEmails').val(data.slaEmails.join());
+			}
+			
+			var allJobNames = data.allJobNames;
+			
+			indexToName[0] = "";
+			nameToIndex[flowName] = 0;
+			indexToText[0] = "flow " + flowName;
+			for (var i = 1; i <= allJobNames.length; i++) {
+				indexToName[i] = allJobNames[i-1];
+				nameToIndex[allJobNames[i-1]] = i;
+				indexToText[i] = "job " + allJobNames[i-1];
+			}
+			
+			// populate with existing settings
+			if (data.settings) {
+				var tFlowRules = document.getElementById("flowRulesTbl").tBodies[0];
+				for (var setting in data.settings) {
+					var rFlowRule = tFlowRules.insertRow(0);
+					
+					var cId = rFlowRule.insertCell(-1);
+					var idSelect = document.createElement("select");
+					idSelect.setAttribute("class", "form-control");
+					for (var i in indexToName) {
+						idSelect.options[i] = new Option(indexToText[i], indexToName[i]);
+						if (data.settings[setting].id == indexToName[i]) {
+							idSelect.options[i].selected = true;
 						}
-						
-						var allJobNames = data.allJobNames;
-						
-						indexToName[0] = "";
-						nameToIndex[flowName] = 0;
-						indexToText[0] = "flow " + flowName;
-						for(var i = 1; i <= allJobNames.length; i++) {
-							indexToName[i] = allJobNames[i-1];
-							nameToIndex[allJobNames[i-1]] = i;
-							indexToText[i] = "job " + allJobNames[i-1];
+					}								
+					cId.appendChild(idSelect);
+					
+					var cRule = rFlowRule.insertCell(-1);
+					var ruleSelect = document.createElement("select");
+					ruleSelect.setAttribute("class", "form-control");
+					for (var i in ruleBoxOptions) {
+						ruleSelect.options[i] = new Option(ruleBoxOptions[i], ruleBoxOptions[i]);
+						if (data.settings[setting].rule == ruleBoxOptions[i]) {
+							ruleSelect.options[i].selected = true;
 						}
-						
-						
-						
-						
-						
-						// populate with existing settings
-						if(data.settings) {
-							
-							var tFlowRules = document.getElementById("flowRulesTbl").tBodies[0];
-							
-							for(var setting in data.settings) {
-								var rFlowRule = tFlowRules.insertRow(0);
-								
-								var cId = rFlowRule.insertCell(-1);
-								var idSelect = document.createElement("select");
-								for(var i in indexToName) {
-									idSelect.options[i] = new Option(indexToText[i], indexToName[i]);
-									if(data.settings[setting].id == indexToName[i]) {
-										idSelect.options[i].selected = true;
-									}
-								}								
-								cId.appendChild(idSelect);
-								
-								var cRule = rFlowRule.insertCell(-1);
-								var ruleSelect = document.createElement("select");
-								for(var i in ruleBoxOptions) {
-									ruleSelect.options[i] = new Option(ruleBoxOptions[i], ruleBoxOptions[i]);
-									if(data.settings[setting].rule == ruleBoxOptions[i]) {
-										ruleSelect.options[i].selected = true;
-									}
-								}
-								cRule.appendChild(ruleSelect);
-								
-								var cDuration = rFlowRule.insertCell(-1);
-								var duration = document.createElement("input");
-								duration.type = "text";
-								duration.setAttribute("class", "durationpick");
-								var rawMinutes = data.settings[setting].duration;
-								var intMinutes = rawMinutes.substring(0, rawMinutes.length-1);
-								var minutes = parseInt(intMinutes);
-								var hours = Math.floor(minutes / 60);
-								minutes = minutes % 60;
-								duration.value = hours + ":" + minutes;
-								cDuration.appendChild(duration);
+					}
+					cRule.appendChild(ruleSelect);
+					
+					var cDuration = rFlowRule.insertCell(-1);
+					var duration = document.createElement("input");
+					duration.type = "text";
+					duration.setAttribute("class", "form-control durationpick");
+					var rawMinutes = data.settings[setting].duration;
+					var intMinutes = rawMinutes.substring(0, rawMinutes.length-1);
+					var minutes = parseInt(intMinutes);
+					var hours = Math.floor(minutes / 60);
+					minutes = minutes % 60;
+					duration.value = hours + ":" + minutes;
+					cDuration.appendChild(duration);
 
-								var cEmail = rFlowRule.insertCell(-1);
-								var emailCheck = document.createElement("input");
-								emailCheck.type = "checkbox";
-								for(var act in data.settings[setting].actions) {
-									if(data.settings[setting].actions[act] == "EMAIL") {
-										emailCheck.checked = true;
-									}
-								}
-								cEmail.appendChild(emailCheck);
-								
-								var cKill = rFlowRule.insertCell(-1);
-								var killCheck = document.createElement("input");
-								killCheck.type = "checkbox";
-								for(var act in data.settings[setting].actions) {
-									if(data.settings[setting].actions[act] == "KILL") {
-										killCheck.checked = true;
-									}
-								}
-								cKill.appendChild(killCheck);
-								
-								$('.durationpick').timepicker({hourMax: 99});
-							}
+					var cEmail = rFlowRule.insertCell(-1);
+					var emailCheck = document.createElement("input");
+					emailCheck.type = "checkbox";
+					for (var act in data.settings[setting].actions) {
+						if (data.settings[setting].actions[act] == "EMAIL") {
+							emailCheck.checked = true;
 						}
-						$('.durationpick').timepicker({hourMax: 99});
 					}
-				},
-				"json"
-			);
+					cEmail.appendChild(emailCheck);
+					
+					var cKill = rFlowRule.insertCell(-1);
+					var killCheck = document.createElement("input");
+					killCheck.type = "checkbox";
+					for (var act in data.settings[setting].actions) {
+						if (data.settings[setting].actions[act] == "KILL") {
+							killCheck.checked = true;
+						}
+					}
+					cKill.appendChild(killCheck);
+					$('.durationpick').datetimepicker({
+            pickDate: false,
+            use24hours: true
+          });
+				}
+			}
+      $('.durationpick').datetimepicker({
+        pickDate: false,
+        use24hours: true
+      });
+		};
+
+		$.get(this.scheduleURL, fetchScheduleData, successHandler, "json");
 		
-		$('#slaModalBackground').show();
-		$('#sla-options').show();
+		$('#sla-options').modal();
 		
-//		this.schedFlowOptions = sched.flowOptions
+		//this.schedFlowOptions = sched.flowOptions
 		console.log("Loaded schedule info. Ready to set SLA.");
-
 	},
+	
 	handleRemoveSla: function(evt) {
 		console.log("Clicked remove sla button");
 		var scheduleURL = this.scheduleURL;
 		var redirectURL = this.scheduleURL;
-		$.post(
-				scheduleURL,
-				{"action":"removeSla", "scheduleId":this.scheduleId},
-				function(data) {
-				if (data.error) {
-						$('#errorMsg').text(data.error)
-					}
-					else {
-						window.location = redirectURL
-					}
-				"json"
-				}
-			);
-
+		var requestData = {
+			"action": "removeSla", 
+			"scheduleId": this.scheduleId
+		};
+		var successHandler = function(data) {
+			if (data.error) {
+				$('#errorMsg').text(data.error)
+			}
+			else {
+				window.location = redirectURL
+			}
+		};
+		$.post(scheduleURL, requestData, successHanlder, "json");
 	},
+	
 	handleSetSla: function(evt) {
-
 		var slaEmails = $('#slaEmails').val();
 		var settings = {};
-		
-		
 		var tFlowRules = document.getElementById("flowRulesTbl").tBodies[0];
-		for(var row = 0; row < tFlowRules.rows.length-1; row++) {
+		for (var row = 0; row < tFlowRules.rows.length-1; row++) {
 			var rFlowRule = tFlowRules.rows[row];
 			var id = rFlowRule.cells[0].firstChild.value;
 			var rule = rFlowRule.cells[1].firstChild.value;
@@ -231,24 +238,19 @@ azkaban.ChangeSlaView = Backbone.View.extend({
 		};
 
 		var scheduleURL = this.scheduleURL;
-		
-		$.post(
-			scheduleURL,
-			slaData,
-			function(data) {
-				if (data.error) {
-					alert(data.error);
-				}
-				else {
-					tFlowRules.length = 0;
-					window.location = scheduleURL;
-				}
-			},
-			"json"
-		);
+		var successHandler = function(data) {
+			if (data.error) {
+				alert(data.error);
+			}
+			else {
+				tFlowRules.length = 0;
+				window.location = scheduleURL;
+			}
+		};
+		$.post(scheduleURL, slaData, successHandler, "json");
 	},
+	
 	handleAddRow: function(evt) {
-		
 		var indexToName = this.indexToName;
 		var nameToIndex = this.nameToIndex;
 		var indexToText = this.indexToText;
@@ -259,15 +261,16 @@ azkaban.ChangeSlaView = Backbone.View.extend({
 		
 		var cId = rFlowRule.insertCell(-1);
 		var idSelect = document.createElement("select");
-		for(var i in indexToName) {
+		idSelect.setAttribute("class", "form-control");
+		for (var i in indexToName) {
 			idSelect.options[i] = new Option(indexToText[i], indexToName[i]);
 		}
-		
 		cId.appendChild(idSelect);
 		
 		var cRule = rFlowRule.insertCell(-1);
 		var ruleSelect = document.createElement("select");
-		for(var i in ruleBoxOptions) {
+		ruleSelect.setAttribute("class", "form-control");
+		for (var i in ruleBoxOptions) {
 			ruleSelect.options[i] = new Option(ruleBoxOptions[i], ruleBoxOptions[i]);
 		}
 		cRule.appendChild(ruleSelect);
@@ -275,7 +278,7 @@ azkaban.ChangeSlaView = Backbone.View.extend({
 		var cDuration = rFlowRule.insertCell(-1);
 		var duration = document.createElement("input");
 		duration.type = "text";
-		duration.setAttribute("class", "durationpick");
+		duration.setAttribute("class", "durationpick form-control");
 		cDuration.appendChild(duration);
 
 		var cEmail = rFlowRule.insertCell(-1);
@@ -288,13 +291,15 @@ azkaban.ChangeSlaView = Backbone.View.extend({
 		killCheck.type = "checkbox";
 		cKill.appendChild(killCheck);
 		
-		$('.durationpick').timepicker({hourMax: 99});
-
+    $('.durationpick').datetimepicker({ 
+      pickDate: false,
+      use24hours: true
+    });
 		return rFlowRule;
 	},
-	handleEditColumn : function(evt) {
-		var curTarget = evt.currentTarget;
 	
+	handleEditColumn: function(evt) {
+		var curTarget = evt.currentTarget;
 		if (this.editingTarget != curTarget) {
 			this.closeEditingTarget();
 			
@@ -311,32 +316,26 @@ azkaban.ChangeSlaView = Backbone.View.extend({
 			this.editingTarget = curTarget;
 		}
 	},
-	handleRemoveColumn : function(evt) {
+	
+	handleRemoveColumn: function(evt) {
 		var curTarget = evt.currentTarget;
 		// Should be the table
 		var row = curTarget.parentElement.parentElement;
 		$(row).remove();
 	},
+	
 	closeEditingTarget: function(evt) {
-
 	}
 });
 
-var slaView;
 var tableSorterView;
 $(function() {
-	var selected;
-
-
 	slaView = new azkaban.ChangeSlaView({el:$('#sla-options')});
 	tableSorterView = new azkaban.TableSorter({el:$('#scheduledFlowsTbl')});
-//	var requestURL = contextURL + "/manager";
+	//var requestURL = contextURL + "/manager";
 
 	// Set up the Flow options view. Create a new one every time :p
-//	 $('#addSlaBtn').click( function() {
-//		 slaView.show();
-//	 });
-
-	 
-	
-});
\ No newline at end of file
+	//$('#addSlaBtn').click( function() {
+	//	slaView.show();
+	//});
+});
diff --git a/src/web/js/azkaban.svg.graph.view.js b/src/web/js/azkaban.svg.graph.view.js
index 009b4e4..67d2530 100644
--- a/src/web/js/azkaban.svg.graph.view.js
+++ b/src/web/js/azkaban.svg.graph.view.js
@@ -1,29 +1,18 @@
-function hasClass(el, name) 
-{
-	var classes = el.getAttribute("class");
-	if (classes == null) {
-		return false;
-	}
-   return new RegExp('(\\s|^)'+name+'(\\s|$)').test(classes);
-}
-
-function addClass(el, name)
-{
-   if (!hasClass(el, name)) { 
-   		var classes = el.getAttribute("class");
-   		classes += classes ? ' ' + name : '' +name;
-   		el.setAttribute("class", classes);
-   }
-}
-
-function removeClass(el, name)
-{
-   if (hasClass(el, name)) {
-      var classes = el.getAttribute("class");
-      el.setAttribute("class", classes.replace(new RegExp('(\\s|^)'+name+'(\\s|$)'),' ').replace(/^\s+|\s+$/g, ''));
-   }
-}
-
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 
 azkaban.SvgGraphView = Backbone.View.extend({
 	events: {
@@ -32,7 +21,8 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		"contextmenu g" : "handleRightClick",
 		"contextmenu polyline": "handleRightClick"
 	},
-	initialize: function(settings) {
+	
+  initialize: function(settings) {
 		this.model.bind('change:selected', this.changeSelected, this);
 		this.model.bind('change:graph', this.render, this);
 		this.model.bind('resetPanZoom', this.resetPanZoom, this);
@@ -57,7 +47,8 @@ azkaban.SvgGraphView = Backbone.View.extend({
 
 		$(svg).svgNavigate();
 	},
-	initializeDefs: function(self) {
+	
+  initializeDefs: function(self) {
 		var def = document.createElementNS(svgns, 'defs');
 		def.setAttributeNS(null, "id", "buttonDefs");
 
@@ -79,7 +70,8 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		
 		this.svgGraph.appendChild(def);
 	},
-	render: function(self) {
+	
+  render: function(self) {
 		console.log("graph render");
 
 		// Clean everything
@@ -142,7 +134,8 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		this.graphBounds = bounds;
 		this.resetPanZoom(0);
 	},
-	handleDisabledChange: function(evt) {
+	
+  handleDisabledChange: function(evt) {
 		var disabledMap = this.model.get("disabled");
 
 		for(var id in this.nodes) {
@@ -151,13 +144,14 @@ azkaban.SvgGraphView = Backbone.View.extend({
 				this.nodes[id].disabled = true;
 				addClass(g, "disabled");
 			}
-		    else {
-		    	this.nodes[id].disabled = false;
-		    	removeClass(g, "disabled");
+			else {
+				this.nodes[id].disabled = false;
+				removeClass(g, "disabled");
 			}
 		}
 	},
-	assignInitialStatus: function(evt) {
+	
+  assignInitialStatus: function(evt) {
 		var data = this.model.get("data");
 		for (var i = 0; i < data.nodes.length; ++i) {
 			var updateNode = data.nodes[i];
@@ -165,7 +159,8 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			addClass(g, updateNode.status);
 		}
 	},
-	changeSelected: function(self) {
+	
+  changeSelected: function(self) {
 		console.log("change selected");
 		var selected = this.model.get("selected");
 		var previous = this.model.previous("selected");
@@ -187,10 +182,16 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			var x = node.x - offset;
 			var y = node.y - offset;
 			
-			$(this.svgGraph).svgNavigate("transformToBox", {x: x, y: y, width: widthHeight, height: widthHeight});
+			$(this.svgGraph).svgNavigate("transformToBox", {
+				x: x, 
+				y: y, 
+				width: widthHeight, 
+				height: widthHeight
+			});
 		}
 	},
-	handleStatusUpdate: function(evt) {
+	
+  handleStatusUpdate: function(evt) {
 		var updateData = this.model.get("update");
 		if (updateData.nodes) {
 			for (var i = 0; i < updateData.nodes.length; ++i) {
@@ -203,26 +204,31 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			}
 		}
 	},
-	handleRemoveAllStatus: function(gNode) {
+	
+  handleRemoveAllStatus: function(gNode) {
 		for (var j = 0; j < statusList.length; ++j) {
 			var status = statusList[j];
 			removeClass(gNode, status);
 		}
 	},
-	clickGraph: function(self) {
+	
+  clickGraph: function(self) {
 		console.log("click");
 		if (self.currentTarget.jobid) {
 			this.model.set({"selected": self.currentTarget.jobid});
 		}
 	},
-	handleRightClick: function(self) {
+	
+  handleRightClick: function(self) {
 		if (this.rightClick) {
 			var callbacks = this.rightClick;
 			var currentTarget = self.currentTarget;
 			if (callbacks.node && currentTarget.jobid) {
 				callbacks.node(self);
 			}
-			else if (callbacks.edge && (currentTarget.nodeName == "polyline" || currentTarget.nodeName == "line")) {
+			else if (callbacks.edge && 
+					(currentTarget.nodeName == "polyline" || 
+					 currentTarget.nodeName == "line")) {
 				callbacks.edge(self);
 			}
 			else if (callbacks.graph) {
@@ -233,7 +239,8 @@ azkaban.SvgGraphView = Backbone.View.extend({
 	
 		return true;
 	},	
-	drawEdge: function(self, edge) {
+	
+  drawEdge: function(self, edge) {
 		var svg = self.svgGraph;
 		var svgns = self.svgns;
 		
@@ -266,7 +273,8 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			self.mainG.appendChild(line);
 		}
 	},
-	drawNode: function(self, node, bounds) {
+	
+  drawNode: function(self, node, bounds) {
 		var svg = self.svgGraph;
 		var svgns = self.svgns;
 
@@ -288,7 +296,6 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		circle.setAttributeNS(null, "r", 12);
 		circle.setAttributeNS(null, "style", "width:inherit;stroke-opacity:1");
 		
-		
 		var text = document.createElementNS(svgns, 'text');
 		var textLabel = document.createTextNode(node.label);
 		text.appendChild(textLabel);
@@ -296,7 +303,12 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		text.setAttributeNS(null, "y", 15);
 		text.setAttributeNS(null, "height", 10); 
 				
-		this.addBounds(bounds, {minX:node.x - xOffset, minY: node.y - yOffset, maxX: node.x + xOffset, maxY: node.y + yOffset});
+		this.addBounds(bounds, {
+			minX: node.x - xOffset, 
+			minY: node.y - yOffset, 
+			maxX: node.x + xOffset, 
+			maxY: node.y + yOffset
+		});
 		
 		var backRect = document.createElementNS(svgns, 'rect');
 		backRect.setAttributeNS(null, "x", 0);
@@ -323,16 +335,27 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		nodeG.setAttributeNS(null, "class", "node");
 		nodeG.jobid=node.id;
 	},
-	addBounds: function(toBounds, addBounds) {
-		toBounds.minX = toBounds.minX ? Math.min(toBounds.minX, addBounds.minX) : addBounds.minX;
-		toBounds.minY = toBounds.minY ? Math.min(toBounds.minY, addBounds.minY) : addBounds.minY;
-		toBounds.maxX = toBounds.maxX ? Math.max(toBounds.maxX, addBounds.maxX) : addBounds.maxX;
-		toBounds.maxY = toBounds.maxY ? Math.max(toBounds.maxY, addBounds.maxY) : addBounds.maxY;
+	
+  addBounds: function(toBounds, addBounds) {
+		toBounds.minX = toBounds.minX 
+				? Math.min(toBounds.minX, addBounds.minX) : addBounds.minX;
+		toBounds.minY = toBounds.minY 
+				? Math.min(toBounds.minY, addBounds.minY) : addBounds.minY;
+		toBounds.maxX = toBounds.maxX 
+				? Math.max(toBounds.maxX, addBounds.maxX) : addBounds.maxX;
+		toBounds.maxY = toBounds.maxY 
+				? Math.max(toBounds.maxY, addBounds.maxY) : addBounds.maxY;
 	},
-	resetPanZoom : function(duration) {
+	
+  resetPanZoom: function(duration) {
 		var bounds = this.graphBounds;
-		var param = {x: bounds.minX, y: bounds.minY, width: (bounds.maxX - bounds.minX), height: (bounds.maxY - bounds.minY), duration: duration };
-
+		var param = {
+			x: bounds.minX, 
+			y: bounds.minY, 
+			width: (bounds.maxX - bounds.minX), 
+			height: (bounds.maxY - bounds.minY), 
+			duration: duration 
+		};
 		$(this.svgGraph).svgNavigate("transformToBox", param);
 	}
-});
\ No newline at end of file
+});
diff --git a/src/web/js/azkaban.table.sort.js b/src/web/js/azkaban.table.sort.js
index 029e5bd..40f05e8 100644
--- a/src/web/js/azkaban.table.sort.js
+++ b/src/web/js/azkaban.table.sort.js
@@ -1,49 +1,67 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
-azkaban.TableSorter= Backbone.View.extend({
-  events : {
+azkaban.TableSorter = Backbone.View.extend({
+  events: {
   	"click .sortable": "handleClickSort"
   },
-  initialize : function(settings) {
+
+  initialize: function(settings) {
   	$(this.el).addClass("sortableTable");
   
-  	var thead = $(this.el).children("thead");
-	var th = $(thead).find("th");
-	
-	$(th).addClass("sortable");
-	$("th.ignoresort").removeClass("sortable");
-	var sortDiv = document.createElement("div");
+		var thead = $(this.el).children("thead");
+		var th = $(thead).find("th");
+		
+		$(th).addClass("sortable");
+		$("th.ignoresort").removeClass("sortable");
+		var sortDiv = document.createElement("div");
 
-	$(sortDiv).addClass("sortIcon");
-	
-	$(th).append(sortDiv);
-	
-	var tbody = $(this.el).children("tbody");
-	var rows = $(tbody).children("tr");
-	
-	var row;
-	for (var i = 0; i < rows.length; ++i ) {
-		var nextRow = rows[i];
-		if (row && $(nextRow).hasClass("childrow")) {
-			if (!row.childRows) {
-				row.childRows = new Array();
-			}
+		$(sortDiv).addClass("sortIcon");
 		
-			row.childRows.push(nextRow);
+		$(th).append(sortDiv);
+		
+		var tbody = $(this.el).children("tbody");
+		var rows = $(tbody).children("tr");
+		
+		var row;
+		for (var i = 0; i < rows.length; ++i ) {
+			var nextRow = rows[i];
+			if (row && $(nextRow).hasClass("childrow")) {
+				if (!row.childRows) {
+					row.childRows = new Array();
+				}
+				row.childRows.push(nextRow);
+			}
+			else {
+				row = nextRow;
+			}
 		}
-		else {
-			row = nextRow;
+		
+		if (settings.initialSort) {
+			this.toggleSort(settings.initialSort);
 		}
-	}
-	
-	if (settings.initialSort) {
-		this.toggleSort(settings.initialSort);
-	}
   },
+
   handleClickSort: function(evt) {
   	this.toggleSort(evt.currentTarget);
   },
-  toggleSort: function(th) {
+  
+	toggleSort: function(th) {
   	console.log("sorting by index " + $(th).index());
   	if ($(th).hasClass("asc")) {
   		$(th).removeClass("asc");
@@ -65,70 +83,72 @@ azkaban.TableSorter= Backbone.View.extend({
   		this.sort($(th).index(), false);
   	}
   },
-  sort: function(index, desc) {
-	var tbody = $(this.el).children("tbody");
-	var rows = $(tbody).children("tr");
+  
+	sort: function(index, desc) {
+		var tbody = $(this.el).children("tbody");
+		var rows = $(tbody).children("tr");
 
-	var tdToSort = new Array();
-	for (var i = 0; i < rows.length; ++i) {
-		var row = rows[i];
-		if (!$(row).hasClass("childrow")) {
-			var td = row.children[index];
-			tdToSort.push(td);
+		var tdToSort = new Array();
+		for (var i = 0; i < rows.length; ++i) {
+			var row = rows[i];
+			if (!$(row).hasClass("childrow")) {
+				var td = row.children[index];
+				tdToSort.push(td);
+			}
 		}
-	}
 
-	if (desc) {
-		tdToSort.sort(function(a,b) {
-			var texta = $(a).text().trim().toLowerCase();
-			var textb = $(b).text().trim().toLowerCase();
-			
-			if (texta < textb) {
-				return 1;
-			}
-			else if (texta > textb) {
-				return -1;
-			} 
-			else {
-				return 0;
-			}
-		});
-	}
-	else {
-		tdToSort.sort(function(a,b) {
-			var texta = $(a).text().trim().toLowerCase();
-			var textb = $(b).text().trim().toLowerCase();
+		if (desc) {
+			tdToSort.sort(function(a,b) {
+				var texta = $(a).text().trim().toLowerCase();
+				var textb = $(b).text().trim().toLowerCase();
+				
+				if (texta < textb) {
+					return 1;
+				}
+				else if (texta > textb) {
+					return -1;
+				} 
+				else {
+					return 0;
+				}
+			});
+		}
+		else {
+			tdToSort.sort(function(a,b) {
+				var texta = $(a).text().trim().toLowerCase();
+				var textb = $(b).text().trim().toLowerCase();
+				
+				if (texta < textb) {
+					return -1;
+				}
+				else if (texta > textb) {
+					return 1;
+				} 
+				else {
+					return 0;
+				}
+			});
+		}
+
+		var sortedTR = new Array();
+		for (var i = 0; i < tdToSort.length; ++i) {
+			var tr = $(tdToSort[i]).parent();
+			sortedTR.push(tr);
 			
-			if (texta < textb) {
-				return -1;
-			}
-			else if (texta > textb) {
-				return 1;
-			} 
-			else {
-				return 0;
+			var childRows = tr[0].childRows;
+			if (childRows) {
+				for(var j=0; j < childRows.length; ++j) {
+					sortedTR.push(childRows[j]);
+				}
 			}
-		});
-	}
-
-	var sortedTR = new Array();
-	for (var i = 0; i < tdToSort.length; ++i) {
-		var tr = $(tdToSort[i]).parent();
-		sortedTR.push(tr);
+		}
 		
-		var childRows = tr[0].childRows;
-		if (childRows) {
-			for(var j=0; j < childRows.length; ++j) {
-				sortedTR.push(childRows[j]);
-			}
+		for (var i = 0; i < sortedTR.length; ++i) {
+			$(tbody).append(sortedTR[i]);
 		}
-	}
-	
-	for (var i = 0; i < sortedTR.length; ++i) {
-		$(tbody).append(sortedTR[i]);
-	}
   },
+
   render: function() {
-  	console.log("render graph");
+  	console.log("render sorted table");
   }
-});
\ No newline at end of file
+});
diff --git a/src/web/js/azkaban.triggers.view.js b/src/web/js/azkaban.triggers.view.js
new file mode 100644
index 0000000..83771e9
--- /dev/null
+++ b/src/web/js/azkaban.triggers.view.js
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+$.namespace('azkaban');
+
+function expireTrigger(triggerId) {
+	var triggerURL = contextURL + "/triggers"
+	var redirectURL = contextURL + "/triggers"
+	var requestData = {"ajax": "expireTrigger", "triggerId": triggerId};
+	var successHandler = function(data) {
+    if (data.error) {
+      //alert(data.error)
+      $('#errorMsg').text(data.error);
+    }
+    else {
+      //alert("Schedule "+schedId+" removed!")
+      window.location = redirectURL;
+    }
+  };
+	$.post(triggerURL, requestData, successHandler, "json");
+}
+
+function removeSched(scheduleId) {
+	var scheduleURL = contextURL + "/schedule"
+	var redirectURL = contextURL + "/schedule"
+	var requestData = {"action": "removeSched", "scheduleId": scheduleId};
+	var successHandler = function(data) {
+    if (data.error) {
+      //alert(data.error)
+      $('#errorMsg').text(data.error);
+    }
+    else {
+      //alert("Schedule "+schedId+" removed!")
+      window.location = redirectURL;
+    }
+  };
+	$.post(scheduleURL, requestData, successHandler, "json");
+}
+
+function removeSla(scheduleId) {
+	var scheduleURL = contextURL + "/schedule"
+	var redirectURL = contextURL + "/schedule"
+  var requestData = {"action": "removeSla", "scheduleId": scheduleId};
+	var successHandler = function(data) {
+    if (data.error) {
+      //alert(data.error)
+      $('#errorMsg').text(data.error)
+    }
+    else {
+      //alert("Schedule "+schedId+" removed!")
+      window.location = redirectURL
+    }
+  };
+	$.post(scheduleURL, requestData, successHandler, "json");
+}
+
+azkaban.ChangeSlaView = Backbone.View.extend({
+	events: {
+		"click" : "closeEditingTarget",
+		"click #set-sla-btn": "handleSetSla",	
+		"click #remove-sla-btn": "handleRemoveSla",
+		"click #sla-cancel-btn": "handleSlaCancel",
+		"click .modal-close": "handleSlaCancel",
+		"click #addRow": "handleAddRow"
+	},
+	
+  initialize: function(setting) {
+	},
+	
+  handleSlaCancel: function(evt) {
+		console.log("Clicked cancel button");
+		var scheduleURL = contextURL + "/schedule";
+
+		$('#slaModalBackground').hide();
+		$('#sla-options').hide();
+		
+		var tFlowRules = document.getElementById("flowRulesTbl").tBodies[0];
+		var rows = tFlowRules.rows;
+		var rowLength = rows.length
+		for (var i = 0; i < rowLength-1; i++) {
+			tFlowRules.deleteRow(0);
+		}
+	},
+
+	initFromSched: function(scheduleId, flowName) {
+		this.scheduleId = scheduleId;
+		
+		var scheduleURL = contextURL + "/schedule"
+		this.scheduleURL = scheduleURL;
+		var indexToName = {};
+		var nameToIndex = {};
+		var indexToText = {};
+		this.indexToName = indexToName;
+		this.nameToIndex = nameToIndex;
+		this.indexToText = indexToText;
+		var ruleBoxOptions = ["SUCCESS", "FINISH"];
+		this.ruleBoxOptions = ruleBoxOptions;
+		
+		var fetchScheduleData = {"scheduleId": this.scheduleId, "ajax": "slaInfo"};
+    var successHandler = function(data) {
+      if (data.error) {
+        alert(data.error);
+        return;
+      }
+      if (data.slaEmails) {
+        $('#slaEmails').val(data.slaEmails.join());
+      }
+      
+      var allJobNames = data.allJobNames;
+      
+      indexToName[0] = "";
+      nameToIndex[flowName] = 0;
+      indexToText[0] = "flow " + flowName;
+      for (var i = 1; i <= allJobNames.length; i++) {
+        indexToName[i] = allJobNames[i-1];
+        nameToIndex[allJobNames[i-1]] = i;
+        indexToText[i] = "job " + allJobNames[i-1];
+      }
+      
+      // populate with existing settings
+      if (data.settings) {
+        $('.durationpick').timepicker({hourMax: 99});
+        return;
+      }
+
+      var tFlowRules = document.getElementById("flowRulesTbl").tBodies[0];
+      for (var setting in data.settings) {
+        var rFlowRule = tFlowRules.insertRow(0);
+        
+        var cId = rFlowRule.insertCell(-1);
+        var idSelect = document.createElement("select");
+        for (var i in indexToName) {
+          idSelect.options[i] = new Option(indexToText[i], indexToName[i]);
+          if (data.settings[setting].id == indexToName[i]) {
+            idSelect.options[i].selected = true;
+          }
+        }								
+        cId.appendChild(idSelect);
+        
+        var cRule = rFlowRule.insertCell(-1);
+        var ruleSelect = document.createElement("select");
+        for (var i in ruleBoxOptions) {
+          ruleSelect.options[i] = new Option(ruleBoxOptions[i], ruleBoxOptions[i]);
+          if (data.settings[setting].rule == ruleBoxOptions[i]) {
+            ruleSelect.options[i].selected = true;
+          }
+        }
+        cRule.appendChild(ruleSelect);
+        
+        var cDuration = rFlowRule.insertCell(-1);
+        var duration = document.createElement("input");
+        duration.type = "text";
+        duration.setAttribute("class", "durationpick");
+        var rawMinutes = data.settings[setting].duration;
+        var intMinutes = rawMinutes.substring(0, rawMinutes.length-1);
+        var minutes = parseInt(intMinutes);
+        var hours = Math.floor(minutes / 60);
+        minutes = minutes % 60;
+        duration.value = hours + ":" + minutes;
+        cDuration.appendChild(duration);
+
+        var cEmail = rFlowRule.insertCell(-1);
+        var emailCheck = document.createElement("input");
+        emailCheck.type = "checkbox";
+        for (var act in data.settings[setting].actions) {
+          if (data.settings[setting].actions[act] == "EMAIL") {
+            emailCheck.checked = true;
+          }
+        }
+        cEmail.appendChild(emailCheck);
+        
+        var cKill = rFlowRule.insertCell(-1);
+        var killCheck = document.createElement("input");
+        killCheck.type = "checkbox";
+        for (var act in data.settings[setting].actions) {
+          if (data.settings[setting].actions[act] == "KILL") {
+            killCheck.checked = true;
+          }
+        }
+        cKill.appendChild(killCheck);
+        $('.durationpick').timepicker({hourMax: 99});
+      }
+      $('.durationpick').timepicker({hourMax: 99});
+    };
+		
+		$.get(this.scheduleURL, fetchScheduleData, successHandler, "json");
+		$('#slaModalBackground').show();
+		$('#sla-options').show();
+		
+    //this.schedFlowOptions = sched.flowOptions
+		console.log("Loaded schedule info. Ready to set SLA.");
+	},
+	
+  handleRemoveSla: function(evt) {
+		console.log("Clicked remove sla button");
+		var scheduleURL = this.scheduleURL;
+		var redirectURL = this.scheduleURL;
+    var requestData = {"action": "removeSla", "scheduleId": this.scheduleId};
+    var successHandler = function(data) {
+      if (data.error) {
+        $('#errorMsg').text(data.error)
+      }
+      else {
+        window.location = redirectURL
+      }
+    };
+		$.post(scheduleURL, requestData, successHandler, "json");
+	},
+	
+  handleSetSla: function(evt) {
+		var slaEmails = $('#slaEmails').val();
+		var settings = {};
+		
+		var tFlowRules = document.getElementById("flowRulesTbl").tBodies[0];
+		for (var row = 0; row < tFlowRules.rows.length - 1; row++) {
+			var rFlowRule = tFlowRules.rows[row];
+			var id = rFlowRule.cells[0].firstChild.value;
+			var rule = rFlowRule.cells[1].firstChild.value;
+			var duration = rFlowRule.cells[2].firstChild.value;
+			var email = rFlowRule.cells[3].firstChild.checked;
+			var kill = rFlowRule.cells[4].firstChild.checked;
+			settings[row] = id + "," + rule + "," + duration + "," + email + "," + kill; 
+		}
+
+		var slaData = {
+			scheduleId: this.scheduleId,
+			ajax: "setSla",			
+			slaEmails: slaEmails,
+			settings: settings
+		};
+
+		var scheduleURL = this.scheduleURL;
+    var successHandler = function(data) {
+      if (data.error) {
+        alert(data.error);
+      }
+      else {
+        tFlowRules.length = 0;
+        window.location = scheduleURL;
+      }
+    };
+		
+		$.post(scheduleURL, slaData, successHandler, "json");
+	},
+	
+  handleAddRow: function(evt) {
+		var indexToName = this.indexToName;
+		var nameToIndex = this.nameToIndex;
+		var indexToText = this.indexToText;
+		var ruleBoxOptions = this.ruleBoxOptions;
+
+		var tFlowRules = document.getElementById("flowRulesTbl").tBodies[0];
+		var rFlowRule = tFlowRules.insertRow(tFlowRules.rows.length-1);
+		
+		var cId = rFlowRule.insertCell(-1);
+		var idSelect = document.createElement("select");
+		for (var i in indexToName) {
+			idSelect.options[i] = new Option(indexToText[i], indexToName[i]);
+		}
+		
+		cId.appendChild(idSelect);
+		
+		var cRule = rFlowRule.insertCell(-1);
+		var ruleSelect = document.createElement("select");
+		for (var i in ruleBoxOptions) {
+			ruleSelect.options[i] = new Option(ruleBoxOptions[i], ruleBoxOptions[i]);
+		}
+		cRule.appendChild(ruleSelect);
+		
+		var cDuration = rFlowRule.insertCell(-1);
+		var duration = document.createElement("input");
+		duration.type = "text";
+		duration.setAttribute("class", "durationpick");
+		cDuration.appendChild(duration);
+
+		var cEmail = rFlowRule.insertCell(-1);
+		var emailCheck = document.createElement("input");
+		emailCheck.type = "checkbox";
+		cEmail.appendChild(emailCheck);
+		
+		var cKill = rFlowRule.insertCell(-1);
+		var killCheck = document.createElement("input");
+		killCheck.type = "checkbox";
+		cKill.appendChild(killCheck);
+		
+		$('.durationpick').timepicker({hourMax: 99});
+		return rFlowRule;
+	},
+
+	handleEditColumn: function(evt) {
+		var curTarget = evt.currentTarget;
+	
+		if (this.editingTarget != curTarget) {
+			this.closeEditingTarget();
+			
+			var text = $(curTarget).children(".spanValue").text();
+			$(curTarget).empty();
+						
+			var input = document.createElement("input");
+			$(input).attr("type", "text");
+			$(input).css("width", "100%");
+			$(input).val(text);
+			$(curTarget).addClass("editing");
+			$(curTarget).append(input);
+			$(input).focus();
+			this.editingTarget = curTarget;
+		}
+	},
+
+	handleRemoveColumn: function(evt) {
+		var curTarget = evt.currentTarget;
+		// Should be the table
+		var row = curTarget.parentElement.parentElement;
+		$(row).remove();
+	},
+
+	closeEditingTarget: function(evt) {
+	}
+});
+
+var slaView;
+var tableSorterView;
+$(function() {
+	var selected;
+
+	slaView = new azkaban.ChangeSlaView({el:$('#sla-options')});
+	tableSorterView = new azkaban.TableSorter({el:$('#scheduledFlowsTbl')});
+  /*
+  var requestURL = contextURL + "/manager";
+
+	// Set up the Flow options view. Create a new one every time :p
+  $('#addSlaBtn').click( function() {
+    slaView.show();
+  });
+  */
+});

src/web/js/bootstrap.js 2002(+2002 -0)

diff --git a/src/web/js/bootstrap.js b/src/web/js/bootstrap.js
new file mode 100644
index 0000000..1c638ab
--- /dev/null
+++ b/src/web/js/bootstrap.js
@@ -0,0 +1,2002 @@
+/*!
+ * Bootstrap v3.0.2 by @fat and @mdo
+ * Copyright 2013 Twitter, Inc.
+ * Licensed under http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */
+
+if (typeof jQuery === "undefined") { throw new Error("Bootstrap requires jQuery") }
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.0.2
+ * http://getbootstrap.com/javascript/#transitions
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+  // ============================================================
+
+  function transitionEnd() {
+    var el = document.createElement('bootstrap')
+
+    var transEndEventNames = {
+      'WebkitTransition' : 'webkitTransitionEnd'
+    , 'MozTransition'    : 'transitionend'
+    , 'OTransition'      : 'oTransitionEnd otransitionend'
+    , 'transition'       : 'transitionend'
+    }
+
+    for (var name in transEndEventNames) {
+      if (el.style[name] !== undefined) {
+        return { end: transEndEventNames[name] }
+      }
+    }
+  }
+
+  // http://blog.alexmaccaw.com/css-transitions
+  $.fn.emulateTransitionEnd = function (duration) {
+    var called = false, $el = this
+    $(this).one($.support.transition.end, function () { called = true })
+    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+    setTimeout(callback, duration)
+    return this
+  }
+
+  $(function () {
+    $.support.transition = transitionEnd()
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.0.2
+ * http://getbootstrap.com/javascript/#alerts
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // ALERT CLASS DEFINITION
+  // ======================
+
+  var dismiss = '[data-dismiss="alert"]'
+  var Alert   = function (el) {
+    $(el).on('click', dismiss, this.close)
+  }
+
+  Alert.prototype.close = function (e) {
+    var $this    = $(this)
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = $(selector)
+
+    if (e) e.preventDefault()
+
+    if (!$parent.length) {
+      $parent = $this.hasClass('alert') ? $this : $this.parent()
+    }
+
+    $parent.trigger(e = $.Event('close.bs.alert'))
+
+    if (e.isDefaultPrevented()) return
+
+    $parent.removeClass('in')
+
+    function removeElement() {
+      $parent.trigger('closed.bs.alert').remove()
+    }
+
+    $.support.transition && $parent.hasClass('fade') ?
+      $parent
+        .one($.support.transition.end, removeElement)
+        .emulateTransitionEnd(150) :
+      removeElement()
+  }
+
+
+  // ALERT PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.alert
+
+  $.fn.alert = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.alert')
+
+      if (!data) $this.data('bs.alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.alert.Constructor = Alert
+
+
+  // ALERT NO CONFLICT
+  // =================
+
+  $.fn.alert.noConflict = function () {
+    $.fn.alert = old
+    return this
+  }
+
+
+  // ALERT DATA-API
+  // ==============
+
+  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.0.2
+ * http://getbootstrap.com/javascript/#buttons
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // BUTTON PUBLIC CLASS DEFINITION
+  // ==============================
+
+  var Button = function (element, options) {
+    this.$element = $(element)
+    this.options  = $.extend({}, Button.DEFAULTS, options)
+  }
+
+  Button.DEFAULTS = {
+    loadingText: 'loading...'
+  }
+
+  Button.prototype.setState = function (state) {
+    var d    = 'disabled'
+    var $el  = this.$element
+    var val  = $el.is('input') ? 'val' : 'html'
+    var data = $el.data()
+
+    state = state + 'Text'
+
+    if (!data.resetText) $el.data('resetText', $el[val]())
+
+    $el[val](data[state] || this.options[state])
+
+    // push to event loop to allow forms to submit
+    setTimeout(function () {
+      state == 'loadingText' ?
+        $el.addClass(d).attr(d, d) :
+        $el.removeClass(d).removeAttr(d);
+    }, 0)
+  }
+
+  Button.prototype.toggle = function () {
+    var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+    if ($parent.length) {
+      var $input = this.$element.find('input')
+        .prop('checked', !this.$element.hasClass('active'))
+        .trigger('change')
+      if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active')
+    }
+
+    this.$element.toggleClass('active')
+  }
+
+
+  // BUTTON PLUGIN DEFINITION
+  // ========================
+
+  var old = $.fn.button
+
+  $.fn.button = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.button')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+      if (option == 'toggle') data.toggle()
+      else if (option) data.setState(option)
+    })
+  }
+
+  $.fn.button.Constructor = Button
+
+
+  // BUTTON NO CONFLICT
+  // ==================
+
+  $.fn.button.noConflict = function () {
+    $.fn.button = old
+    return this
+  }
+
+
+  // BUTTON DATA-API
+  // ===============
+
+  $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) {
+    var $btn = $(e.target)
+    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+    $btn.button('toggle')
+    e.preventDefault()
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.0.2
+ * http://getbootstrap.com/javascript/#carousel
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // CAROUSEL CLASS DEFINITION
+  // =========================
+
+  var Carousel = function (element, options) {
+    this.$element    = $(element)
+    this.$indicators = this.$element.find('.carousel-indicators')
+    this.options     = options
+    this.paused      =
+    this.sliding     =
+    this.interval    =
+    this.$active     =
+    this.$items      = null
+
+    this.options.pause == 'hover' && this.$element
+      .on('mouseenter', $.proxy(this.pause, this))
+      .on('mouseleave', $.proxy(this.cycle, this))
+  }
+
+  Carousel.DEFAULTS = {
+    interval: 5000
+  , pause: 'hover'
+  , wrap: true
+  }
+
+  Carousel.prototype.cycle =  function (e) {
+    e || (this.paused = false)
+
+    this.interval && clearInterval(this.interval)
+
+    this.options.interval
+      && !this.paused
+      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+    return this
+  }
+
+  Carousel.prototype.getActiveIndex = function () {
+    this.$active = this.$element.find('.item.active')
+    this.$items  = this.$active.parent().children()
+
+    return this.$items.index(this.$active)
+  }
+
+  Carousel.prototype.to = function (pos) {
+    var that        = this
+    var activeIndex = this.getActiveIndex()
+
+    if (pos > (this.$items.length - 1) || pos < 0) return
+
+    if (this.sliding)       return this.$element.one('slid', function () { that.to(pos) })
+    if (activeIndex == pos) return this.pause().cycle()
+
+    return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
+  }
+
+  Carousel.prototype.pause = function (e) {
+    e || (this.paused = true)
+
+    if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+      this.$element.trigger($.support.transition.end)
+      this.cycle(true)
+    }
+
+    this.interval = clearInterval(this.interval)
+
+    return this
+  }
+
+  Carousel.prototype.next = function () {
+    if (this.sliding) return
+    return this.slide('next')
+  }
+
+  Carousel.prototype.prev = function () {
+    if (this.sliding) return
+    return this.slide('prev')
+  }
+
+  Carousel.prototype.slide = function (type, next) {
+    var $active   = this.$element.find('.item.active')
+    var $next     = next || $active[type]()
+    var isCycling = this.interval
+    var direction = type == 'next' ? 'left' : 'right'
+    var fallback  = type == 'next' ? 'first' : 'last'
+    var that      = this
+
+    if (!$next.length) {
+      if (!this.options.wrap) return
+      $next = this.$element.find('.item')[fallback]()
+    }
+
+    this.sliding = true
+
+    isCycling && this.pause()
+
+    var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction })
+
+    if ($next.hasClass('active')) return
+
+    if (this.$indicators.length) {
+      this.$indicators.find('.active').removeClass('active')
+      this.$element.one('slid', function () {
+        var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
+        $nextIndicator && $nextIndicator.addClass('active')
+      })
+    }
+
+    if ($.support.transition && this.$element.hasClass('slide')) {
+      this.$element.trigger(e)
+      if (e.isDefaultPrevented()) return
+      $next.addClass(type)
+      $next[0].offsetWidth // force reflow
+      $active.addClass(direction)
+      $next.addClass(direction)
+      $active
+        .one($.support.transition.end, function () {
+          $next.removeClass([type, direction].join(' ')).addClass('active')
+          $active.removeClass(['active', direction].join(' '))
+          that.sliding = false
+          setTimeout(function () { that.$element.trigger('slid') }, 0)
+        })
+        .emulateTransitionEnd(600)
+    } else {
+      this.$element.trigger(e)
+      if (e.isDefaultPrevented()) return
+      $active.removeClass('active')
+      $next.addClass('active')
+      this.sliding = false
+      this.$element.trigger('slid')
+    }
+
+    isCycling && this.cycle()
+
+    return this
+  }
+
+
+  // CAROUSEL PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.carousel
+
+  $.fn.carousel = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.carousel')
+      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+      var action  = typeof option == 'string' ? option : options.slide
+
+      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+      if (typeof option == 'number') data.to(option)
+      else if (action) data[action]()
+      else if (options.interval) data.pause().cycle()
+    })
+  }
+
+  $.fn.carousel.Constructor = Carousel
+
+
+  // CAROUSEL NO CONFLICT
+  // ====================
+
+  $.fn.carousel.noConflict = function () {
+    $.fn.carousel = old
+    return this
+  }
+
+
+  // CAROUSEL DATA-API
+  // =================
+
+  $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
+    var $this   = $(this), href
+    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+    var options = $.extend({}, $target.data(), $this.data())
+    var slideIndex = $this.attr('data-slide-to')
+    if (slideIndex) options.interval = false
+
+    $target.carousel(options)
+
+    if (slideIndex = $this.attr('data-slide-to')) {
+      $target.data('bs.carousel').to(slideIndex)
+    }
+
+    e.preventDefault()
+  })
+
+  $(window).on('load', function () {
+    $('[data-ride="carousel"]').each(function () {
+      var $carousel = $(this)
+      $carousel.carousel($carousel.data())
+    })
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.0.2
+ * http://getbootstrap.com/javascript/#collapse
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // COLLAPSE PUBLIC CLASS DEFINITION
+  // ================================
+
+  var Collapse = function (element, options) {
+    this.$element      = $(element)
+    this.options       = $.extend({}, Collapse.DEFAULTS, options)
+    this.transitioning = null
+
+    if (this.options.parent) this.$parent = $(this.options.parent)
+    if (this.options.toggle) this.toggle()
+  }
+
+  Collapse.DEFAULTS = {
+    toggle: true
+  }
+
+  Collapse.prototype.dimension = function () {
+    var hasWidth = this.$element.hasClass('width')
+    return hasWidth ? 'width' : 'height'
+  }
+
+  Collapse.prototype.show = function () {
+    if (this.transitioning || this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('show.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var actives = this.$parent && this.$parent.find('> .panel > .in')
+
+    if (actives && actives.length) {
+      var hasData = actives.data('bs.collapse')
+      if (hasData && hasData.transitioning) return
+      actives.collapse('hide')
+      hasData || actives.data('bs.collapse', null)
+    }
+
+    var dimension = this.dimension()
+
+    this.$element
+      .removeClass('collapse')
+      .addClass('collapsing')
+      [dimension](0)
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.$element
+        .removeClass('collapsing')
+        .addClass('in')
+        [dimension]('auto')
+      this.transitioning = 0
+      this.$element.trigger('shown.bs.collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+    this.$element
+      .one($.support.transition.end, $.proxy(complete, this))
+      .emulateTransitionEnd(350)
+      [dimension](this.$element[0][scrollSize])
+  }
+
+  Collapse.prototype.hide = function () {
+    if (this.transitioning || !this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('hide.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var dimension = this.dimension()
+
+    this.$element
+      [dimension](this.$element[dimension]())
+      [0].offsetHeight
+
+    this.$element
+      .addClass('collapsing')
+      .removeClass('collapse')
+      .removeClass('in')
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.transitioning = 0
+      this.$element
+        .trigger('hidden.bs.collapse')
+        .removeClass('collapsing')
+        .addClass('collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    this.$element
+      [dimension](0)
+      .one($.support.transition.end, $.proxy(complete, this))
+      .emulateTransitionEnd(350)
+  }
+
+  Collapse.prototype.toggle = function () {
+    this[this.$element.hasClass('in') ? 'hide' : 'show']()
+  }
+
+
+  // COLLAPSE PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.collapse
+
+  $.fn.collapse = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.collapse')
+      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.collapse.Constructor = Collapse
+
+
+  // COLLAPSE NO CONFLICT
+  // ====================
+
+  $.fn.collapse.noConflict = function () {
+    $.fn.collapse = old
+    return this
+  }
+
+
+  // COLLAPSE DATA-API
+  // =================
+
+  $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) {
+    var $this   = $(this), href
+    var target  = $this.attr('data-target')
+        || e.preventDefault()
+        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+    var $target = $(target)
+    var data    = $target.data('bs.collapse')
+    var option  = data ? 'toggle' : $this.data()
+    var parent  = $this.attr('data-parent')
+    var $parent = parent && $(parent)
+
+    if (!data || !data.transitioning) {
+      if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed')
+      $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+    }
+
+    $target.collapse(option)
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.0.2
+ * http://getbootstrap.com/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // DROPDOWN CLASS DEFINITION
+  // =========================
+
+  var backdrop = '.dropdown-backdrop'
+  var toggle   = '[data-toggle=dropdown]'
+  var Dropdown = function (element) {
+    var $el = $(element).on('click.bs.dropdown', this.toggle)
+  }
+
+  Dropdown.prototype.toggle = function (e) {
+    var $this = $(this)
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    clearMenus()
+
+    if (!isActive) {
+      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+        // if mobile we we use a backdrop because click events don't delegate
+        $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
+      }
+
+      $parent.trigger(e = $.Event('show.bs.dropdown'))
+
+      if (e.isDefaultPrevented()) return
+
+      $parent
+        .toggleClass('open')
+        .trigger('shown.bs.dropdown')
+
+      $this.focus()
+    }
+
+    return false
+  }
+
+  Dropdown.prototype.keydown = function (e) {
+    if (!/(38|40|27)/.test(e.keyCode)) return
+
+    var $this = $(this)
+
+    e.preventDefault()
+    e.stopPropagation()
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    if (!isActive || (isActive && e.keyCode == 27)) {
+      if (e.which == 27) $parent.find(toggle).focus()
+      return $this.click()
+    }
+
+    var $items = $('[role=menu] li:not(.divider):visible a', $parent)
+
+    if (!$items.length) return
+
+    var index = $items.index($items.filter(':focus'))
+
+    if (e.keyCode == 38 && index > 0)                 index--                        // up
+    if (e.keyCode == 40 && index < $items.length - 1) index++                        // down
+    if (!~index)                                      index=0
+
+    $items.eq(index).focus()
+  }
+
+  function clearMenus() {
+    $(backdrop).remove()
+    $(toggle).each(function (e) {
+      var $parent = getParent($(this))
+      if (!$parent.hasClass('open')) return
+      $parent.trigger(e = $.Event('hide.bs.dropdown'))
+      if (e.isDefaultPrevented()) return
+      $parent.removeClass('open').trigger('hidden.bs.dropdown')
+    })
+  }
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    var $parent = selector && $(selector)
+
+    return $parent && $parent.length ? $parent : $this.parent()
+  }
+
+
+  // DROPDOWN PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('dropdown')
+
+      if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.dropdown.Constructor = Dropdown
+
+
+  // DROPDOWN NO CONFLICT
+  // ====================
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  // APPLY TO STANDARD DROPDOWN ELEMENTS
+  // ===================================
+
+  $(document)
+    .on('click.bs.dropdown.data-api', clearMenus)
+    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('click.bs.dropdown.data-api'  , toggle, Dropdown.prototype.toggle)
+    .on('keydown.bs.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.0.2
+ * http://getbootstrap.com/javascript/#modals
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // MODAL CLASS DEFINITION
+  // ======================
+
+  var Modal = function (element, options) {
+    this.options   = options
+    this.$element  = $(element)
+    this.$backdrop =
+    this.isShown   = null
+
+    if (this.options.remote) this.$element.load(this.options.remote)
+  }
+
+  Modal.DEFAULTS = {
+      backdrop: true
+    , keyboard: true
+    , show: true
+  }
+
+  Modal.prototype.toggle = function (_relatedTarget) {
+    return this[!this.isShown ? 'show' : 'hide'](_relatedTarget)
+  }
+
+  Modal.prototype.show = function (_relatedTarget) {
+    var that = this
+    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+    this.$element.trigger(e)
+
+    if (this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = true
+
+    this.escape()
+
+    this.$element.on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+    this.backdrop(function () {
+      var transition = $.support.transition && that.$element.hasClass('fade')
+
+      if (!that.$element.parent().length) {
+        that.$element.appendTo(document.body) // don't move modals dom position
+      }
+
+      that.$element.show()
+
+      if (transition) {
+        that.$element[0].offsetWidth // force reflow
+      }
+
+      that.$element
+        .addClass('in')
+        .attr('aria-hidden', false)
+
+      that.enforceFocus()
+
+      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+      transition ?
+        that.$element.find('.modal-dialog') // wait for modal to slide in
+          .one($.support.transition.end, function () {
+            that.$element.focus().trigger(e)
+          })
+          .emulateTransitionEnd(300) :
+        that.$element.focus().trigger(e)
+    })
+  }
+
+  Modal.prototype.hide = function (e) {
+    if (e) e.preventDefault()
+
+    e = $.Event('hide.bs.modal')
+
+    this.$element.trigger(e)
+
+    if (!this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = false
+
+    this.escape()
+
+    $(document).off('focusin.bs.modal')
+
+    this.$element
+      .removeClass('in')
+      .attr('aria-hidden', true)
+      .off('click.dismiss.modal')
+
+    $.support.transition && this.$element.hasClass('fade') ?
+      this.$element
+        .one($.support.transition.end, $.proxy(this.hideModal, this))
+        .emulateTransitionEnd(300) :
+      this.hideModal()
+  }
+
+  Modal.prototype.enforceFocus = function () {
+    $(document)
+      .off('focusin.bs.modal') // guard against infinite focus loop
+      .on('focusin.bs.modal', $.proxy(function (e) {
+        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
+          this.$element.focus()
+        }
+      }, this))
+  }
+
+  Modal.prototype.escape = function () {
+    if (this.isShown && this.options.keyboard) {
+      this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) {
+        e.which == 27 && this.hide()
+      }, this))
+    } else if (!this.isShown) {
+      this.$element.off('keyup.dismiss.bs.modal')
+    }
+  }
+
+  Modal.prototype.hideModal = function () {
+    var that = this
+    this.$element.hide()
+    this.backdrop(function () {
+      that.removeBackdrop()
+      that.$element.trigger('hidden.bs.modal')
+    })
+  }
+
+  Modal.prototype.removeBackdrop = function () {
+    this.$backdrop && this.$backdrop.remove()
+    this.$backdrop = null
+  }
+
+  Modal.prototype.backdrop = function (callback) {
+    var that    = this
+    var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+    if (this.isShown && this.options.backdrop) {
+      var doAnimate = $.support.transition && animate
+
+      this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+        .appendTo(document.body)
+
+      this.$element.on('click.dismiss.modal', $.proxy(function (e) {
+        if (e.target !== e.currentTarget) return
+        this.options.backdrop == 'static'
+          ? this.$element[0].focus.call(this.$element[0])
+          : this.hide.call(this)
+      }, this))
+
+      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+      this.$backdrop.addClass('in')
+
+      if (!callback) return
+
+      doAnimate ?
+        this.$backdrop
+          .one($.support.transition.end, callback)
+          .emulateTransitionEnd(150) :
+        callback()
+
+    } else if (!this.isShown && this.$backdrop) {
+      this.$backdrop.removeClass('in')
+
+      $.support.transition && this.$element.hasClass('fade')?
+        this.$backdrop
+          .one($.support.transition.end, callback)
+          .emulateTransitionEnd(150) :
+        callback()
+
+    } else if (callback) {
+      callback()
+    }
+  }
+
+
+  // MODAL PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.modal
+
+  $.fn.modal = function (option, _relatedTarget) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.modal')
+      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+      if (typeof option == 'string') data[option](_relatedTarget)
+      else if (options.show) data.show(_relatedTarget)
+    })
+  }
+
+  $.fn.modal.Constructor = Modal
+
+
+  // MODAL NO CONFLICT
+  // =================
+
+  $.fn.modal.noConflict = function () {
+    $.fn.modal = old
+    return this
+  }
+
+
+  // MODAL DATA-API
+  // ==============
+
+  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+    var $this   = $(this)
+    var href    = $this.attr('href')
+    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+    var option  = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+    e.preventDefault()
+
+    $target
+      .modal(option, this)
+      .one('hide', function () {
+        $this.is(':visible') && $this.focus()
+      })
+  })
+
+  $(document)
+    .on('show.bs.modal',  '.modal', function () { $(document.body).addClass('modal-open') })
+    .on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.0.2
+ * http://getbootstrap.com/javascript/#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // TOOLTIP PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Tooltip = function (element, options) {
+    this.type       =
+    this.options    =
+    this.enabled    =
+    this.timeout    =
+    this.hoverState =
+    this.$element   = null
+
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.DEFAULTS = {
+    animation: true
+  , placement: 'top'
+  , selector: false
+  , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+  , trigger: 'hover focus'
+  , title: ''
+  , delay: 0
+  , html: false
+  , container: false
+  }
+
+  Tooltip.prototype.init = function (type, element, options) {
+    this.enabled  = true
+    this.type     = type
+    this.$element = $(element)
+    this.options  = this.getOptions(options)
+
+    var triggers = this.options.trigger.split(' ')
+
+    for (var i = triggers.length; i--;) {
+      var trigger = triggers[i]
+
+      if (trigger == 'click') {
+        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+      } else if (trigger != 'manual') {
+        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focus'
+        var eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
+
+        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+      }
+    }
+
+    this.options.selector ?
+      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+      this.fixTitle()
+  }
+
+  Tooltip.prototype.getDefaults = function () {
+    return Tooltip.DEFAULTS
+  }
+
+  Tooltip.prototype.getOptions = function (options) {
+    options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+    if (options.delay && typeof options.delay == 'number') {
+      options.delay = {
+        show: options.delay
+      , hide: options.delay
+      }
+    }
+
+    return options
+  }
+
+  Tooltip.prototype.getDelegateOptions = function () {
+    var options  = {}
+    var defaults = this.getDefaults()
+
+    this._options && $.each(this._options, function (key, value) {
+      if (defaults[key] != value) options[key] = value
+    })
+
+    return options
+  }
+
+  Tooltip.prototype.enter = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'in'
+
+    if (!self.options.delay || !self.options.delay.show) return self.show()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'in') self.show()
+    }, self.options.delay.show)
+  }
+
+  Tooltip.prototype.leave = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'out'
+
+    if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'out') self.hide()
+    }, self.options.delay.hide)
+  }
+
+  Tooltip.prototype.show = function () {
+    var e = $.Event('show.bs.'+ this.type)
+
+    if (this.hasContent() && this.enabled) {
+      this.$element.trigger(e)
+
+      if (e.isDefaultPrevented()) return
+
+      var $tip = this.tip()
+
+      this.setContent()
+
+      if (this.options.animation) $tip.addClass('fade')
+
+      var placement = typeof this.options.placement == 'function' ?
+        this.options.placement.call(this, $tip[0], this.$element[0]) :
+        this.options.placement
+
+      var autoToken = /\s?auto?\s?/i
+      var autoPlace = autoToken.test(placement)
+      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+      $tip
+        .detach()
+        .css({ top: 0, left: 0, display: 'block' })
+        .addClass(placement)
+
+      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+      var pos          = this.getPosition()
+      var actualWidth  = $tip[0].offsetWidth
+      var actualHeight = $tip[0].offsetHeight
+
+      if (autoPlace) {
+        var $parent = this.$element.parent()
+
+        var orgPlacement = placement
+        var docScroll    = document.documentElement.scrollTop || document.body.scrollTop
+        var parentWidth  = this.options.container == 'body' ? window.innerWidth  : $parent.outerWidth()
+        var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight()
+        var parentLeft   = this.options.container == 'body' ? 0 : $parent.offset().left
+
+        placement = placement == 'bottom' && pos.top   + pos.height  + actualHeight - docScroll > parentHeight  ? 'top'    :
+                    placement == 'top'    && pos.top   - docScroll   - actualHeight < 0                         ? 'bottom' :
+                    placement == 'right'  && pos.right + actualWidth > parentWidth                              ? 'left'   :
+                    placement == 'left'   && pos.left  - actualWidth < parentLeft                               ? 'right'  :
+                    placement
+
+        $tip
+          .removeClass(orgPlacement)
+          .addClass(placement)
+      }
+
+      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+      this.applyPlacement(calculatedOffset, placement)
+      this.$element.trigger('shown.bs.' + this.type)
+    }
+  }
+
+  Tooltip.prototype.applyPlacement = function(offset, placement) {
+    var replace
+    var $tip   = this.tip()
+    var width  = $tip[0].offsetWidth
+    var height = $tip[0].offsetHeight
+
+    // manually read margins because getBoundingClientRect includes difference
+    var marginTop = parseInt($tip.css('margin-top'), 10)
+    var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+    // we must check for NaN for ie 8/9
+    if (isNaN(marginTop))  marginTop  = 0
+    if (isNaN(marginLeft)) marginLeft = 0
+
+    offset.top  = offset.top  + marginTop
+    offset.left = offset.left + marginLeft
+
+    $tip
+      .offset(offset)
+      .addClass('in')
+
+    // check to see if placing tip in new offset caused the tip to resize itself
+    var actualWidth  = $tip[0].offsetWidth
+    var actualHeight = $tip[0].offsetHeight
+
+    if (placement == 'top' && actualHeight != height) {
+      replace = true
+      offset.top = offset.top + height - actualHeight
+    }
+
+    if (/bottom|top/.test(placement)) {
+      var delta = 0
+
+      if (offset.left < 0) {
+        delta       = offset.left * -2
+        offset.left = 0
+
+        $tip.offset(offset)
+
+        actualWidth  = $tip[0].offsetWidth
+        actualHeight = $tip[0].offsetHeight
+      }
+
+      this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
+    } else {
+      this.replaceArrow(actualHeight - height, actualHeight, 'top')
+    }
+
+    if (replace) $tip.offset(offset)
+  }
+
+  Tooltip.prototype.replaceArrow = function(delta, dimension, position) {
+    this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
+  }
+
+  Tooltip.prototype.setContent = function () {
+    var $tip  = this.tip()
+    var title = this.getTitle()
+
+    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+    $tip.removeClass('fade in top bottom left right')
+  }
+
+  Tooltip.prototype.hide = function () {
+    var that = this
+    var $tip = this.tip()
+    var e    = $.Event('hide.bs.' + this.type)
+
+    function complete() {
+      if (that.hoverState != 'in') $tip.detach()
+    }
+
+    this.$element.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    $tip.removeClass('in')
+
+    $.support.transition && this.$tip.hasClass('fade') ?
+      $tip
+        .one($.support.transition.end, complete)
+        .emulateTransitionEnd(150) :
+      complete()
+
+    this.$element.trigger('hidden.bs.' + this.type)
+
+    return this
+  }
+
+  Tooltip.prototype.fixTitle = function () {
+    var $e = this.$element
+    if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+    }
+  }
+
+  Tooltip.prototype.hasContent = function () {
+    return this.getTitle()
+  }
+
+  Tooltip.prototype.getPosition = function () {
+    var el = this.$element[0]
+    return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
+      width: el.offsetWidth
+    , height: el.offsetHeight
+    }, this.$element.offset())
+  }
+
+  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2  } :
+           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2  } :
+           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width   }
+  }
+
+  Tooltip.prototype.getTitle = function () {
+    var title
+    var $e = this.$element
+    var o  = this.options
+
+    title = $e.attr('data-original-title')
+      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+    return title
+  }
+
+  Tooltip.prototype.tip = function () {
+    return this.$tip = this.$tip || $(this.options.template)
+  }
+
+  Tooltip.prototype.arrow = function () {
+    return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')
+  }
+
+  Tooltip.prototype.validate = function () {
+    if (!this.$element[0].parentNode) {
+      this.hide()
+      this.$element = null
+      this.options  = null
+    }
+  }
+
+  Tooltip.prototype.enable = function () {
+    this.enabled = true
+  }
+
+  Tooltip.prototype.disable = function () {
+    this.enabled = false
+  }
+
+  Tooltip.prototype.toggleEnabled = function () {
+    this.enabled = !this.enabled
+  }
+
+  Tooltip.prototype.toggle = function (e) {
+    var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this
+    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+  }
+
+  Tooltip.prototype.destroy = function () {
+    this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
+  }
+
+
+  // TOOLTIP PLUGIN DEFINITION
+  // =========================
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.tooltip')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tooltip.Constructor = Tooltip
+
+
+  // TOOLTIP NO CONFLICT
+  // ===================
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.0.2
+ * http://getbootstrap.com/javascript/#popovers
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // POPOVER PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Popover = function (element, options) {
+    this.init('popover', element, options)
+  }
+
+  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+  Popover.DEFAULTS = $.extend({} , $.fn.tooltip.Constructor.DEFAULTS, {
+    placement: 'right'
+  , trigger: 'click'
+  , content: ''
+  , template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+  })
+
+
+  // NOTE: POPOVER EXTENDS tooltip.js
+  // ================================
+
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+  Popover.prototype.constructor = Popover
+
+  Popover.prototype.getDefaults = function () {
+    return Popover.DEFAULTS
+  }
+
+  Popover.prototype.setContent = function () {
+    var $tip    = this.tip()
+    var title   = this.getTitle()
+    var content = this.getContent()
+
+    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+    $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
+
+    $tip.removeClass('fade top bottom left right in')
+
+    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+    // this manually by checking the contents.
+    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+  }
+
+  Popover.prototype.hasContent = function () {
+    return this.getTitle() || this.getContent()
+  }
+
+  Popover.prototype.getContent = function () {
+    var $e = this.$element
+    var o  = this.options
+
+    return $e.attr('data-content')
+      || (typeof o.content == 'function' ?
+            o.content.call($e[0]) :
+            o.content)
+  }
+
+  Popover.prototype.arrow = function () {
+    return this.$arrow = this.$arrow || this.tip().find('.arrow')
+  }
+
+  Popover.prototype.tip = function () {
+    if (!this.$tip) this.$tip = $(this.options.template)
+    return this.$tip
+  }
+
+
+  // POPOVER PLUGIN DEFINITION
+  // =========================
+
+  var old = $.fn.popover
+
+  $.fn.popover = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.popover')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.popover.Constructor = Popover
+
+
+  // POPOVER NO CONFLICT
+  // ===================
+
+  $.fn.popover.noConflict = function () {
+    $.fn.popover = old
+    return this
+  }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.0.2
+ * http://getbootstrap.com/javascript/#scrollspy
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // SCROLLSPY CLASS DEFINITION
+  // ==========================
+
+  function ScrollSpy(element, options) {
+    var href
+    var process  = $.proxy(this.process, this)
+
+    this.$element       = $(element).is('body') ? $(window) : $(element)
+    this.$body          = $('body')
+    this.$scrollElement = this.$element.on('scroll.bs.scroll-spy.data-api', process)
+    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
+    this.selector       = (this.options.target
+      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+      || '') + ' .nav li > a'
+    this.offsets        = $([])
+    this.targets        = $([])
+    this.activeTarget   = null
+
+    this.refresh()
+    this.process()
+  }
+
+  ScrollSpy.DEFAULTS = {
+    offset: 10
+  }
+
+  ScrollSpy.prototype.refresh = function () {
+    var offsetMethod = this.$element[0] == window ? 'offset' : 'position'
+
+    this.offsets = $([])
+    this.targets = $([])
+
+    var self     = this
+    var $targets = this.$body
+      .find(this.selector)
+      .map(function () {
+        var $el   = $(this)
+        var href  = $el.data('target') || $el.attr('href')
+        var $href = /^#\w/.test(href) && $(href)
+
+        return ($href
+          && $href.length
+          && [[ $href[offsetMethod]().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]]) || null
+      })
+      .sort(function (a, b) { return a[0] - b[0] })
+      .each(function () {
+        self.offsets.push(this[0])
+        self.targets.push(this[1])
+      })
+  }
+
+  ScrollSpy.prototype.process = function () {
+    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
+    var scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+    var maxScroll    = scrollHeight - this.$scrollElement.height()
+    var offsets      = this.offsets
+    var targets      = this.targets
+    var activeTarget = this.activeTarget
+    var i
+
+    if (scrollTop >= maxScroll) {
+      return activeTarget != (i = targets.last()[0]) && this.activate(i)
+    }
+
+    for (i = offsets.length; i--;) {
+      activeTarget != targets[i]
+        && scrollTop >= offsets[i]
+        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+        && this.activate( targets[i] )
+    }
+  }
+
+  ScrollSpy.prototype.activate = function (target) {
+    this.activeTarget = target
+
+    $(this.selector)
+      .parents('.active')
+      .removeClass('active')
+
+    var selector = this.selector
+      + '[data-target="' + target + '"],'
+      + this.selector + '[href="' + target + '"]'
+
+    var active = $(selector)
+      .parents('li')
+      .addClass('active')
+
+    if (active.parent('.dropdown-menu').length)  {
+      active = active
+        .closest('li.dropdown')
+        .addClass('active')
+    }
+
+    active.trigger('activate')
+  }
+
+
+  // SCROLLSPY PLUGIN DEFINITION
+  // ===========================
+
+  var old = $.fn.scrollspy
+
+  $.fn.scrollspy = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.scrollspy')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.scrollspy.Constructor = ScrollSpy
+
+
+  // SCROLLSPY NO CONFLICT
+  // =====================
+
+  $.fn.scrollspy.noConflict = function () {
+    $.fn.scrollspy = old
+    return this
+  }
+
+
+  // SCROLLSPY DATA-API
+  // ==================
+
+  $(window).on('load', function () {
+    $('[data-spy="scroll"]').each(function () {
+      var $spy = $(this)
+      $spy.scrollspy($spy.data())
+    })
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.0.2
+ * http://getbootstrap.com/javascript/#tabs
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // TAB CLASS DEFINITION
+  // ====================
+
+  var Tab = function (element) {
+    this.element = $(element)
+  }
+
+  Tab.prototype.show = function () {
+    var $this    = this.element
+    var $ul      = $this.closest('ul:not(.dropdown-menu)')
+    var selector = $this.data('target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    if ($this.parent('li').hasClass('active')) return
+
+    var previous = $ul.find('.active:last a')[0]
+    var e        = $.Event('show.bs.tab', {
+      relatedTarget: previous
+    })
+
+    $this.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    var $target = $(selector)
+
+    this.activate($this.parent('li'), $ul)
+    this.activate($target, $target.parent(), function () {
+      $this.trigger({
+        type: 'shown.bs.tab'
+      , relatedTarget: previous
+      })
+    })
+  }
+
+  Tab.prototype.activate = function (element, container, callback) {
+    var $active    = container.find('> .active')
+    var transition = callback
+      && $.support.transition
+      && $active.hasClass('fade')
+
+    function next() {
+      $active
+        .removeClass('active')
+        .find('> .dropdown-menu > .active')
+        .removeClass('active')
+
+      element.addClass('active')
+
+      if (transition) {
+        element[0].offsetWidth // reflow for transition
+        element.addClass('in')
+      } else {
+        element.removeClass('fade')
+      }
+
+      if (element.parent('.dropdown-menu')) {
+        element.closest('li.dropdown').addClass('active')
+      }
+
+      callback && callback()
+    }
+
+    transition ?
+      $active
+        .one($.support.transition.end, next)
+        .emulateTransitionEnd(150) :
+      next()
+
+    $active.removeClass('in')
+  }
+
+
+  // TAB PLUGIN DEFINITION
+  // =====================
+
+  var old = $.fn.tab
+
+  $.fn.tab = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.tab')
+
+      if (!data) $this.data('bs.tab', (data = new Tab(this)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tab.Constructor = Tab
+
+
+  // TAB NO CONFLICT
+  // ===============
+
+  $.fn.tab.noConflict = function () {
+    $.fn.tab = old
+    return this
+  }
+
+
+  // TAB DATA-API
+  // ============
+
+  $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+    e.preventDefault()
+    $(this).tab('show')
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.0.2
+ * http://getbootstrap.com/javascript/#affix
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+  // AFFIX CLASS DEFINITION
+  // ======================
+
+  var Affix = function (element, options) {
+    this.options = $.extend({}, Affix.DEFAULTS, options)
+    this.$window = $(window)
+      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))
+
+    this.$element = $(element)
+    this.affixed  =
+    this.unpin    = null
+
+    this.checkPosition()
+  }
+
+  Affix.RESET = 'affix affix-top affix-bottom'
+
+  Affix.DEFAULTS = {
+    offset: 0
+  }
+
+  Affix.prototype.checkPositionWithEventLoop = function () {
+    setTimeout($.proxy(this.checkPosition, this), 1)
+  }
+
+  Affix.prototype.checkPosition = function () {
+    if (!this.$element.is(':visible')) return
+
+    var scrollHeight = $(document).height()
+    var scrollTop    = this.$window.scrollTop()
+    var position     = this.$element.offset()
+    var offset       = this.options.offset
+    var offsetTop    = offset.top
+    var offsetBottom = offset.bottom
+
+    if (typeof offset != 'object')         offsetBottom = offsetTop = offset
+    if (typeof offsetTop == 'function')    offsetTop    = offset.top()
+    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+    var affix = this.unpin   != null && (scrollTop + this.unpin <= position.top) ? false :
+                offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' :
+                offsetTop    != null && (scrollTop <= offsetTop) ? 'top' : false
+
+    if (this.affixed === affix) return
+    if (this.unpin) this.$element.css('top', '')
+
+    this.affixed = affix
+    this.unpin   = affix == 'bottom' ? position.top - scrollTop : null
+
+    this.$element.removeClass(Affix.RESET).addClass('affix' + (affix ? '-' + affix : ''))
+
+    if (affix == 'bottom') {
+      this.$element.offset({ top: document.body.offsetHeight - offsetBottom - this.$element.height() })
+    }
+  }
+
+
+  // AFFIX PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.affix
+
+  $.fn.affix = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.affix')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.affix.Constructor = Affix
+
+
+  // AFFIX NO CONFLICT
+  // =================
+
+  $.fn.affix.noConflict = function () {
+    $.fn.affix = old
+    return this
+  }
+
+
+  // AFFIX DATA-API
+  // ==============
+
+  $(window).on('load', function () {
+    $('[data-spy="affix"]').each(function () {
+      var $spy = $(this)
+      var data = $spy.data()
+
+      data.offset = data.offset || {}
+
+      if (data.offsetBottom) data.offset.bottom = data.offsetBottom
+      if (data.offsetTop)    data.offset.top    = data.offsetTop
+
+      $spy.affix(data)
+    })
+  })
+
+}(jQuery);
diff --git a/src/web/js/bootstrap.min.js b/src/web/js/bootstrap.min.js
new file mode 100644
index 0000000..0e668e8
--- /dev/null
+++ b/src/web/js/bootstrap.min.js
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap v3.0.2 by @fat and @mdo
+ * Copyright 2013 Twitter, Inc.
+ * Licensed under http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */
+
+if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b),f.trigger(d=a.Event("show.bs.dropdown")),d.isDefaultPrevented())return;f.toggleClass("open").trigger("shown.bs.dropdown"),e.focus()}return!1}},f.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var f=c(d),g=f.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&f.find(e).focus(),d.click();var h=a("[role=menu] li:not(.divider):visible a",f);if(h.length){var i=h.index(h.filter(":focus"));38==b.keyCode&&i>0&&i--,40==b.keyCode&&i<h.length-1&&i++,~i||(i=0),h.eq(i).focus()}}}};var g=a.fn.dropdown;a.fn.dropdown=function(b){return this.each(function(){var c=a(this),d=c.data("dropdown");d||c.data("dropdown",d=new f(this)),"string"==typeof b&&d[b].call(c)})},a.fn.dropdown.Constructor=f,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=g,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",e,f.prototype.toggle).on("keydown.bs.dropdown.data-api",e+", [role=menu]",f.prototype.keydown)}(jQuery),+function(a){"use strict";var b=function(b,c){this.options=c,this.$element=a(b),this.$backdrop=this.isShown=null,this.options.remote&&this.$element.load(this.options.remote)};b.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},b.prototype.toggle=function(a){return this[this.isShown?"hide":"show"](a)},b.prototype.show=function(b){var c=this,d=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(d),this.isShown||d.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.$element.on("click.dismiss.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.backdrop(function(){var d=a.support.transition&&c.$element.hasClass("fade");c.$element.parent().length||c.$element.appendTo(document.body),c.$element.show(),d&&c.$element[0].offsetWidth,c.$element.addClass("in").attr("aria-hidden",!1),c.enforceFocus();var e=a.Event("shown.bs.modal",{relatedTarget:b});d?c.$element.find(".modal-dialog").one(a.support.transition.end,function(){c.$element.focus().trigger(e)}).emulateTransitionEnd(300):c.$element.focus().trigger(e)}))},b.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one(a.support.transition.end,a.proxy(this.hideModal,this)).emulateTransitionEnd(300):this.hideModal())},b.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.focus()},this))},b.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keyup.dismiss.bs.modal")},b.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.removeBackdrop(),a.$element.trigger("hidden.bs.modal")})},b.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},b.prototype.backdrop=function(b){var c=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var d=a.support.transition&&c;if(this.$backdrop=a('<div class="modal-backdrop '+c+'" />').appendTo(document.body),this.$element.on("click.dismiss.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),d&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;d?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()):b&&b()};var c=a.fn.modal;a.fn.modal=function(c,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},b.DEFAULTS,e.data(),"object"==typeof c&&c);f||e.data("bs.modal",f=new b(this,g)),"string"==typeof c?f[c](d):g.show&&f.show(d)})},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f,this).one("hide",function(){c.is(":visible")&&c.focus()})}),a(document).on("show.bs.modal",".modal",function(){a(document.body).addClass("modal-open")}).on("hidden.bs.modal",".modal",function(){a(document.body).removeClass("modal-open")})}(jQuery),+function(a){"use strict";var b=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};b.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},b.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focus",i="hover"==g?"mouseleave":"blur";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},b.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},b.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show),void 0):c.show()},b.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide),void 0):c.hide()},b.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){if(this.$element.trigger(b),b.isDefaultPrevented())return;var c=this.tip();this.setContent(),this.options.animation&&c.addClass("fade");var d="function"==typeof this.options.placement?this.options.placement.call(this,c[0],this.$element[0]):this.options.placement,e=/\s?auto?\s?/i,f=e.test(d);f&&(d=d.replace(e,"")||"top"),c.detach().css({top:0,left:0,display:"block"}).addClass(d),this.options.container?c.appendTo(this.options.container):c.insertAfter(this.$element);var g=this.getPosition(),h=c[0].offsetWidth,i=c[0].offsetHeight;if(f){var j=this.$element.parent(),k=d,l=document.documentElement.scrollTop||document.body.scrollTop,m="body"==this.options.container?window.innerWidth:j.outerWidth(),n="body"==this.options.container?window.innerHeight:j.outerHeight(),o="body"==this.options.container?0:j.offset().left;d="bottom"==d&&g.top+g.height+i-l>n?"top":"top"==d&&g.top-l-i<0?"bottom":"right"==d&&g.right+h>m?"left":"left"==d&&g.left-h<o?"right":d,c.removeClass(k).addClass(d)}var p=this.getCalculatedOffset(d,g,h,i);this.applyPlacement(p,d),this.$element.trigger("shown.bs."+this.type)}},b.prototype.applyPlacement=function(a,b){var c,d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),a.top=a.top+g,a.left=a.left+h,d.offset(a).addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;if("top"==b&&j!=f&&(c=!0,a.top=a.top+f-j),/bottom|top/.test(b)){var k=0;a.left<0&&(k=-2*a.left,a.left=0,d.offset(a),i=d[0].offsetWidth,j=d[0].offsetHeight),this.replaceArrow(k-e+i,i,"left")}else this.replaceArrow(j-f,j,"top");c&&d.offset(a)},b.prototype.replaceArrow=function(a,b,c){this.arrow().css(c,a?50*(1-a/b)+"%":"")},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},b.prototype.hide=function(){function b(){"in"!=c.hoverState&&d.detach()}var c=this,d=this.tip(),e=a.Event("hide.bs."+this.type);return this.$element.trigger(e),e.isDefaultPrevented()?void 0:(d.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d.one(a.support.transition.end,b).emulateTransitionEnd(150):b(),this.$element.trigger("hidden.bs."+this.type),this)},b.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},b.prototype.hasContent=function(){return this.getTitle()},b.prototype.getPosition=function(){var b=this.$element[0];return a.extend({},"function"==typeof b.getBoundingClientRect?b.getBoundingClientRect():{width:b.offsetWidth,height:b.offsetHeight},this.$element.offset())},b.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},b.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},b.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},b.prototype.validate=function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},b.prototype.enable=function(){this.enabled=!0},b.prototype.disable=function(){this.enabled=!1},b.prototype.toggleEnabled=function(){this.enabled=!this.enabled},b.prototype.toggle=function(b){var c=b?a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type):this;c.tip().hasClass("in")?c.leave(c):c.enter(c)},b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var c=a.fn.tooltip;a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof c&&c;e||d.data("bs.tooltip",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=c,this}}(jQuery),+function(a){"use strict";var b=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");b.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery);
\ No newline at end of file
diff --git a/src/web/js/bootstrap-datetimepicker.min.js b/src/web/js/bootstrap-datetimepicker.min.js
new file mode 100644
index 0000000..ba92ad9
--- /dev/null
+++ b/src/web/js/bootstrap-datetimepicker.min.js
@@ -0,0 +1,28 @@
+/**
+ * version 2.1.11
+ * @license
+ * =========================================================
+ * bootstrap-datetimepicker.js
+ * http://www.eyecon.ro/bootstrap-datepicker
+ * =========================================================
+ * Copyright 2012 Stefan Petre
+ *
+ * Contributions:
+ * - updated for Bootstrap v3 by Jonathan Peterson (@Eonasdan) and (almost)
+ *  completely rewritten to use Momentjs
+ * - based on tarruda's bootstrap-datepicker
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================
+ */
+(function(factory){if(typeof define==="function"&&define.amd){define(["jquery","moment"],factory)}else{if(!jQuery){throw"bootstrap-datetimepicker requires jQuery to be loaded first"}else if(!moment){throw"bootstrap-datetimepicker requires moment.js to be loaded first"}else{factory(jQuery,moment)}}})(function($,moment){if(typeof moment==="undefined"){alert("momentjs is requried");throw new Error("momentjs is requried")}var dpgId=0,pMoment=moment,DateTimePicker=function(element,options){var defaults={pickDate:true,pickTime:true,use24hours:false,startDate:new pMoment({y:1970}),endDate:(new pMoment).add(50,"y"),collapse:true,language:"en",defaultDate:"",disabledDates:[],enabledDates:false,icons:{},useStrict:false},icons={time:"glyphicon glyphicon-time",date:"glyphicon glyphicon-calendar",up:"glyphicon glyphicon-chevron-up",down:"glyphicon glyphicon-chevron-down"},picker=this,initFormat=function(){picker.format=picker.options.format;var longDateFormat=pMoment()._lang._longDateFormat;if(!picker.format){if(picker.isInput){picker.format=picker.element.data("format")}else{picker.format=picker.element.find("input").data("format")}if(!picker.format){picker.format=picker.options.pickDate?longDateFormat.L:"";if(picker.options.pickDate&&picker.options.pickTime){picker.format+=" "}picker.format+=picker.options.pickTime?longDateFormat.LT:""}}var ampmIndex=picker.format.toLowerCase().indexOf("a");if(picker.options.use24hours&&ampmIndex>=1){picker.format=picker.format.substring(0,ampmIndex)+picker.format.substring(ampmIndex+1,picker.format.length);picker.format=picker.format.trim();picker.format=picker.format.replace("h","H")}picker.use24hours=picker.format.toLowerCase().indexOf("a")<1},initComponent=function(){if(picker.element.hasClass("input-group")){if(picker.element.find(".datepickerbutton").size()==0){picker.component=picker.element.find("[class^='input-group-']")}else{picker.component=picker.element.find(".datepickerbutton")}}var icon=null;if(picker.component){icon=picker.component.find("span")}if(picker.options.pickTime){if(icon){icon.addClass(picker.options.icons.time)}}if(picker.options.pickDate){if(icon){icon.removeClass(picker.options.icons.time);icon.addClass(picker.options.icons.date)}}},initViewMode=function(){picker.widget=$(getTemplate(picker.options.pickDate,picker.options.pickTime,picker.options.collapse)).appendTo("body");picker.minViewMode=picker.options.minViewMode||picker.element.data("date-minviewmode")||0;if(typeof picker.minViewMode==="string"){switch(picker.minViewMode){case"months":picker.minViewMode=1;break;case"years":picker.minViewMode=2;break;default:picker.minViewMode=0;break}}picker.viewMode=picker.options.viewMode||picker.element.data("date-viewmode")||0;if(typeof picker.viewMode==="string"){switch(picker.viewMode){case"months":picker.viewMode=1;break;case"years":picker.viewMode=2;break;default:picker.viewMode=0;break}}},initDateBounds=function(){for(var i=0;i<picker.options.disabledDates.length;i++){var dDate=picker.options.disabledDates[i];dDate=pMoment(dDate);if(!dDate.isValid()){dDate=pMoment(picker.options.startDate).subtract(1,"day")}picker.options.disabledDates[i]=dDate.format("L")}for(var i=0;i<picker.options.enabledDates.length;i++){var dDate=picker.options.enabledDates[i];dDate=pMoment(dDate);if(!dDate.isValid()){dDate=pMoment(picker.options.startDate).subtract(1,"day")}picker.options.enabledDates[i]=dDate.format("L")}},init=function(){picker.options=$.extend({},defaults,options);picker.options.icons=$.extend({},icons,picker.options.icons);if(!(picker.options.pickTime||picker.options.pickDate)){throw new Error("Must choose at least one picker")}picker.id=dpgId++;pMoment.lang(picker.options.language);picker.date=pMoment();picker.element=$(element);picker.unset=false;picker.isInput=picker.element.is("input");picker.component=false;initFormat();initComponent();initViewMode();initDateBounds();picker.startViewMode=picker.viewMode;picker.setStartDate(picker.options.startDate||picker.element.data("date-startdate"));picker.setEndDate(picker.options.endDate||picker.element.data("date-enddate"));fillDow();fillMonths();fillHours();fillMinutes();update();showMode();attachDatePickerEvents();if(picker.options.defaultDate!==""){picker.setValue(picker.options.defaultDate)}},place=function(){var position="absolute";var offset=picker.component?picker.component.offset():picker.element.offset();var $window=$(window);picker.width=picker.component?picker.component.outerWidth():picker.element.outerWidth();offset.top=offset.top+picker.element.outerHeight();if(picker.options.width!==undefined){picker.widget.width(picker.options.width)}if(picker.options.orientation==="left"){picker.widget.addClass("left-oriented");offset.left=offset.left-picker.widget.width()+20}if(isInFixed()){position="fixed";offset.top-=$window.scrollTop();offset.left-=$window.scrollLeft()}if($window.width()<offset.left+picker.widget.outerWidth()){offset.right=$window.width()-offset.left-picker.width;offset.left="auto";picker.widget.addClass("pull-right")}else{offset.right="auto";picker.widget.removeClass("pull-right")}picker.widget.css({position:position,top:offset.top,left:offset.left,right:offset.right})},notifyChange=function(oldDate){picker.element.trigger({type:"change.dp",date:picker.getDate(),oldDate:oldDate})},notifyError=function(date){picker.element.trigger({type:"error.dp",date:date})},update=function(newDate){pMoment.lang(picker.options.language);var dateStr=newDate;if(!dateStr){if(picker.isInput){dateStr=picker.element.val()}else{dateStr=picker.element.find("input").val()}if(dateStr){picker.date=pMoment(dateStr,picker.format,picker.options.useStrict)}if(!picker.date){picker.date=pMoment()}}picker.viewDate=pMoment(picker.date).startOf("month");fillDate();fillTime()},fillDow=function(){pMoment.lang(picker.options.language);var html=$("<tr>"),weekdaysMin=pMoment.weekdaysMin(),i;if(pMoment()._lang._week.dow==0){for(i=0;i<7;i++){html.append('<th class="dow">'+weekdaysMin[i]+"</th>")}}else{for(i=1;i<8;i++){if(i==7){html.append('<th class="dow">'+weekdaysMin[0]+"</th>")}else{html.append('<th class="dow">'+weekdaysMin[i]+"</th>")}}}picker.widget.find(".datepicker-days thead").append(html)},fillMonths=function(){pMoment.lang(picker.options.language);var html="",i=0,monthsShort=pMoment.monthsShort();while(i<12){html+='<span class="month">'+monthsShort[i++]+"</span>"}picker.widget.find(".datepicker-months td").append(html)},fillDate=function(){pMoment.lang(picker.options.language);var year=picker.viewDate.year();var month=picker.viewDate.month();var startYear=picker.options.startDate.year();var startMonth=picker.options.startDate.month();var endYear=picker.options.endDate.year();var endMonth=picker.options.endDate.month();var html=[];var months=pMoment.months();picker.widget.find(".datepicker-days").find(".disabled").removeClass("disabled");picker.widget.find(".datepicker-months").find(".disabled").removeClass("disabled");picker.widget.find(".datepicker-years").find(".disabled").removeClass("disabled");picker.widget.find(".datepicker-days th:eq(1)").text(months[month]+" "+year);var prevMonth=pMoment(picker.viewDate).subtract("months",1);var days=prevMonth.daysInMonth();prevMonth.date(days).startOf("week");if(year==startYear&&month<=startMonth||year<startYear){picker.widget.find(".datepicker-days th:eq(0)").addClass("disabled")}if(year==endYear&&month>=endMonth||year>endYear){picker.widget.find(".datepicker-days th:eq(2)").addClass("disabled")}var nextMonth=pMoment(prevMonth).add(42,"d");var row;while(prevMonth.isBefore(nextMonth)){if(prevMonth.weekday()===pMoment().startOf("week").weekday()){row=$("<tr>");html.push(row)}var clsName="";if(prevMonth.year()<year||prevMonth.year()==year&&prevMonth.month()<month){clsName+=" old"}else if(prevMonth.year()>year||prevMonth.year()==year&&prevMonth.month()>month){clsName+=" new"}if(prevMonth.isSame(pMoment({y:picker.date.year(),M:picker.date.month(),d:picker.date.date()}))){clsName+=" active"}if(pMoment(prevMonth).add(1,"d")<=picker.options.startDate||prevMonth>picker.options.endDate||isInDisableDates(prevMonth)||!isInEnableDates(prevMonth)){clsName+=" disabled"}row.append('<td class="day'+clsName+'">'+prevMonth.date()+"</td>");prevMonth.add(1,"d")}picker.widget.find(".datepicker-days tbody").empty().append(html);var currentYear=pMoment().year(),months=picker.widget.find(".datepicker-months").find("th:eq(1)").text(year).end().find("span").removeClass("active");if(currentYear===year){months.eq(pMoment().month()).addClass("active")}if(currentYear-1<startYear){picker.widget.find(".datepicker-months th:eq(0)").addClass("disabled")}if(currentYear+1>endYear){picker.widget.find(".datepicker-months th:eq(2)").addClass("disabled")}for(var i=0;i<12;i++){if(year==startYear&&startMonth>i||year<startYear){$(months[i]).addClass("disabled")}else if(year==endYear&&endMonth<i||year>endYear){$(months[i]).addClass("disabled")}}html="";year=parseInt(year/10,10)*10;var yearCont=picker.widget.find(".datepicker-years").find("th:eq(1)").text(year+"-"+(year+9)).end().find("td");picker.widget.find(".datepicker-years").find("th").removeClass("disabled");if(startYear>year){picker.widget.find(".datepicker-years").find("th:eq(0)").addClass("disabled")}if(endYear<year+9){picker.widget.find(".datepicker-years").find("th:eq(2)").addClass("disabled")}year-=1;for(var i=-1;i<11;i++){html+='<span class="year'+(i===-1||i===10?" old":"")+(currentYear===year?" active":"")+(year<startYear||year>endYear?" disabled":"")+'">'+year+"</span>";year+=1}yearCont.html(html)},fillHours=function(){pMoment.lang(picker.options.language);var table=picker.widget.find(".timepicker .timepicker-hours table");var html="";table.parent().hide();if(picker.use24hours){var current=0;for(var i=0;i<6;i+=1){html+="<tr>";for(var j=0;j<4;j+=1){html+='<td class="hour">'+padLeft(current.toString())+"</td>";current++}html+="</tr>"}}else{var current=1;for(var i=0;i<3;i+=1){html+="<tr>";for(var j=0;j<4;j+=1){html+='<td class="hour">'+padLeft(current.toString())+"</td>";current++}html+="</tr>"}}table.html(html)},fillMinutes=function(){var table=picker.widget.find(".timepicker .timepicker-minutes table");var html="";var current=0;table.parent().hide();for(var i=0;i<5;i++){html+="<tr>";for(var j=0;j<4;j+=1){html+='<td class="minute">'+padLeft(current.toString())+"</td>";current+=3}html+="</tr>"}table.html(html)},fillTime=function(){if(!picker.date){return}var timeComponents=picker.widget.find(".timepicker span[data-time-component]"),hour=picker.date.hours(),period="AM";if(!picker.use24hours){if(hour>=12){period="PM"}if(hour===0){hour=12}else if(hour!=12){hour=hour%12}picker.widget.find(".timepicker [data-action=togglePeriod]").text(period)}timeComponents.filter("[data-time-component=hours]").text(padLeft(hour));timeComponents.filter("[data-time-component=minutes]").text(padLeft(picker.date.minutes()))},click=function(e){e.stopPropagation();e.preventDefault();picker.unset=false;var target=$(e.target).closest("span, td, th");var oldDate=picker.date;if(target.length!==1){return}if(target.is(".disabled")){return}switch(target[0].nodeName.toLowerCase()){case"th":switch(target[0].className){case"switch":showMode(1);break;case"prev":case"next":var step=dpGlobal.modes[picker.viewMode].navStep;if(target[0].className==="prev"){step=step*-1}picker.viewDate.add(step,dpGlobal.modes[picker.viewMode].navFnc);fillDate();break}break;case"span":if(target.is(".month")){var month=target.parent().find("span").index(target);picker.viewDate.month(month)}else{var year=parseInt(target.text(),10)||0;picker.viewDate.year(year)}if(picker.viewMode!==0){picker.date=pMoment({y:picker.viewDate.year(),M:picker.viewDate.month(),d:picker.viewDate.date(),h:picker.date.hours(),m:picker.date.minutes()});notifyChange(oldDate)}showMode(-1);fillDate();break;case"td":if(!target.is(".day")){break}var day=parseInt(target.text(),10)||1;var month=picker.viewDate.month();var year=picker.viewDate.year();if(target.is(".old")){if(month===0){month=11;year-=1}else{month-=1}}else if(target.is(".new")){if(month==11){month=0;year+=1}else{month+=1}}picker.date=pMoment({y:year,M:month,d:day,h:picker.date.hours(),m:picker.date.minutes()});picker.viewDate=pMoment({y:year,M:month,d:Math.min(28,day)});fillDate();set();notifyChange(oldDate);break}},actions={incrementHours:function(){checkDate("add","hours")},incrementMinutes:function(){checkDate("add","minutes")},decrementHours:function(){checkDate("subtract","hours")},decrementMinutes:function(){checkDate("subtract","minutes")},togglePeriod:function(){var hour=picker.date.hours();if(hour>=12)hour-=12;else hour+=12;picker.date.hours(hour)},showPicker:function(){picker.widget.find(".timepicker > div:not(.timepicker-picker)").hide();picker.widget.find(".timepicker .timepicker-picker").show()},showHours:function(){picker.widget.find(".timepicker .timepicker-picker").hide();picker.widget.find(".timepicker .timepicker-hours").show()},showMinutes:function(){picker.widget.find(".timepicker .timepicker-picker").hide();picker.widget.find(".timepicker .timepicker-minutes").show()},selectHour:function(e){picker.date.hours(parseInt($(e.target).text(),10));actions.showPicker.call(picker)},selectMinute:function(e){picker.date.minutes(parseInt($(e.target).text(),10));actions.showPicker.call(picker)}},doAction=function(e){var action=$(e.currentTarget).data("action");var rv=actions[action].apply(picker,arguments);var oldDate=picker.date;stopEvent(e);if(!picker.date){picker.date=pMoment({y:1970})}set();fillTime();notifyChange(oldDate);return rv},stopEvent=function(e){e.stopPropagation();e.preventDefault()},change=function(e){pMoment.lang(picker.options.language);var input=$(e.target);var oldDate=picker.date;var d=pMoment(input.val(),picker.format,picker.options.useStrict);if(d.isValid()){update();picker.setValue(d);notifyChange(oldDate);set()}else{picker.viewDate=oldDate;notifyChange(oldDate);notifyError(d);picker.unset=true;input.val("")}},showMode=function(dir){if(dir){picker.viewMode=Math.max(picker.minViewMode,Math.min(2,picker.viewMode+dir))}picker.widget.find(".datepicker > div").hide().filter(".datepicker-"+dpGlobal.modes[picker.viewMode].clsName).show()},attachDatePickerEvents=function(){picker.widget.on("click",".datepicker *",$.proxy(click,this));picker.widget.on("click","[data-action]",$.proxy(doAction,this));picker.widget.on("mousedown",$.proxy(stopEvent,this));if(picker.options.pickDate&&picker.options.pickTime){picker.widget.on("click.togglePicker",".accordion-toggle",function(e){e.stopPropagation();var $this=$(this);var $parent=$this.closest("ul");var expanded=$parent.find(".in");var closed=$parent.find(".collapse:not(.in)");if(expanded&&expanded.length){var collapseData=expanded.data("collapse");if(collapseData&&collapseData.transitioning){return}expanded.collapse("hide");closed.collapse("show");$this.find("span").toggleClass(picker.options.icons.time+" "+picker.options.icons.date);picker.element.find(".input-group-addon span").toggleClass(picker.options.icons.time+" "+picker.options.icons.date)}})}if(picker.isInput){picker.element.on({focus:$.proxy(picker.show,this),change:$.proxy(change,this),blur:$.proxy(picker.hide,this)})}else{picker.element.on({change:$.proxy(change,this)},"input");if(picker.component){picker.component.on("click",$.proxy(picker.show,this))}else{picker.element.on("click",$.proxy(picker.show,this))}}},attachDatePickerGlobalEvents=function(){$(window).on("resize.datetimepicker"+picker.id,$.proxy(place,this));if(!picker.isInput){$(document).on("mousedown.datetimepicker"+picker.id,$.proxy(picker.hide,this))}},detachDatePickerEvents=function(){picker.widget.off("click",".datepicker *",picker.click);picker.widget.off("click","[data-action]");picker.widget.off("mousedown",picker.stopEvent);if(picker.options.pickDate&&picker.options.pickTime){picker.widget.off("click.togglePicker")}if(picker.isInput){picker.element.off({focus:picker.show,change:picker.change})}else{picker.element.off({change:picker.change},"input");if(picker.component){picker.component.off("click",picker.show)}else{picker.element.off("click",picker.show)}}},detachDatePickerGlobalEvents=function(){$(window).off("resize.datetimepicker"+picker.id);if(!picker.isInput){$(document).off("mousedown.datetimepicker"+picker.id)}},isInFixed=function(){if(picker.element){var parents=picker.element.parents(),inFixed=false,i;for(i=0;i<parents.length;i++){if($(parents[i]).css("position")=="fixed"){inFixed=true;break}}return inFixed}else{return false}},set=function(){pMoment.lang(picker.options.language);var formatted="",input;if(!picker.unset){formatted=pMoment(picker.date).format(picker.format)}if(!picker.isInput){if(picker.component){input=picker.element.find("input");input.val(formatted)}picker.element.data("date",formatted)}else{picker.element.val(formatted)}if(!picker.options.pickTime)picker.hide()},checkDate=function(direction,unit){pMoment.lang(picker.options.language);var newDate;if(direction=="add"){newDate=pMoment(picker.date);if(newDate.hours()==23){newDate.add(1,unit)}newDate.add(1,unit)}else{newDate=pMoment(picker.date).subtract(1,unit)}if(newDate.isAfter(picker.options.endDate)||newDate.subtract(1,unit).isBefore(picker.options.startDate)||isInDisableDates(newDate)||!isInEnableDates(newDate)){notifyError(newDate.format(picker.format));return}if(direction=="add"){picker.date.add(1,unit)}else{picker.date.subtract(1,unit)}},isInDisableDates=function(date){pMoment.lang(picker.options.language);var disabled=picker.options.disabledDates,i;for(i in disabled){if(disabled[i]==pMoment(date).format("L")){return true}}return false},isInEnableDates=function(date){pMoment.lang(picker.options.language);var enabled=picker.options.enabledDates,i;if(enabled.length){for(i in enabled){if(enabled[i]==pMoment(date).format("L")){return true}}return false}return enabled===false?true:false},padLeft=function(string){string=string.toString();if(string.length>=2){return string}else{return"0"+string}},getTemplate=function(pickDate,pickTime,collapse){if(pickDate&&pickTime){return'<div class="bootstrap-datetimepicker-widget dropdown-menu" style="z-index:9999 !important;">'+'<ul class="list-unstyled">'+"<li"+(collapse?' class="collapse in"':"")+">"+'<div class="datepicker">'+dpGlobal.template+"</div>"+"</li>"+'<li class="picker-switch accordion-toggle"><a class="btn" style="width:100%"><span class="'+picker.options.icons.time+'"></span></a></li>'+"<li"+(collapse?' class="collapse"':"")+">"+'<div class="timepicker">'+tpGlobal.getTemplate()+"</div>"+"</li>"+"</ul>"+"</div>"}else if(pickTime){return'<div class="bootstrap-datetimepicker-widget dropdown-menu">'+'<div class="timepicker">'+tpGlobal.getTemplate()+"</div>"+"</div>"}else{return'<div class="bootstrap-datetimepicker-widget dropdown-menu">'+'<div class="datepicker">'+dpGlobal.template+"</div>"+"</div>"}},dpGlobal={modes:[{clsName:"days",navFnc:"month",navStep:1},{clsName:"months",navFnc:"year",navStep:1},{clsName:"years",navFnc:"year",navStep:10}],headTemplate:"<thead>"+"<tr>"+'<th class="prev">&lsaquo;</th><th colspan="5" class="switch"></th><th class="next">&rsaquo;</th>'+"</tr>"+"</thead>",contTemplate:'<tbody><tr><td colspan="7"></td></tr></tbody>'},tpGlobal={hourTemplate:'<span data-action="showHours" data-time-component="hours" class="timepicker-hour"></span>',minuteTemplate:'<span data-action="showMinutes" data-time-component="minutes" class="timepicker-minute"></span>'};dpGlobal.template='<div class="datepicker-days">'+'<table class="table-condensed">'+dpGlobal.headTemplate+"<tbody></tbody></table>"+"</div>"+'<div class="datepicker-months">'+'<table class="table-condensed">'+dpGlobal.headTemplate+dpGlobal.contTemplate+"</table>"+"</div>"+'<div class="datepicker-years">'+'<table class="table-condensed">'+dpGlobal.headTemplate+dpGlobal.contTemplate+"</table>"+"</div>";tpGlobal.getTemplate=function(){return'<div class="timepicker-picker">'+'<table class="table-condensed">'+"<tr>"+'<td><a href="#" class="btn" data-action="incrementHours"><span class="'+picker.options.icons.up+'"></span></a></td>'+'<td class="separator"></td>'+'<td><a href="#" class="btn" data-action="incrementMinutes"><span class="'+picker.options.icons.up+'"></span></a></td>'+(!picker.use24hours?'<td class="separator"></td>':"")+"</tr>"+"<tr>"+"<td>"+tpGlobal.hourTemplate+"</td> "+'<td class="separator">:</td>'+"<td>"+tpGlobal.minuteTemplate+"</td> "+(!picker.use24hours?'<td class="separator"></td>'+'<td><button type="button" class="btn btn-primary" data-action="togglePeriod"></button></td>':"")+"</tr>"+"<tr>"+'<td><a href="#" class="btn" data-action="decrementHours"><span class="'+picker.options.icons.down+'"></span></a></td>'+'<td class="separator"></td>'+'<td><a href="#" class="btn" data-action="decrementMinutes"><span class="'+picker.options.icons.down+'"></span></a></td>'+(!picker.use24hours?'<td class="separator"></td>':"")+"</tr>"+"</table>"+"</div>"+'<div class="timepicker-hours" data-action="selectHour">'+'<table class="table-condensed"></table>'+"</div>"+'<div class="timepicker-minutes" data-action="selectMinute">'+'<table class="table-condensed"></table>'+"</div>"};picker.destroy=function(){detachDatePickerEvents();detachDatePickerGlobalEvents();picker.widget.remove();picker.element.removeData("DateTimePicker");if(picker.component)picker.component.removeData("DateTimePicker")};picker.show=function(e){picker.widget.show();picker.height=picker.component?picker.component.outerHeight():picker.element.outerHeight();place();picker.element.trigger({type:"show.dp",date:picker.date});attachDatePickerGlobalEvents();if(e){stopEvent(e)}},picker.disable=function(){picker.element.find("input").prop("disabled",true);detachDatePickerEvents()},picker.enable=function(){picker.element.find("input").prop("disabled",false);attachDatePickerEvents()},picker.hide=function(){var collapse=picker.widget.find(".collapse"),i,collapseData;for(i=0;i<collapse.length;i++){collapseData=collapse.eq(i).data("collapse");if(collapseData&&collapseData.transitioning)return}picker.widget.hide();picker.viewMode=picker.startViewMode;showMode();picker.element.trigger({type:"hide.dp",date:picker.date});detachDatePickerGlobalEvents()},picker.setValue=function(newDate){pMoment.lang(picker.options.language);if(!newDate){picker.unset=true}else{picker.unset=false}if(!pMoment.isMoment(newDate)){newDate=pMoment(newDate)}if(newDate.isValid()){picker.date=newDate;set();picker.viewDate=pMoment({y:picker.date.year(),M:picker.date.month()});fillDate();fillTime()}else{notifyError(newDate)}},picker.getDate=function(){if(picker.unset){return null}return picker.date},picker.setDate=function(date){if(!date){picker.setValue(null)}else{picker.setValue(date)}},picker.setEnabledDates=function(dates){if(!dates){picker.options.enabledDates=false}else{picker.options.enabledDates=dates}if(picker.viewDate){update()}},picker.setEndDate=function(date){picker.options.endDate=pMoment(date);if(!picker.options.endDate.isValid()){picker.options.endDate=pMoment().add(50,"y")}if(picker.viewDate){update()}},picker.setStartDate=function(date){picker.options.startDate=pMoment(date);if(!picker.options.startDate.isValid()){picker.options.startDate=pMoment({y:1970})}if(picker.viewDate){update()}};init()};$.fn.datetimepicker=function(options){return this.each(function(){var $this=$(this),data=$this.data("DateTimePicker");if(!data){$this.data("DateTimePicker",new DateTimePicker(this,options))}})}});
\ No newline at end of file
diff --git a/src/web/js/d3.v3.min.js b/src/web/js/d3.v3.min.js
new file mode 100644
index 0000000..f3e380c
--- /dev/null
+++ b/src/web/js/d3.v3.min.js
@@ -0,0 +1,5 @@
+d3=function(){function n(n){return null!=n&&!isNaN(n)}function t(n){return n.length}function e(n){for(var t=1;n*t%1;)t*=10;return t}function r(n,t){try{for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}catch(r){n.prototype=t}}function u(){}function i(){}function o(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function a(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.substring(1);for(var e=0,r=aa.length;r>e;++e){var u=aa[e]+t;if(u in n)return u}}function c(){}function s(){}function l(n){function t(){for(var t,r=e,u=-1,i=r.length;++u<i;)(t=r[u].on)&&t.apply(this,arguments);return n}var e=[],r=new u;return t.on=function(t,u){var i,o=r.get(t);return arguments.length<2?o&&o.on:(o&&(o.on=null,e=e.slice(0,i=e.indexOf(o)).concat(e.slice(i+1)),r.remove(t)),u&&e.push(r.set(t,{on:u})),n)},t}function f(){Zo.event.preventDefault()}function h(){for(var n,t=Zo.event;n=t.sourceEvent;)t=n;return t}function g(n){for(var t=new s,e=0,r=arguments.length;++e<r;)t[arguments[e]]=l(t);return t.of=function(e,r){return function(u){try{var i=u.sourceEvent=Zo.event;u.target=n,Zo.event=u,t[u.type].apply(e,r)}finally{Zo.event=i}}},t}function p(n){return sa(n,pa),n}function v(n){return"function"==typeof n?n:function(){return la(n,this)}}function d(n){return"function"==typeof n?n:function(){return fa(n,this)}}function m(n,t){function e(){this.removeAttribute(n)}function r(){this.removeAttributeNS(n.space,n.local)}function u(){this.setAttribute(n,t)}function i(){this.setAttributeNS(n.space,n.local,t)}function o(){var e=t.apply(this,arguments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function a(){var e=t.apply(this,arguments);null==e?this.removeAttributeNS(n.space,n.local):this.setAttributeNS(n.space,n.local,e)}return n=Zo.ns.qualify(n),null==t?n.local?r:e:"function"==typeof t?n.local?a:o:n.local?i:u}function y(n){return n.trim().replace(/\s+/g," ")}function x(n){return new RegExp("(?:^|\\s+)"+Zo.requote(n)+"(?:\\s+|$)","g")}function M(n,t){function e(){for(var e=-1;++e<u;)n[e](this,t)}function r(){for(var e=-1,r=t.apply(this,arguments);++e<u;)n[e](this,r)}n=n.trim().split(/\s+/).map(_);var u=n.length;return"function"==typeof t?r:e}function _(n){var t=x(n);return function(e,r){if(u=e.classList)return r?u.add(n):u.remove(n);var u=e.getAttribute("class")||"";r?(t.lastIndex=0,t.test(u)||e.setAttribute("class",y(u+" "+n))):e.setAttribute("class",y(u.replace(t," ")))}}function b(n,t,e){function r(){this.style.removeProperty(n)}function u(){this.style.setProperty(n,t,e)}function i(){var r=t.apply(this,arguments);null==r?this.style.removeProperty(n):this.style.setProperty(n,r,e)}return null==t?r:"function"==typeof t?i:u}function w(n,t){function e(){delete this[n]}function r(){this[n]=t}function u(){var e=t.apply(this,arguments);null==e?delete this[n]:this[n]=e}return null==t?e:"function"==typeof t?u:r}function S(n){return"function"==typeof n?n:(n=Zo.ns.qualify(n)).local?function(){return this.ownerDocument.createElementNS(n.space,n.local)}:function(){return this.ownerDocument.createElementNS(this.namespaceURI,n)}}function k(n){return{__data__:n}}function E(n){return function(){return ga(this,n)}}function A(n){return arguments.length||(n=Zo.ascending),function(t,e){return t&&e?n(t.__data__,e.__data__):!t-!e}}function C(n,t){for(var e=0,r=n.length;r>e;e++)for(var u,i=n[e],o=0,a=i.length;a>o;o++)(u=i[o])&&t(u,o,e);return n}function N(n){return sa(n,da),n}function L(n){var t,e;return function(r,u,i){var o,a=n[i].update,c=a.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(o=a[t])&&++t<c;);return o}}function T(){var n=this.__transition__;n&&++n.active}function q(n,t,e){function r(){var t=this[o];t&&(this.removeEventListener(n,t,t.$),delete this[o])}function u(){var u=s(t,Xo(arguments));r.call(this),this.addEventListener(n,this[o]=u,u.$=e),u._=t}function i(){var t,e=new RegExp("^__on([^.]+)"+Zo.requote(n)+"$");for(var r in this)if(t=r.match(e)){var u=this[r];this.removeEventListener(t[1],u,u.$),delete this[r]}}var o="__on"+n,a=n.indexOf("."),s=z;a>0&&(n=n.substring(0,a));var l=ya.get(n);return l&&(n=l,s=R),a?t?u:r:t?c:i}function z(n,t){return function(e){var r=Zo.event;Zo.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{Zo.event=r}}}function R(n,t){var e=z(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function D(){var n=".dragsuppress-"+ ++Ma,t="touchmove"+n,e="selectstart"+n,r="dragstart"+n,u="click"+n,i=Zo.select(Wo).on(t,f).on(e,f).on(r,f),o=Bo.style,a=o[xa];return o[xa]="none",function(t){function e(){i.on(u,null)}i.on(n,null),o[xa]=a,t&&(i.on(u,function(){f(),e()},!0),setTimeout(e,0))}}function P(n,t){t.changedTouches&&(t=t.changedTouches[0]);var e=n.ownerSVGElement||n;if(e.createSVGPoint){var r=e.createSVGPoint();if(0>_a&&(Wo.scrollX||Wo.scrollY)){e=Zo.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var u=e[0][0].getScreenCTM();_a=!(u.f||u.e),e.remove()}return _a?(r.x=t.pageX,r.y=t.pageY):(r.x=t.clientX,r.y=t.clientY),r=r.matrixTransform(n.getScreenCTM().inverse()),[r.x,r.y]}var i=n.getBoundingClientRect();return[t.clientX-i.left-n.clientLeft,t.clientY-i.top-n.clientTop]}function U(n){return n>0?1:0>n?-1:0}function j(n){return n>1?0:-1>n?ba:Math.acos(n)}function H(n){return n>1?Sa:-1>n?-Sa:Math.asin(n)}function F(n){return((n=Math.exp(n))-1/n)/2}function O(n){return((n=Math.exp(n))+1/n)/2}function Y(n){return((n=Math.exp(2*n))-1)/(n+1)}function I(n){return(n=Math.sin(n/2))*n}function Z(){}function V(n,t,e){return new X(n,t,e)}function X(n,t,e){this.h=n,this.s=t,this.l=e}function $(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(o-i)*n/60:180>n?o:240>n?i+(o-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,i=2*e-o,ot(u(n+120),u(n),u(n-120))}function B(n,t,e){return new W(n,t,e)}function W(n,t,e){this.h=n,this.c=t,this.l=e}function J(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),G(e,Math.cos(n*=Aa)*t,Math.sin(n)*t)}function G(n,t,e){return new K(n,t,e)}function K(n,t,e){this.l=n,this.a=t,this.b=e}function Q(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=tt(u)*ja,r=tt(r)*Ha,i=tt(i)*Fa,ot(rt(3.2404542*u-1.5371385*r-.4985314*i),rt(-.969266*u+1.8760108*r+.041556*i),rt(.0556434*u-.2040259*r+1.0572252*i))}function nt(n,t,e){return n>0?B(Math.atan2(e,t)*Ca,Math.sqrt(t*t+e*e),n):B(0/0,0/0,n)}function tt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function et(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function rt(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function ut(n){return ot(n>>16,255&n>>8,255&n)}function it(n){return ut(n)+""}function ot(n,t,e){return new at(n,t,e)}function at(n,t,e){this.r=n,this.g=t,this.b=e}function ct(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function st(n,t,e){var r,u,i,o=0,a=0,c=0;if(r=/([a-z]+)\((.*)\)/i.exec(n))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(gt(u[0]),gt(u[1]),gt(u[2]))}return(i=Ia.get(n))?t(i.r,i.g,i.b):(null!=n&&"#"===n.charAt(0)&&(4===n.length?(o=n.charAt(1),o+=o,a=n.charAt(2),a+=a,c=n.charAt(3),c+=c):7===n.length&&(o=n.substring(1,3),a=n.substring(3,5),c=n.substring(5,7)),o=parseInt(o,16),a=parseInt(a,16),c=parseInt(c,16)),t(o,a,c))}function lt(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-i,c=(o+i)/2;return a?(u=.5>c?a/(o+i):a/(2-o-i),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=0/0,u=c>0&&1>c?0:r),V(r,u,c)}function ft(n,t,e){n=ht(n),t=ht(t),e=ht(e);var r=et((.4124564*n+.3575761*t+.1804375*e)/ja),u=et((.2126729*n+.7151522*t+.072175*e)/Ha),i=et((.0193339*n+.119192*t+.9503041*e)/Fa);return G(116*u-16,500*(r-u),200*(u-i))}function ht(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function gt(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function pt(n){return"function"==typeof n?n:function(){return n}}function vt(n){return n}function dt(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),mt(t,e,n,r)}}function mt(n,t,e,r){function u(){var n,t=c.status;if(!t&&c.responseText||t>=200&&300>t||304===t){try{n=e.call(i,c)}catch(r){return o.error.call(i,r),void 0}o.load.call(i,n)}else o.error.call(i,c)}var i={},o=Zo.dispatch("beforesend","progress","load","error"),a={},c=new XMLHttpRequest,s=null;return!Wo.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=u:c.onreadystatechange=function(){c.readyState>3&&u()},c.onprogress=function(n){var t=Zo.event;Zo.event=n;try{o.progress.call(i,c)}finally{Zo.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(s=n,i):s},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(Xo(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),c.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),c.setRequestHeader)for(var l in a)c.setRequestHeader(l,a[l]);return null!=t&&c.overrideMimeType&&c.overrideMimeType(t),null!=s&&(c.responseType=s),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),o.beforesend.call(i,c),c.send(null==r?null:r),i},i.abort=function(){return c.abort(),i},Zo.rebind(i,o,"on"),null==r?i:i.get(yt(r))}function yt(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function xt(){var n=Mt(),t=_t()-n;t>24?(isFinite(t)&&(clearTimeout($a),$a=setTimeout(xt,t)),Xa=0):(Xa=1,Wa(xt))}function Mt(){var n=Date.now();for(Ba=Za;Ba;)n>=Ba.t&&(Ba.f=Ba.c(n-Ba.t)),Ba=Ba.n;return n}function _t(){for(var n,t=Za,e=1/0;t;)t.f?t=n?n.n=t.n:Za=t.n:(t.t<e&&(e=t.t),t=(n=t).n);return Va=n,e}function bt(n,t){var e=Math.pow(10,3*ua(8-t));return{scale:t>8?function(n){return n/e}:function(n){return n*e},symbol:n}}function wt(n,t){return t-(n?Math.ceil(Math.log(n)/Math.LN10):1)}function St(n){return n+""}function kt(){}function Et(n,t,e){var r=e.s=n+t,u=r-n,i=r-u;e.t=n-i+(t-u)}function At(n,t){n&&ac.hasOwnProperty(n.type)&&ac[n.type](n,t)}function Ct(n,t,e){var r,u=-1,i=n.length-e;for(t.lineStart();++u<i;)r=n[u],t.point(r[0],r[1],r[2]);t.lineEnd()}function Nt(n,t){var e=-1,r=n.length;for(t.polygonStart();++e<r;)Ct(n[e],t,1);t.polygonEnd()}function Lt(){function n(n,t){n*=Aa,t=t*Aa/2+ba/4;var e=n-r,o=Math.cos(t),a=Math.sin(t),c=i*a,s=u*o+c*Math.cos(e),l=c*Math.sin(e);sc.add(Math.atan2(l,s)),r=n,u=o,i=a}var t,e,r,u,i;lc.point=function(o,a){lc.point=n,r=(t=o)*Aa,u=Math.cos(a=(e=a)*Aa/2+ba/4),i=Math.sin(a)},lc.lineEnd=function(){n(t,e)}}function Tt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function qt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function zt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Rt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function Dt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function Pt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function Ut(n){return[Math.atan2(n[1],n[0]),H(n[2])]}function jt(n,t){return ua(n[0]-t[0])<ka&&ua(n[1]-t[1])<ka}function Ht(n,t){n*=Aa;var e=Math.cos(t*=Aa);Ft(e*Math.cos(n),e*Math.sin(n),Math.sin(t))}function Ft(n,t,e){++fc,gc+=(n-gc)/fc,pc+=(t-pc)/fc,vc+=(e-vc)/fc}function Ot(){function n(n,u){n*=Aa;var i=Math.cos(u*=Aa),o=i*Math.cos(n),a=i*Math.sin(n),c=Math.sin(u),s=Math.atan2(Math.sqrt((s=e*c-r*a)*s+(s=r*o-t*c)*s+(s=t*a-e*o)*s),t*o+e*a+r*c);hc+=s,dc+=s*(t+(t=o)),mc+=s*(e+(e=a)),yc+=s*(r+(r=c)),Ft(t,e,r)}var t,e,r;bc.point=function(u,i){u*=Aa;var o=Math.cos(i*=Aa);t=o*Math.cos(u),e=o*Math.sin(u),r=Math.sin(i),bc.point=n,Ft(t,e,r)}}function Yt(){bc.point=Ht}function It(){function n(n,t){n*=Aa;var e=Math.cos(t*=Aa),o=e*Math.cos(n),a=e*Math.sin(n),c=Math.sin(t),s=u*c-i*a,l=i*o-r*c,f=r*a-u*o,h=Math.sqrt(s*s+l*l+f*f),g=r*o+u*a+i*c,p=h&&-j(g)/h,v=Math.atan2(h,g);xc+=p*s,Mc+=p*l,_c+=p*f,hc+=v,dc+=v*(r+(r=o)),mc+=v*(u+(u=a)),yc+=v*(i+(i=c)),Ft(r,u,i)}var t,e,r,u,i;bc.point=function(o,a){t=o,e=a,bc.point=n,o*=Aa;var c=Math.cos(a*=Aa);r=c*Math.cos(o),u=c*Math.sin(o),i=Math.sin(a),Ft(r,u,i)},bc.lineEnd=function(){n(t,e),bc.lineEnd=Yt,bc.point=Ht}}function Zt(){return!0}function Vt(n,t,e,r,u){var i=[],o=[];if(n.forEach(function(n){if(!((t=n.length-1)<=0)){var t,e=n[0],r=n[t];if(jt(e,r)){u.lineStart();for(var a=0;t>a;++a)u.point((e=n[a])[0],e[1]);return u.lineEnd(),void 0}var c=new $t(e,n,null,!0),s=new $t(e,null,c,!1);c.o=s,i.push(c),o.push(s),c=new $t(r,n,null,!1),s=new $t(r,null,c,!0),c.o=s,i.push(c),o.push(s)}}),o.sort(t),Xt(i),Xt(o),i.length){for(var a=0,c=e,s=o.length;s>a;++a)o[a].e=c=!c;for(var l,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;l=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var a=0,s=l.length;s>a;++a)u.point((f=l[a])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){l=g.p.z;for(var a=l.length-1;a>=0;--a)u.point((f=l[a])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,l=g.z,p=!p}while(!g.v);u.lineEnd()}}}function Xt(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r<t;)u.n=e=n[r],e.p=u,u=e;u.n=e=n[0],e.p=u}}function $t(n,t,e,r){this.x=n,this.z=t,this.o=e,this.e=r,this.v=!1,this.n=this.p=null}function Bt(n,t,e,r){return function(u,i){function o(t,e){var r=u(t,e);n(t=r[0],e=r[1])&&i.point(t,e)}function a(n,t){var e=u(n,t);d.point(e[0],e[1])}function c(){y.point=a,d.lineStart()}function s(){y.point=o,d.lineEnd()}function l(n,t){v.push([n,t]);var e=u(n,t);M.point(e[0],e[1])}function f(){M.lineStart(),v=[]}function h(){l(v[0][0],v[0][1]),M.lineEnd();var n,t=M.clean(),e=x.buffer(),r=e.length;if(v.pop(),p.push(v),v=null,r){if(1&t){n=e[0];var u,r=n.length-1,o=-1;for(i.lineStart();++o<r;)i.point((u=n[o])[0],u[1]);return i.lineEnd(),void 0}r>1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Wt))}}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:o,lineStart:c,lineEnd:s,polygonStart:function(){y.point=l,y.lineStart=f,y.lineEnd=h,g=[],p=[],i.polygonStart()},polygonEnd:function(){y.point=o,y.lineStart=c,y.lineEnd=s,g=Zo.merge(g);var n=Kt(m,p);g.length?Vt(g,Gt,n,e,i):n&&(i.lineStart(),e(null,null,1,i),i.lineEnd()),i.polygonEnd(),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},x=Jt(),M=t(x);return y}}function Wt(n){return n.length>1}function Jt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:c,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Gt(n,t){return((n=n.x)[0]<0?n[1]-Sa-ka:Sa-n[1])-((t=t.x)[0]<0?t[1]-Sa-ka:Sa-t[1])}function Kt(n,t){var e=n[0],r=n[1],u=[Math.sin(e),-Math.cos(e),0],i=0,o=0;sc.reset();for(var a=0,c=t.length;c>a;++a){var s=t[a],l=s.length;if(l)for(var f=s[0],h=f[0],g=f[1]/2+ba/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===l&&(d=0),n=s[d];var m=n[0],y=n[1]/2+ba/4,x=Math.sin(y),M=Math.cos(y),_=m-h,b=ua(_)>ba,w=p*x;if(sc.add(Math.atan2(w*Math.sin(_),v*M+w*Math.cos(_))),i+=b?_+(_>=0?wa:-wa):_,b^h>=e^m>=e){var S=zt(Tt(f),Tt(n));Pt(S);var k=zt(u,S);Pt(k);var E=(b^_>=0?-1:1)*H(k[2]);(r>E||r===E&&(S[0]||S[1]))&&(o+=b^_>=0?1:-1)}if(!d++)break;h=m,p=x,v=M,f=n}}return(-ka>i||ka>i&&0>sc)^1&o}function Qt(n){var t,e=0/0,r=0/0,u=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(i,o){var a=i>0?ba:-ba,c=ua(i-e);ua(c-ba)<ka?(n.point(e,r=(r+o)/2>0?Sa:-Sa),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(i,r),t=0):u!==a&&c>=ba&&(ua(e-u)<ka&&(e-=u*ka),ua(i-a)<ka&&(i-=a*ka),r=ne(e,r,i,o),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),t=0),n.point(e=i,r=o),u=a},lineEnd:function(){n.lineEnd(),e=r=0/0},clean:function(){return 2-t}}}function ne(n,t,e,r){var u,i,o=Math.sin(n-e);return ua(o)>ka?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*o)):(t+r)/2}function te(n,t,e,r){var u;if(null==n)u=e*Sa,r.point(-ba,u),r.point(0,u),r.point(ba,u),r.point(ba,0),r.point(ba,-u),r.point(0,-u),r.point(-ba,-u),r.point(-ba,0),r.point(-ba,u);else if(ua(n[0]-t[0])>ka){var i=n[0]<t[0]?ba:-ba;u=e*i/2,r.point(-i,u),r.point(0,u),r.point(i,u)}else r.point(t[0],t[1])}function ee(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,c,s,l;return{lineStart:function(){s=c=!1,l=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=o?v?0:u(f,h):v?u(f+(0>f?ba:-ba),h):0;if(!e&&(s=c=v)&&n.lineStart(),v!==c&&(g=r(e,p),(jt(e,g)||jt(p,g))&&(p[0]+=ka,p[1]+=ka,v=t(p[0],p[1]))),v!==c)l=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(a&&e&&o^v){var m;d&i||!(m=r(p,e,!0))||(l=0,o?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&jt(e,p)||n.point(p[0],p[1]),e=p,c=v,i=d},lineEnd:function(){c&&n.lineEnd(),e=null},clean:function(){return l|(s&&c)<<1}}}function r(n,t,e){var r=Tt(n),u=Tt(t),o=[1,0,0],a=zt(r,u),c=qt(a,a),s=a[0],l=c-s*s;if(!l)return!e&&n;var f=i*c/l,h=-i*s/l,g=zt(o,a),p=Dt(o,f),v=Dt(a,h);Rt(p,v);var d=g,m=qt(p,d),y=qt(d,d),x=m*m-y*(qt(p,p)-1);if(!(0>x)){var M=Math.sqrt(x),_=Dt(d,(-m-M)/y);if(Rt(_,p),_=Ut(_),!e)return _;var b,w=n[0],S=t[0],k=n[1],E=t[1];w>S&&(b=w,w=S,S=b);var A=S-w,C=ua(A-ba)<ka,N=C||ka>A;if(!C&&k>E&&(b=k,k=E,E=b),N?C?k+E>0^_[1]<(ua(_[0]-w)<ka?k:E):k<=_[1]&&_[1]<=E:A>ba^(w<=_[0]&&_[0]<=S)){var L=Dt(d,(-m+M)/y);return Rt(L,p),[_,Ut(L)]}}}function u(t,e){var r=o?n:ba-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),o=i>0,a=ua(i)>ka,c=Ne(n,6*Aa);return Bt(t,e,c,o?[0,-n]:[-ba,n-ba])}function re(n,t,e,r){return function(u){var i,o=u.a,a=u.b,c=o.x,s=o.y,l=a.x,f=a.y,h=0,g=1,p=l-c,v=f-s;if(i=n-c,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-c,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-s,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-s,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:c+h*p,y:s+h*v}),1>g&&(u.b={x:c+g*p,y:s+g*v}),u}}}}}}function ue(n,t,e,r){function u(r,u){return ua(r[0]-n)<ka?u>0?0:3:ua(r[0]-e)<ka?u>0?2:1:ua(r[1]-t)<ka?u>0?1:0:u>0?3:2}function i(n,t){return o(n.x,t.x)}function o(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function c(n){for(var t=0,e=m.length,r=n[1],u=0;e>u;++u)for(var i,o=1,a=m[u],c=a.length,l=a[0];c>o;++o)i=a[o],l[1]<=r?i[1]>r&&s(l,i,n)>0&&++t:i[1]<=r&&s(l,i,n)<0&&--t,l=i;return 0!==t}function s(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(e[0]-n[0])*(t[1]-n[1])}function l(i,a,c,s){var l=0,f=0;if(null==i||(l=u(i,c))!==(f=u(a,c))||o(i,a)<0^c>0){do s.point(0===l||3===l?n:e,l>1?r:t);while((l=(l+c+4)%4)!==f)}else s.point(a[0],a[1])}function f(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function h(n,t){f(n,t)&&a.point(n,t)}function g(){L.point=v,m&&m.push(y=[]),k=!0,S=!1,b=w=0/0}function p(){d&&(v(x,M),_&&S&&C.rejoin(),d.push(C.buffer())),L.point=h,S&&a.lineEnd()}function v(n,t){n=Math.max(-Sc,Math.min(Sc,n)),t=Math.max(-Sc,Math.min(Sc,t));var e=f(n,t);if(m&&y.push([n,t]),k)x=n,M=t,_=e,k=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&S)a.point(n,t);else{var r={a:{x:b,y:w},b:{x:n,y:t}};N(r)?(S||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),E=!1):e&&(a.lineStart(),a.point(n,t),E=!1)}b=n,w=t,S=e}var d,m,y,x,M,_,b,w,S,k,E,A=a,C=Jt(),N=re(n,t,e,r),L={point:h,lineStart:g,lineEnd:p,polygonStart:function(){a=C,d=[],m=[],E=!0},polygonEnd:function(){a=A,d=Zo.merge(d);var t=c([n,r]),e=E&&t,u=d.length;(e||u)&&(a.polygonStart(),e&&(a.lineStart(),l(null,null,1,a),a.lineEnd()),u&&Vt(d,i,t,l,a),a.polygonEnd()),d=m=y=null}};return L}}function ie(n,t){function e(e,r){return e=n(e,r),t(e[0],e[1])}return n.invert&&t.invert&&(e.invert=function(e,r){return e=t.invert(e,r),e&&n.invert(e[0],e[1])}),e}function oe(n){var t=0,e=ba/3,r=_e(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*ba/180,e=n[1]*ba/180):[180*(t/ba),180*(e/ba)]},u}function ae(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),o-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),o=Math.sqrt(i)/u;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/u,H((i-(n*n+e*e)*u*u)/(2*u))]},e}function ce(){function n(n,t){Ec+=u*n-r*t,r=n,u=t}var t,e,r,u;Tc.point=function(i,o){Tc.point=n,t=r=i,e=u=o},Tc.lineEnd=function(){n(t,e)}}function se(n,t){Ac>n&&(Ac=n),n>Nc&&(Nc=n),Cc>t&&(Cc=t),t>Lc&&(Lc=t)}function le(){function n(n,t){o.push("M",n,",",t,i)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function u(){o.push("Z")}var i=fe(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return i=fe(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function fe(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function he(n,t){gc+=n,pc+=t,++vc}function ge(){function n(n,r){var u=n-t,i=r-e,o=Math.sqrt(u*u+i*i);dc+=o*(t+n)/2,mc+=o*(e+r)/2,yc+=o,he(t=n,e=r)}var t,e;zc.point=function(r,u){zc.point=n,he(t=r,e=u)}}function pe(){zc.point=he}function ve(){function n(n,t){var e=n-r,i=t-u,o=Math.sqrt(e*e+i*i);dc+=o*(r+n)/2,mc+=o*(u+t)/2,yc+=o,o=u*n-r*t,xc+=o*(r+n),Mc+=o*(u+t),_c+=3*o,he(r=n,u=t)}var t,e,r,u;zc.point=function(i,o){zc.point=n,he(t=r=i,e=u=o)},zc.lineEnd=function(){n(t,e)}}function de(n){function t(t,e){n.moveTo(t,e),n.arc(t,e,o,0,wa)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function u(){a.point=t}function i(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:u,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=u,a.point=t},pointRadius:function(n){return o=n,a},result:c};return a}function me(n){function t(t){function r(e,r){e=n(e,r),t.point(e[0],e[1])}function u(){x=0/0,S.point=o,t.lineStart()}function o(r,u){var o=Tt([r,u]),a=n(r,u);e(x,M,y,_,b,w,x=a[0],M=a[1],y=r,_=o[0],b=o[1],w=o[2],i,t),t.point(x,M)}function a(){S.point=r,t.lineEnd()}function c(){u(),S.point=s,S.lineEnd=l}function s(n,t){o(f=n,h=t),g=x,p=M,v=_,d=b,m=w,S.point=o}function l(){e(x,M,y,_,b,w,g,p,f,v,d,m,i,t),S.lineEnd=a,a()}var f,h,g,p,v,d,m,y,x,M,_,b,w,S={point:r,lineStart:u,lineEnd:a,polygonStart:function(){t.polygonStart(),S.lineStart=c},polygonEnd:function(){t.polygonEnd(),S.lineStart=u}};return S}function e(t,i,o,a,c,s,l,f,h,g,p,v,d,m){var y=l-t,x=f-i,M=y*y+x*x;if(M>4*r&&d--){var _=a+g,b=c+p,w=s+v,S=Math.sqrt(_*_+b*b+w*w),k=Math.asin(w/=S),E=ua(ua(w)-1)<ka?(o+h)/2:Math.atan2(b,_),A=n(E,k),C=A[0],N=A[1],L=C-t,T=N-i,q=x*L-y*T;(q*q/M>r||ua((y*L+x*T)/M-.5)>.3||u>a*g+c*p+s*v)&&(e(t,i,o,a,c,s,C,N,E,_/=S,b/=S,w,d,m),m.point(C,N),e(C,N,E,_,b,w,l,f,h,g,p,v,d,m))}}var r=.5,u=Math.cos(30*Aa),i=16;return t.precision=function(n){return arguments.length?(i=(r=n*n)>0&&16,t):Math.sqrt(r)},t}function ye(n){this.stream=n}function xe(n){var t=me(function(t,e){return n([t*Ca,e*Ca])});return function(n){var e=new ye(n=t(n));return e.point=function(t,e){n.point(t*Aa,e*Aa)},e}}function Me(n){return _e(function(){return n})()}function _e(n){function t(n){return n=a(n[0]*Aa,n[1]*Aa),[n[0]*h+c,s-n[1]*h]}function e(n){return n=a.invert((n[0]-c)/h,(s-n[1])/h),n&&[n[0]*Ca,n[1]*Ca]}function r(){a=ie(o=ke(m,y,x),i);var n=i(v,d);return c=g-n[0]*h,s=p+n[1]*h,u()}function u(){return l&&(l.valid=!1,l=null),t}var i,o,a,c,s,l,f=me(function(n,t){return n=i(n,t),[n[0]*h+c,s-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,y=0,x=0,M=wc,_=vt,b=null,w=null;return t.stream=function(n){return l&&(l.valid=!1),l=be(M(o,f(_(n)))),l.valid=!0,l},t.clipAngle=function(n){return arguments.length?(M=null==n?(b=n,wc):ee((b=+n)*Aa),u()):b},t.clipExtent=function(n){return arguments.length?(w=n,_=n?ue(n[0][0],n[0][1],n[1][0],n[1][1]):vt,u()):w},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Aa,d=n[1]%360*Aa,r()):[v*Ca,d*Ca]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Aa,y=n[1]%360*Aa,x=n.length>2?n[2]%360*Aa:0,r()):[m*Ca,y*Ca,x*Ca]},Zo.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function be(n){var t=new ye(n);return t.point=function(t,e){n.point(t*Aa,e*Aa)},t}function we(n,t){return[n,t]}function Se(n,t){return[n>ba?n-wa:-ba>n?n+wa:n,t]}function ke(n,t,e){return n?t||e?ie(Ae(n),Ce(t,e)):Ae(n):t||e?Ce(t,e):Se}function Ee(n){return function(t,e){return t+=n,[t>ba?t-wa:-ba>t?t+wa:t,e]}}function Ae(n){var t=Ee(n);return t.invert=Ee(-n),t}function Ce(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,s=Math.sin(t),l=s*r+a*u;return[Math.atan2(c*i-l*o,a*r-s*u),H(l*i+c*o)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,s=Math.sin(t),l=s*i-c*o;return[Math.atan2(c*i+s*o,a*r+l*u),H(l*r-a*u)]},e}function Ne(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,o,a){var c=o*t;null!=u?(u=Le(e,u),i=Le(e,i),(o>0?i>u:u>i)&&(u+=o*wa)):(u=n+o*wa,i=n-.5*c);for(var s,l=u;o>0?l>i:i>l;l-=c)a.point((s=Ut([e,-r*Math.cos(l),-r*Math.sin(l)]))[0],s[1])}}function Le(n,t){var e=Tt(t);e[0]-=n,Pt(e);var r=j(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-ka)%(2*Math.PI)}function Te(n,t,e){var r=Zo.range(n,t-ka,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function qe(n,t,e){var r=Zo.range(n,t-ka,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function ze(n){return n.source}function Re(n){return n.target}function De(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),o=Math.cos(r),a=Math.sin(r),c=u*Math.cos(n),s=u*Math.sin(n),l=o*Math.cos(e),f=o*Math.sin(e),h=2*Math.asin(Math.sqrt(I(r-t)+u*o*I(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*c+t*l,u=e*s+t*f,o=e*i+t*a;return[Math.atan2(u,r)*Ca,Math.atan2(o,Math.sqrt(r*r+u*u))*Ca]}:function(){return[n*Ca,t*Ca]};return p.distance=h,p}function Pe(){function n(n,u){var i=Math.sin(u*=Aa),o=Math.cos(u),a=ua((n*=Aa)-t),c=Math.cos(a);Rc+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*i-e*o*c)*a),e*i+r*o*c),t=n,e=i,r=o}var t,e,r;Dc.point=function(u,i){t=u*Aa,e=Math.sin(i*=Aa),r=Math.cos(i),Dc.point=n},Dc.lineEnd=function(){Dc.point=Dc.lineEnd=c}}function Ue(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),o=Math.cos(u);return[Math.atan2(n*i,r*o),Math.asin(r&&e*i/r)]},e}function je(n,t){function e(n,t){var e=ua(ua(t)-Sa)<ka?0:o/Math.pow(u(t),i);return[e*Math.sin(i*n),o-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(ba/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),o=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=o-t,r=U(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(o/r,1/i))-Sa]},e):Fe}function He(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return ua(u)<ka?we:(e.invert=function(n,t){var e=i-t;return[Math.atan2(n,e)/u,i-U(u)*Math.sqrt(n*n+e*e)]},e)}function Fe(n,t){return[n,Math.log(Math.tan(ba/4+t/2))]}function Oe(n){var t,e=Me(n),r=e.scale,u=e.translate,i=e.clipExtent;return e.scale=function(){var n=r.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.translate=function(){var n=u.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.clipExtent=function(n){var o=i.apply(e,arguments);if(o===e){if(t=null==n){var a=ba*r(),c=u();i([[c[0]-a,c[1]-a],[c[0]+a,c[1]+a]])}}else t&&(o=null);return o},e.clipExtent(null)}function Ye(n,t){var e=Math.cos(t)*Math.sin(n);return[Math.log((1+e)/(1-e))/2,Math.atan2(Math.tan(t),Math.cos(n))]}function Ie(n){return n[0]}function Ze(n){return n[1]}function Ve(n,t,e,r){var u,i,o,a,c,s,l;return u=r[n],i=u[0],o=u[1],u=r[t],a=u[0],c=u[1],u=r[e],s=u[0],l=u[1],(l-o)*(a-i)-(c-o)*(s-i)>0}function Xe(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function $e(n,t,e,r){var u=n[0],i=e[0],o=t[0]-u,a=r[0]-i,c=n[1],s=e[1],l=t[1]-c,f=r[1]-s,h=(a*(c-s)-f*(u-i))/(f*o-a*l);return[u+h*o,c+h*l]}function Be(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function We(){dr(this),this.edge=this.site=this.circle=null}function Je(n){var t=$c.pop()||new We;return t.site=n,t}function Ge(n){ar(n),Zc.remove(n),$c.push(n),dr(n)}function Ke(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,o=n.N,a=[n];Ge(n);for(var c=i;c.circle&&ua(e-c.circle.x)<ka&&ua(r-c.circle.cy)<ka;)i=c.P,a.unshift(c),Ge(c),c=i;a.unshift(c),ar(c);for(var s=o;s.circle&&ua(e-s.circle.x)<ka&&ua(r-s.circle.cy)<ka;)o=s.N,a.push(s),Ge(s),s=o;a.push(s),ar(s);var l,f=a.length;for(l=1;f>l;++l)s=a[l],c=a[l-1],gr(s.edge,c.site,s.site,u);c=a[0],s=a[f-1],s.edge=fr(c.site,s.site,null,u),or(c),or(s)}function Qe(n){for(var t,e,r,u,i=n.x,o=n.y,a=Zc._;a;)if(r=nr(a,o)-i,r>ka)a=a.L;else{if(u=i-tr(a,o),!(u>ka)){r>-ka?(t=a.P,e=a):u>-ka?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var c=Je(n);if(Zc.insert(t,c),t||e){if(t===e)return ar(t),e=Je(t.site),Zc.insert(c,e),c.edge=e.edge=fr(t.site,c.site),or(t),or(e),void 0;if(!e)return c.edge=fr(t.site,c.site),void 0;ar(t),ar(e);var s=t.site,l=s.x,f=s.y,h=n.x-l,g=n.y-f,p=e.site,v=p.x-l,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,x=v*v+d*d,M={x:(d*y-g*x)/m+l,y:(h*x-v*y)/m+f};gr(e.edge,s,p,M),c.edge=fr(s,n,null,M),e.edge=fr(n,p,null,M),or(t),or(e)}}function nr(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var o=n.P;if(!o)return-1/0;e=o.site;var a=e.x,c=e.y,s=c-t;if(!s)return a;var l=a-r,f=1/i-1/s,h=l/s;return f?(-h+Math.sqrt(h*h-2*f*(l*l/(-2*s)-c+s/2+u-i/2)))/f+r:(r+a)/2}function tr(n,t){var e=n.N;if(e)return nr(e,t);var r=n.site;return r.y===t?r.x:1/0}function er(n){this.site=n,this.edges=[]}function rr(n){for(var t,e,r,u,i,o,a,c,s,l,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=Ic,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(a=i.edges,c=a.length,o=0;c>o;)l=a[o].end(),r=l.x,u=l.y,s=a[++o%c].start(),t=s.x,e=s.y,(ua(r-t)>ka||ua(u-e)>ka)&&(a.splice(o,0,new pr(hr(i.site,l,ua(r-f)<ka&&p-u>ka?{x:f,y:ua(t-f)<ka?e:p}:ua(u-p)<ka&&h-r>ka?{x:ua(e-p)<ka?t:h,y:p}:ua(r-h)<ka&&u-g>ka?{x:h,y:ua(t-h)<ka?e:g}:ua(u-g)<ka&&r-f>ka?{x:ua(e-g)<ka?t:f,y:g}:null),i.site,null)),++c)}function ur(n,t){return t.angle-n.angle}function ir(){dr(this),this.x=this.y=this.arc=this.site=this.cy=null}function or(n){var t=n.P,e=n.N;if(t&&e){var r=t.site,u=n.site,i=e.site;if(r!==i){var o=u.x,a=u.y,c=r.x-o,s=r.y-a,l=i.x-o,f=i.y-a,h=2*(c*f-s*l);if(!(h>=-Ea)){var g=c*c+s*s,p=l*l+f*f,v=(f*g-s*p)/h,d=(c*p-l*g)/h,f=d+a,m=Bc.pop()||new ir;m.arc=n,m.site=u,m.x=v+o,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,x=Xc._;x;)if(m.y<x.y||m.y===x.y&&m.x<=x.x){if(!x.L){y=x.P;break}x=x.L}else{if(!x.R){y=x;break}x=x.R}Xc.insert(y,m),y||(Vc=m)}}}}function ar(n){var t=n.circle;t&&(t.P||(Vc=t.N),Xc.remove(t),Bc.push(t),dr(t),n.circle=null)}function cr(n){for(var t,e=Yc,r=re(n[0][0],n[0][1],n[1][0],n[1][1]),u=e.length;u--;)t=e[u],(!sr(t,n)||!r(t)||ua(t.a.x-t.b.x)<ka&&ua(t.a.y-t.b.y)<ka)&&(t.a=t.b=null,e.splice(u,1))}function sr(n,t){var e=n.b;if(e)return!0;var r,u,i=n.a,o=t[0][0],a=t[1][0],c=t[0][1],s=t[1][1],l=n.l,f=n.r,h=l.x,g=l.y,p=f.x,v=f.y,d=(h+p)/2,m=(g+v)/2;if(v===g){if(o>d||d>=a)return;if(h>p){if(i){if(i.y>=s)return}else i={x:d,y:c};e={x:d,y:s}}else{if(i){if(i.y<c)return}else i={x:d,y:s};e={x:d,y:c}}}else if(r=(h-p)/(v-g),u=m-r*d,-1>r||r>1)if(h>p){if(i){if(i.y>=s)return
+}else i={x:(c-u)/r,y:c};e={x:(s-u)/r,y:s}}else{if(i){if(i.y<c)return}else i={x:(s-u)/r,y:s};e={x:(c-u)/r,y:c}}else if(v>g){if(i){if(i.x>=a)return}else i={x:o,y:r*o+u};e={x:a,y:r*a+u}}else{if(i){if(i.x<o)return}else i={x:a,y:r*a+u};e={x:o,y:r*o+u}}return n.a=i,n.b=e,!0}function lr(n,t){this.l=n,this.r=t,this.a=this.b=null}function fr(n,t,e,r){var u=new lr(n,t);return Yc.push(u),e&&gr(u,n,t,e),r&&gr(u,t,n,r),Ic[n.i].edges.push(new pr(u,n,t)),Ic[t.i].edges.push(new pr(u,t,n)),u}function hr(n,t,e){var r=new lr(n,null);return r.a=t,r.b=e,Yc.push(r),r}function gr(n,t,e,r){n.a||n.b?n.l===e?n.b=r:n.a=r:(n.a=r,n.l=t,n.r=e)}function pr(n,t,e){var r=n.a,u=n.b;this.edge=n,this.site=t,this.angle=e?Math.atan2(e.y-t.y,e.x-t.x):n.l===t?Math.atan2(u.x-r.x,r.y-u.y):Math.atan2(r.x-u.x,u.y-r.y)}function vr(){this._=null}function dr(n){n.U=n.C=n.L=n.R=n.P=n.N=null}function mr(n,t){var e=t,r=t.R,u=e.U;u?u.L===e?u.L=r:u.R=r:n._=r,r.U=u,e.U=r,e.R=r.L,e.R&&(e.R.U=e),r.L=e}function yr(n,t){var e=t,r=t.L,u=e.U;u?u.L===e?u.L=r:u.R=r:n._=r,r.U=u,e.U=r,e.L=r.R,e.L&&(e.L.U=e),r.R=e}function xr(n){for(;n.L;)n=n.L;return n}function Mr(n,t){var e,r,u,i=n.sort(_r).pop();for(Yc=[],Ic=new Array(n.length),Zc=new vr,Xc=new vr;;)if(u=Vc,i&&(!u||i.y<u.y||i.y===u.y&&i.x<u.x))(i.x!==e||i.y!==r)&&(Ic[i.i]=new er(i),Qe(i),e=i.x,r=i.y),i=n.pop();else{if(!u)break;Ke(u.arc)}t&&(cr(t),rr(t));var o={cells:Ic,edges:Yc};return Zc=Xc=Yc=Ic=null,o}function _r(n,t){return t.y-n.y||t.x-n.x}function br(n,t,e){return(n.x-e.x)*(t.y-n.y)-(n.x-t.x)*(e.y-n.y)}function wr(n){return n.x}function Sr(n){return n.y}function kr(){return{leaf:!0,nodes:[],point:null,x:null,y:null}}function Er(n,t,e,r,u,i){if(!n(t,e,r,u,i)){var o=.5*(e+u),a=.5*(r+i),c=t.nodes;c[0]&&Er(n,c[0],e,r,o,a),c[1]&&Er(n,c[1],o,r,u,a),c[2]&&Er(n,c[2],e,a,o,i),c[3]&&Er(n,c[3],o,a,u,i)}}function Ar(n,t){n=Zo.rgb(n),t=Zo.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,o=t.g-r,a=t.b-u;return function(n){return"#"+ct(Math.round(e+i*n))+ct(Math.round(r+o*n))+ct(Math.round(u+a*n))}}function Cr(n,t){var e,r={},u={};for(e in n)e in t?r[e]=Tr(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function Nr(n,t){return t-=n=+n,function(e){return n+t*e}}function Lr(n,t){var e,r,u,i,o,a=0,c=0,s=[],l=[];for(n+="",t+="",Jc.lastIndex=0,r=0;e=Jc.exec(t);++r)e.index&&s.push(t.substring(a,c=e.index)),l.push({i:s.length,x:e[0]}),s.push(null),a=Jc.lastIndex;for(a<t.length&&s.push(t.substring(a)),r=0,i=l.length;(e=Jc.exec(n))&&i>r;++r)if(o=l[r],o.x==e[0]){if(o.i)if(null==s[o.i+1])for(s[o.i-1]+=o.x,s.splice(o.i,1),u=r+1;i>u;++u)l[u].i--;else for(s[o.i-1]+=o.x+s[o.i+1],s.splice(o.i,2),u=r+1;i>u;++u)l[u].i-=2;else if(null==s[o.i+1])s[o.i]=o.x;else for(s[o.i]=o.x+s[o.i+1],s.splice(o.i+1,1),u=r+1;i>u;++u)l[u].i--;l.splice(r,1),i--,r--}else o.x=Nr(parseFloat(e[0]),parseFloat(o.x));for(;i>r;)o=l.pop(),null==s[o.i+1]?s[o.i]=o.x:(s[o.i]=o.x+s[o.i+1],s.splice(o.i+1,1)),i--;return 1===s.length?null==s[0]?(o=l[0].x,function(n){return o(n)+""}):function(){return t}:function(n){for(r=0;i>r;++r)s[(o=l[r]).i]=o.x(n);return s.join("")}}function Tr(n,t){for(var e,r=Zo.interpolators.length;--r>=0&&!(e=Zo.interpolators[r](n,t)););return e}function qr(n,t){var e,r=[],u=[],i=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Tr(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;o>e;++e)u[e]=t[e];return function(n){for(e=0;a>e;++e)u[e]=r[e](n);return u}}function zr(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function Rr(n){return function(t){return 1-n(1-t)}}function Dr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Pr(n){return n*n}function Ur(n){return n*n*n}function jr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Hr(n){return function(t){return Math.pow(t,n)}}function Fr(n){return 1-Math.cos(n*Sa)}function Or(n){return Math.pow(2,10*(n-1))}function Yr(n){return 1-Math.sqrt(1-n*n)}function Ir(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/wa*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*wa/t)}}function Zr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Vr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Xr(n,t){n=Zo.hcl(n),t=Zo.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,o=t.c-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return J(e+i*n,r+o*n,u+a*n)+""}}function $r(n,t){n=Zo.hsl(n),t=Zo.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,o=t.s-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return $(e+i*n,r+o*n,u+a*n)+""}}function Br(n,t){n=Zo.lab(n),t=Zo.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,o=t.a-r,a=t.b-u;return function(n){return Q(e+i*n,r+o*n,u+a*n)+""}}function Wr(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Kr(t),u=Gr(t,e),i=Kr(Qr(e,t,-u))||0;t[0]*e[1]<e[0]*t[1]&&(t[0]*=-1,t[1]*=-1,r*=-1,u*=-1),this.rotate=(r?Math.atan2(t[1],t[0]):Math.atan2(-e[0],e[1]))*Ca,this.translate=[n.e,n.f],this.scale=[r,i],this.skew=i?Math.atan2(u,i)*Ca:0}function Gr(n,t){return n[0]*t[0]+n[1]*t[1]}function Kr(n){var t=Math.sqrt(Gr(n,n));return t&&(n[0]/=t,n[1]/=t),t}function Qr(n,t,e){return n[0]+=e*t[0],n[1]+=e*t[1],n}function nu(n,t){var e,r=[],u=[],i=Zo.transform(n),o=Zo.transform(t),a=i.translate,c=o.translate,s=i.rotate,l=o.rotate,f=i.skew,h=o.skew,g=i.scale,p=o.scale;return a[0]!=c[0]||a[1]!=c[1]?(r.push("translate(",null,",",null,")"),u.push({i:1,x:Nr(a[0],c[0])},{i:3,x:Nr(a[1],c[1])})):c[0]||c[1]?r.push("translate("+c+")"):r.push(""),s!=l?(s-l>180?l+=360:l-s>180&&(s+=360),u.push({i:r.push(r.pop()+"rotate(",null,")")-2,x:Nr(s,l)})):l&&r.push(r.pop()+"rotate("+l+")"),f!=h?u.push({i:r.push(r.pop()+"skewX(",null,")")-2,x:Nr(f,h)}):h&&r.push(r.pop()+"skewX("+h+")"),g[0]!=p[0]||g[1]!=p[1]?(e=r.push(r.pop()+"scale(",null,",",null,")"),u.push({i:e-4,x:Nr(g[0],p[0])},{i:e-2,x:Nr(g[1],p[1])})):(1!=p[0]||1!=p[1])&&r.push(r.pop()+"scale("+p+")"),e=u.length,function(n){for(var t,i=-1;++i<e;)r[(t=u[i]).i]=t.x(n);return r.join("")}}function tu(n,t){return t=t-(n=+n)?1/(t-n):0,function(e){return(e-n)*t}}function eu(n,t){return t=t-(n=+n)?1/(t-n):0,function(e){return Math.max(0,Math.min(1,(e-n)*t))}}function ru(n){for(var t=n.source,e=n.target,r=iu(t,e),u=[t];t!==r;)t=t.parent,u.push(t);for(var i=u.length;e!==r;)u.splice(i,0,e),e=e.parent;return u}function uu(n){for(var t=[],e=n.parent;null!=e;)t.push(n),n=e,e=e.parent;return t.push(n),t}function iu(n,t){if(n===t)return n;for(var e=uu(n),r=uu(t),u=e.pop(),i=r.pop(),o=null;u===i;)o=u,u=e.pop(),i=r.pop();return o}function ou(n){n.fixed|=2}function au(n){n.fixed&=-7}function cu(n){n.fixed|=4,n.px=n.x,n.py=n.y}function su(n){n.fixed&=-5}function lu(n,t,e){var r=0,u=0;if(n.charge=0,!n.leaf)for(var i,o=n.nodes,a=o.length,c=-1;++c<a;)i=o[c],null!=i&&(lu(i,t,e),n.charge+=i.charge,r+=i.charge*i.cx,u+=i.charge*i.cy);if(n.point){n.leaf||(n.point.x+=Math.random()-.5,n.point.y+=Math.random()-.5);var s=t*e[n.point.index];n.charge+=n.pointCharge=s,r+=s*n.point.x,u+=s*n.point.y}n.cx=r/n.charge,n.cy=u/n.charge}function fu(n,t){return Zo.rebind(n,t,"sort","children","value"),n.nodes=n,n.links=vu,n}function hu(n){return n.children}function gu(n){return n.value}function pu(n,t){return t.value-n.value}function vu(n){return Zo.merge(n.map(function(n){return(n.children||[]).map(function(t){return{source:n,target:t}})}))}function du(n){return n.x}function mu(n){return n.y}function yu(n,t,e){n.y0=t,n.y=e}function xu(n){return Zo.range(n.length)}function Mu(n){for(var t=-1,e=n[0].length,r=[];++t<e;)r[t]=0;return r}function _u(n){for(var t,e=1,r=0,u=n[0][1],i=n.length;i>e;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function bu(n){return n.reduce(wu,0)}function wu(n,t){return n+t[1]}function Su(n,t){return ku(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function ku(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function Eu(n){return[Zo.min(n),Zo.max(n)]}function Au(n,t){return n.parent==t.parent?1:2}function Cu(n){var t=n.children;return t&&t.length?t[0]:n._tree.thread}function Nu(n){var t,e=n.children;return e&&(t=e.length)?e[t-1]:n._tree.thread}function Lu(n,t){var e=n.children;if(e&&(u=e.length))for(var r,u,i=-1;++i<u;)t(r=Lu(e[i],t),n)>0&&(n=r);return n}function Tu(n,t){return n.x-t.x}function qu(n,t){return t.x-n.x}function zu(n,t){return n.depth-t.depth}function Ru(n,t){function e(n,r){var u=n.children;if(u&&(o=u.length))for(var i,o,a=null,c=-1;++c<o;)i=u[c],e(i,a),a=i;t(n,r)}e(n,null)}function Du(n){for(var t,e=0,r=0,u=n.children,i=u.length;--i>=0;)t=u[i]._tree,t.prelim+=e,t.mod+=e,e+=t.shift+(r+=t.change)}function Pu(n,t,e){n=n._tree,t=t._tree;var r=e/(t.number-n.number);n.change+=r,t.change-=r,t.shift+=e,t.prelim+=e,t.mod+=e}function Uu(n,t,e){return n._tree.ancestor.parent==t.parent?n._tree.ancestor:e}function ju(n,t){return n.value-t.value}function Hu(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Fu(n,t){n._pack_next=t,t._pack_prev=n}function Ou(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Yu(n){function t(n){l=Math.min(n.x-n.r,l),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(s=e.length)){var e,r,u,i,o,a,c,s,l=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(Iu),r=e[0],r.x=-r.r,r.y=0,t(r),s>1&&(u=e[1],u.x=u.r,u.y=0,t(u),s>2))for(i=e[2],Xu(r,u,i),t(i),Hu(r,i),r._pack_prev=i,Hu(i,u),u=r._pack_next,o=3;s>o;o++){Xu(r,u,i=e[o]);var p=0,v=1,d=1;for(a=u._pack_next;a!==u;a=a._pack_next,v++)if(Ou(a,i)){p=1;break}if(1==p)for(c=r._pack_prev;c!==a._pack_prev&&!Ou(c,i);c=c._pack_prev,d++);p?(d>v||v==d&&u.r<r.r?Fu(r,u=a):Fu(r=c,u),o--):(Hu(r,i),u=i,t(i))}var m=(l+f)/2,y=(h+g)/2,x=0;for(o=0;s>o;o++)i=e[o],i.x-=m,i.y-=y,x=Math.max(x,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=x,e.forEach(Zu)}}function Iu(n){n._pack_next=n._pack_prev=n}function Zu(n){delete n._pack_next,delete n._pack_prev}function Vu(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,o=u.length;++i<o;)Vu(u[i],t,e,r)}function Xu(n,t,e){var r=n.r+e.r,u=t.x-n.x,i=t.y-n.y;if(r&&(u||i)){var o=t.r+e.r,a=u*u+i*i;o*=o,r*=r;var c=.5+(r-o)/(2*a),s=Math.sqrt(Math.max(0,2*o*(r+a)-(r-=a)*r-o*o))/(2*a);e.x=n.x+c*u+s*i,e.y=n.y+c*i-s*u}else e.x=n.x+r,e.y=n.y}function $u(n){return 1+Zo.max(n,function(n){return n.y})}function Bu(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Wu(n){var t=n.children;return t&&t.length?Wu(t[0]):n}function Ju(n){var t,e=n.children;return e&&(t=e.length)?Ju(e[t-1]):n}function Gu(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ku(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Qu(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function ni(n){return n.rangeExtent?n.rangeExtent():Qu(n.range())}function ti(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function ei(n,t){var e,r=0,u=n.length-1,i=n[r],o=n[u];return i>o&&(e=r,r=u,u=e,e=i,i=o,o=e),n[r]=t.floor(i),n[u]=t.ceil(o),n}function ri(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:os}function ui(n,t,e,r){var u=[],i=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]<n[0]&&(n=n.slice().reverse(),t=t.slice().reverse());++o<=a;)u.push(e(n[o-1],n[o])),i.push(r(t[o-1],t[o]));return function(t){var e=Zo.bisect(n,t,1,a)-1;return i[e](u[e](t))}}function ii(n,t,e,r){function u(){var u=Math.min(n.length,t.length)>2?ui:ti,c=r?eu:tu;return o=u(n,t,c,e),a=u(t,n,c,Tr),i}function i(n){return o(n)}var o,a;return i.invert=function(n){return a(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Wr)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return si(n,t)},i.tickFormat=function(t,e){return li(n,t,e)},i.nice=function(t){return ai(n,t),u()},i.copy=function(){return ii(n,t,e,r)},u()}function oi(n,t){return Zo.rebind(n,t,"range","rangeRound","interpolate","clamp")}function ai(n,t){return ei(n,ri(ci(n,t)[2]))}function ci(n,t){null==t&&(t=10);var e=Qu(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function si(n,t){return Zo.range.apply(Zo,ci(n,t))}function li(n,t,e){var r=-Math.floor(Math.log(ci(n,t)[2])/Math.LN10+.01);return Zo.format(e?e.replace(tc,function(n,t,e,u,i,o,a,c,s,l){return[t,e,u,i,o,a,c,s||"."+(r-2*("%"===l)),l].join("")}):",."+r+"f")}function fi(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(u(t))}return o.invert=function(t){return i(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),o):t},o.nice=function(){var t=ei(r.map(u),e?Math:cs);return n.domain(t),r=t.map(i),o},o.ticks=function(){var n=Qu(r),o=[],a=n[0],c=n[1],s=Math.floor(u(a)),l=Math.ceil(u(c)),f=t%1?2:t;if(isFinite(l-s)){if(e){for(;l>s;s++)for(var h=1;f>h;h++)o.push(i(s)*h);o.push(i(s))}else for(o.push(i(s));s++<l;)for(var h=f-1;h>0;h--)o.push(i(s)*h);for(s=0;o[s]<a;s++);for(l=o.length;o[l-1]>c;l--);o=o.slice(s,l)}return o},o.tickFormat=function(n,t){if(!arguments.length)return as;arguments.length<2?t=as:"function"!=typeof t&&(t=Zo.format(t));var r,a=Math.max(.1,n/o.ticks().length),c=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(c(u(n)+r))<=a?t(n):""}},o.copy=function(){return fi(n.copy(),t,e,r)},oi(o,n)}function hi(n,t,e){function r(t){return n(u(t))}var u=gi(t),i=gi(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return si(e,n)},r.tickFormat=function(n,t){return li(e,n,t)},r.nice=function(n){return r.domain(ai(e,n))},r.exponent=function(o){return arguments.length?(u=gi(t=o),i=gi(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return hi(n.copy(),t,e)},oi(r,n)}function gi(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function pi(n,t){function e(e){return o[((i.get(e)||"range"===t.t&&i.set(e,n.push(e)))-1)%o.length]}function r(t,e){return Zo.range(n.length).map(function(n){return t+e*n})}var i,o,a;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new u;for(var o,a=-1,c=r.length;++a<c;)i.has(o=r[a])||i.set(o,n.push(o));return e[t.t].apply(e,t.a)},e.range=function(n){return arguments.length?(o=n,a=0,t={t:"range",a:arguments},e):o},e.rangePoints=function(u,i){arguments.length<2&&(i=0);var c=u[0],s=u[1],l=(s-c)/(Math.max(1,n.length-1)+i);return o=r(n.length<2?(c+s)/2:c+l*i/2,l),a=0,t={t:"rangePoints",a:arguments},e},e.rangeBands=function(u,i,c){arguments.length<2&&(i=0),arguments.length<3&&(c=i);var s=u[1]<u[0],l=u[s-0],f=u[1-s],h=(f-l)/(n.length-i+2*c);return o=r(l+h*c,h),s&&o.reverse(),a=h*(1-i),t={t:"rangeBands",a:arguments},e},e.rangeRoundBands=function(u,i,c){arguments.length<2&&(i=0),arguments.length<3&&(c=i);var s=u[1]<u[0],l=u[s-0],f=u[1-s],h=Math.floor((f-l)/(n.length-i+2*c)),g=f-l-(n.length-i)*h;return o=r(l+Math.round(g/2),h),s&&o.reverse(),a=Math.round(h*(1-i)),t={t:"rangeRoundBands",a:arguments},e},e.rangeBand=function(){return a},e.rangeExtent=function(){return Qu(t.a[0])},e.copy=function(){return pi(n,t)},e.domain(n)}function vi(n,t){function e(){var e=0,i=t.length;for(u=[];++e<i;)u[e-1]=Zo.quantile(n,e/i);return r}function r(n){return isNaN(n=+n)?void 0:t[Zo.bisect(u,n)]}var u;return r.domain=function(t){return arguments.length?(n=t.filter(function(n){return!isNaN(n)}).sort(Zo.ascending),e()):n},r.range=function(n){return arguments.length?(t=n,e()):t},r.quantiles=function(){return u},r.invertExtent=function(e){return e=t.indexOf(e),0>e?[0/0,0/0]:[e>0?u[e-1]:n[0],e<u.length?u[e]:n[n.length-1]]},r.copy=function(){return vi(n,t)},e()}function di(n,t,e){function r(t){return e[Math.max(0,Math.min(o,Math.floor(i*(t-n))))]}function u(){return i=e.length/(t-n),o=e.length-1,r}var i,o;return r.domain=function(e){return arguments.length?(n=+e[0],t=+e[e.length-1],u()):[n,t]},r.range=function(n){return arguments.length?(e=n,u()):e},r.invertExtent=function(t){return t=e.indexOf(t),t=0>t?0/0:t/i+n,[t,t+1/i]},r.copy=function(){return di(n,t,e)},u()}function mi(n,t){function e(e){return e>=e?t[Zo.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return mi(n,t)},e}function yi(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return si(n,t)},t.tickFormat=function(t,e){return li(n,t,e)},t.copy=function(){return yi(n)},t}function xi(n){return n.innerRadius}function Mi(n){return n.outerRadius}function _i(n){return n.startAngle}function bi(n){return n.endAngle}function wi(n){function t(t){function o(){s.push("M",i(n(l),a))}for(var c,s=[],l=[],f=-1,h=t.length,g=pt(e),p=pt(r);++f<h;)u.call(this,c=t[f],f)?l.push([+g.call(this,c,f),+p.call(this,c,f)]):l.length&&(o(),l=[]);return l.length&&o(),s.length?s.join(""):null}var e=Ie,r=Ze,u=Zt,i=Si,o=i.key,a=.7;return t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t.defined=function(n){return arguments.length?(u=n,t):u},t.interpolate=function(n){return arguments.length?(o="function"==typeof n?i=n:(i=vs.get(n)||Si).key,t):o},t.tension=function(n){return arguments.length?(a=n,t):a},t}function Si(n){return n.join("L")}function ki(n){return Si(n)+"Z"}function Ei(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("H",(r[0]+(r=n[t])[0])/2,"V",r[1]);return e>1&&u.push("H",r[0]),u.join("")}function Ai(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("V",(r=n[t])[1],"H",r[0]);return u.join("")}function Ci(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("H",(r=n[t])[0],"V",r[1]);return u.join("")}function Ni(n,t){return n.length<4?Si(n):n[1]+qi(n.slice(1,n.length-1),zi(n,t))}function Li(n,t){return n.length<3?Si(n):n[0]+qi((n.push(n[0]),n),zi([n[n.length-2]].concat(n,[n[1]]),t))}function Ti(n,t){return n.length<3?Si(n):n[0]+qi(n,zi(n,t))}function qi(n,t){if(t.length<1||n.length!=t.length&&n.length!=t.length+2)return Si(n);var e=n.length!=t.length,r="",u=n[0],i=n[1],o=t[0],a=o,c=1;if(e&&(r+="Q"+(i[0]-2*o[0]/3)+","+(i[1]-2*o[1]/3)+","+i[0]+","+i[1],u=n[1],c=2),t.length>1){a=t[1],i=n[c],c++,r+="C"+(u[0]+o[0])+","+(u[1]+o[1])+","+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1];for(var s=2;s<t.length;s++,c++)i=n[c],a=t[s],r+="S"+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1]}if(e){var l=n[c];r+="Q"+(i[0]+2*a[0]/3)+","+(i[1]+2*a[1]/3)+","+l[0]+","+l[1]}return r}function zi(n,t){for(var e,r=[],u=(1-t)/2,i=n[0],o=n[1],a=1,c=n.length;++a<c;)e=i,i=o,o=n[a],r.push([u*(o[0]-e[0]),u*(o[1]-e[1])]);return r}function Ri(n){if(n.length<3)return Si(n);var t=1,e=n.length,r=n[0],u=r[0],i=r[1],o=[u,u,u,(r=n[1])[0]],a=[i,i,i,r[1]],c=[u,",",i,"L",ji(ys,o),",",ji(ys,a)];for(n.push(n[e-1]);++t<=e;)r=n[t],o.shift(),o.push(r[0]),a.shift(),a.push(r[1]),Hi(c,o,a);return n.pop(),c.push("L",r),c.join("")}function Di(n){if(n.length<4)return Si(n);for(var t,e=[],r=-1,u=n.length,i=[0],o=[0];++r<3;)t=n[r],i.push(t[0]),o.push(t[1]);for(e.push(ji(ys,i)+","+ji(ys,o)),--r;++r<u;)t=n[r],i.shift(),i.push(t[0]),o.shift(),o.push(t[1]),Hi(e,i,o);return e.join("")}function Pi(n){for(var t,e,r=-1,u=n.length,i=u+4,o=[],a=[];++r<4;)e=n[r%u],o.push(e[0]),a.push(e[1]);for(t=[ji(ys,o),",",ji(ys,a)],--r;++r<i;)e=n[r%u],o.shift(),o.push(e[0]),a.shift(),a.push(e[1]),Hi(t,o,a);return t.join("")}function Ui(n,t){var e=n.length-1;if(e)for(var r,u,i=n[0][0],o=n[0][1],a=n[e][0]-i,c=n[e][1]-o,s=-1;++s<=e;)r=n[s],u=s/e,r[0]=t*r[0]+(1-t)*(i+u*a),r[1]=t*r[1]+(1-t)*(o+u*c);return Ri(n)}function ji(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]+n[3]*t[3]}function Hi(n,t,e){n.push("C",ji(ds,t),",",ji(ds,e),",",ji(ms,t),",",ji(ms,e),",",ji(ys,t),",",ji(ys,e))}function Fi(n,t){return(t[1]-n[1])/(t[0]-n[0])}function Oi(n){for(var t=0,e=n.length-1,r=[],u=n[0],i=n[1],o=r[0]=Fi(u,i);++t<e;)r[t]=(o+(o=Fi(u=i,i=n[t+1])))/2;return r[t]=o,r}function Yi(n){for(var t,e,r,u,i=[],o=Oi(n),a=-1,c=n.length-1;++a<c;)t=Fi(n[a],n[a+1]),ua(t)<ka?o[a]=o[a+1]=0:(e=o[a]/t,r=o[a+1]/t,u=e*e+r*r,u>9&&(u=3*t/Math.sqrt(u),o[a]=u*e,o[a+1]=u*r));for(a=-1;++a<=c;)u=(n[Math.min(c,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),i.push([u||0,o[a]*u||0]);return i}function Ii(n){return n.length<3?Si(n):n[0]+qi(n,Yi(n))}function Zi(n){for(var t,e,r,u=-1,i=n.length;++u<i;)t=n[u],e=t[0],r=t[1]+gs,t[0]=e*Math.cos(r),t[1]=e*Math.sin(r);return n}function Vi(n){function t(t){function c(){v.push("M",a(n(m),f),l,s(n(d.reverse()),f),"Z")}for(var h,g,p,v=[],d=[],m=[],y=-1,x=t.length,M=pt(e),_=pt(u),b=e===r?function(){return g}:pt(r),w=u===i?function(){return p}:pt(i);++y<x;)o.call(this,h=t[y],y)?(d.push([g=+M.call(this,h,y),p=+_.call(this,h,y)]),m.push([+b.call(this,h,y),+w.call(this,h,y)])):d.length&&(c(),d=[],m=[]);return d.length&&c(),v.length?v.join(""):null}var e=Ie,r=Ie,u=0,i=Ze,o=Zt,a=Si,c=a.key,s=a,l="L",f=.7;return t.x=function(n){return arguments.length?(e=r=n,t):r},t.x0=function(n){return arguments.length?(e=n,t):e},t.x1=function(n){return arguments.length?(r=n,t):r},t.y=function(n){return arguments.length?(u=i=n,t):i},t.y0=function(n){return arguments.length?(u=n,t):u},t.y1=function(n){return arguments.length?(i=n,t):i},t.defined=function(n){return arguments.length?(o=n,t):o},t.interpolate=function(n){return arguments.length?(c="function"==typeof n?a=n:(a=vs.get(n)||Si).key,s=a.reverse||a,l=a.closed?"M":"L",t):c},t.tension=function(n){return arguments.length?(f=n,t):f},t}function Xi(n){return n.radius}function $i(n){return[n.x,n.y]}function Bi(n){return function(){var t=n.apply(this,arguments),e=t[0],r=t[1]+gs;return[e*Math.cos(r),e*Math.sin(r)]}}function Wi(){return 64}function Ji(){return"circle"}function Gi(n){var t=Math.sqrt(n/ba);return"M0,"+t+"A"+t+","+t+" 0 1,1 0,"+-t+"A"+t+","+t+" 0 1,1 0,"+t+"Z"}function Ki(n,t){return sa(n,Ss),n.id=t,n}function Qi(n,t,e,r){var u=n.id;return C(n,"function"==typeof e?function(n,i,o){n.__transition__[u].tween.set(t,r(e.call(n,n.__data__,i,o)))}:(e=r(e),function(n){n.__transition__[u].tween.set(t,e)}))}function no(n){return null==n&&(n=""),function(){this.textContent=n}}function to(n,t,e,r){var i=n.__transition__||(n.__transition__={active:0,count:0}),o=i[e];if(!o){var a=r.time;o=i[e]={tween:new u,time:a,ease:r.ease,delay:r.delay,duration:r.duration},++i.count,Zo.timer(function(r){function u(r){return i.active>e?s():(i.active=e,o.event&&o.event.start.call(n,l,t),o.tween.forEach(function(e,r){(r=r.call(n,l,t))&&v.push(r)}),Zo.timer(function(){return p.c=c(r||1)?Zt:c,1},0,a),void 0)}function c(r){if(i.active!==e)return s();for(var u=r/g,a=f(u),c=v.length;c>0;)v[--c].call(n,a);return u>=1?(o.event&&o.event.end.call(n,l,t),s()):void 0}function s(){return--i.count?delete i[e]:delete n.__transition__,1}var l=n.__data__,f=o.ease,h=o.delay,g=o.duration,p=Ba,v=[];return p.t=h+a,r>=h?u(r-h):(p.c=u,void 0)},0,a)}}function eo(n,t){n.attr("transform",function(n){return"translate("+t(n)+",0)"})}function ro(n,t){n.attr("transform",function(n){return"translate(0,"+t(n)+")"})}function uo(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function io(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new Ts(e-1)),1),e}function i(n,e){return t(n=new Ts(+n),e),n}function o(n,r,i){var o=u(n),a=[];if(i>1)for(;r>o;)e(o)%i||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{Ts=uo;var r=new uo;return r._=n,o(r,t,e)}finally{Ts=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=o;var c=n.utc=oo(n);return c.floor=c,c.round=oo(r),c.ceil=oo(u),c.offset=oo(i),c.range=a,n}function oo(n){return function(t,e){try{Ts=uo;var r=new uo;return r._=t,n(r,e)._}finally{Ts=Date}}}function ao(n){function t(t){for(var r,u,i,o=[],a=-1,c=0;++a<e;)37===n.charCodeAt(a)&&(o.push(n.substring(c,a)),null!=(u=Js[r=n.charAt(++a)])&&(r=n.charAt(++a)),(i=Gs[r])&&(r=i(t,null==u?"e"===r?" ":"0":u)),o.push(r),c=a+1);return o.push(n.substring(c,a)),o.join("")}var e=n.length;return t.parse=function(t){var e={y:1900,m:0,d:1,H:0,M:0,S:0,L:0,Z:null},r=co(e,n,t,0);if(r!=t.length)return null;"p"in e&&(e.H=e.H%12+12*e.p);var u=null!=e.Z&&Ts!==uo,i=new(u?uo:Ts);return"j"in e?i.setFullYear(e.y,0,e.j):"w"in e&&("W"in e||"U"in e)?(i.setFullYear(e.y,0,1),i.setFullYear(e.y,0,"W"in e?(e.w+6)%7+7*e.W-(i.getDay()+5)%7:e.w+7*e.U-(i.getDay()+6)%7)):i.setFullYear(e.y,e.m,e.d),i.setHours(e.H+Math.floor(e.Z/100),e.M+e.Z%100,e.S,e.L),u?i._:i},t.toString=function(){return n},t}function co(n,t,e,r){for(var u,i,o,a=0,c=t.length,s=e.length;c>a;){if(r>=s)return-1;if(u=t.charCodeAt(a++),37===u){if(o=t.charAt(a++),i=Ks[o in Js?t.charAt(a++):o],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function so(n){return new RegExp("^(?:"+n.map(Zo.requote).join("|")+")","i")}function lo(n){for(var t=new u,e=-1,r=n.length;++e<r;)t.set(n[e].toLowerCase(),e);return t}function fo(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function ho(n,t,e){Is.lastIndex=0;var r=Is.exec(t.substring(e));return r?(n.w=Zs.get(r[0].toLowerCase()),e+r[0].length):-1}function go(n,t,e){Os.lastIndex=0;var r=Os.exec(t.substring(e));return r?(n.w=Ys.get(r[0].toLowerCase()),e+r[0].length):-1}function po(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e,e+1));return r?(n.w=+r[0],e+r[0].length):-1}function vo(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e));return r?(n.U=+r[0],e+r[0].length):-1}function mo(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e));return r?(n.W=+r[0],e+r[0].length):-1}function yo(n,t,e){$s.lastIndex=0;var r=$s.exec(t.substring(e));return r?(n.m=Bs.get(r[0].toLowerCase()),e+r[0].length):-1}function xo(n,t,e){Vs.lastIndex=0;var r=Vs.exec(t.substring(e));return r?(n.m=Xs.get(r[0].toLowerCase()),e+r[0].length):-1}function Mo(n,t,e){return co(n,Gs.c.toString(),t,e)}function _o(n,t,e){return co(n,Gs.x.toString(),t,e)}function bo(n,t,e){return co(n,Gs.X.toString(),t,e)}function wo(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e,e+4));return r?(n.y=+r[0],e+r[0].length):-1}function So(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e,e+2));return r?(n.y=Eo(+r[0]),e+r[0].length):-1}function ko(n,t,e){return/^[+-]\d{4}$/.test(t=t.substring(e,e+5))?(n.Z=+t,e+5):-1}function Eo(n){return n+(n>68?1900:2e3)}function Ao(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function Co(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function No(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function Lo(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function To(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function qo(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function zo(n,t,e){Qs.lastIndex=0;var r=Qs.exec(t.substring(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function Ro(n,t,e){var r=nl.get(t.substring(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}function Do(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=~~(ua(t)/60),u=ua(t)%60;return e+fo(r,"0",2)+fo(u,"0",2)}function Po(n,t,e){Ws.lastIndex=0;var r=Ws.exec(t.substring(e,e+1));return r?e+r[0].length:-1}function Uo(n){function t(n){try{Ts=uo;var t=new Ts;return t._=n,e(t)}finally{Ts=Date}}var e=ao(n);return t.parse=function(n){try{Ts=uo;var t=e.parse(n);return t&&t._}finally{Ts=Date}},t.toString=e.toString,t}function jo(n){return n.toISOString()}function Ho(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=Zo.bisect(el,u);return i==el.length?[t.year,ci(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/el[i-1]<el[i]/u?i-1:i]:[ol,ci(n,e)[2]]}return r.invert=function(t){return Fo(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain(t),r):n.domain().map(Fo)},r.nice=function(n,t){function e(e){return!isNaN(e)&&!n.range(e,Fo(+e+1),t).length}var i=r.domain(),o=Qu(i),a=null==n?u(o,10):"number"==typeof n&&u(o,n);return a&&(n=a[0],t=a[1]),r.domain(ei(i,t>1?{floor:function(t){for(;e(t=n.floor(t));)t=Fo(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=Fo(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Qu(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],Fo(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return Ho(n.copy(),t,e)},oi(r,n)}function Fo(n){return new Date(n)}function Oo(n){return function(t){for(var e=n.length-1,r=n[e];!r[1](t);)r=n[--e];return r[0](t)}}function Yo(n){return JSON.parse(n.responseText)}function Io(n){var t=$o.createRange();return t.selectNode($o.body),t.createContextualFragment(n.responseText)}var Zo={version:"3.3.8"};Date.now||(Date.now=function(){return+new Date});var Vo=[].slice,Xo=function(n){return Vo.call(n)},$o=document,Bo=$o.documentElement,Wo=window;try{Xo(Bo.childNodes)[0].nodeType}catch(Jo){Xo=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}try{$o.createElement("div").style.setProperty("opacity",0,"")}catch(Go){var Ko=Wo.Element.prototype,Qo=Ko.setAttribute,na=Ko.setAttributeNS,ta=Wo.CSSStyleDeclaration.prototype,ea=ta.setProperty;Ko.setAttribute=function(n,t){Qo.call(this,n,t+"")},Ko.setAttributeNS=function(n,t,e){na.call(this,n,t,e+"")},ta.setProperty=function(n,t,e){ea.call(this,n,t+"",e)}}Zo.ascending=function(n,t){return t>n?-1:n>t?1:n>=t?0:0/0},Zo.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},Zo.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u<i&&!(null!=(e=n[u])&&e>=e);)e=void 0;for(;++u<i;)null!=(r=n[u])&&e>r&&(e=r)}else{for(;++u<i&&!(null!=(e=t.call(n,n[u],u))&&e>=e);)e=void 0;for(;++u<i;)null!=(r=t.call(n,n[u],u))&&e>r&&(e=r)}return e},Zo.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u<i&&!(null!=(e=n[u])&&e>=e);)e=void 0;for(;++u<i;)null!=(r=n[u])&&r>e&&(e=r)}else{for(;++u<i&&!(null!=(e=t.call(n,n[u],u))&&e>=e);)e=void 0;for(;++u<i;)null!=(r=t.call(n,n[u],u))&&r>e&&(e=r)}return e},Zo.extent=function(n,t){var e,r,u,i=-1,o=n.length;if(1===arguments.length){for(;++i<o&&!(null!=(e=u=n[i])&&e>=e);)e=u=void 0;for(;++i<o;)null!=(r=n[i])&&(e>r&&(e=r),r>u&&(u=r))}else{for(;++i<o&&!(null!=(e=u=t.call(n,n[i],i))&&e>=e);)e=void 0;for(;++i<o;)null!=(r=t.call(n,n[i],i))&&(e>r&&(e=r),r>u&&(u=r))}return[e,u]},Zo.sum=function(n,t){var e,r=0,u=n.length,i=-1;if(1===arguments.length)for(;++i<u;)isNaN(e=+n[i])||(r+=e);else for(;++i<u;)isNaN(e=+t.call(n,n[i],i))||(r+=e);return r},Zo.mean=function(t,e){var r,u=t.length,i=0,o=-1,a=0;if(1===arguments.length)for(;++o<u;)n(r=t[o])&&(i+=(r-i)/++a);else for(;++o<u;)n(r=e.call(t,t[o],o))&&(i+=(r-i)/++a);return a?i:void 0},Zo.quantile=function(n,t){var e=(n.length-1)*t+1,r=Math.floor(e),u=+n[r-1],i=e-r;return i?u+i*(n[r]-u):u},Zo.median=function(t,e){return arguments.length>1&&(t=t.map(e)),t=t.filter(n),t.length?Zo.quantile(t.sort(Zo.ascending),.5):void 0},Zo.bisector=function(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;
+n.call(t,t[i],i)<e?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;e<n.call(t,t[i],i)?u=i:r=i+1}return r}}};var ra=Zo.bisector(function(n){return n});Zo.bisectLeft=ra.left,Zo.bisect=Zo.bisectRight=ra.right,Zo.shuffle=function(n){for(var t,e,r=n.length;r;)e=0|Math.random()*r--,t=n[r],n[r]=n[e],n[e]=t;return n},Zo.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},Zo.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},Zo.zip=function(){if(!(u=arguments.length))return[];for(var n=-1,e=Zo.min(arguments,t),r=new Array(e);++n<e;)for(var u,i=-1,o=r[n]=new Array(u);++i<u;)o[i]=arguments[i][n];return r},Zo.transpose=function(n){return Zo.zip.apply(Zo,n)},Zo.keys=function(n){var t=[];for(var e in n)t.push(e);return t},Zo.values=function(n){var t=[];for(var e in n)t.push(n[e]);return t},Zo.entries=function(n){var t=[];for(var e in n)t.push({key:e,value:n[e]});return t},Zo.merge=function(n){for(var t,e,r,u=n.length,i=-1,o=0;++i<u;)o+=n[i].length;for(e=new Array(o);--u>=0;)for(r=n[u],t=r.length;--t>=0;)e[--o]=r[t];return e};var ua=Math.abs;Zo.range=function(n,t,r){if(arguments.length<3&&(r=1,arguments.length<2&&(t=n,n=0)),1/0===(t-n)/r)throw new Error("infinite range");var u,i=[],o=e(ua(r)),a=-1;if(n*=o,t*=o,r*=o,0>r)for(;(u=n+r*++a)>t;)i.push(u/o);else for(;(u=n+r*++a)<t;)i.push(u/o);return i},Zo.map=function(n){var t=new u;if(n instanceof u)n.forEach(function(n,e){t.set(n,e)});else for(var e in n)t.set(e,n[e]);return t},r(u,{has:function(n){return ia+n in this},get:function(n){return this[ia+n]},set:function(n,t){return this[ia+n]=t},remove:function(n){return n=ia+n,n in this&&delete this[n]},keys:function(){var n=[];return this.forEach(function(t){n.push(t)}),n},values:function(){var n=[];return this.forEach(function(t,e){n.push(e)}),n},entries:function(){var n=[];return this.forEach(function(t,e){n.push({key:t,value:e})}),n},forEach:function(n){for(var t in this)t.charCodeAt(0)===oa&&n.call(this,t.substring(1),this[t])}});var ia="\x00",oa=ia.charCodeAt(0);Zo.nest=function(){function n(t,a,c){if(c>=o.length)return r?r.call(i,a):e?a.sort(e):a;for(var s,l,f,h,g=-1,p=a.length,v=o[c++],d=new u;++g<p;)(h=d.get(s=v(l=a[g])))?h.push(l):d.set(s,[l]);return t?(l=t(),f=function(e,r){l.set(e,n(t,r,c))}):(l={},f=function(e,r){l[e]=n(t,r,c)}),d.forEach(f),l}function t(n,e){if(e>=o.length)return n;var r=[],u=a[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,i={},o=[],a=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(Zo.map,e,0),0)},i.key=function(n){return o.push(n),i},i.sortKeys=function(n){return a[o.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},Zo.set=function(n){var t=new i;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},r(i,{has:function(n){return ia+n in this},add:function(n){return this[ia+n]=!0,n},remove:function(n){return n=ia+n,n in this&&delete this[n]},values:function(){var n=[];return this.forEach(function(t){n.push(t)}),n},forEach:function(n){for(var t in this)t.charCodeAt(0)===oa&&n.call(this,t.substring(1))}}),Zo.behavior={},Zo.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r<u;)n[e=arguments[r]]=o(n,t,t[e]);return n};var aa=["webkit","ms","moz","Moz","o","O"];Zo.dispatch=function(){for(var n=new s,t=-1,e=arguments.length;++t<e;)n[arguments[t]]=l(n);return n},s.prototype.on=function(n,t){var e=n.indexOf("."),r="";if(e>=0&&(r=n.substring(e+1),n=n.substring(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},Zo.event=null,Zo.requote=function(n){return n.replace(ca,"\\$&")};var ca=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,sa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},la=function(n,t){return t.querySelector(n)},fa=function(n,t){return t.querySelectorAll(n)},ha=Bo[a(Bo,"matchesSelector")],ga=function(n,t){return ha.call(n,t)};"function"==typeof Sizzle&&(la=function(n,t){return Sizzle(n,t)[0]||null},fa=function(n,t){return Sizzle.uniqueSort(Sizzle(n,t))},ga=Sizzle.matchesSelector),Zo.selection=function(){return ma};var pa=Zo.selection.prototype=[];pa.select=function(n){var t,e,r,u,i=[];n=v(n);for(var o=-1,a=this.length;++o<a;){i.push(t=[]),t.parentNode=(r=this[o]).parentNode;for(var c=-1,s=r.length;++c<s;)(u=r[c])?(t.push(e=n.call(u,u.__data__,c,o)),e&&"__data__"in u&&(e.__data__=u.__data__)):t.push(null)}return p(i)},pa.selectAll=function(n){var t,e,r=[];n=d(n);for(var u=-1,i=this.length;++u<i;)for(var o=this[u],a=-1,c=o.length;++a<c;)(e=o[a])&&(r.push(t=Xo(n.call(e,e.__data__,a,u))),t.parentNode=e);return p(r)};var va={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};Zo.ns={prefix:va,qualify:function(n){var t=n.indexOf(":"),e=n;return t>=0&&(e=n.substring(0,t),n=n.substring(t+1)),va.hasOwnProperty(e)?{space:va[e],local:n}:n}},pa.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=Zo.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(m(t,n[t]));return this}return this.each(m(n,t))},pa.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=n.trim().split(/^|\s+/g)).length,u=-1;if(t=e.classList){for(;++u<r;)if(!t.contains(n[u]))return!1}else for(t=e.getAttribute("class");++u<r;)if(!x(n[u]).test(t))return!1;return!0}for(t in n)this.each(M(t,n[t]));return this}return this.each(M(n,t))},pa.style=function(n,t,e){var r=arguments.length;if(3>r){if("string"!=typeof n){2>r&&(t="");for(e in n)this.each(b(e,n[e],t));return this}if(2>r)return Wo.getComputedStyle(this.node(),null).getPropertyValue(n);e=""}return this.each(b(n,t,e))},pa.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(w(t,n[t]));return this}return this.each(w(n,t))},pa.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},pa.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},pa.append=function(n){return n=S(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},pa.insert=function(n,t){return n=S(n),t=v(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},pa.remove=function(){return this.each(function(){var n=this.parentNode;n&&n.removeChild(this)})},pa.data=function(n,t){function e(n,e){var r,i,o,a=n.length,f=e.length,h=Math.min(a,f),g=new Array(f),p=new Array(f),v=new Array(a);if(t){var d,m=new u,y=new u,x=[];for(r=-1;++r<a;)d=t.call(i=n[r],i.__data__,r),m.has(d)?v[r]=i:m.set(d,i),x.push(d);for(r=-1;++r<f;)d=t.call(e,o=e[r],r),(i=m.get(d))?(g[r]=i,i.__data__=o):y.has(d)||(p[r]=k(o)),y.set(d,o),m.remove(d);for(r=-1;++r<a;)m.has(x[r])&&(v[r]=n[r])}else{for(r=-1;++r<h;)i=n[r],o=e[r],i?(i.__data__=o,g[r]=i):p[r]=k(o);for(;f>r;++r)p[r]=k(e[r]);for(;a>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,c.push(p),s.push(g),l.push(v)}var r,i,o=-1,a=this.length;if(!arguments.length){for(n=new Array(a=(r=this[0]).length);++o<a;)(i=r[o])&&(n[o]=i.__data__);return n}var c=N([]),s=p([]),l=p([]);if("function"==typeof n)for(;++o<a;)e(r=this[o],n.call(r,r.parentNode.__data__,o));else for(;++o<a;)e(r=this[o],n);return s.enter=function(){return c},s.exit=function(){return l},s},pa.datum=function(n){return arguments.length?this.property("__data__",n):this.property("__data__")},pa.filter=function(n){var t,e,r,u=[];"function"!=typeof n&&(n=E(n));for(var i=0,o=this.length;o>i;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a)&&t.push(r)}return p(u)},pa.order=function(){for(var n=-1,t=this.length;++n<t;)for(var e,r=this[n],u=r.length-1,i=r[u];--u>=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},pa.sort=function(n){n=A.apply(this,arguments);for(var t=-1,e=this.length;++t<e;)this[t].sort(n);return this.order()},pa.each=function(n){return C(this,function(t,e,r){n.call(t,t.__data__,e,r)})},pa.call=function(n){var t=Xo(arguments);return n.apply(t[0]=this,t),this},pa.empty=function(){return!this.node()},pa.node=function(){for(var n=0,t=this.length;t>n;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},pa.size=function(){var n=0;return this.each(function(){++n}),n};var da=[];Zo.selection.enter=N,Zo.selection.enter.prototype=da,da.append=pa.append,da.empty=pa.empty,da.node=pa.node,da.call=pa.call,da.size=pa.size,da.select=function(n){for(var t,e,r,u,i,o=[],a=-1,c=this.length;++a<c;){r=(u=this[a]).update,o.push(t=[]),t.parentNode=u.parentNode;for(var s=-1,l=u.length;++s<l;)(i=u[s])?(t.push(r[s]=e=n.call(u.parentNode,i.__data__,s,a)),e.__data__=i.__data__):t.push(null)}return p(o)},da.insert=function(n,t){return arguments.length<2&&(t=L(this)),pa.insert.call(this,n,t)},pa.transition=function(){for(var n,t,e=Ms||++ks,r=[],u=_s||{time:Date.now(),ease:jr,delay:0,duration:250},i=-1,o=this.length;++i<o;){r.push(n=[]);for(var a=this[i],c=-1,s=a.length;++c<s;)(t=a[c])&&to(t,c,e,u),n.push(t)}return Ki(r,e)},pa.interrupt=function(){return this.each(T)},Zo.select=function(n){var t=["string"==typeof n?la(n,$o):n];return t.parentNode=Bo,p([t])},Zo.selectAll=function(n){var t=Xo("string"==typeof n?fa(n,$o):n);return t.parentNode=Bo,p([t])};var ma=Zo.select(Bo);pa.on=function(n,t,e){var r=arguments.length;if(3>r){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(q(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(q(n,t,e))};var ya=Zo.map({mouseenter:"mouseover",mouseleave:"mouseout"});ya.forEach(function(n){"on"+n in $o&&ya.remove(n)});var xa=a(Bo.style,"userSelect"),Ma=0;Zo.mouse=function(n){return P(n,h())};var _a=/WebKit/.test(Wo.navigator.userAgent)?-1:0;Zo.touches=function(n,t){return arguments.length<2&&(t=h().touches),t?Xo(t).map(function(t){var e=P(n,t);return e.identifier=t.identifier,e}):[]},Zo.behavior.drag=function(){function n(){this.on("mousedown.drag",o).on("touchstart.drag",a)}function t(){return Zo.event.changedTouches[0].identifier}function e(n,t){return Zo.touches(n).filter(function(n){return n.identifier===t})[0]}function r(n,t,e,r){return function(){function o(){var n=t(l,g),e=n[0]-v[0],r=n[1]-v[1];d|=e|r,v=n,f({type:"drag",x:n[0]+c[0],y:n[1]+c[1],dx:e,dy:r})}function a(){m.on(e+"."+p,null).on(r+"."+p,null),y(d&&Zo.event.target===h),f({type:"dragend"})}var c,s=this,l=s.parentNode,f=u.of(s,arguments),h=Zo.event.target,g=n(),p=null==g?"drag":"drag-"+g,v=t(l,g),d=0,m=Zo.select(Wo).on(e+"."+p,o).on(r+"."+p,a),y=D();i?(c=i.apply(s,arguments),c=[c.x-v[0],c.y-v[1]]):c=[0,0],f({type:"dragstart"})}}var u=g(n,"drag","dragstart","dragend"),i=null,o=r(c,Zo.mouse,"mousemove","mouseup"),a=r(t,e,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},Zo.rebind(n,u,"on")};var ba=Math.PI,wa=2*ba,Sa=ba/2,ka=1e-6,Ea=ka*ka,Aa=ba/180,Ca=180/ba,Na=Math.SQRT2,La=2,Ta=4;Zo.interpolateZoom=function(n,t){function e(n){var t=n*y;if(m){var e=O(v),o=i/(La*h)*(e*Y(Na*t+v)-F(v));return[r+o*s,u+o*l,i*e/O(Na*t+v)]}return[r+n*s,u+n*l,i*Math.exp(Na*t)]}var r=n[0],u=n[1],i=n[2],o=t[0],a=t[1],c=t[2],s=o-r,l=a-u,f=s*s+l*l,h=Math.sqrt(f),g=(c*c-i*i+Ta*f)/(2*i*La*h),p=(c*c-i*i-Ta*f)/(2*c*La*h),v=Math.log(Math.sqrt(g*g+1)-g),d=Math.log(Math.sqrt(p*p+1)-p),m=d-v,y=(m||Math.log(c/i))/Na;return e.duration=1e3*y,e},Zo.behavior.zoom=function(){function n(n){n.on(A,s).on(Ra+".zoom",h).on(C,p).on("dblclick.zoom",v).on(L,l)}function t(n){return[(n[0]-S.x)/S.k,(n[1]-S.y)/S.k]}function e(n){return[n[0]*S.k+S.x,n[1]*S.k+S.y]}function r(n){S.k=Math.max(E[0],Math.min(E[1],n))}function u(n,t){t=e(t),S.x+=n[0]-t[0],S.y+=n[1]-t[1]}function i(){_&&_.domain(M.range().map(function(n){return(n-S.x)/S.k}).map(M.invert)),w&&w.domain(b.range().map(function(n){return(n-S.y)/S.k}).map(b.invert))}function o(n){n({type:"zoomstart"})}function a(n){i(),n({type:"zoom",scale:S.k,translate:[S.x,S.y]})}function c(n){n({type:"zoomend"})}function s(){function n(){l=1,u(Zo.mouse(r),h),a(i)}function e(){f.on(C,Wo===r?p:null).on(N,null),g(l&&Zo.event.target===s),c(i)}var r=this,i=q.of(r,arguments),s=Zo.event.target,l=0,f=Zo.select(Wo).on(C,n).on(N,e),h=t(Zo.mouse(r)),g=D();T.call(r),o(i)}function l(){function n(){var n=Zo.touches(p);return g=S.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=t(n))}),n}function e(){for(var t=Zo.event.changedTouches,e=0,i=t.length;i>e;++e)d[t[e].identifier]=null;var o=n(),c=Date.now();if(1===o.length){if(500>c-x){var s=o[0],l=d[s.identifier];r(2*S.k),u(s,l),f(),a(v)}x=c}else if(o.length>1){var s=o[0],h=o[1],g=s[0]-h[0],p=s[1]-h[1];m=g*g+p*p}}function i(){for(var n,t,e,i,o=Zo.touches(p),c=0,s=o.length;s>c;++c,i=null)if(e=o[c],i=d[e.identifier]){if(t)break;n=e,t=i}if(i){var l=(l=e[0]-n[0])*l+(l=e[1]-n[1])*l,f=m&&Math.sqrt(l/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+i[0])/2,(t[1]+i[1])/2],r(f*g)}x=null,u(n,t),a(v)}function h(){if(Zo.event.touches.length){for(var t=Zo.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}b.on(M,null).on(_,null),w.on(A,s).on(L,l),k(),c(v)}var g,p=this,v=q.of(p,arguments),d={},m=0,y=Zo.event.changedTouches[0].identifier,M="touchmove.zoom-"+y,_="touchend.zoom-"+y,b=Zo.select(Wo).on(M,i).on(_,h),w=Zo.select(p).on(A,null).on(L,e),k=D();T.call(p),e(),o(v)}function h(){var n=q.of(this,arguments);y?clearTimeout(y):(T.call(this),o(n)),y=setTimeout(function(){y=null,c(n)},50),f();var e=m||Zo.mouse(this);d||(d=t(e)),r(Math.pow(2,.002*qa())*S.k),u(e,d),a(n)}function p(){d=null}function v(){var n=q.of(this,arguments),e=Zo.mouse(this),i=t(e),s=Math.log(S.k)/Math.LN2;o(n),r(Math.pow(2,Zo.event.shiftKey?Math.ceil(s)-1:Math.floor(s)+1)),u(e,i),a(n),c(n)}var d,m,y,x,M,_,b,w,S={x:0,y:0,k:1},k=[960,500],E=za,A="mousedown.zoom",C="mousemove.zoom",N="mouseup.zoom",L="touchstart.zoom",q=g(n,"zoomstart","zoom","zoomend");return n.event=function(n){n.each(function(){var n=q.of(this,arguments),t=S;Ms?Zo.select(this).transition().each("start.zoom",function(){S=this.__chart__||{x:0,y:0,k:1},o(n)}).tween("zoom:zoom",function(){var e=k[0],r=k[1],u=e/2,i=r/2,o=Zo.interpolateZoom([(u-S.x)/S.k,(i-S.y)/S.k,e/S.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=o(t),c=e/r[2];this.__chart__=S={x:u-r[0]*c,y:i-r[1]*c,k:c},a(n)}}).each("end.zoom",function(){c(n)}):(this.__chart__=S,o(n),a(n),c(n))})},n.translate=function(t){return arguments.length?(S={x:+t[0],y:+t[1],k:S.k},i(),n):[S.x,S.y]},n.scale=function(t){return arguments.length?(S={x:S.x,y:S.y,k:+t},i(),n):S.k},n.scaleExtent=function(t){return arguments.length?(E=null==t?za:[+t[0],+t[1]],n):E},n.center=function(t){return arguments.length?(m=t&&[+t[0],+t[1]],n):m},n.size=function(t){return arguments.length?(k=t&&[+t[0],+t[1]],n):k},n.x=function(t){return arguments.length?(_=t,M=t.copy(),S={x:0,y:0,k:1},n):_},n.y=function(t){return arguments.length?(w=t,b=t.copy(),S={x:0,y:0,k:1},n):w},Zo.rebind(n,q,"on")};var qa,za=[0,1/0],Ra="onwheel"in $o?(qa=function(){return-Zo.event.deltaY*(Zo.event.deltaMode?120:1)},"wheel"):"onmousewheel"in $o?(qa=function(){return Zo.event.wheelDelta},"mousewheel"):(qa=function(){return-Zo.event.detail},"MozMousePixelScroll");Z.prototype.toString=function(){return this.rgb()+""},Zo.hsl=function(n,t,e){return 1===arguments.length?n instanceof X?V(n.h,n.s,n.l):st(""+n,lt,V):V(+n,+t,+e)};var Da=X.prototype=new Z;Da.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),V(this.h,this.s,this.l/n)},Da.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),V(this.h,this.s,n*this.l)},Da.rgb=function(){return $(this.h,this.s,this.l)},Zo.hcl=function(n,t,e){return 1===arguments.length?n instanceof W?B(n.h,n.c,n.l):n instanceof K?nt(n.l,n.a,n.b):nt((n=ft((n=Zo.rgb(n)).r,n.g,n.b)).l,n.a,n.b):B(+n,+t,+e)};var Pa=W.prototype=new Z;Pa.brighter=function(n){return B(this.h,this.c,Math.min(100,this.l+Ua*(arguments.length?n:1)))},Pa.darker=function(n){return B(this.h,this.c,Math.max(0,this.l-Ua*(arguments.length?n:1)))},Pa.rgb=function(){return J(this.h,this.c,this.l).rgb()},Zo.lab=function(n,t,e){return 1===arguments.length?n instanceof K?G(n.l,n.a,n.b):n instanceof W?J(n.l,n.c,n.h):ft((n=Zo.rgb(n)).r,n.g,n.b):G(+n,+t,+e)};var Ua=18,ja=.95047,Ha=1,Fa=1.08883,Oa=K.prototype=new Z;Oa.brighter=function(n){return G(Math.min(100,this.l+Ua*(arguments.length?n:1)),this.a,this.b)},Oa.darker=function(n){return G(Math.max(0,this.l-Ua*(arguments.length?n:1)),this.a,this.b)},Oa.rgb=function(){return Q(this.l,this.a,this.b)},Zo.rgb=function(n,t,e){return 1===arguments.length?n instanceof at?ot(n.r,n.g,n.b):st(""+n,ot,$):ot(~~n,~~t,~~e)};var Ya=at.prototype=new Z;Ya.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),ot(Math.min(255,~~(t/n)),Math.min(255,~~(e/n)),Math.min(255,~~(r/n)))):ot(u,u,u)},Ya.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),ot(~~(n*this.r),~~(n*this.g),~~(n*this.b))},Ya.hsl=function(){return lt(this.r,this.g,this.b)},Ya.toString=function(){return"#"+ct(this.r)+ct(this.g)+ct(this.b)};var Ia=Zo.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});Ia.forEach(function(n,t){Ia.set(n,ut(t))}),Zo.functor=pt,Zo.xhr=dt(vt),Zo.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var o=Zo.xhr(n,t,i);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:u(n)):e},o.row(e)}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function o(t){return t.map(a).join(n)}function a(n){return c.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var c=new RegExp('["'+n+"\n]"),s=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(l>=c)return o;if(u)return u=!1,i;var t=l;if(34===n.charCodeAt(t)){for(var e=t;e++<c;)if(34===n.charCodeAt(e)){if(34!==n.charCodeAt(e+1))break;++e}l=e+2;var r=n.charCodeAt(e+1);return 13===r?(u=!0,10===n.charCodeAt(e+2)&&++l):10===r&&(u=!0),n.substring(t+1,e).replace(/""/g,'"')}for(;c>l;){var r=n.charCodeAt(l++),a=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(l)&&(++l,++a);else if(r!==s)continue;return n.substring(t,l-a)}return n.substring(t)}for(var r,u,i={},o={},a=[],c=n.length,l=0,f=0;(r=e())!==o;){for(var h=[];r!==i&&r!==o;)h.push(r),r=e();(!t||(h=t(h,f++)))&&a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new i,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(a).join(n)].concat(t.map(function(t){return u.map(function(n){return a(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(o).join("\n")},e},Zo.csv=Zo.dsv(",","text/csv"),Zo.tsv=Zo.dsv("	","text/tab-separated-values");var Za,Va,Xa,$a,Ba,Wa=Wo[a(Wo,"requestAnimationFrame")]||function(n){setTimeout(n,17)};Zo.timer=function(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,f:!1,n:null};Va?Va.n=i:Za=i,Va=i,Xa||($a=clearTimeout($a),Xa=1,Wa(xt))},Zo.timer.flush=function(){Mt(),_t()};var Ja=".",Ga=",",Ka=[3,3],Qa="$",nc=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(bt);Zo.formatPrefix=function(n,t){var e=0;return n&&(0>n&&(n*=-1),t&&(n=Zo.round(n,wt(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((0>=e?e+1:e-1)/3)))),nc[8+e/3]},Zo.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)},Zo.format=function(n){var t=tc.exec(n),e=t[1]||" ",r=t[2]||">",u=t[3]||"",i=t[4]||"",o=t[5],a=+t[6],c=t[7],s=t[8],l=t[9],f=1,h="",g=!1;switch(s&&(s=+s.substring(1)),(o||"0"===e&&"="===r)&&(o=e="0",r="=",c&&(a-=Math.floor((a-1)/4))),l){case"n":c=!0,l="g";break;case"%":f=100,h="%",l="f";break;case"p":f=100,h="%",l="r";break;case"b":case"o":case"x":case"X":"#"===i&&(i="0"+l.toLowerCase());case"c":case"d":g=!0,s=0;break;case"s":f=-1,l="r"}"#"===i?i="":"$"===i&&(i=Qa),"r"!=l||s||(l="g"),null!=s&&("g"==l?s=Math.max(1,Math.min(21,s)):("e"==l||"f"==l)&&(s=Math.max(0,Math.min(20,s)))),l=ec.get(l)||St;var p=o&&c;return function(n){if(g&&n%1)return"";var t=0>n||0===n&&0>1/n?(n=-n,"-"):u;if(0>f){var v=Zo.formatPrefix(n,s);n=v.scale(n),h=v.symbol}else n*=f;n=l(n,s);var d=n.lastIndexOf("."),m=0>d?n:n.substring(0,d),y=0>d?"":Ja+n.substring(d+1);!o&&c&&(m=rc(m));var x=i.length+m.length+y.length+(p?0:t.length),M=a>x?new Array(x=a-x+1).join(e):"";return p&&(m=rc(M+m)),t+=i,n=m+y,("<"===r?t+n+M:">"===r?M+t+n:"^"===r?M.substring(0,x>>=1)+t+n+M.substring(x):t+(p?n:M+n))+h}};var tc=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,ec=Zo.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=Zo.round(n,wt(n,t))).toFixed(Math.max(0,Math.min(20,wt(n*(1+1e-15),t))))}}),rc=vt;if(Ka){var uc=Ka.length;rc=function(n){for(var t=n.length,e=[],r=0,u=Ka[0];t>0&&u>0;)e.push(n.substring(t-=u,t+u)),u=Ka[r=(r+1)%uc];return e.reverse().join(Ga)}}Zo.geo={},kt.prototype={s:0,t:0,add:function(n){Et(n,this.t,ic),Et(ic.s,this.s,this),this.s?this.t+=ic.t:this.s=ic.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ic=new kt;Zo.geo.stream=function(n,t){n&&oc.hasOwnProperty(n.type)?oc[n.type](n,t):At(n,t)};var oc={Feature:function(n,t){At(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++r<u;)At(e[r].geometry,t)}},ac={Sphere:function(n,t){t.sphere()},Point:function(n,t){n=n.coordinates,t.point(n[0],n[1],n[2])},MultiPoint:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)n=e[r],t.point(n[0],n[1],n[2])},LineString:function(n,t){Ct(n.coordinates,t,0)},MultiLineString:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)Ct(e[r],t,0)},Polygon:function(n,t){Nt(n.coordinates,t)},MultiPolygon:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)Nt(e[r],t)},GeometryCollection:function(n,t){for(var e=n.geometries,r=-1,u=e.length;++r<u;)At(e[r],t)}};Zo.geo.area=function(n){return cc=0,Zo.geo.stream(n,lc),cc};var cc,sc=new kt,lc={sphere:function(){cc+=4*ba},point:c,lineStart:c,lineEnd:c,polygonStart:function(){sc.reset(),lc.lineStart=Lt},polygonEnd:function(){var n=2*sc;cc+=0>n?4*ba+n:n,lc.lineStart=lc.lineEnd=lc.point=c}};Zo.geo.bounds=function(){function n(n,t){x.push(M=[l=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=Tt([t*Aa,e*Aa]);if(m){var u=zt(m,r),i=[u[1],-u[0],0],o=zt(i,u);Pt(o),o=Ut(o);var c=t-p,s=c>0?1:-1,v=o[0]*Ca*s,d=ua(c)>180;if(d^(v>s*p&&s*t>v)){var y=o[1]*Ca;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>s*p&&s*t>v)){var y=-o[1]*Ca;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?a(l,t)>a(l,h)&&(h=t):a(t,h)>a(l,h)&&(l=t):h>=l?(l>t&&(l=t),t>h&&(h=t)):t>p?a(l,t)>a(l,h)&&(h=t):a(t,h)>a(l,h)&&(l=t)}else n(t,e);m=r,p=t}function e(){_.point=t}function r(){M[0]=l,M[1]=h,_.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=ua(r)>180?r+(r>0?360:-360):r}else v=n,d=e;lc.point(n,e),t(n,e)}function i(){lc.lineStart()}function o(){u(v,d),lc.lineEnd(),ua(y)>ka&&(l=-(h=180)),M[0]=l,M[1]=h,m=null}function a(n,t){return(t-=n)<0?t+360:t}function c(n,t){return n[0]-t[0]}function s(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:n<t[0]||t[1]<n}var l,f,h,g,p,v,d,m,y,x,M,_={point:n,lineStart:e,lineEnd:r,polygonStart:function(){_.point=u,_.lineStart=i,_.lineEnd=o,y=0,lc.polygonStart()},polygonEnd:function(){lc.polygonEnd(),_.point=n,_.lineStart=e,_.lineEnd=r,0>sc?(l=-(h=180),f=-(g=90)):y>ka?g=90:-ka>y&&(f=-90),M[0]=l,M[1]=h}};return function(n){g=h=-(l=f=1/0),x=[],Zo.geo.stream(n,_);var t=x.length;if(t){x.sort(c);for(var e,r=1,u=x[0],i=[u];t>r;++r)e=x[r],s(e[0],u)||s(e[1],u)?(a(u[0],e[1])>a(u[0],u[1])&&(u[1]=e[1]),a(e[0],u[1])>a(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var o,e,p=-1/0,t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(o=a(u[1],e[0]))>p&&(p=o,l=e[0],h=u[1])}return x=M=null,1/0===l||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[l,f],[h,g]]}}(),Zo.geo.centroid=function(n){fc=hc=gc=pc=vc=dc=mc=yc=xc=Mc=_c=0,Zo.geo.stream(n,bc);var t=xc,e=Mc,r=_c,u=t*t+e*e+r*r;return Ea>u&&(t=dc,e=mc,r=yc,ka>hc&&(t=gc,e=pc,r=vc),u=t*t+e*e+r*r,Ea>u)?[0/0,0/0]:[Math.atan2(e,t)*Ca,H(r/Math.sqrt(u))*Ca]};var fc,hc,gc,pc,vc,dc,mc,yc,xc,Mc,_c,bc={sphere:c,point:Ht,lineStart:Ot,lineEnd:Yt,polygonStart:function(){bc.lineStart=It},polygonEnd:function(){bc.lineStart=Ot}},wc=Bt(Zt,Qt,te,[-ba,-ba/2]),Sc=1e9;Zo.geo.clipExtent=function(){var n,t,e,r,u,i,o={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(a){return arguments.length?(i=ue(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),u&&(u.valid=!1,u=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(Zo.geo.conicEqualArea=function(){return oe(ae)}).raw=ae,Zo.geo.albers=function(){return Zo.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},Zo.geo.albersUsa=function(){function n(n){var i=n[0],o=n[1];return t=null,e(i,o),t||(r(i,o),t)||u(i,o),t}var t,e,r,u,i=Zo.geo.albers(),o=Zo.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=Zo.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),c={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?o:u>=.166&&.234>u&&r>=-.214&&-.115>r?a:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),o.precision(t),a.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),o.scale(.35*t),a.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var s=i.scale(),l=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[l-.455*s,f-.238*s],[l+.455*s,f+.238*s]]).stream(c).point,r=o.translate([l-.307*s,f+.201*s]).clipExtent([[l-.425*s+ka,f+.12*s+ka],[l-.214*s-ka,f+.234*s-ka]]).stream(c).point,u=a.translate([l-.205*s,f+.212*s]).clipExtent([[l-.214*s+ka,f+.166*s+ka],[l-.115*s-ka,f+.234*s-ka]]).stream(c).point,n},n.scale(1070)};var kc,Ec,Ac,Cc,Nc,Lc,Tc={point:c,lineStart:c,lineEnd:c,polygonStart:function(){Ec=0,Tc.lineStart=ce},polygonEnd:function(){Tc.lineStart=Tc.lineEnd=Tc.point=c,kc+=ua(Ec/2)}},qc={point:se,lineStart:c,lineEnd:c,polygonStart:c,polygonEnd:c},zc={point:he,lineStart:ge,lineEnd:pe,polygonStart:function(){zc.lineStart=ve},polygonEnd:function(){zc.point=he,zc.lineStart=ge,zc.lineEnd=pe}};Zo.geo.transform=function(n){return{stream:function(t){var e=new ye(t);for(var r in n)e[r]=n[r];return e}}},ye.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},Zo.geo.path=function(){function n(n){return n&&("function"==typeof a&&i.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=u(i)),Zo.geo.stream(n,o)),i.result()}function t(){return o=null,n}var e,r,u,i,o,a=4.5;return n.area=function(n){return kc=0,Zo.geo.stream(n,u(Tc)),kc},n.centroid=function(n){return gc=pc=vc=dc=mc=yc=xc=Mc=_c=0,Zo.geo.stream(n,u(zc)),_c?[xc/_c,Mc/_c]:yc?[dc/yc,mc/yc]:vc?[gc/vc,pc/vc]:[0/0,0/0]},n.bounds=function(n){return Nc=Lc=-(Ac=Cc=1/0),Zo.geo.stream(n,u(qc)),[[Ac,Cc],[Nc,Lc]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||xe(n):vt,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new le:new de(n),"function"!=typeof a&&i.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(i.pointRadius(+t),+t),n):a},n.projection(Zo.geo.albersUsa()).context(null)},Zo.geo.projection=Me,Zo.geo.projectionMutator=_e,(Zo.geo.equirectangular=function(){return Me(we)}).raw=we.invert=we,Zo.geo.rotation=function(n){function t(t){return t=n(t[0]*Aa,t[1]*Aa),t[0]*=Ca,t[1]*=Ca,t
+}return n=ke(n[0]%360*Aa,n[1]*Aa,n.length>2?n[2]*Aa:0),t.invert=function(t){return t=n.invert(t[0]*Aa,t[1]*Aa),t[0]*=Ca,t[1]*=Ca,t},t},Se.invert=we,Zo.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=ke(-n[0]*Aa,-n[1]*Aa,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Ca,n[1]*=Ca}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=Ne((t=+r)*Aa,u*Aa),n):t},n.precision=function(r){return arguments.length?(e=Ne(t*Aa,(u=+r)*Aa),n):u},n.angle(90)},Zo.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Aa,u=n[1]*Aa,i=t[1]*Aa,o=Math.sin(r),a=Math.cos(r),c=Math.sin(u),s=Math.cos(u),l=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*o)*e+(e=s*l-c*f*a)*e),c*l+s*f*a)},Zo.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return Zo.range(Math.ceil(i/d)*d,u,d).map(h).concat(Zo.range(Math.ceil(s/m)*m,c,m).map(g)).concat(Zo.range(Math.ceil(r/p)*p,e,p).filter(function(n){return ua(n%d)>ka}).map(l)).concat(Zo.range(Math.ceil(a/v)*v,o,v).filter(function(n){return ua(n%m)>ka}).map(f))}var e,r,u,i,o,a,c,s,l,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(c).slice(1),h(u).reverse().slice(1),g(s).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],s=+t[0][1],c=+t[1][1],i>u&&(t=i,i=u,u=t),s>c&&(t=s,s=c,c=t),n.precision(y)):[[i,s],[u,c]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(y)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,l=Te(a,o,90),f=qe(r,e,y),h=Te(s,c,90),g=qe(i,u,y),n):y},n.majorExtent([[-180,-90+ka],[180,90-ka]]).minorExtent([[-180,-80-ka],[180,80+ka]])},Zo.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=ze,u=Re;return n.distance=function(){return Zo.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},Zo.geo.interpolate=function(n,t){return De(n[0]*Aa,n[1]*Aa,t[0]*Aa,t[1]*Aa)},Zo.geo.length=function(n){return Rc=0,Zo.geo.stream(n,Dc),Rc};var Rc,Dc={sphere:c,point:c,lineStart:Pe,lineEnd:c,polygonStart:c,polygonEnd:c},Pc=Ue(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(Zo.geo.azimuthalEqualArea=function(){return Me(Pc)}).raw=Pc;var Uc=Ue(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},vt);(Zo.geo.azimuthalEquidistant=function(){return Me(Uc)}).raw=Uc,(Zo.geo.conicConformal=function(){return oe(je)}).raw=je,(Zo.geo.conicEquidistant=function(){return oe(He)}).raw=He;var jc=Ue(function(n){return 1/n},Math.atan);(Zo.geo.gnomonic=function(){return Me(jc)}).raw=jc,Fe.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Sa]},(Zo.geo.mercator=function(){return Oe(Fe)}).raw=Fe;var Hc=Ue(function(){return 1},Math.asin);(Zo.geo.orthographic=function(){return Me(Hc)}).raw=Hc;var Fc=Ue(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(Zo.geo.stereographic=function(){return Me(Fc)}).raw=Fc,Ye.invert=function(n,t){return[Math.atan2(F(n),Math.cos(t)),H(Math.sin(t)/O(n))]},(Zo.geo.transverseMercator=function(){return Oe(Ye)}).raw=Ye,Zo.geom={},Zo.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u,i,o,a,c,s,l,f,h,g,p,v=pt(e),d=pt(r),m=n.length,y=m-1,x=[],M=[],_=0;if(v===Ie&&r===Ze)t=n;else for(i=0,t=[];m>i;++i)t.push([+v.call(this,u=n[i],i),+d.call(this,u,i)]);for(i=1;m>i;++i)(t[i][1]<t[_][1]||t[i][1]==t[_][1]&&t[i][0]<t[_][0])&&(_=i);for(i=0;m>i;++i)i!==_&&(c=t[i][1]-t[_][1],a=t[i][0]-t[_][0],x.push({angle:Math.atan2(c,a),index:i}));for(x.sort(function(n,t){return n.angle-t.angle}),g=x[0].angle,h=x[0].index,f=0,i=1;y>i;++i){if(o=x[i].index,g==x[i].angle){if(a=t[h][0]-t[_][0],c=t[h][1]-t[_][1],s=t[o][0]-t[_][0],l=t[o][1]-t[_][1],a*a+c*c>=s*s+l*l){x[i].index=-1;continue}x[f].index=-1}g=x[i].angle,f=i,h=o}for(M.push(_),i=0,o=0;2>i;++o)x[o].index>-1&&(M.push(x[o].index),i++);for(p=M.length;y>o;++o)if(!(x[o].index<0)){for(;!Ve(M[p-2],M[p-1],x[o].index,t);)--p;M[p++]=x[o].index}var b=[];for(i=p-1;i>=0;--i)b.push(n[M[i]]);return b}var e=Ie,r=Ze;return arguments.length?t(n):(t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t)},Zo.geom.polygon=function(n){return sa(n,Oc),n};var Oc=Zo.geom.polygon.prototype=[];Oc.area=function(){for(var n,t=-1,e=this.length,r=this[e-1],u=0;++t<e;)n=r,r=this[t],u+=n[1]*r[0]-n[0]*r[1];return.5*u},Oc.centroid=function(n){var t,e,r=-1,u=this.length,i=0,o=0,a=this[u-1];for(arguments.length||(n=-1/(6*this.area()));++r<u;)t=a,a=this[r],e=t[0]*a[1]-a[0]*t[1],i+=(t[0]+a[0])*e,o+=(t[1]+a[1])*e;return[i*n,o*n]},Oc.clip=function(n){for(var t,e,r,u,i,o,a=Be(n),c=-1,s=this.length-Be(this),l=this[s-1];++c<s;){for(t=n.slice(),n.length=0,u=this[c],i=t[(r=t.length-a)-1],e=-1;++e<r;)o=t[e],Xe(o,l,u)?(Xe(i,l,u)||n.push($e(i,o,l,u)),n.push(o)):Xe(i,l,u)&&n.push($e(i,o,l,u)),i=o;a&&n.push(n[0]),l=u}return n};var Yc,Ic,Zc,Vc,Xc,$c=[],Bc=[];er.prototype.prepare=function(){for(var n,t=this.edges,e=t.length;e--;)n=t[e].edge,n.b&&n.a||t.splice(e,1);return t.sort(ur),t.length},pr.prototype={start:function(){return this.edge.l===this.site?this.edge.a:this.edge.b},end:function(){return this.edge.l===this.site?this.edge.b:this.edge.a}},vr.prototype={insert:function(n,t){var e,r,u;if(n){if(t.P=n,t.N=n.N,n.N&&(n.N.P=t),n.N=t,n.R){for(n=n.R;n.L;)n=n.L;n.L=t}else n.R=t;e=n}else this._?(n=xr(this._),t.P=null,t.N=n,n.P=n.L=t,e=n):(t.P=t.N=null,this._=t,e=null);for(t.L=t.R=null,t.U=e,t.C=!0,n=t;e&&e.C;)r=e.U,e===r.L?(u=r.R,u&&u.C?(e.C=u.C=!1,r.C=!0,n=r):(n===e.R&&(mr(this,e),n=e,e=n.U),e.C=!1,r.C=!0,yr(this,r))):(u=r.L,u&&u.C?(e.C=u.C=!1,r.C=!0,n=r):(n===e.L&&(yr(this,e),n=e,e=n.U),e.C=!1,r.C=!0,mr(this,r))),e=n.U;this._.C=!1},remove:function(n){n.N&&(n.N.P=n.P),n.P&&(n.P.N=n.N),n.N=n.P=null;var t,e,r,u=n.U,i=n.L,o=n.R;if(e=i?o?xr(o):i:o,u?u.L===n?u.L=e:u.R=e:this._=e,i&&o?(r=e.C,e.C=n.C,e.L=i,i.U=e,e!==o?(u=e.U,e.U=n.U,n=e.R,u.L=n,e.R=o,o.U=e):(e.U=u,u=e,n=e.R)):(r=n.C,n=e),n&&(n.U=u),!r){if(n&&n.C)return n.C=!1,void 0;do{if(n===this._)break;if(n===u.L){if(t=u.R,t.C&&(t.C=!1,u.C=!0,mr(this,u),t=u.R),t.L&&t.L.C||t.R&&t.R.C){t.R&&t.R.C||(t.L.C=!1,t.C=!0,yr(this,t),t=u.R),t.C=u.C,u.C=t.R.C=!1,mr(this,u),n=this._;break}}else if(t=u.L,t.C&&(t.C=!1,u.C=!0,yr(this,u),t=u.L),t.L&&t.L.C||t.R&&t.R.C){t.L&&t.L.C||(t.R.C=!1,t.C=!0,mr(this,t),t=u.L),t.C=u.C,u.C=t.L.C=!1,yr(this,u),n=this._;break}t.C=!0,n=u,u=u.U}while(!n.C);n&&(n.C=!1)}}},Zo.geom.voronoi=function(n){function t(n){var t=new Array(n.length),r=a[0][0],u=a[0][1],i=a[1][0],o=a[1][1];return Mr(e(n),a).cells.forEach(function(e,a){var c=e.edges,s=e.site,l=t[a]=c.length?c.map(function(n){var t=n.start();return[t.x,t.y]}):s.x>=r&&s.x<=i&&s.y>=u&&s.y<=o?[[r,o],[i,o],[i,u],[r,u]]:[];l.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/ka)*ka,y:Math.round(o(n,t)/ka)*ka,i:t}})}var r=Ie,u=Ze,i=r,o=u,a=Wc;return n?t(n):(t.links=function(n){return Mr(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return Mr(e(n)).cells.forEach(function(e,r){for(var u,i,o=e.site,a=e.edges.sort(ur),c=-1,s=a.length,l=a[s-1].edge,f=l.l===o?l.r:l.l;++c<s;)u=l,i=f,l=a[c].edge,f=l.l===o?l.r:l.l,r<i.i&&r<f.i&&br(o,i,f)<0&&t.push([n[r],n[i.i],n[f.i]])}),t},t.x=function(n){return arguments.length?(i=pt(r=n),t):r},t.y=function(n){return arguments.length?(o=pt(u=n),t):u},t.clipExtent=function(n){return arguments.length?(a=null==n?Wc:n,t):a===Wc?null:a},t.size=function(n){return arguments.length?t.clipExtent(n&&[[0,0],n]):a===Wc?null:a&&a[1]},t)};var Wc=[[-1e6,-1e6],[1e6,1e6]];Zo.geom.delaunay=function(n){return Zo.geom.voronoi().triangles(n)},Zo.geom.quadtree=function(n,t,e,r,u){function i(n){function i(n,t,e,r,u,i,o,a){if(!isNaN(e)&&!isNaN(r))if(n.leaf){var c=n.x,l=n.y;if(null!=c)if(ua(c-e)+ua(l-r)<.01)s(n,t,e,r,u,i,o,a);else{var f=n.point;n.x=n.y=n.point=null,s(n,f,c,l,u,i,o,a),s(n,t,e,r,u,i,o,a)}else n.x=e,n.y=r,n.point=t}else s(n,t,e,r,u,i,o,a)}function s(n,t,e,r,u,o,a,c){var s=.5*(u+a),l=.5*(o+c),f=e>=s,h=r>=l,g=(h<<1)+f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=kr()),f?u=s:a=s,h?o=l:c=l,i(n,t,e,r,u,o,a,c)}var l,f,h,g,p,v,d,m,y,x=pt(a),M=pt(c);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,o)for(g=0;p>g;++g)l=n[g],l.x<v&&(v=l.x),l.y<d&&(d=l.y),l.x>m&&(m=l.x),l.y>y&&(y=l.y),f.push(l.x),h.push(l.y);else for(g=0;p>g;++g){var _=+x(l=n[g],g),b=+M(l,g);v>_&&(v=_),d>b&&(d=b),_>m&&(m=_),b>y&&(y=b),f.push(_),h.push(b)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=kr();if(k.add=function(n){i(k,n,+x(n,++g),+M(n,g),v,d,m,y)},k.visit=function(n){Er(n,k,v,d,m,y)},g=-1,null==t){for(;++g<p;)i(k,n[g],f[g],h[g],v,d,m,y);--g}else n.forEach(k.add);return f=h=n=l=null,k}var o,a=Ie,c=Ze;return(o=arguments.length)?(a=wr,c=Sr,3===o&&(u=e,r=t,e=t=0),i(n)):(i.x=function(n){return arguments.length?(a=n,i):a},i.y=function(n){return arguments.length?(c=n,i):c},i.extent=function(n){return arguments.length?(null==n?t=e=r=u=null:(t=+n[0][0],e=+n[0][1],r=+n[1][0],u=+n[1][1]),i):null==t?null:[[t,e],[r,u]]},i.size=function(n){return arguments.length?(null==n?t=e=r=u=null:(t=e=0,r=+n[0],u=+n[1]),i):null==t?null:[r-t,u-e]},i)},Zo.interpolateRgb=Ar,Zo.interpolateObject=Cr,Zo.interpolateNumber=Nr,Zo.interpolateString=Lr;var Jc=/[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g;Zo.interpolate=Tr,Zo.interpolators=[function(n,t){var e=typeof t;return("string"===e?Ia.has(t)||/^(#|rgb\(|hsl\()/.test(t)?Ar:Lr:t instanceof Z?Ar:"object"===e?Array.isArray(t)?qr:Cr:Nr)(n,t)}],Zo.interpolateArray=qr;var Gc=function(){return vt},Kc=Zo.map({linear:Gc,poly:Hr,quad:function(){return Pr},cubic:function(){return Ur},sin:function(){return Fr},exp:function(){return Or},circle:function(){return Yr},elastic:Ir,back:Zr,bounce:function(){return Vr}}),Qc=Zo.map({"in":vt,out:Rr,"in-out":Dr,"out-in":function(n){return Dr(Rr(n))}});Zo.ease=function(n){var t=n.indexOf("-"),e=t>=0?n.substring(0,t):n,r=t>=0?n.substring(t+1):"in";return e=Kc.get(e)||Gc,r=Qc.get(r)||vt,zr(r(e.apply(null,Vo.call(arguments,1))))},Zo.interpolateHcl=Xr,Zo.interpolateHsl=$r,Zo.interpolateLab=Br,Zo.interpolateRound=Wr,Zo.transform=function(n){var t=$o.createElementNS(Zo.ns.prefix.svg,"g");return(Zo.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Jr(e?e.matrix:ns)})(n)},Jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var ns={a:1,b:0,c:0,d:1,e:0,f:0};Zo.interpolateTransform=nu,Zo.layout={},Zo.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++e<r;)t.push(ru(n[e]));return t}},Zo.layout.chord=function(){function n(){var n,s,f,h,g,p={},v=[],d=Zo.range(i),m=[];for(e=[],r=[],n=0,h=-1;++h<i;){for(s=0,g=-1;++g<i;)s+=u[h][g];v.push(s),m.push(Zo.range(i)),n+=s}for(o&&d.sort(function(n,t){return o(v[n],v[t])}),a&&m.forEach(function(n,t){n.sort(function(n,e){return a(u[t][n],u[t][e])})}),n=(wa-l*i)/n,s=0,h=-1;++h<i;){for(f=s,g=-1;++g<i;){var y=d[h],x=m[y][g],M=u[y][x],_=s,b=s+=M*n;p[y+"-"+x]={index:y,subindex:x,startAngle:_,endAngle:b,value:M}}r[y]={index:y,startAngle:f,endAngle:s,value:(s-f)/n},s+=l}for(h=-1;++h<i;)for(g=h-1;++g<i;){var w=p[h+"-"+g],S=p[g+"-"+h];(w.value||S.value)&&e.push(w.value<S.value?{source:S,target:w}:{source:w,target:S})}c&&t()}function t(){e.sort(function(n,t){return c((n.source.value+n.target.value)/2,(t.source.value+t.target.value)/2)})}var e,r,u,i,o,a,c,s={},l=0;return s.matrix=function(n){return arguments.length?(i=(u=n)&&u.length,e=r=null,s):u},s.padding=function(n){return arguments.length?(l=n,e=r=null,s):l},s.sortGroups=function(n){return arguments.length?(o=n,e=r=null,s):o},s.sortSubgroups=function(n){return arguments.length?(a=n,e=null,s):a},s.sortChords=function(n){return arguments.length?(c=n,e&&t(),s):c},s.chords=function(){return e||n(),e},s.groups=function(){return r||n(),r},s},Zo.layout.force=function(){function n(n){return function(t,e,r,u){if(t.point!==n){var i=t.cx-n.x,o=t.cy-n.y,a=1/Math.sqrt(i*i+o*o);if(v>(u-e)*a){var c=t.charge*a*a;return n.px-=i*c,n.py-=o*c,!0}if(t.point&&isFinite(a)){var c=t.pointCharge*a*a;n.px-=i*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=Zo.event.x,n.py=Zo.event.y,a.resume()}var e,r,u,i,o,a={},c=Zo.dispatch("start","tick","end"),s=[1,1],l=.9,f=ts,h=es,g=-30,p=.1,v=.8,d=[],m=[];return a.tick=function(){if((r*=.99)<.005)return c.end({type:"end",alpha:r=0}),!0;var t,e,a,f,h,v,y,x,M,_=d.length,b=m.length;for(e=0;b>e;++e)a=m[e],f=a.source,h=a.target,x=h.x-f.x,M=h.y-f.y,(v=x*x+M*M)&&(v=r*i[e]*((v=Math.sqrt(v))-u[e])/v,x*=v,M*=v,h.x-=x*(y=f.weight/(h.weight+f.weight)),h.y-=M*y,f.x+=x*(y=1-y),f.y+=M*y);if((y=r*p)&&(x=s[0]/2,M=s[1]/2,e=-1,y))for(;++e<_;)a=d[e],a.x+=(x-a.x)*y,a.y+=(M-a.y)*y;if(g)for(lu(t=Zo.geom.quadtree(d),r,o),e=-1;++e<_;)(a=d[e]).fixed||t.visit(n(a));for(e=-1;++e<_;)a=d[e],a.fixed?(a.x=a.px,a.y=a.py):(a.x-=(a.px-(a.px=a.x))*l,a.y-=(a.py-(a.py=a.y))*l);c.tick({type:"tick",alpha:r})},a.nodes=function(n){return arguments.length?(d=n,a):d},a.links=function(n){return arguments.length?(m=n,a):m},a.size=function(n){return arguments.length?(s=n,a):s},a.linkDistance=function(n){return arguments.length?(f="function"==typeof n?n:+n,a):f},a.distance=a.linkDistance,a.linkStrength=function(n){return arguments.length?(h="function"==typeof n?n:+n,a):h},a.friction=function(n){return arguments.length?(l=+n,a):l},a.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,a):g},a.gravity=function(n){return arguments.length?(p=+n,a):p},a.theta=function(n){return arguments.length?(v=+n,a):v},a.alpha=function(n){return arguments.length?(n=+n,r?r=n>0?n:0:n>0&&(c.start({type:"start",alpha:r=n}),Zo.timer(a.tick)),a):r},a.start=function(){function n(n,r){if(!e){for(e=new Array(c),a=0;c>a;++a)e[a]=[];for(a=0;s>a;++a){var u=m[a];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var i,o=e[t],a=-1,s=o.length;++a<s;)if(!isNaN(i=o[a][n]))return i;return Math.random()*r}var t,e,r,c=d.length,l=m.length,p=s[0],v=s[1];for(t=0;c>t;++t)(r=d[t]).index=t,r.weight=0;for(t=0;l>t;++t)r=m[t],"number"==typeof r.source&&(r.source=d[r.source]),"number"==typeof r.target&&(r.target=d[r.target]),++r.source.weight,++r.target.weight;for(t=0;c>t;++t)r=d[t],isNaN(r.x)&&(r.x=n("x",p)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof f)for(t=0;l>t;++t)u[t]=+f.call(this,m[t],t);else for(t=0;l>t;++t)u[t]=f;if(i=[],"function"==typeof h)for(t=0;l>t;++t)i[t]=+h.call(this,m[t],t);else for(t=0;l>t;++t)i[t]=h;if(o=[],"function"==typeof g)for(t=0;c>t;++t)o[t]=+g.call(this,d[t],t);else for(t=0;c>t;++t)o[t]=g;return a.resume()},a.resume=function(){return a.alpha(.1)},a.stop=function(){return a.alpha(0)},a.drag=function(){return e||(e=Zo.behavior.drag().origin(vt).on("dragstart.force",ou).on("drag.force",t).on("dragend.force",au)),arguments.length?(this.on("mouseover.force",cu).on("mouseout.force",su).call(e),void 0):e},Zo.rebind(a,c,"on")};var ts=20,es=1;Zo.layout.hierarchy=function(){function n(t,o,a){var c=u.call(e,t,o);if(t.depth=o,a.push(t),c&&(s=c.length)){for(var s,l,f=-1,h=t.children=new Array(s),g=0,p=o+1;++f<s;)l=h[f]=n(c[f],p,a),l.parent=t,g+=l.value;r&&h.sort(r),i&&(t.value=g)}else delete t.children,i&&(t.value=+i.call(e,t,o)||0);return t}function t(n,r){var u=n.children,o=0;if(u&&(a=u.length))for(var a,c=-1,s=r+1;++c<a;)o+=t(u[c],s);else i&&(o=+i.call(e,n,r)||0);return i&&(n.value=o),o}function e(t){var e=[];return n(t,0,e),e}var r=pu,u=hu,i=gu;return e.sort=function(n){return arguments.length?(r=n,e):r},e.children=function(n){return arguments.length?(u=n,e):u},e.value=function(n){return arguments.length?(i=n,e):i},e.revalue=function(n){return t(n,0),n},e},Zo.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(o=i.length)){var o,a,c,s=-1;for(r=t.value?r/t.value:0;++s<o;)n(a=i[s],e,c=a.value*r,u),e+=c}}function t(n){var e=n.children,r=0;if(e&&(u=e.length))for(var u,i=-1;++i<u;)r=Math.max(r,t(e[i]));return 1+r}function e(e,i){var o=r.call(this,e,i);return n(o[0],0,u[0],u[1]/t(o[0])),o}var r=Zo.layout.hierarchy(),u=[1,1];return e.size=function(n){return arguments.length?(u=n,e):u},fu(e,r)},Zo.layout.pie=function(){function n(i){var o=i.map(function(e,r){return+t.call(n,e,r)}),a=+("function"==typeof r?r.apply(this,arguments):r),c=(("function"==typeof u?u.apply(this,arguments):u)-a)/Zo.sum(o),s=Zo.range(i.length);null!=e&&s.sort(e===rs?function(n,t){return o[t]-o[n]}:function(n,t){return e(i[n],i[t])});var l=[];return s.forEach(function(n){var t;l[n]={data:i[n],value:t=o[n],startAngle:a,endAngle:a+=t*c}}),l}var t=Number,e=rs,r=0,u=wa;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n};var rs={};Zo.layout.stack=function(){function n(a,c){var s=a.map(function(e,r){return t.call(n,e,r)}),l=s.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),o.call(n,t,e)]})}),f=e.call(n,l,c);s=Zo.permute(s,f),l=Zo.permute(l,f);var h,g,p,v=r.call(n,l,c),d=s.length,m=s[0].length;for(g=0;m>g;++g)for(u.call(n,s[0][g],p=v[g],l[0][g][1]),h=1;d>h;++h)u.call(n,s[h][g],p+=l[h-1][g][1],l[h][g][1]);return a}var t=vt,e=xu,r=Mu,u=yu,i=du,o=mu;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:us.get(t)||xu,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:is.get(t)||Mu,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(u=t,n):u},n};var us=Zo.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(_u),i=n.map(bu),o=Zo.range(r).sort(function(n,t){return u[n]-u[t]}),a=0,c=0,s=[],l=[];for(t=0;r>t;++t)e=o[t],c>a?(a+=i[e],s.push(e)):(c+=i[e],l.push(e));return l.reverse().concat(s)},reverse:function(n){return Zo.range(n.length).reverse()},"default":xu}),is=Zo.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,o=[],a=0,c=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;i>e;++e)c[e]=(a-o[e])/2;return c},wiggle:function(n){var t,e,r,u,i,o,a,c,s,l=n.length,f=n[0],h=f.length,g=[];for(g[0]=c=s=0,e=1;h>e;++e){for(t=0,u=0;l>t;++t)u+=n[t][e][1];for(t=0,i=0,a=f[e][0]-f[e-1][0];l>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;i+=o*n[t][e][1]}g[e]=c-=u?i/u*a:0,s>c&&(s=c)}for(e=0;h>e;++e)g[e]-=s;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,o=1/u,a=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=o}for(e=0;i>e;++e)a[e]=0;return a},zero:Mu});Zo.layout.histogram=function(){function n(n,i){for(var o,a,c=[],s=n.map(e,this),l=r.call(this,s,i),f=u.call(this,l,s,i),i=-1,h=s.length,g=f.length-1,p=t?1:1/h;++i<g;)o=c[i]=[],o.dx=f[i+1]-(o.x=f[i]),o.y=0;if(g>0)for(i=-1;++i<h;)a=s[i],a>=l[0]&&a<=l[1]&&(o=c[Zo.bisect(f,a,1,g)-1],o.y+=p,o.push(n[i]));return c}var t=!0,e=Number,r=Eu,u=Su;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=pt(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return ku(n,t)}:pt(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},Zo.layout.tree=function(){function n(n,i){function o(n,t){var r=n.children,u=n._tree;if(r&&(i=r.length)){for(var i,a,s,l=r[0],f=l,h=-1;++h<i;)s=r[h],o(s,a),f=c(s,a,f),a=s;Du(n);var g=.5*(l._tree.prelim+s._tree.prelim);t?(u.prelim=t._tree.prelim+e(n,t),u.mod=u.prelim-g):u.prelim=g}else t&&(u.prelim=t._tree.prelim+e(n,t))}function a(n,t){n.x=n._tree.prelim+t;var e=n.children;if(e&&(r=e.length)){var r,u=-1;for(t+=n._tree.mod;++u<r;)a(e[u],t)}}function c(n,t,r){if(t){for(var u,i=n,o=n,a=t,c=n.parent.children[0],s=i._tree.mod,l=o._tree.mod,f=a._tree.mod,h=c._tree.mod;a=Nu(a),i=Cu(i),a&&i;)c=Cu(c),o=Nu(o),o._tree.ancestor=n,u=a._tree.prelim+f-i._tree.prelim-s+e(a,i),u>0&&(Pu(Uu(a,n,r),n,u),s+=u,l+=u),f+=a._tree.mod,s+=i._tree.mod,h+=c._tree.mod,l+=o._tree.mod;a&&!Nu(o)&&(o._tree.thread=a,o._tree.mod+=f-l),i&&!Cu(c)&&(c._tree.thread=i,c._tree.mod+=s-h,r=n)}return r}var s=t.call(this,n,i),l=s[0];Ru(l,function(n,t){n._tree={ancestor:n,prelim:0,mod:0,change:0,shift:0,number:t?t._tree.number+1:0}}),o(l),a(l,-l._tree.prelim);var f=Lu(l,qu),h=Lu(l,Tu),g=Lu(l,zu),p=f.x-e(f,h)/2,v=h.x+e(h,f)/2,d=g.depth||1;return Ru(l,u?function(n){n.x*=r[0],n.y=n.depth*r[1],delete n._tree}:function(n){n.x=(n.x-p)/(v-p)*r[0],n.y=n.depth/d*r[1],delete n._tree}),s}var t=Zo.layout.hierarchy().sort(null).value(null),e=Au,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},fu(n,t)},Zo.layout.pack=function(){function n(n,i){var o=e.call(this,n,i),a=o[0],c=u[0],s=u[1],l=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,Ru(a,function(n){n.r=+l(n.value)}),Ru(a,Yu),r){var f=r*(t?1:Math.max(2*a.r/c,2*a.r/s))/2;Ru(a,function(n){n.r+=f}),Ru(a,Yu),Ru(a,function(n){n.r-=f})}return Vu(a,c/2,s/2,t?1:1/Math.max(2*a.r/c,2*a.r/s)),o}var t,e=Zo.layout.hierarchy().sort(ju),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},fu(n,e)},Zo.layout.cluster=function(){function n(n,i){var o,a=t.call(this,n,i),c=a[0],s=0;Ru(c,function(n){var t=n.children;t&&t.length?(n.x=Bu(t),n.y=$u(t)):(n.x=o?s+=e(n,o):0,n.y=0,o=n)});var l=Wu(c),f=Ju(c),h=l.x-e(l,f)/2,g=f.x+e(f,l)/2;return Ru(c,u?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),a}var t=Zo.layout.hierarchy().sort(null).value(null),e=Au,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},fu(n,t)},Zo.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++u<i;)r=(e=n[u]).value*(0>t?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var o,a,c,s=f(e),l=[],h=i.slice(),p=1/0,v="slice"===g?s.dx:"dice"===g?s.dy:"slice-dice"===g?1&e.depth?s.dy:s.dx:Math.min(s.dx,s.dy);for(n(h,s.dx*s.dy/e.value),l.area=0;(c=h.length)>0;)l.push(o=h[c-1]),l.area+=o.area,"squarify"!==g||(a=r(l,v))<=p?(h.pop(),p=a):(l.area-=l.pop().area,u(l,v,s,!1),v=Math.min(s.dx,s.dy),l.length=l.area=0,p=1/0);l.length&&(u(l,v,s,!0),l.length=l.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,o=f(t),a=r.slice(),c=[];for(n(a,o.dx*o.dy/t.value),c.area=0;i=a.pop();)c.push(i),c.area+=i.area,null!=i.z&&(u(c,i.z?o.dx:o.dy,o,!a.length),c.length=c.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,o=-1,a=n.length;++o<a;)(e=n[o].area)&&(i>e&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,o=n.length,a=e.x,s=e.y,l=t?c(n.area/t):0;if(t==e.dx){for((r||l>e.dy)&&(l=e.dy);++i<o;)u=n[i],u.x=a,u.y=s,u.dy=l,a+=u.dx=Math.min(e.x+e.dx-a,l?c(u.area/l):0);u.z=!0,u.dx+=e.x+e.dx-a,e.y+=l,e.dy-=l}else{for((r||l>e.dx)&&(l=e.dx);++i<o;)u=n[i],u.x=a,u.y=s,u.dx=l,s+=u.dy=Math.min(e.y+e.dy-s,l?c(u.area/l):0);u.z=!1,u.dy+=e.y+e.dy-s,e.x+=l,e.dx-=l}}function i(r){var u=o||a(r),i=u[0];return i.x=0,i.y=0,i.dx=s[0],i.dy=s[1],o&&a.revalue(i),n([i],i.dx*i.dy/i.value),(o?e:t)(i),h&&(o=u),u}var o,a=Zo.layout.hierarchy(),c=Math.round,s=[1,1],l=null,f=Gu,h=!1,g="squarify",p=.5*(1+Math.sqrt(5));return i.size=function(n){return arguments.length?(s=n,i):s},i.padding=function(n){function t(t){var e=n.call(i,t,t.depth);return null==e?Gu(t):Ku(t,"number"==typeof e?[e,e,e,e]:e)}function e(t){return Ku(t,n)}if(!arguments.length)return l;var r;return f=null==(l=n)?Gu:"function"==(r=typeof n)?t:"number"===r?(n=[n,n,n,n],e):e,i},i.round=function(n){return arguments.length?(c=n?Math.round:Number,i):c!=Number},i.sticky=function(n){return arguments.length?(h=n,o=null,i):h},i.ratio=function(n){return arguments.length?(p=n,i):p},i.mode=function(n){return arguments.length?(g=n+"",i):g},fu(i,a)},Zo.random={normal:function(n,t){var e=arguments.length;return 2>e&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=Zo.random.normal.apply(Zo,arguments);return function(){return Math.exp(n())}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t/n}}},Zo.scale={};var os={floor:vt,ceil:vt};Zo.scale.linear=function(){return ii([0,1],[0,1],Tr,!1)},Zo.scale.log=function(){return fi(Zo.scale.linear().domain([0,1]),10,!0,[1,10])};var as=Zo.format(".0e"),cs={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};Zo.scale.pow=function(){return hi(Zo.scale.linear(),1,[0,1])},Zo.scale.sqrt=function(){return Zo.scale.pow().exponent(.5)},Zo.scale.ordinal=function(){return pi([],{t:"range",a:[[]]})},Zo.scale.category10=function(){return Zo.scale.ordinal().range(ss)},Zo.scale.category20=function(){return Zo.scale.ordinal().range(ls)},Zo.scale.category20b=function(){return Zo.scale.ordinal().range(fs)},Zo.scale.category20c=function(){return Zo.scale.ordinal().range(hs)};var ss=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(it),ls=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(it),fs=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(it),hs=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(it);Zo.scale.quantile=function(){return vi([],[])},Zo.scale.quantize=function(){return di(0,1,[0,1])},Zo.scale.threshold=function(){return mi([.5],[0,1])},Zo.scale.identity=function(){return yi([0,1])},Zo.svg={},Zo.svg.arc=function(){function n(){var n=t.apply(this,arguments),i=e.apply(this,arguments),o=r.apply(this,arguments)+gs,a=u.apply(this,arguments)+gs,c=(o>a&&(c=o,o=a,a=c),a-o),s=ba>c?"0":"1",l=Math.cos(o),f=Math.sin(o),h=Math.cos(a),g=Math.sin(a);return c>=ps?n?"M0,"+i+"A"+i+","+i+" 0 1,1 0,"+-i+"A"+i+","+i+" 0 1,1 0,"+i+"M0,"+n+"A"+n+","+n+" 0 1,0 0,"+-n+"A"+n+","+n+" 0 1,0 0,"+n+"Z":"M0,"+i+"A"+i+","+i+" 0 1,1 0,"+-i+"A"+i+","+i+" 0 1,1 0,"+i+"Z":n?"M"+i*l+","+i*f+"A"+i+","+i+" 0 "+s+",1 "+i*h+","+i*g+"L"+n*h+","+n*g+"A"+n+","+n+" 0 "+s+",0 "+n*l+","+n*f+"Z":"M"+i*l+","+i*f+"A"+i+","+i+" 0 "+s+",1 "+i*h+","+i*g+"L0,0"+"Z"}var t=xi,e=Mi,r=_i,u=bi;return n.innerRadius=function(e){return arguments.length?(t=pt(e),n):t},n.outerRadius=function(t){return arguments.length?(e=pt(t),n):e},n.startAngle=function(t){return arguments.length?(r=pt(t),n):r},n.endAngle=function(t){return arguments.length?(u=pt(t),n):u},n.centroid=function(){var n=(t.apply(this,arguments)+e.apply(this,arguments))/2,i=(r.apply(this,arguments)+u.apply(this,arguments))/2+gs;return[Math.cos(i)*n,Math.sin(i)*n]},n};var gs=-Sa,ps=wa-ka;Zo.svg.line=function(){return wi(vt)};var vs=Zo.map({linear:Si,"linear-closed":ki,step:Ei,"step-before":Ai,"step-after":Ci,basis:Ri,"basis-open":Di,"basis-closed":Pi,bundle:Ui,cardinal:Ti,"cardinal-open":Ni,"cardinal-closed":Li,monotone:Ii});vs.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var ds=[0,2/3,1/3,0],ms=[0,1/3,2/3,0],ys=[0,1/6,2/3,1/6];Zo.svg.line.radial=function(){var n=wi(Zi);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},Ai.reverse=Ci,Ci.reverse=Ai,Zo.svg.area=function(){return Vi(vt)},Zo.svg.area.radial=function(){var n=Vi(Zi);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},Zo.svg.chord=function(){function n(n,a){var c=t(this,i,n,a),s=t(this,o,n,a);return"M"+c.p0+r(c.r,c.p1,c.a1-c.a0)+(e(c,s)?u(c.r,c.p1,c.r,c.p0):u(c.r,c.p1,s.r,s.p0)+r(s.r,s.p1,s.a1-s.a0)+u(s.r,s.p1,c.r,c.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=a.call(n,u,r),o=c.call(n,u,r)+gs,l=s.call(n,u,r)+gs;return{r:i,a0:o,a1:l,p0:[i*Math.cos(o),i*Math.sin(o)],p1:[i*Math.cos(l),i*Math.sin(l)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>ba)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=ze,o=Re,a=Xi,c=_i,s=bi;return n.radius=function(t){return arguments.length?(a=pt(t),n):a},n.source=function(t){return arguments.length?(i=pt(t),n):i},n.target=function(t){return arguments.length?(o=pt(t),n):o},n.startAngle=function(t){return arguments.length?(c=pt(t),n):c},n.endAngle=function(t){return arguments.length?(s=pt(t),n):s},n},Zo.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),o=e.call(this,n,u),a=(i.y+o.y)/2,c=[i,{x:i.x,y:a},{x:o.x,y:a},o];return c=c.map(r),"M"+c[0]+"C"+c[1]+" "+c[2]+" "+c[3]}var t=ze,e=Re,r=$i;return n.source=function(e){return arguments.length?(t=pt(e),n):t},n.target=function(t){return arguments.length?(e=pt(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},Zo.svg.diagonal.radial=function(){var n=Zo.svg.diagonal(),t=$i,e=n.projection;return n.projection=function(n){return arguments.length?e(Bi(t=n)):t},n},Zo.svg.symbol=function(){function n(n,r){return(xs.get(t.call(this,n,r))||Gi)(e.call(this,n,r))}var t=Ji,e=Wi;return n.type=function(e){return arguments.length?(t=pt(e),n):t},n.size=function(t){return arguments.length?(e=pt(t),n):e},n};var xs=Zo.map({circle:Gi,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*ws)),e=t*ws;return"M0,"+-t+"L"+e+",0"+" 0,"+t+" "+-e+",0"+"Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/bs),e=t*bs/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/bs),e=t*bs/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});Zo.svg.symbolTypes=xs.keys();var Ms,_s,bs=Math.sqrt(3),ws=Math.tan(30*Aa),Ss=[],ks=0;Ss.call=pa.call,Ss.empty=pa.empty,Ss.node=pa.node,Ss.size=pa.size,Zo.transition=function(n){return arguments.length?Ms?n.transition():n:ma.transition()},Zo.transition.prototype=Ss,Ss.select=function(n){var t,e,r,u=this.id,i=[];n=v(n);for(var o=-1,a=this.length;++o<a;){i.push(t=[]);for(var c=this[o],s=-1,l=c.length;++s<l;)(r=c[s])&&(e=n.call(r,r.__data__,s,o))?("__data__"in r&&(e.__data__=r.__data__),to(e,s,u,r.__transition__[u]),t.push(e)):t.push(null)
+}return Ki(i,u)},Ss.selectAll=function(n){var t,e,r,u,i,o=this.id,a=[];n=d(n);for(var c=-1,s=this.length;++c<s;)for(var l=this[c],f=-1,h=l.length;++f<h;)if(r=l[f]){i=r.__transition__[o],e=n.call(r,r.__data__,f,c),a.push(t=[]);for(var g=-1,p=e.length;++g<p;)(u=e[g])&&to(u,g,o,i),t.push(u)}return Ki(a,o)},Ss.filter=function(n){var t,e,r,u=[];"function"!=typeof n&&(n=E(n));for(var i=0,o=this.length;o>i;i++){u.push(t=[]);for(var e=this[i],a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a)&&t.push(r)}return Ki(u,this.id)},Ss.tween=function(n,t){var e=this.id;return arguments.length<2?this.node().__transition__[e].tween.get(n):C(this,null==t?function(t){t.__transition__[e].tween.remove(n)}:function(r){r.__transition__[e].tween.set(n,t)})},Ss.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?nu:Tr,a=Zo.ns.qualify(n);return Qi(this,"attr."+n,t,a.local?i:u)},Ss.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=Zo.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Ss.style=function(n,t,e){function r(){this.style.removeProperty(n)}function u(t){return null==t?r:(t+="",function(){var r,u=Wo.getComputedStyle(this,null).getPropertyValue(n);return u!==t&&(r=Tr(u,t),function(t){this.style.setProperty(n,r(t),e)})})}var i=arguments.length;if(3>i){if("string"!=typeof n){2>i&&(t="");for(e in n)this.style(e,n[e],t);return this}e=""}return Qi(this,"style."+n,t,u)},Ss.styleTween=function(n,t,e){function r(r,u){var i=t.call(this,r,u,Wo.getComputedStyle(this,null).getPropertyValue(n));return i&&function(t){this.style.setProperty(n,i(t),e)}}return arguments.length<3&&(e=""),this.tween("style."+n,r)},Ss.text=function(n){return Qi(this,"text",n,no)},Ss.remove=function(){return this.each("end.transition",function(){var n;this.__transition__.count<2&&(n=this.parentNode)&&n.removeChild(this)})},Ss.ease=function(n){var t=this.id;return arguments.length<1?this.node().__transition__[t].ease:("function"!=typeof n&&(n=Zo.ease.apply(Zo,arguments)),C(this,function(e){e.__transition__[t].ease=n}))},Ss.delay=function(n){var t=this.id;return C(this,"function"==typeof n?function(e,r,u){e.__transition__[t].delay=+n.call(e,e.__data__,r,u)}:(n=+n,function(e){e.__transition__[t].delay=n}))},Ss.duration=function(n){var t=this.id;return C(this,"function"==typeof n?function(e,r,u){e.__transition__[t].duration=Math.max(1,n.call(e,e.__data__,r,u))}:(n=Math.max(1,n),function(e){e.__transition__[t].duration=n}))},Ss.each=function(n,t){var e=this.id;if(arguments.length<2){var r=_s,u=Ms;Ms=e,C(this,function(t,r,u){_s=t.__transition__[e],n.call(t,t.__data__,r,u)}),_s=r,Ms=u}else C(this,function(r){var u=r.__transition__[e];(u.event||(u.event=Zo.dispatch("start","end"))).on(n,t)});return this},Ss.transition=function(){for(var n,t,e,r,u=this.id,i=++ks,o=[],a=0,c=this.length;c>a;a++){o.push(n=[]);for(var t=this[a],s=0,l=t.length;l>s;s++)(e=t[s])&&(r=Object.create(e.__transition__[u]),r.delay+=r.duration,to(e,s,i,r)),n.push(e)}return Ki(o,i)},Zo.svg.axis=function(){function n(n){n.each(function(){var n,s=Zo.select(this),l=this.__chart__||e,f=this.__chart__=e.copy(),h=null==c?f.ticks?f.ticks.apply(f,a):f.domain():c,g=null==t?f.tickFormat?f.tickFormat.apply(f,a):vt:t,p=s.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",ka),d=Zo.transition(p.exit()).style("opacity",ka).remove(),m=Zo.transition(p).style("opacity",1),y=ni(f),x=s.selectAll(".domain").data([0]),M=(x.enter().append("path").attr("class","domain"),Zo.transition(x));v.append("line"),v.append("text");var _=v.select("line"),b=m.select("line"),w=p.select("text").text(g),S=v.select("text"),k=m.select("text");switch(r){case"bottom":n=eo,_.attr("y2",u),S.attr("y",Math.max(u,0)+o),b.attr("x2",0).attr("y2",u),k.attr("x",0).attr("y",Math.max(u,0)+o),w.attr("dy",".71em").style("text-anchor","middle"),M.attr("d","M"+y[0]+","+i+"V0H"+y[1]+"V"+i);break;case"top":n=eo,_.attr("y2",-u),S.attr("y",-(Math.max(u,0)+o)),b.attr("x2",0).attr("y2",-u),k.attr("x",0).attr("y",-(Math.max(u,0)+o)),w.attr("dy","0em").style("text-anchor","middle"),M.attr("d","M"+y[0]+","+-i+"V0H"+y[1]+"V"+-i);break;case"left":n=ro,_.attr("x2",-u),S.attr("x",-(Math.max(u,0)+o)),b.attr("x2",-u).attr("y2",0),k.attr("x",-(Math.max(u,0)+o)).attr("y",0),w.attr("dy",".32em").style("text-anchor","end"),M.attr("d","M"+-i+","+y[0]+"H0V"+y[1]+"H"+-i);break;case"right":n=ro,_.attr("x2",u),S.attr("x",Math.max(u,0)+o),b.attr("x2",u).attr("y2",0),k.attr("x",Math.max(u,0)+o).attr("y",0),w.attr("dy",".32em").style("text-anchor","start"),M.attr("d","M"+i+","+y[0]+"H0V"+y[1]+"H"+i)}if(f.rangeBand){var E=f.rangeBand()/2,A=function(n){return f(n)+E};v.call(n,A),m.call(n,A)}else v.call(n,l),m.call(n,f),d.call(n,f)})}var t,e=Zo.scale.linear(),r=Es,u=6,i=6,o=3,a=[10],c=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in As?t+"":Es,n):r},n.ticks=function(){return arguments.length?(a=arguments,n):a},n.tickValues=function(t){return arguments.length?(c=t,n):c},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Es="bottom",As={top:1,right:1,bottom:1,left:1};Zo.svg.brush=function(){function n(i){i.each(function(){var i=Zo.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=i.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),i.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=i.selectAll(".resize").data(d,vt);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Cs[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,f=Zo.transition(i),h=Zo.transition(o);c&&(l=ni(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),e(f)),s&&(l=ni(s),h.attr("y",l[0]).attr("height",l[1]-l[0]),r(f)),t(f)})}function t(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+l[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function e(n){n.select(".extent").attr("x",l[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",l[1]-l[0])}function r(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==Zo.event.keyCode&&(C||(x=null,L[0]-=l[1],L[1]-=h[1],C=2),f())}function g(){32==Zo.event.keyCode&&2==C&&(L[0]+=l[1],L[1]+=h[1],C=0,f())}function d(){var n=Zo.mouse(_),u=!1;M&&(n[0]+=M[0],n[1]+=M[1]),C||(Zo.event.altKey?(x||(x=[(l[0]+l[1])/2,(h[0]+h[1])/2]),L[0]=l[+(n[0]<x[0])],L[1]=h[+(n[1]<x[1])]):x=null),E&&m(n,c,0)&&(e(S),u=!0),A&&m(n,s,1)&&(r(S),u=!0),u&&(t(S),w({type:"brush",mode:C?"move":"resize"}))}function m(n,t,e){var r,u,a=ni(t),c=a[0],s=a[1],f=L[e],g=e?h:l,d=g[1]-g[0];return C&&(c-=f,s-=d+f),r=(e?v:p)?Math.max(c,Math.min(s,n[e])):n[e],C?u=(r+=f)+d:(x&&(f=Math.max(c,Math.min(s,2*x[e]-r))),r>f?(u=r,r=f):u=f),g[0]!=r||g[1]!=u?(e?o=null:i=null,g[0]=r,g[1]=u,!0):void 0}function y(){d(),S.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),Zo.select("body").style("cursor",null),T.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),N(),w({type:"brushend"})}var x,M,_=this,b=Zo.select(Zo.event.target),w=a.of(_,arguments),S=Zo.select(_),k=b.datum(),E=!/^(n|s)$/.test(k)&&c,A=!/^(e|w)$/.test(k)&&s,C=b.classed("extent"),N=D(),L=Zo.mouse(_),T=Zo.select(Wo).on("keydown.brush",u).on("keyup.brush",g);if(Zo.event.changedTouches?T.on("touchmove.brush",d).on("touchend.brush",y):T.on("mousemove.brush",d).on("mouseup.brush",y),S.interrupt().selectAll("*").interrupt(),C)L[0]=l[0]-L[0],L[1]=h[0]-L[1];else if(k){var q=+/w$/.test(k),z=+/^n/.test(k);M=[l[1-q]-L[0],h[1-z]-L[1]],L[0]=l[q],L[1]=h[z]}else Zo.event.altKey&&(x=L.slice());S.style("pointer-events","none").selectAll(".resize").style("display",null),Zo.select("body").style("cursor",b.style("cursor")),w({type:"brushstart"}),d()}var i,o,a=g(n,"brushstart","brush","brushend"),c=null,s=null,l=[0,0],h=[0,0],p=!0,v=!0,d=Ns[0];return n.event=function(n){n.each(function(){var n=a.of(this,arguments),t={x:l,y:h,i:i,j:o},e=this.__chart__||t;this.__chart__=t,Ms?Zo.select(this).transition().each("start.brush",function(){i=e.i,o=e.j,l=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=qr(l,t.x),r=qr(h,t.y);return i=o=null,function(u){l=t.x=e(u),h=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){i=t.i,o=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,d=Ns[!c<<1|!s],n):c},n.y=function(t){return arguments.length?(s=t,d=Ns[!c<<1|!s],n):s},n.clamp=function(t){return arguments.length?(c&&s?(p=!!t[0],v=!!t[1]):c?p=!!t:s&&(v=!!t),n):c&&s?[p,v]:c?p:s?v:null},n.extent=function(t){var e,r,u,a,f;return arguments.length?(c&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),i=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(f=e,e=r,r=f),(e!=l[0]||r!=l[1])&&(l=[e,r])),s&&(u=t[0],a=t[1],c&&(u=u[1],a=a[1]),o=[u,a],s.invert&&(u=s(u),a=s(a)),u>a&&(f=u,u=a,a=f),(u!=h[0]||a!=h[1])&&(h=[u,a])),n):(c&&(i?(e=i[0],r=i[1]):(e=l[0],r=l[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(f=e,e=r,r=f))),s&&(o?(u=o[0],a=o[1]):(u=h[0],a=h[1],s.invert&&(u=s.invert(u),a=s.invert(a)),u>a&&(f=u,u=a,a=f))),c&&s?[[e,u],[r,a]]:c?[e,r]:s&&[u,a])},n.clear=function(){return n.empty()||(l=[0,0],h=[0,0],i=o=null),n},n.empty=function(){return!!c&&l[0]==l[1]||!!s&&h[0]==h[1]},Zo.rebind(n,a,"on")};var Cs={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Ns=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Ls=Zo.time={},Ts=Date,qs=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];uo.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){zs.setUTCDate.apply(this._,arguments)},setDay:function(){zs.setUTCDay.apply(this._,arguments)},setFullYear:function(){zs.setUTCFullYear.apply(this._,arguments)},setHours:function(){zs.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){zs.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){zs.setUTCMinutes.apply(this._,arguments)},setMonth:function(){zs.setUTCMonth.apply(this._,arguments)},setSeconds:function(){zs.setUTCSeconds.apply(this._,arguments)},setTime:function(){zs.setTime.apply(this._,arguments)}};var zs=Date.prototype,Rs="%a %b %e %X %Y",Ds="%m/%d/%Y",Ps="%H:%M:%S",Us=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],js=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],Hs=["January","February","March","April","May","June","July","August","September","October","November","December"],Fs=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];Ls.year=io(function(n){return n=Ls.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),Ls.years=Ls.year.range,Ls.years.utc=Ls.year.utc.range,Ls.day=io(function(n){var t=new Ts(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),Ls.days=Ls.day.range,Ls.days.utc=Ls.day.utc.range,Ls.dayOfYear=function(n){var t=Ls.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},qs.forEach(function(n,t){n=n.toLowerCase(),t=7-t;var e=Ls[n]=io(function(n){return(n=Ls.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=Ls.year(n).getDay();return Math.floor((Ls.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});Ls[n+"s"]=e.range,Ls[n+"s"].utc=e.utc.range,Ls[n+"OfYear"]=function(n){var e=Ls.year(n).getDay();return Math.floor((Ls.dayOfYear(n)+(e+t)%7)/7)}}),Ls.week=Ls.sunday,Ls.weeks=Ls.sunday.range,Ls.weeks.utc=Ls.sunday.utc.range,Ls.weekOfYear=Ls.sundayOfYear,Ls.format=ao;var Os=so(Us),Ys=lo(Us),Is=so(js),Zs=lo(js),Vs=so(Hs),Xs=lo(Hs),$s=so(Fs),Bs=lo(Fs),Ws=/^%/,Js={"-":"",_:" ",0:"0"},Gs={a:function(n){return js[n.getDay()]},A:function(n){return Us[n.getDay()]},b:function(n){return Fs[n.getMonth()]},B:function(n){return Hs[n.getMonth()]},c:ao(Rs),d:function(n,t){return fo(n.getDate(),t,2)},e:function(n,t){return fo(n.getDate(),t,2)},H:function(n,t){return fo(n.getHours(),t,2)},I:function(n,t){return fo(n.getHours()%12||12,t,2)},j:function(n,t){return fo(1+Ls.dayOfYear(n),t,3)},L:function(n,t){return fo(n.getMilliseconds(),t,3)},m:function(n,t){return fo(n.getMonth()+1,t,2)},M:function(n,t){return fo(n.getMinutes(),t,2)},p:function(n){return n.getHours()>=12?"PM":"AM"},S:function(n,t){return fo(n.getSeconds(),t,2)},U:function(n,t){return fo(Ls.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return fo(Ls.mondayOfYear(n),t,2)},x:ao(Ds),X:ao(Ps),y:function(n,t){return fo(n.getFullYear()%100,t,2)},Y:function(n,t){return fo(n.getFullYear()%1e4,t,4)},Z:Do,"%":function(){return"%"}},Ks={a:ho,A:go,b:yo,B:xo,c:Mo,d:Co,e:Co,H:Lo,I:Lo,j:No,L:zo,m:Ao,M:To,p:Ro,S:qo,U:vo,w:po,W:mo,x:_o,X:bo,y:So,Y:wo,Z:ko,"%":Po},Qs=/^\s*\d+/,nl=Zo.map({am:0,pm:1});ao.utc=Uo;var tl=Uo("%Y-%m-%dT%H:%M:%S.%LZ");ao.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?jo:tl,jo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},jo.toString=tl.toString,Ls.second=io(function(n){return new Ts(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),Ls.seconds=Ls.second.range,Ls.seconds.utc=Ls.second.utc.range,Ls.minute=io(function(n){return new Ts(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),Ls.minutes=Ls.minute.range,Ls.minutes.utc=Ls.minute.utc.range,Ls.hour=io(function(n){var t=n.getTimezoneOffset()/60;return new Ts(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),Ls.hours=Ls.hour.range,Ls.hours.utc=Ls.hour.utc.range,Ls.month=io(function(n){return n=Ls.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),Ls.months=Ls.month.range,Ls.months.utc=Ls.month.utc.range;var el=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],rl=[[Ls.second,1],[Ls.second,5],[Ls.second,15],[Ls.second,30],[Ls.minute,1],[Ls.minute,5],[Ls.minute,15],[Ls.minute,30],[Ls.hour,1],[Ls.hour,3],[Ls.hour,6],[Ls.hour,12],[Ls.day,1],[Ls.day,2],[Ls.week,1],[Ls.month,1],[Ls.month,3],[Ls.year,1]],ul=[[ao("%Y"),Zt],[ao("%B"),function(n){return n.getMonth()}],[ao("%b %d"),function(n){return 1!=n.getDate()}],[ao("%a %d"),function(n){return n.getDay()&&1!=n.getDate()}],[ao("%I %p"),function(n){return n.getHours()}],[ao("%I:%M"),function(n){return n.getMinutes()}],[ao(":%S"),function(n){return n.getSeconds()}],[ao(".%L"),function(n){return n.getMilliseconds()}]],il=Oo(ul);rl.year=Ls.year,Ls.scale=function(){return Ho(Zo.scale.linear(),rl,il)};var ol={range:function(n,t,e){return Zo.range(+n,+t,e).map(Fo)}},al=rl.map(function(n){return[n[0].utc,n[1]]}),cl=[[Uo("%Y"),Zt],[Uo("%B"),function(n){return n.getUTCMonth()}],[Uo("%b %d"),function(n){return 1!=n.getUTCDate()}],[Uo("%a %d"),function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],[Uo("%I %p"),function(n){return n.getUTCHours()}],[Uo("%I:%M"),function(n){return n.getUTCMinutes()}],[Uo(":%S"),function(n){return n.getUTCSeconds()}],[Uo(".%L"),function(n){return n.getUTCMilliseconds()}]],sl=Oo(cl);return al.year=Ls.year.utc,Ls.scale.utc=function(){return Ho(Zo.scale.linear(),al,sl)},Zo.text=dt(function(n){return n.responseText}),Zo.json=function(n,t){return mt(n,"application/json",Yo,t)},Zo.html=function(n,t){return mt(n,"text/html",Io,t)},Zo.xml=dt(function(n){return n.responseXML}),Zo}();
\ No newline at end of file
diff --git a/src/web/js/dust-core-2.2.2.min.js b/src/web/js/dust-core-2.2.2.min.js
new file mode 100644
index 0000000..3606333
--- /dev/null
+++ b/src/web/js/dust-core-2.2.2.min.js
@@ -0,0 +1,9 @@
+//
+// Dust - Asynchronous Templating v2.2.2
+// http://akdubya.github.com/dustjs
+//
+// Copyright (c) 2010, Aleksander Williams
+// Released under the MIT License.
+//
+
+function getGlobal(){return function(){return this.dust}.call(null)}var dust={};(function(dust){function Context(e,t,n,r){this.stack=e,this.global=t,this.blocks=n,this.templateName=r}function Stack(e,t,n,r){this.tail=t,this.isObject=!dust.isArray(e)&&e&&typeof e=="object",this.head=e,this.index=n,this.of=r}function Stub(e){this.head=new Chunk(this),this.callback=e,this.out=""}function Stream(){this.head=new Chunk(this)}function Chunk(e,t,n){this.root=e,this.next=t,this.data=[],this.flushable=!1,this.taps=n}function Tap(e,t){this.head=e,this.tail=t}if(!dust)return;var ERROR="ERROR",WARN="WARN",INFO="INFO",DEBUG="DEBUG",levels=[DEBUG,INFO,WARN,ERROR],logger=function(){};dust.isDebug=!1,dust.debugLevel=INFO,typeof window!="undefined"&&window&&window.console&&window.console.log?logger=window.console.log:typeof console!="undefined"&&console&&console.log&&(logger=console.log),dust.log=function(e,t){var t=t||INFO;dust.isDebug&&levels.indexOf(t)>=levels.indexOf(dust.debugLevel)&&(dust.logQueue||(dust.logQueue=[]),dust.logQueue.push({message:e,type:t}),logger.call(console||window.console,"[DUST "+t+"]: "+e))},dust.onError=function(e,t){dust.log(e.message||e,ERROR);if(dust.isDebug)throw e;return t},dust.helpers={},dust.cache={},dust.register=function(e,t){if(!e)return;dust.cache[e]=t},dust.render=function(e,t,n){var r=(new Stub(n)).head;try{dust.load(e,r,Context.wrap(t,e)).end()}catch(i){dust.onError(i,r)}},dust.stream=function(e,t){var n=new Stream;return dust.nextTick(function(){try{dust.load(e,n.head,Context.wrap(t,e)).end()}catch(r){dust.onError(r,n.head)}}),n},dust.renderSource=function(e,t,n){return dust.compileFn(e)(t,n)},dust.compileFn=function(e,t){var n=dust.loadSource(dust.compile(e,t));return function(e,r){var i=r?new Stub(r):new Stream;return dust.nextTick(function(){typeof n=="function"?n(i.head,Context.wrap(e,t)).end():dust.onError(new Error("Template ["+t+"] cannot be resolved to a Dust function"))}),i}},dust.load=function(e,t,n){var r=dust.cache[e];return r?r(t,n):dust.onLoad?t.map(function(t){dust.onLoad(e,function(r,i){if(r)return t.setError(r);dust.cache[e]||dust.loadSource(dust.compile(i,e)),dust.cache[e](t,n).end()})}):t.setError(new Error("Template Not Found: "+e))},dust.loadSource=function(source,path){return eval(source)},Array.isArray?dust.isArray=Array.isArray:dust.isArray=function(e){return Object.prototype.toString.call(e)==="[object Array]"},dust.nextTick=function(){return typeof process!="undefined"?process.nextTick:function(e){setTimeout(e,0)}}(),dust.isEmpty=function(e){return dust.isArray(e)&&!e.length?!0:e===0?!1:!e},dust.filter=function(e,t,n){if(n)for(var r=0,i=n.length;r<i;r++){var s=n[r];s==="s"?(t=null,dust.log("Using unescape filter on ["+e+"]",DEBUG)):typeof dust.filters[s]=="function"?e=dust.filters[s](e):dust.onError(new Error("Invalid filter ["+s+"]"))}return t&&(e=dust.filters[t](e)),e},dust.filters={h:function(e){return dust.escapeHtml(e)},j:function(e){return dust.escapeJs(e)},u:encodeURI,uc:encodeURIComponent,js:function(e){return JSON?JSON.stringify(e):(dust.log("JSON is undefined.  JSON stringify has not been used on ["+e+"]",WARN),e)},jp:function(e){return JSON?JSON.parse(e):(dust.log("JSON is undefined.  JSON parse has not been used on ["+e+"]",WARN),e)}},dust.makeBase=function(e){return new Context(new Stack,e)},Context.wrap=function(e,t){return e instanceof Context?e:new Context(new Stack(e),{},null,t)},Context.prototype.get=function(e,t){return typeof e=="string"&&(e[0]==="."&&(t=!0,e=e.substr(1)),e=e.split(".")),this._get(t,e)},Context.prototype._get=function(e,t){var n=this.stack,r=1,i,s,o,u;dust.log("Searching for reference [{"+t.join(".")+"}] in template ["+this.getTemplateName()+"]",DEBUG),s=t[0],o=t.length;if(e&&o===0)u=n,n=n.head;else{if(!e){while(n){if(n.isObject){u=n.head,i=n.head[s];if(i!==undefined)break}n=n.tail}i!==undefined?n=i:n=this.global?this.global[s]:undefined}else n=n.head[s];while(n&&r<o)u=n,n=n[t[r]],r++}if(typeof n=="function"){var a=function(){return n.apply(u,arguments)};return a.isFunction=!0,a}return n===undefined&&dust.log("Cannot find the value for reference [{"+t.join(".")+"}] in template ["+this.getTemplateName()+"]"),n},Context.prototype.getPath=function(e,t){return this._get(e,t)},Context.prototype.push=function(e,t,n){return new Context(new Stack(e,this.stack,t,n),this.global,this.blocks,this.getTemplateName())},Context.prototype.rebase=function(e){return new Context(new Stack(e),this.global,this.blocks,this.getTemplateName())},Context.prototype.current=function(){return this.stack.head},Context.prototype.getBlock=function(e,t,n){if(typeof e=="function"){var r=new Chunk;e=e(r,this).data.join("")}var i=this.blocks;if(!i){dust.log("No blocks for context[{"+e+"}] in template ["+this.getTemplateName()+"]",DEBUG);return}var s=i.length,o;while(s--){o=i[s][e];if(o)return o}},Context.prototype.shiftBlocks=function(e){var t=this.blocks,n;return e?(t?n=t.concat([e]):n=[e],new Context(this.stack,this.global,n,this.getTemplateName())):this},Context.prototype.getTemplateName=function(){return this.templateName},Stub.prototype.flush=function(){var e=this.head;while(e){if(!e.flushable){if(e.error){this.callback(e.error),dust.onError(new Error("Chunk error ["+e.error+"] thrown. Ceasing to render this template.")),this.flush=function(){};return}return}this.out+=e.data.join(""),e=e.next,this.head=e}this.callback(null,this.out)},Stream.prototype.flush=function(){var e=this.head;while(e){if(!e.flushable){if(e.error){this.emit("error",e.error),dust.onError(new Error("Chunk error ["+e.error+"] thrown. Ceasing to render this template.")),this.flush=function(){};return}return}this.emit("data",e.data.join("")),e=e.next,this.head=e}this.emit("end")},Stream.prototype.emit=function(e,t){if(!this.events)return dust.log("No events to emit",INFO),!1;var n=this.events[e];if(!n)return dust.log("Event type ["+e+"] does not exist",WARN),!1;if(typeof n=="function")n(t);else if(dust.isArray(n)){var r=n.slice(0);for(var i=0,s=r.length;i<s;i++)r[i](t)}else dust.onError(new Error("Event Handler ["+n+"] is not of a type that is handled by emit"))},Stream.prototype.on=function(e,t){return this.events||(this.events={}),this.events[e]?typeof this.events[e]=="function"?this.events[e]=[this.events[e],t]:this.events[e].push(t):(dust.log("Event type ["+e+"] does not exist. Using just the specified callback.",WARN),t?this.events[e]=t:dust.log("Callback for type ["+e+"] does not exist. Listener not registered.",WARN)),this},Stream.prototype.pipe=function(e){return this.on("data",function(t){try{e.write(t,"utf8")}catch(n){dust.onError(n,e.head)}}).on("end",function(){try{return e.end()}catch(t){dust.onError(t,e.head)}}).on("error",function(t){e.error(t)}),this},Chunk.prototype.write=function(e){var t=this.taps;return t&&(e=t.go(e)),this.data.push(e),this},Chunk.prototype.end=function(e){return e&&this.write(e),this.flushable=!0,this.root.flush(),this},Chunk.prototype.map=function(e){var t=new Chunk(this.root,this.next,this.taps),n=new Chunk(this.root,t,this.taps);return this.next=n,this.flushable=!0,e(n),t},Chunk.prototype.tap=function(e){var t=this.taps;return t?this.taps=t.push(e):this.taps=new Tap(e),this},Chunk.prototype.untap=function(){return this.taps=this.taps.tail,this},Chunk.prototype.render=function(e,t){return e(this,t)},Chunk.prototype.reference=function(e,t,n,r){if(typeof e=="function"){e.isFunction=!0,e=e.apply(t.current(),[this,t,null,{auto:n,filters:r}]);if(e instanceof Chunk)return e}return dust.isEmpty(e)?this:this.write(dust.filter(e,n,r))},Chunk.prototype.section=function(e,t,n,r){if(typeof e=="function"){e=e.apply(t.current(),[this,t,n,r]);if(e instanceof Chunk)return e}var i=n.block,s=n["else"];r&&(t=t.push(r));if(dust.isArray(e)){if(i){var o=e.length,u=this;if(o>0){t.stack.head&&(t.stack.head.$len=o);for(var a=0;a<o;a++)t.stack.head&&(t.stack.head.$idx=a),u=i(u,t.push(e[a],a,o));return t.stack.head&&(t.stack.head.$idx=undefined,t.stack.head.$len=undefined),u}if(s)return s(this,t)}}else if(e===!0){if(i)return i(this,t)}else if(e||e===0){if(i)return i(this,t.push(e))}else if(s)return s(this,t);return dust.log("Not rendering section (#) block in template ["+t.getTemplateName()+"], because above key was not found",DEBUG),this},Chunk.prototype.exists=function(e,t,n){var r=n.block,i=n["else"];if(!dust.isEmpty(e)){if(r)return r(this,t)}else if(i)return i(this,t);return dust.log("Not rendering exists (?) block in template ["+t.getTemplateName()+"], because above key was not found",DEBUG),this},Chunk.prototype.notexists=function(e,t,n){var r=n.block,i=n["else"];if(dust.isEmpty(e)){if(r)return r(this,t)}else if(i)return i(this,t);return dust.log("Not rendering not exists (^) block check in template ["+t.getTemplateName()+"], because above key was found",DEBUG),this},Chunk.prototype.block=function(e,t,n){var r=n.block;return e&&(r=e),r?r(this,t):this},Chunk.prototype.partial=function(e,t,n){var r;r=dust.makeBase(t.global),r.blocks=t.blocks,t.stack&&t.stack.tail&&(r.stack=t.stack.tail),n&&(r=r.push(n)),typeof e=="string"&&(r.templateName=e),r=r.push(t.stack.head);var i;return typeof e=="function"?i=this.capture(e,r,function(e,t){r.templateName=r.templateName||e,dust.load(e,t,r).end()}):i=dust.load(e,this,r),i},Chunk.prototype.helper=function(e,t,n,r){var i=this;try{return dust.helpers[e]?dust.helpers[e](i,t,n,r):dust.onError(new Error("Invalid helper ["+e+"]"),i)}catch(s){return dust.onError(s,i)}},Chunk.prototype.capture=function(e,t,n){return this.map(function(r){var i=new Stub(function(e,t){e?r.setError(e):n(t,r)});e(i.head,t).end()})},Chunk.prototype.setError=function(e){return this.error=e,this.root.flush(),this},Tap.prototype.push=function(e){return new Tap(e,this)},Tap.prototype.go=function(e){var t=this;while(t)e=t.head(e),t=t.tail;return e};var HCHARS=new RegExp(/[&<>\"\']/),AMP=/&/g,LT=/</g,GT=/>/g,QUOT=/\"/g,SQUOT=/\'/g;dust.escapeHtml=function(e){return typeof e=="string"?HCHARS.test(e)?e.replace(AMP,"&amp;").replace(LT,"&lt;").replace(GT,"&gt;").replace(QUOT,"&quot;").replace(SQUOT,"&#39;"):e:e};var BS=/\\/g,FS=/\//g,CR=/\r/g,LS=/\u2028/g,PS=/\u2029/g,NL=/\n/g,LF=/\f/g,SQ=/'/g,DQ=/"/g,TB=/\t/g;dust.escapeJs=function(e){return typeof e=="string"?e.replace(BS,"\\\\").replace(FS,"\\/").replace(DQ,'\\"').replace(SQ,"\\'").replace(CR,"\\r").replace(LS,"\\u2028").replace(PS,"\\u2029").replace(NL,"\\n").replace(LF,"\\f").replace(TB,"\\t"):e}})(dust),typeof exports!="undefined"&&(typeof process!="undefined"&&require("./server")(dust),module.exports=dust)
\ No newline at end of file
diff --git a/src/web/js/moment.min.js b/src/web/js/moment.min.js
new file mode 100644
index 0000000..568ad05
--- /dev/null
+++ b/src/web/js/moment.min.js
@@ -0,0 +1,6 @@
+//! moment.js
+//! version : 2.4.0
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
+(function(a){function b(a,b){return function(c){return i(a.call(this,c),b)}}function c(a,b){return function(c){return this.lang().ordinal(a.call(this,c),b)}}function d(){}function e(a){u(a),g(this,a)}function f(a){var b=o(a),c=b.year||0,d=b.month||0,e=b.week||0,f=b.day||0,g=b.hour||0,h=b.minute||0,i=b.second||0,j=b.millisecond||0;this._input=a,this._milliseconds=+j+1e3*i+6e4*h+36e5*g,this._days=+f+7*e,this._months=+d+12*c,this._data={},this._bubble()}function g(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return b.hasOwnProperty("toString")&&(a.toString=b.toString),b.hasOwnProperty("valueOf")&&(a.valueOf=b.valueOf),a}function h(a){return 0>a?Math.ceil(a):Math.floor(a)}function i(a,b){for(var c=a+"";c.length<b;)c="0"+c;return c}function j(a,b,c,d){var e,f,g=b._milliseconds,h=b._days,i=b._months;g&&a._d.setTime(+a._d+g*c),(h||i)&&(e=a.minute(),f=a.hour()),h&&a.date(a.date()+h*c),i&&a.month(a.month()+i*c),g&&!d&&bb.updateOffset(a),(h||i)&&(a.minute(e),a.hour(f))}function k(a){return"[object Array]"===Object.prototype.toString.call(a)}function l(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function m(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function n(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=Kb[a]||Lb[b]||b}return a}function o(a){var b,c,d={};for(c in a)a.hasOwnProperty(c)&&(b=n(c),b&&(d[b]=a[c]));return d}function p(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}bb[b]=function(e,f){var g,h,i=bb.fn._lang[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=bb().utc().set(d,a);return i.call(bb.fn._lang,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function r(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function s(a){return t(a)?366:365}function t(a){return 0===a%4&&0!==a%100||0===a%400}function u(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[gb]<0||a._a[gb]>11?gb:a._a[hb]<1||a._a[hb]>r(a._a[fb],a._a[gb])?hb:a._a[ib]<0||a._a[ib]>23?ib:a._a[jb]<0||a._a[jb]>59?jb:a._a[kb]<0||a._a[kb]>59?kb:a._a[lb]<0||a._a[lb]>999?lb:-1,a._pf._overflowDayOfYear&&(fb>b||b>hb)&&(b=hb),a._pf.overflow=b)}function v(a){a._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function w(a){return null==a._isValid&&(a._isValid=!isNaN(a._d.getTime())&&a._pf.overflow<0&&!a._pf.empty&&!a._pf.invalidMonth&&!a._pf.nullInput&&!a._pf.invalidFormat&&!a._pf.userInvalidated,a._strict&&(a._isValid=a._isValid&&0===a._pf.charsLeftOver&&0===a._pf.unusedTokens.length)),a._isValid}function x(a){return a?a.toLowerCase().replace("_","-"):a}function y(a,b){return b.abbr=a,mb[a]||(mb[a]=new d),mb[a].set(b),mb[a]}function z(a){delete mb[a]}function A(a){var b,c,d,e,f=0,g=function(a){if(!mb[a]&&nb)try{require("./lang/"+a)}catch(b){}return mb[a]};if(!a)return bb.fn._lang;if(!k(a)){if(c=g(a))return c;a=[a]}for(;f<a.length;){for(e=x(a[f]).split("-"),b=e.length,d=x(a[f+1]),d=d?d.split("-"):null;b>0;){if(c=g(e.slice(0,b).join("-")))return c;if(d&&d.length>=b&&m(e,d,!0)>=b-1)break;b--}f++}return bb.fn._lang}function B(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function C(a){var b,c,d=a.match(rb);for(b=0,c=d.length;c>b;b++)d[b]=Pb[d[b]]?Pb[d[b]]:B(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function D(a,b){return a.isValid()?(b=E(b,a.lang()),Mb[b]||(Mb[b]=C(b)),Mb[b](a)):a.lang().invalidDate()}function E(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(sb.lastIndex=0;d>=0&&sb.test(a);)a=a.replace(sb,c),sb.lastIndex=0,d-=1;return a}function F(a,b){var c;switch(a){case"DDDD":return vb;case"YYYY":case"GGGG":case"gggg":return wb;case"YYYYY":case"GGGGG":case"ggggg":return xb;case"S":case"SS":case"SSS":case"DDD":return ub;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return zb;case"a":case"A":return A(b._l)._meridiemParse;case"X":return Cb;case"Z":case"ZZ":return Ab;case"T":return Bb;case"SSSS":return yb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"ww":case"W":case"WW":case"e":case"E":return tb;default:return c=new RegExp(N(M(a.replace("\\","")),"i"))}}function G(a){var b=(Ab.exec(a)||[])[0],c=(b+"").match(Hb)||["-",0,0],d=+(60*c[1])+q(c[2]);return"+"===c[0]?-d:d}function H(a,b,c){var d,e=c._a;switch(a){case"M":case"MM":null!=b&&(e[gb]=q(b)-1);break;case"MMM":case"MMMM":d=A(c._l).monthsParse(b),null!=d?e[gb]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[hb]=q(b));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=q(b));break;case"YY":e[fb]=q(b)+(q(b)>68?1900:2e3);break;case"YYYY":case"YYYYY":e[fb]=q(b);break;case"a":case"A":c._isPm=A(c._l).isPM(b);break;case"H":case"HH":case"h":case"hh":e[ib]=q(b);break;case"m":case"mm":e[jb]=q(b);break;case"s":case"ss":e[kb]=q(b);break;case"S":case"SS":case"SSS":case"SSSS":e[lb]=q(1e3*("0."+b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=G(b);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":a=a.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=b)}}function I(a){var b,c,d,e,f,g,h,i,j,k,l=[];if(!a._d){for(d=K(a),a._w&&null==a._a[hb]&&null==a._a[gb]&&(f=function(b){return b?b.length<3?parseInt(b,10)>68?"19"+b:"20"+b:b:null==a._a[fb]?bb().weekYear():a._a[fb]},g=a._w,null!=g.GG||null!=g.W||null!=g.E?h=X(f(g.GG),g.W||1,g.E,4,1):(i=A(a._l),j=null!=g.d?T(g.d,i):null!=g.e?parseInt(g.e,10)+i._week.dow:0,k=parseInt(g.w,10)||1,null!=g.d&&j<i._week.dow&&k++,h=X(f(g.gg),k,j,i._week.doy,i._week.dow)),a._a[fb]=h.year,a._dayOfYear=h.dayOfYear),a._dayOfYear&&(e=null==a._a[fb]?d[fb]:a._a[fb],a._dayOfYear>s(e)&&(a._pf._overflowDayOfYear=!0),c=S(e,0,a._dayOfYear),a._a[gb]=c.getUTCMonth(),a._a[hb]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=l[b]=d[b];for(;7>b;b++)a._a[b]=l[b]=null==a._a[b]?2===b?1:0:a._a[b];l[ib]+=q((a._tzm||0)/60),l[jb]+=q((a._tzm||0)%60),a._d=(a._useUTC?S:R).apply(null,l)}}function J(a){var b;a._d||(b=o(a._i),a._a=[b.year,b.month,b.day,b.hour,b.minute,b.second,b.millisecond],I(a))}function K(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function L(a){a._a=[],a._pf.empty=!0;var b,c,d,e,f,g=A(a._l),h=""+a._i,i=h.length,j=0;for(d=E(a._f,g).match(rb)||[],b=0;b<d.length;b++)e=d[b],c=(F(e,a).exec(h)||[])[0],c&&(f=h.substr(0,h.indexOf(c)),f.length>0&&a._pf.unusedInput.push(f),h=h.slice(h.indexOf(c)+c.length),j+=c.length),Pb[e]?(c?a._pf.empty=!1:a._pf.unusedTokens.push(e),H(e,c,a)):a._strict&&!c&&a._pf.unusedTokens.push(e);a._pf.charsLeftOver=i-j,h.length>0&&a._pf.unusedInput.push(h),a._isPm&&a._a[ib]<12&&(a._a[ib]+=12),a._isPm===!1&&12===a._a[ib]&&(a._a[ib]=0),I(a),u(a)}function M(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function N(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function O(a){var b,c,d,e,f;if(0===a._f.length)return a._pf.invalidFormat=!0,a._d=new Date(0/0),void 0;for(e=0;e<a._f.length;e++)f=0,b=g({},a),v(b),b._f=a._f[e],L(b),w(b)&&(f+=b._pf.charsLeftOver,f+=10*b._pf.unusedTokens.length,b._pf.score=f,(null==d||d>f)&&(d=f,c=b));g(a,c||b)}function P(a){var b,c=a._i,d=Db.exec(c);if(d){for(a._pf.iso=!0,b=4;b>0;b--)if(d[b]){a._f=Fb[b-1]+(d[6]||" ");break}for(b=0;4>b;b++)if(Gb[b][1].exec(c)){a._f+=Gb[b][0];break}Ab.exec(c)&&(a._f+="Z"),L(a)}else a._d=new Date(c)}function Q(b){var c=b._i,d=ob.exec(c);c===a?b._d=new Date:d?b._d=new Date(+d[1]):"string"==typeof c?P(b):k(c)?(b._a=c.slice(0),I(b)):l(c)?b._d=new Date(+c):"object"==typeof c?J(b):b._d=new Date(c)}function R(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function S(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function T(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function U(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function V(a,b,c){var d=eb(Math.abs(a)/1e3),e=eb(d/60),f=eb(e/60),g=eb(f/24),h=eb(g/365),i=45>d&&["s",d]||1===e&&["m"]||45>e&&["mm",e]||1===f&&["h"]||22>f&&["hh",f]||1===g&&["d"]||25>=g&&["dd",g]||45>=g&&["M"]||345>g&&["MM",eb(g/30)]||1===h&&["y"]||["yy",h];return i[2]=b,i[3]=a>0,i[4]=c,U.apply({},i)}function W(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=bb(a).add("d",f),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function X(a,b,c,d,e){var f,g,h=new Date(Date.UTC(a,0)).getUTCDay();return c=null!=c?c:e,f=e-h+(h>d?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:s(a-1)+g}}function Y(a){var b=a._i,c=a._f;return"undefined"==typeof a._pf&&v(a),null===b?bb.invalid({nullInput:!0}):("string"==typeof b&&(a._i=b=A().preparse(b)),bb.isMoment(b)?(a=g({},b),a._d=new Date(+b._d)):c?k(c)?O(a):L(a):Q(a),new e(a))}function Z(a,b){bb.fn[a]=bb.fn[a+"s"]=function(a){var c=this._isUTC?"UTC":"";return null!=a?(this._d["set"+c+b](a),bb.updateOffset(this),this):this._d["get"+c+b]()}}function $(a){bb.duration.fn[a]=function(){return this._data[a]}}function _(a,b){bb.duration.fn["as"+a]=function(){return+this/b}}function ab(a){var b=!1,c=bb;"undefined"==typeof ender&&(this.moment=a?function(){return!b&&console&&console.warn&&(b=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),c.apply(null,arguments)}:bb)}for(var bb,cb,db="2.4.0",eb=Math.round,fb=0,gb=1,hb=2,ib=3,jb=4,kb=5,lb=6,mb={},nb="undefined"!=typeof module&&module.exports,ob=/^\/?Date\((\-?\d+)/i,pb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,qb=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,rb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,sb=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,tb=/\d\d?/,ub=/\d{1,3}/,vb=/\d{3}/,wb=/\d{1,4}/,xb=/[+\-]?\d{1,6}/,yb=/\d+/,zb=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Ab=/Z|[\+\-]\d\d:?\d\d/i,Bb=/T/i,Cb=/[\+\-]?\d+(\.\d{1,3})?/,Db=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d:?\d\d|Z)?)?$/,Eb="YYYY-MM-DDTHH:mm:ssZ",Fb=["YYYY-MM-DD","GGGG-[W]WW","GGGG-[W]WW-E","YYYY-DDD"],Gb=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Hb=/([\+\-]|\d\d)/gi,Ib="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Jb={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Kb={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Lb={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Mb={},Nb="DDD w W M D d".split(" "),Ob="M D H h m s w W".split(" "),Pb={M:function(){return this.month()+1},MMM:function(a){return this.lang().monthsShort(this,a)},MMMM:function(a){return this.lang().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.lang().weekdaysMin(this,a)},ddd:function(a){return this.lang().weekdaysShort(this,a)},dddd:function(a){return this.lang().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return i(this.year()%100,2)},YYYY:function(){return i(this.year(),4)},YYYYY:function(){return i(this.year(),5)},gg:function(){return i(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return i(this.weekYear(),5)},GG:function(){return i(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return i(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return q(this.milliseconds()/100)},SS:function(){return i(q(this.milliseconds()/10),2)},SSS:function(){return i(this.milliseconds(),3)},SSSS:function(){return i(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+":"+i(q(a)%60,2)},ZZ:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(10*a/6),4)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()}},Qb=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Nb.length;)cb=Nb.pop(),Pb[cb+"o"]=c(Pb[cb],cb);for(;Ob.length;)cb=Ob.pop(),Pb[cb+cb]=b(Pb[cb],2);for(Pb.DDDD=b(Pb.DDD,3),g(d.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a){var b,c,d;for(this._monthsParse||(this._monthsParse=[]),b=0;12>b;b++)if(this._monthsParse[b]||(c=bb.utc([2e3,b]),d="^"+this.months(c,"")+"|^"+this.monthsShort(c,""),this._monthsParse[b]=new RegExp(d.replace(".",""),"i")),this._monthsParse[b].test(a))return b},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=bb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b){var c=this._calendar[a];return"function"==typeof c?c.apply(b):c},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",preparse:function(a){return a},postformat:function(a){return a},week:function(a){return W(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),bb=function(b,c,d,e){return"boolean"==typeof d&&(e=d,d=a),Y({_i:b,_f:c,_l:d,_strict:e,_isUTC:!1})},bb.utc=function(b,c,d,e){var f;return"boolean"==typeof d&&(e=d,d=a),f=Y({_useUTC:!0,_isUTC:!0,_l:d,_i:b,_f:c,_strict:e}).utc()},bb.unix=function(a){return bb(1e3*a)},bb.duration=function(a,b){var c,d,e,g=bb.isDuration(a),h="number"==typeof a,i=g?a._input:h?{}:a,j=null;return h?b?i[b]=a:i.milliseconds=a:(j=pb.exec(a))?(c="-"===j[1]?-1:1,i={y:0,d:q(j[hb])*c,h:q(j[ib])*c,m:q(j[jb])*c,s:q(j[kb])*c,ms:q(j[lb])*c}):(j=qb.exec(a))&&(c="-"===j[1]?-1:1,e=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*c},i={y:e(j[2]),M:e(j[3]),d:e(j[4]),h:e(j[5]),m:e(j[6]),s:e(j[7]),w:e(j[8])}),d=new f(i),g&&a.hasOwnProperty("_lang")&&(d._lang=a._lang),d},bb.version=db,bb.defaultFormat=Eb,bb.updateOffset=function(){},bb.lang=function(a,b){var c;return a?(b?y(x(a),b):null===b?(z(a),a="en"):mb[a]||A(a),c=bb.duration.fn._lang=bb.fn._lang=A(a),c._abbr):bb.fn._lang._abbr},bb.langData=function(a){return a&&a._lang&&a._lang._abbr&&(a=a._lang._abbr),A(a)},bb.isMoment=function(a){return a instanceof e},bb.isDuration=function(a){return a instanceof f},cb=Qb.length-1;cb>=0;--cb)p(Qb[cb]);for(bb.normalizeUnits=function(a){return n(a)},bb.invalid=function(a){var b=bb.utc(0/0);return null!=a?g(b._pf,a):b._pf.userInvalidated=!0,b},bb.parseZone=function(a){return bb(a).parseZone()},g(bb.fn=e.prototype,{clone:function(){return bb(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){return D(bb(this).utc(),"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds()]},isValid:function(){return w(this)},isDSTShifted:function(){return this._a?this.isValid()&&m(this._a,(this._isUTC?bb.utc(this._a):bb(this._a)).toArray())>0:!1},parsingFlags:function(){return g({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(a){var b=D(this,a||bb.defaultFormat);return this.lang().postformat(b)},add:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,1),this},subtract:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,-1),this},diff:function(a,b,c){var d,e,f=this._isUTC?bb(a).zone(this._offset||0):bb(a).local(),g=6e4*(this.zone()-f.zone());return b=n(b),"year"===b||"month"===b?(d=432e5*(this.daysInMonth()+f.daysInMonth()),e=12*(this.year()-f.year())+(this.month()-f.month()),e+=(this-bb(this).startOf("month")-(f-bb(f).startOf("month")))/d,e-=6e4*(this.zone()-bb(this).startOf("month").zone()-(f.zone()-bb(f).startOf("month").zone()))/d,"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:h(e)},from:function(a,b){return bb.duration(this.diff(a)).lang(this.lang()._abbr).humanize(!b)},fromNow:function(a){return this.from(bb(),a)},calendar:function(){var a=this.diff(bb().zone(this.zone()).startOf("day"),"days",!0),b=-6>a?"sameElse":-1>a?"lastWeek":0>a?"lastDay":1>a?"sameDay":2>a?"nextDay":7>a?"nextWeek":"sameElse";return this.format(this.lang().calendar(b,this))},isLeapYear:function(){return t(this.year())},isDST:function(){return this.zone()<this.clone().month(0).zone()||this.zone()<this.clone().month(5).zone()},day:function(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=T(a,this.lang()),this.add({d:a-b})):b},month:function(a){var b,c=this._isUTC?"UTC":"";return null!=a?"string"==typeof a&&(a=this.lang().monthsParse(a),"number"!=typeof a)?this:(b=this.date(),this.date(1),this._d["set"+c+"Month"](a),this.date(Math.min(b,this.daysInMonth())),bb.updateOffset(this),this):this._d["get"+c+"Month"]()},startOf:function(a){switch(a=n(a)){case"year":this.month(0);case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a?this.weekday(0):"isoWeek"===a&&this.isoWeekday(1),this},endOf:function(a){return a=n(a),this.startOf(a).add("isoWeek"===a?"week":a,1).subtract("ms",1)},isAfter:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)>+bb(a).startOf(b)},isBefore:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)<+bb(a).startOf(b)},isSame:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)===+bb(a).startOf(b)},min:function(a){return a=bb.apply(null,arguments),this>a?this:a},max:function(a){return a=bb.apply(null,arguments),a>this?this:a},zone:function(a){var b=this._offset||0;return null==a?this._isUTC?b:this._d.getTimezoneOffset():("string"==typeof a&&(a=G(a)),Math.abs(a)<16&&(a=60*a),this._offset=a,this._isUTC=!0,b!==a&&j(this,bb.duration(b-a,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(a){return a=a?bb(a).zone():0,0===(this.zone()-a)%60},daysInMonth:function(){return r(this.year(),this.month())},dayOfYear:function(a){var b=eb((bb(this).startOf("day")-bb(this).startOf("year"))/864e5)+1;return null==a?b:this.add("d",a-b)},weekYear:function(a){var b=W(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==a?b:this.add("y",a-b)},isoWeekYear:function(a){var b=W(this,1,4).year;return null==a?b:this.add("y",a-b)},week:function(a){var b=this.lang().week(this);return null==a?b:this.add("d",7*(a-b))},isoWeek:function(a){var b=W(this,1,4).week;return null==a?b:this.add("d",7*(a-b))},weekday:function(a){var b=(this.day()+7-this.lang()._week.dow)%7;return null==a?b:this.add("d",a-b)},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},get:function(a){return a=n(a),this[a]()},set:function(a,b){return a=n(a),"function"==typeof this[a]&&this[a](b),this},lang:function(b){return b===a?this._lang:(this._lang=A(b),this)}}),cb=0;cb<Ib.length;cb++)Z(Ib[cb].toLowerCase().replace(/s$/,""),Ib[cb]);Z("year","FullYear"),bb.fn.days=bb.fn.day,bb.fn.months=bb.fn.month,bb.fn.weeks=bb.fn.week,bb.fn.isoWeeks=bb.fn.isoWeek,bb.fn.toJSON=bb.fn.toISOString,g(bb.duration.fn=f.prototype,{_bubble:function(){var a,b,c,d,e=this._milliseconds,f=this._days,g=this._months,i=this._data;i.milliseconds=e%1e3,a=h(e/1e3),i.seconds=a%60,b=h(a/60),i.minutes=b%60,c=h(b/60),i.hours=c%24,f+=h(c/24),i.days=f%30,g+=h(f/30),i.months=g%12,d=h(g/12),i.years=d},weeks:function(){return h(this.days()/7)},valueOf:function(){return this._milliseconds+864e5*this._days+2592e6*(this._months%12)+31536e6*q(this._months/12)},humanize:function(a){var b=+this,c=V(b,!a,this.lang());return a&&(c=this.lang().pastFuture(b,c)),this.lang().postformat(c)},add:function(a,b){var c=bb.duration(a,b);return this._milliseconds+=c._milliseconds,this._days+=c._days,this._months+=c._months,this._bubble(),this},subtract:function(a,b){var c=bb.duration(a,b);return this._milliseconds-=c._milliseconds,this._days-=c._days,this._months-=c._months,this._bubble(),this},get:function(a){return a=n(a),this[a.toLowerCase()+"s"]()},as:function(a){return a=n(a),this["as"+a.charAt(0).toUpperCase()+a.slice(1)+"s"]()},lang:bb.fn.lang,toIsoString:function(){var a=Math.abs(this.years()),b=Math.abs(this.months()),c=Math.abs(this.days()),d=Math.abs(this.hours()),e=Math.abs(this.minutes()),f=Math.abs(this.seconds()+this.milliseconds()/1e3);return this.asSeconds()?(this.asSeconds()<0?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"}});for(cb in Jb)Jb.hasOwnProperty(cb)&&(_(cb,Jb[cb]),$(cb.toLowerCase()));_("Weeks",6048e5),bb.duration.fn.asMonths=function(){return(+this-31536e6*this.years())/2592e6+12*this.years()},bb.lang("en",{ordinal:function(a){var b=a%10,c=1===q(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),nb?(module.exports=bb,ab(!0)):"function"==typeof define&&define.amd?define("moment",function(b,c,d){return d.config().noGlobal!==!0&&ab(d.config().noGlobal===a),bb}):ab()}).call(this);
\ No newline at end of file
diff --git a/src/web/js/svgNavigate.js b/src/web/js/svgNavigate.js
index 68121f9..b42f8c1 100644
--- a/src/web/js/svgNavigate.js
+++ b/src/web/js/svgNavigate.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 (function($) {	
 
 	var mouseUp = function(evt) {
diff --git a/unit/java/azkaban/test/database/AzkabanDatabaseSetupTest.java b/unit/java/azkaban/test/database/AzkabanDatabaseSetupTest.java
index 23d548c..0b2bd48 100644
--- a/unit/java/azkaban/test/database/AzkabanDatabaseSetupTest.java
+++ b/unit/java/azkaban/test/database/AzkabanDatabaseSetupTest.java
@@ -6,11 +6,10 @@ import java.sql.SQLException;
 
 import javax.sql.DataSource;
 
-import junit.framework.Assert;
-
 import org.apache.commons.dbutils.QueryRunner;
 import org.apache.commons.io.FileUtils;
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
diff --git a/unit/java/azkaban/test/execapp/event/BlockingStatusTest.java b/unit/java/azkaban/test/execapp/event/BlockingStatusTest.java
index 3acd91b..124f9a0 100644
--- a/unit/java/azkaban/test/execapp/event/BlockingStatusTest.java
+++ b/unit/java/azkaban/test/execapp/event/BlockingStatusTest.java
@@ -1,8 +1,8 @@
 package azkaban.test.execapp.event;
 
+import org.junit.Assert;
 import org.junit.Test;
 
-import junit.framework.Assert;
 import azkaban.execapp.event.BlockingStatus;
 import azkaban.executor.Status;
 
diff --git a/unit/java/azkaban/test/execapp/FlowRunnerTest.java b/unit/java/azkaban/test/execapp/FlowRunnerTest.java
index da1ed27..7628c13 100644
--- a/unit/java/azkaban/test/execapp/FlowRunnerTest.java
+++ b/unit/java/azkaban/test/execapp/FlowRunnerTest.java
@@ -4,10 +4,9 @@ import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
 
-import junit.framework.Assert;
-
 import org.apache.commons.io.FileUtils;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/unit/java/azkaban/test/execapp/JobRunnerTest.java b/unit/java/azkaban/test/execapp/JobRunnerTest.java
index 1f8edc2..02f35ae 100644
--- a/unit/java/azkaban/test/execapp/JobRunnerTest.java
+++ b/unit/java/azkaban/test/execapp/JobRunnerTest.java
@@ -4,11 +4,10 @@ import java.io.File;
 import java.io.IOException;
 import java.util.HashSet;
 
-import junit.framework.Assert;
-
 import org.apache.commons.io.FileUtils;
 import org.apache.log4j.Logger;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/unit/java/azkaban/test/execapp/MockProjectLoader.java b/unit/java/azkaban/test/execapp/MockProjectLoader.java
index b10ea29..ef76de7 100644
--- a/unit/java/azkaban/test/execapp/MockProjectLoader.java
+++ b/unit/java/azkaban/test/execapp/MockProjectLoader.java
@@ -224,4 +224,10 @@ public class MockProjectLoader implements ProjectLoader {
 		// TODO Auto-generated method stub
 		
 	}
+
+	@Override
+	public void updateFlow(Project project, int version, Flow flow) throws ProjectManagerException {
+		// TODO Auto-generated method stub
+		
+	}
 }
\ No newline at end of file
diff --git a/unit/java/azkaban/test/execapp/ProjectVersionsTest.java b/unit/java/azkaban/test/execapp/ProjectVersionsTest.java
index 5a0a31b..9120729 100644
--- a/unit/java/azkaban/test/execapp/ProjectVersionsTest.java
+++ b/unit/java/azkaban/test/execapp/ProjectVersionsTest.java
@@ -3,8 +3,7 @@ package azkaban.test.execapp;
 import java.util.ArrayList;
 import java.util.Collections;
 
-import junit.framework.Assert;
-
+import org.junit.Assert;
 import org.junit.Test;
 
 import azkaban.execapp.ProjectVersion;
diff --git a/unit/java/azkaban/test/executor/JdbcExecutorLoaderTest.java b/unit/java/azkaban/test/executor/JdbcExecutorLoaderTest.java
index ba89a20..302eaac 100644
--- a/unit/java/azkaban/test/executor/JdbcExecutorLoaderTest.java
+++ b/unit/java/azkaban/test/executor/JdbcExecutorLoaderTest.java
@@ -6,17 +6,16 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.HashMap;
-import java.util.HashSet;
+import java.util.HashSet;	
 import java.util.Map;
 
 import javax.sql.DataSource;
 
-import junit.framework.Assert;
-
 import org.apache.commons.dbutils.DbUtils;
 import org.apache.commons.dbutils.QueryRunner;
 import org.apache.commons.dbutils.ResultSetHandler;
 import org.joda.time.DateTime;
+import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
diff --git a/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java b/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java
index 85f874c..0de4121 100644
--- a/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java
+++ b/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java
@@ -9,15 +9,13 @@ import java.util.List;
 
 import javax.sql.DataSource;
 
-import junit.framework.Assert;
-
 import org.apache.commons.dbutils.DbUtils;
 import org.apache.commons.dbutils.QueryRunner;
 import org.apache.commons.dbutils.ResultSetHandler;
+import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-
 import azkaban.flow.Edge;
 import azkaban.flow.Flow;
 import azkaban.flow.Node;
diff --git a/unit/java/azkaban/test/trigger/BasicTimeCheckerTest.java b/unit/java/azkaban/test/trigger/BasicTimeCheckerTest.java
new file mode 100644
index 0000000..dc9b970
--- /dev/null
+++ b/unit/java/azkaban/test/trigger/BasicTimeCheckerTest.java
@@ -0,0 +1,63 @@
+package azkaban.test.trigger;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.joda.time.DateTime;
+import org.joda.time.ReadablePeriod;
+import org.junit.Test;
+
+import azkaban.trigger.Condition;
+import azkaban.trigger.ConditionChecker;
+import azkaban.trigger.builtin.BasicTimeChecker;
+import azkaban.utils.Utils;
+
+public class BasicTimeCheckerTest {
+
+	@Test
+	public void basicTimerTest(){
+		
+		Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
+		
+		// get a new timechecker, start from now, repeat every minute. should evaluate to false now, and true a minute later.
+		DateTime now = DateTime.now();
+		ReadablePeriod period = Utils.parsePeriodString("10s");
+		
+		BasicTimeChecker timeChecker = new BasicTimeChecker("BasicTimeChecket_1", now.getMillis(), now.getZone(), true, true, period);
+		checkers.put(timeChecker.getId(), timeChecker);
+		String expr = timeChecker.getId() + ".eval()";
+		
+		Condition cond = new Condition(checkers, expr);
+		System.out.println(expr);
+		
+		assertFalse(cond.isMet());
+		
+		//sleep for 1 min
+		try {
+			Thread.sleep(10000);
+		} catch (InterruptedException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		
+		assertTrue(cond.isMet());
+		
+		cond.resetCheckers();
+		
+		assertFalse(cond.isMet());
+		
+		//sleep for 1 min
+		try {
+			Thread.sleep(10000);
+		} catch (InterruptedException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		
+		assertTrue(cond.isMet());
+		
+	}
+}
diff --git a/unit/java/azkaban/test/trigger/ConditionTest.java b/unit/java/azkaban/test/trigger/ConditionTest.java
new file mode 100644
index 0000000..76ced5e
--- /dev/null
+++ b/unit/java/azkaban/test/trigger/ConditionTest.java
@@ -0,0 +1,89 @@
+package azkaban.test.trigger;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import org.joda.time.DateTime;
+import org.junit.Test;
+
+import azkaban.trigger.CheckerTypeLoader;
+import azkaban.trigger.Condition;
+import azkaban.trigger.ConditionChecker;
+import azkaban.trigger.builtin.BasicTimeChecker;
+import azkaban.utils.JSONUtils;
+import azkaban.utils.Props;
+import azkaban.utils.Utils;
+
+public class ConditionTest {
+	
+	@Test
+	public void conditionTest(){
+		
+		Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
+
+		ThresholdChecker fake1 = new ThresholdChecker("thresholdchecker1", 10);
+		ThresholdChecker fake2 = new ThresholdChecker("thresholdchecker2", 20);
+		ThresholdChecker.setVal(15);
+		checkers.put(fake1.getId(), fake1);
+		checkers.put(fake2.getId(), fake2);
+
+		String expr1 = "( " + fake1.getId()+ ".eval()" + " && " + fake2.getId()+ ".eval()" + " )" + " || " + "( " + fake1.getId()+".eval()" + " && " + "!" + fake2.getId()+".eval()" + " )";
+		String expr2 = "( " + fake1.getId()+ ".eval()" + " && " + fake2.getId()+ ".eval()" + " )" + " || " + "( " + fake1.getId()+".eval()" + " && " + fake2.getId()+".eval()" + " )";
+
+		Condition cond = new Condition(checkers, expr1);
+
+		System.out.println("Setting expression " + expr1);
+		assertTrue(cond.isMet());
+		cond.setExpression(expr2);
+		System.out.println("Setting expression " + expr2);
+		assertFalse(cond.isMet());
+		
+	}
+	
+	@Test
+	public void jsonConversionTest() throws Exception {
+		
+		CheckerTypeLoader checkerTypeLoader = new CheckerTypeLoader();
+		checkerTypeLoader.init(new Props());
+		Condition.setCheckerLoader(checkerTypeLoader);
+
+		Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
+		
+		// get a new timechecker, start from now, repeat every minute. should evaluate to false now, and true a minute later.
+		DateTime now = DateTime.now();
+		String period = "6s";
+		
+		//BasicTimeChecker timeChecker = new BasicTimeChecker(now, true, true, period);
+		ConditionChecker timeChecker = new BasicTimeChecker("BasicTimeChecker_1", now.getMillis(), now.getZone(), true, true, Utils.parsePeriodString(period));
+		System.out.println("checker id is " + timeChecker.getId());
+		
+		checkers.put(timeChecker.getId(), timeChecker);
+		String expr = timeChecker.getId() + ".eval()";
+		
+		Condition cond = new Condition(checkers, expr);
+		
+		File temp = File.createTempFile("temptest", "temptest");
+		temp.deleteOnExit();
+		Object obj = cond.toJson();
+		JSONUtils.toJSON(obj, temp);
+		
+		Condition cond2 = Condition.fromJson(JSONUtils.parseJSONFromFile(temp));
+		
+		Map<String, ConditionChecker> checkers2 = cond2.getCheckers();
+		
+		assertTrue(cond.getExpression().equals(cond2.getExpression()));
+		System.out.println("cond1: " + cond.getExpression());
+		System.out.println("cond2: " + cond2.getExpression());
+		assertTrue(checkers2.size() == 1);
+		ConditionChecker checker2 = checkers2.get(timeChecker.getId());
+		//assertTrue(checker2.getId().equals(timeChecker.getId()));
+		System.out.println("checker1: " + timeChecker.getId());
+		System.out.println("checker2: " + checker2.getId());
+		assertTrue(timeChecker.getId().equals(checker2.getId()));
+	}
+	
+}
diff --git a/unit/java/azkaban/test/trigger/DummyTriggerAction.java b/unit/java/azkaban/test/trigger/DummyTriggerAction.java
new file mode 100644
index 0000000..cffbed6
--- /dev/null
+++ b/unit/java/azkaban/test/trigger/DummyTriggerAction.java
@@ -0,0 +1,56 @@
+package azkaban.test.trigger;
+
+import java.util.Map;
+
+import azkaban.trigger.TriggerAction;
+
+public class DummyTriggerAction implements TriggerAction{
+
+	public static final String type = "DummyAction";
+	
+	private String message;
+	
+	public DummyTriggerAction(String message) {
+		this.message = message;
+	}
+	
+	@Override
+	public String getType() {
+		return type;
+	}
+
+	@Override
+	public TriggerAction fromJson(Object obj) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public Object toJson() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public void doAction() {
+		System.out.println(getType() + " invoked.");
+		System.out.println(message);
+	}
+
+	@Override
+	public String getDescription() {
+		return "this is real dummy action";
+	}
+
+	@Override
+	public String getId() {
+		return null;
+	}
+
+	@Override
+	public void setContext(Map<String, Object> context) {
+		// TODO Auto-generated method stub
+		
+	}
+
+}
diff --git a/unit/java/azkaban/test/trigger/ExecuteFlowActionTest.java b/unit/java/azkaban/test/trigger/ExecuteFlowActionTest.java
new file mode 100644
index 0000000..905ca3d
--- /dev/null
+++ b/unit/java/azkaban/test/trigger/ExecuteFlowActionTest.java
@@ -0,0 +1,39 @@
+package azkaban.test.trigger;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import azkaban.executor.ExecutionOptions;
+import azkaban.trigger.ActionTypeLoader;
+import azkaban.trigger.builtin.ExecuteFlowAction;
+import azkaban.utils.Props;
+
+
+public class ExecuteFlowActionTest {
+	
+	@Test
+	public void jsonConversionTest() throws Exception {
+		ActionTypeLoader loader = new ActionTypeLoader();
+		loader.init(new Props());
+		
+		ExecutionOptions options = new ExecutionOptions();
+		List<String> disabledJobs = new ArrayList<String>();
+		options.setDisabledJobs(disabledJobs);
+		
+		ExecuteFlowAction executeFlowAction = new ExecuteFlowAction("ExecuteFlowAction", 1, "testproject", "testflow", "azkaban", options, null);
+		
+		Object obj = executeFlowAction.toJson();
+		
+		ExecuteFlowAction action = (ExecuteFlowAction) loader.createActionFromJson(ExecuteFlowAction.type, obj);
+		assertTrue(executeFlowAction.getProjectId() == action.getProjectId());
+		assertTrue(executeFlowAction.getFlowName().equals(action.getFlowName()));
+		assertTrue(executeFlowAction.getSubmitUser().equals(action.getSubmitUser()));
+	}
+
+	
+	
+}
diff --git a/unit/java/azkaban/test/trigger/ThresholdChecker.java b/unit/java/azkaban/test/trigger/ThresholdChecker.java
new file mode 100644
index 0000000..3368eae
--- /dev/null
+++ b/unit/java/azkaban/test/trigger/ThresholdChecker.java
@@ -0,0 +1,102 @@
+package azkaban.test.trigger;
+
+import java.util.Map;
+
+import azkaban.trigger.ConditionChecker;
+
+
+public class ThresholdChecker implements ConditionChecker{
+	
+	private int threshold = -1; 
+	
+	private static int curVal = -1;
+	
+	public static final String type = "ThresholdChecker";
+	
+	private String id;
+	
+	private boolean checkerMet = false;
+	private boolean checkerReset  = false;
+	
+	public ThresholdChecker(String id, int threshold){
+		this.id = id;
+		this.threshold = threshold;
+	}
+	
+	public synchronized static void setVal(int val) {
+		curVal = val;
+	}
+	
+	@Override
+	public Boolean eval() {
+		if(curVal > threshold) {
+			checkerMet = true;
+		}
+		return checkerMet;
+	}
+	
+	public boolean isCheckerMet() {
+		return checkerMet;
+	}
+
+	@Override
+	public void reset() {
+		checkerMet = false;
+		checkerReset = true;
+	}
+	
+	public boolean isCheckerReset() {
+		return checkerReset;
+	}
+	
+	/*
+	 * TimeChecker format:
+	 * type_first-time-in-millis_next-time-in-millis_timezone_is-recurring_skip-past-checks_period
+	 */
+	@Override
+	public String getId() {
+		return id;
+	}
+
+	@Override
+	public String getType() {
+		return type;
+	}
+
+	@Override
+	public ConditionChecker fromJson(Object obj) {
+		return null;
+	}
+
+	@Override
+	public Object getNum() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public Object toJson() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public void stopChecker() {
+		return;
+		
+	}
+
+	@Override
+	public void setContext(Map<String, Object> context) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	@Override
+	public long getNextCheckTime() {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+
+}
diff --git a/unit/java/azkaban/test/trigger/TriggerManagerTest.java b/unit/java/azkaban/test/trigger/TriggerManagerTest.java
new file mode 100644
index 0000000..dfd2da5
--- /dev/null
+++ b/unit/java/azkaban/test/trigger/TriggerManagerTest.java
@@ -0,0 +1,189 @@
+package azkaban.test.trigger;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.joda.time.DateTime;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import azkaban.trigger.Condition;
+import azkaban.trigger.ConditionChecker;
+import azkaban.trigger.Trigger;
+import azkaban.trigger.TriggerAction;
+import azkaban.trigger.TriggerException;
+import azkaban.trigger.TriggerLoader;
+import azkaban.trigger.TriggerLoaderException;
+import azkaban.trigger.TriggerManager;
+import azkaban.trigger.TriggerManagerException;
+import azkaban.utils.Props;
+
+public class TriggerManagerTest {
+	
+	private TriggerLoader triggerLoader;
+	
+	@Before
+	public void setup() throws TriggerException, TriggerManagerException {
+		triggerLoader = new MockTriggerLoader();
+		
+	}
+	
+	@After
+	public void tearDown() {
+		
+	}
+	
+	@Test
+	public void TriggerManagerSimpleTest() throws TriggerManagerException {
+
+		
+		Props props = new Props();
+		props.put("trigger.scan.interval", 4000);
+		TriggerManager triggerManager = new TriggerManager(props, triggerLoader, null);
+		
+		triggerManager.registerCheckerType(ThresholdChecker.type, ThresholdChecker.class);
+		triggerManager.registerActionType(DummyTriggerAction.type, DummyTriggerAction.class);
+		
+		ThresholdChecker.setVal(1);
+		
+		triggerManager.insertTrigger(createDummyTrigger("test1", "triggerLoader", 10), "testUser");
+		List<Trigger> triggers = triggerManager.getTriggers();
+		assertTrue(triggers.size() == 1);
+		Trigger t1 = triggers.get(0);
+		t1.setResetOnTrigger(false);
+		triggerManager.updateTrigger(t1, "testUser");
+		ThresholdChecker checker1 = (ThresholdChecker) t1.getTriggerCondition().getCheckers().values().toArray()[0];
+		assertTrue(t1.getSource().equals("triggerLoader"));
+		
+		Trigger t2 = createDummyTrigger("test2: add new trigger", "addNewTriggerTest", 20);
+		triggerManager.insertTrigger(t2, "testUser");
+		ThresholdChecker checker2 = (ThresholdChecker) t2.getTriggerCondition().getCheckers().values().toArray()[0];
+		
+		ThresholdChecker.setVal(15);
+		try {
+			Thread.sleep(2000);
+		} catch (InterruptedException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		
+		assertTrue(checker1.isCheckerMet() == false);
+		assertTrue(checker2.isCheckerMet() == false);
+		assertTrue(checker1.isCheckerReset() == false);
+		assertTrue(checker2.isCheckerReset() == false);
+		
+		try {
+			Thread.sleep(2000);
+		} catch (InterruptedException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		
+		assertTrue(checker1.isCheckerMet() == true);
+		assertTrue(checker2.isCheckerMet() == false);
+		assertTrue(checker1.isCheckerReset() == false);
+		assertTrue(checker2.isCheckerReset() == false);
+		
+		ThresholdChecker.setVal(25);
+		try {
+			Thread.sleep(4000);
+		} catch (InterruptedException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		
+		assertTrue(checker1.isCheckerMet() == true);
+		assertTrue(checker1.isCheckerReset() == false);
+		assertTrue(checker2.isCheckerReset() == true);
+		
+		triggers = triggerManager.getTriggers();
+		assertTrue(triggers.size() == 1);
+		
+	}
+	
+	public class MockTriggerLoader implements TriggerLoader {
+
+		private Map<Integer, Trigger> triggers = new HashMap<Integer, Trigger>();
+		private int idIndex = 0;
+		
+		@Override
+		public void addTrigger(Trigger t) throws TriggerLoaderException {
+			t.setTriggerId(idIndex++);
+			triggers.put(t.getTriggerId(), t);
+		}
+
+		@Override
+		public void removeTrigger(Trigger s) throws TriggerLoaderException {
+			triggers.remove(s.getTriggerId());
+			
+		}
+
+		@Override
+		public void updateTrigger(Trigger t) throws TriggerLoaderException {
+			triggers.put(t.getTriggerId(), t);
+		}
+
+		@Override
+		public List<Trigger> loadTriggers() {
+			return new ArrayList<Trigger>(triggers.values());
+		}
+
+		@Override
+		public Trigger loadTrigger(int triggerId)
+				throws TriggerLoaderException {
+			// TODO Auto-generated method stub
+			return null;
+		}
+
+		@Override
+		public List<Trigger> getUpdatedTriggers(long lastUpdateTime)
+				throws TriggerLoaderException {
+			// TODO Auto-generated method stub
+			return null;
+		}
+		
+	}
+	
+	private Trigger createDummyTrigger(String message, String source, int threshold) {
+		
+		Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
+		ConditionChecker checker = new ThresholdChecker(ThresholdChecker.type, threshold);
+		checkers.put(checker.getId(), checker);
+		
+		List<TriggerAction> actions = new ArrayList<TriggerAction>();
+		TriggerAction act  = new DummyTriggerAction(message);
+		actions.add(act);
+		
+		String expr = checker.getId() + ".eval()";
+		
+		Condition triggerCond = new Condition(checkers, expr);
+		Condition expireCond = new Condition(checkers, expr);
+		
+		Trigger fakeTrigger = new Trigger(DateTime.now().getMillis(), DateTime.now().getMillis(), "azkaban", source, triggerCond, expireCond, actions);
+		fakeTrigger.setResetOnTrigger(true);
+		fakeTrigger.setResetOnExpire(true);
+		
+		return fakeTrigger;
+	}
+
+//	public class MockCheckerLoader extends CheckerTypeLoader{
+//		
+//		@Override
+//		public void init(Props props) {
+//			checkerToClass.put(ThresholdChecker.type, ThresholdChecker.class);
+//		}
+//	}
+//	
+//	public class MockActionLoader extends ActionTypeLoader {
+//		@Override
+//		public void init(Props props) {
+//			actionToClass.put(DummyTriggerAction.type, DummyTriggerAction.class);
+//		}
+//	}
+
+}
diff --git a/unit/java/azkaban/test/trigger/TriggerTest.java b/unit/java/azkaban/test/trigger/TriggerTest.java
new file mode 100644
index 0000000..2b79266
--- /dev/null
+++ b/unit/java/azkaban/test/trigger/TriggerTest.java
@@ -0,0 +1,70 @@
+package azkaban.test.trigger;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.joda.time.DateTime;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+import azkaban.executor.ExecutionOptions;
+import azkaban.trigger.ActionTypeLoader;
+import azkaban.trigger.CheckerTypeLoader;
+import azkaban.trigger.Condition;
+import azkaban.trigger.ConditionChecker;
+import azkaban.trigger.Trigger;
+import azkaban.trigger.TriggerAction;
+import azkaban.trigger.TriggerException;
+import azkaban.trigger.builtin.BasicTimeChecker;
+import azkaban.trigger.builtin.ExecuteFlowAction;
+import azkaban.utils.JSONUtils;
+import azkaban.utils.Props;
+import azkaban.utils.Utils;
+
+public class TriggerTest {
+	
+	private CheckerTypeLoader checkerLoader;
+	private ActionTypeLoader actionLoader;
+	
+	@Before
+	public void setup() throws TriggerException {
+		checkerLoader = new CheckerTypeLoader();
+		checkerLoader.init(new Props());
+		Condition.setCheckerLoader(checkerLoader);
+		actionLoader = new ActionTypeLoader();
+		actionLoader.init(new Props());
+		Trigger.setActionTypeLoader(actionLoader);
+	}
+	
+	@Test
+	public void jsonConversionTest() throws Exception {
+		DateTime now = DateTime.now();
+		ConditionChecker checker1 = new BasicTimeChecker("timeChecker1", now.getMillis(), now.getZone(), true, true, Utils.parsePeriodString("1h"));
+		Map<String, ConditionChecker> checkers1 = new HashMap<String, ConditionChecker>();
+		checkers1.put(checker1.getId(), checker1);
+		String expr1 = checker1.getId() + ".eval()";
+		Condition triggerCond = new Condition(checkers1, expr1);
+		Condition expireCond = new Condition(checkers1, expr1);
+		List<TriggerAction> actions = new ArrayList<TriggerAction>();
+		TriggerAction action = new ExecuteFlowAction("executeAction", 1, "testProj", "testFlow", "azkaban", new ExecutionOptions(), null);
+		actions.add(action);
+		Trigger t = new Trigger(now.getMillis(), now.getMillis(), "azkaban", "test", triggerCond, expireCond, actions);
+		
+		File temp = File.createTempFile("temptest", "temptest");
+		temp.deleteOnExit();
+		Object obj = t.toJson();
+		JSONUtils.toJSON(obj, temp);
+		
+		Trigger t2 = Trigger.fromJson(JSONUtils.parseJSONFromFile(temp));
+		
+		assertTrue(t.getSource().equals(t2.getSource()));
+		assertTrue(t.getTriggerId() == t2.getTriggerId());
+		
+	}
+
+}
diff --git a/unit/java/azkaban/test/utils/cache/CacheTest.java b/unit/java/azkaban/test/utils/cache/CacheTest.java
index bc5e2b9..3fcc0f9 100644
--- a/unit/java/azkaban/test/utils/cache/CacheTest.java
+++ b/unit/java/azkaban/test/utils/cache/CacheTest.java
@@ -1,7 +1,6 @@
 package azkaban.test.utils.cache;
 
-import junit.framework.Assert;
-
+import org.junit.Assert;
 import org.junit.Test;
 
 import azkaban.utils.cache.Cache;
diff --git a/unit/java/azkaban/test/utils/FileIOUtilsTest.java b/unit/java/azkaban/test/utils/FileIOUtilsTest.java
index 241162e..3157663 100644
--- a/unit/java/azkaban/test/utils/FileIOUtilsTest.java
+++ b/unit/java/azkaban/test/utils/FileIOUtilsTest.java
@@ -4,10 +4,9 @@ import java.io.File;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 
-import junit.framework.Assert;
-
 import org.apache.commons.io.FileUtils;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/unit/java/azkaban/test/utils/JsonUtilsTest.java b/unit/java/azkaban/test/utils/JsonUtilsTest.java
index 87c81e9..c12f17c 100644
--- a/unit/java/azkaban/test/utils/JsonUtilsTest.java
+++ b/unit/java/azkaban/test/utils/JsonUtilsTest.java
@@ -5,8 +5,7 @@ import java.io.StringWriter;
 import java.util.HashMap;
 import java.util.Map;
 
-import junit.framework.Assert;
-
+import org.junit.Assert;
 import org.junit.Test;
 
 import azkaban.utils.JSONUtils;
diff --git a/unit/java/azkaban/test/utils/PropsUtilsTest.java b/unit/java/azkaban/test/utils/PropsUtilsTest.java
index e2c914f..1623005 100644
--- a/unit/java/azkaban/test/utils/PropsUtilsTest.java
+++ b/unit/java/azkaban/test/utils/PropsUtilsTest.java
@@ -2,8 +2,7 @@ package azkaban.test.utils;
 
 import java.io.IOException;
 
-import junit.framework.Assert;
-
+import org.junit.Assert;
 import org.junit.Test;
 
 import azkaban.utils.Props;
@@ -88,7 +87,6 @@ public class PropsUtilsTest {
 	
 	private void failIfNotException(Props props) {
 		try {
-			Props resolved = PropsUtils.resolveProps(props);
 			Assert.fail();
 		}
 		catch (UndefinedPropertyException e) {