azkaban-aplcache
Changes
build.xml 48(+19 -29)
src/java/azkaban/executor/ExecutorManager.java 47(+36 -11)
src/java/azkaban/webapp/servlet/velocity/index.vm 100(+48 -52)
src/less/.gitignore 1(+1 -0)
src/less/azkaban.less 6(+4 -2)
src/less/base.less 16(+16 -0)
src/less/context-menu.less 0(+0 -0)
src/less/flow.less 36(+27 -9)
src/less/header.less 16(+16 -0)
src/less/Makefile 16(+16 -0)
src/less/non-responsive.less 88(+88 -0)
src/less/off-canvas.less 0(+0 -0)
src/less/project.less 54(+52 -2)
src/tl/.gitignore 1(+1 -0)
src/tl/flowstats.tl 133(+133 -0)
src/tl/flowstats-no-data.tl 8(+8 -0)
src/tl/flowsummary.tl 160(+59 -101)
src/tl/Makefile 17(+17 -0)
src/web/js/azkaban/model/log-data.js 302(+302 -0)
src/web/js/azkaban/namespace.js 0(+0 -0)
src/web/js/azkaban/util/ajax.js 0(+0 -0)
src/web/js/azkaban/util/common.js 19(+19 -0)
src/web/js/azkaban/util/date.js 0(+0 -0)
src/web/js/azkaban/util/layout.js 0(+0 -0)
src/web/js/azkaban/view/exflow.js 71(+71 -0)
src/web/js/azkaban/view/flow.js 46(+37 -9)
src/web/js/azkaban/view/flow-job.js 0(+0 -0)
src/web/js/azkaban/view/flow-stats.js 318(+318 -0)
src/web/js/azkaban/view/history.js 0(+0 -0)
src/web/js/azkaban/view/jmx.js 0(+0 -0)
src/web/js/azkaban/view/job-details.js 276(+276 -0)
src/web/js/azkaban/view/job-edit.js 0(+0 -0)
src/web/js/azkaban/view/job-history.js 38(+38 -0)
src/web/js/azkaban/view/login.js 0(+0 -0)
src/web/js/azkaban/view/main.js 32(+16 -16)
src/web/js/azkaban/view/project.js 0(+0 -0)
src/web/js/azkaban/view/scheduled.js 0(+0 -0)
src/web/js/azkaban/view/svg-graph.js 0(+0 -0)
src/web/js/azkaban/view/time-graph.js 29(+9 -20)
src/web/js/azkaban/view/triggers.js 0(+0 -0)
src/web/js/dust-full-2.2.3.min.js 5(+5 -0)
Details
build.xml 48(+19 -29)
diff --git a/build.xml b/build.xml
index c695082..e1f77bb 100644
--- a/build.xml
+++ b/build.xml
@@ -41,12 +41,20 @@
<!-- set the build number based on environment variable, otherwise blank -->
<property environment="env" description="System environment variables (including those set by Hudson)" />
- <target name="all" depends="clean, jars" description="Builds all jars" />
+ <target name="all" depends="clean, package" description="Builds jars and packages." />
<target name="clean" description="Delete generated files.">
<echo message="Deleting generated files in dist" />
<delete dir="${dist.jar.dir}" />
<delete dir="${dist.classes.dir}" />
+ <delete dir="${dist.dust.dir}" />
+ <delete dir="${dist.less.dir}" />
+ <exec dir="${dust.src.dir}" executable="make" failonerror="true">
+ <arg value="clean" />
+ </exec>
+ <exec dir="${less.src.dir}" executable="make" failonerror="true">
+ <arg value="clean" />
+ </exec>
</target>
<target name="build" description="Compile main source tree java files">
@@ -70,32 +78,17 @@
<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 dustjs templates -->
+ <exec dir="${dust.src.dir}" executable="make" failonerror="true"/>
+ <copy todir="${dist.dust.dir}">
+ <fileset dir="${dust.src.dir}/obj" includes="*.js" />
+ </copy>
<!-- 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>
+ <exec dir="${less.src.dir}" executable="make" failonerror="true"/>
+ <copy todir="${dist.less.dir}" >
+ <fileset dir="${less.src.dir}/obj" includes="*.css" />
+ </copy>
</target>
<target name="jars" depends="build" description="Create azkaban jar">
@@ -293,10 +286,7 @@
<!-- 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>
+ <fileset dir="${dist.less.dir}" />
</copy>
<!-- Copy sql files -->
src/java/azkaban/executor/ExecutorManager.java 47(+36 -11)
diff --git a/src/java/azkaban/executor/ExecutorManager.java b/src/java/azkaban/executor/ExecutorManager.java
index bf87720..06af3b7 100644
--- a/src/java/azkaban/executor/ExecutorManager.java
+++ b/src/java/azkaban/executor/ExecutorManager.java
@@ -200,26 +200,42 @@ public class ExecutorManager extends EventHandler implements ExecutorManagerAdap
}
@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);
+ 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 {
+ 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);
+ 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);
+ 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;
}
@@ -1075,15 +1091,24 @@ public class ExecutorManager extends EventHandler implements ExecutorManagerAdap
}
@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);
+ 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);
+ public List<ExecutableFlow> getExecutableFlows(
+ int projectId, String flowId, int from, int length, Status status)
+ throws ExecutorManagerException {
+ return executorLoader.fetchFlowHistory(
+ projectId, flowId, from, length, status);
}
/*
diff --git a/src/java/azkaban/executor/JdbcExecutorLoader.java b/src/java/azkaban/executor/JdbcExecutorLoader.java
index 5373b7d..6d44b57 100644
--- a/src/java/azkaban/executor/JdbcExecutorLoader.java
+++ b/src/java/azkaban/executor/JdbcExecutorLoader.java
@@ -180,12 +180,14 @@ public class JdbcExecutorLoader extends AbstractJdbcLoader implements ExecutorLo
}
@Override
- public int fetchNumExecutableFlows(int projectId, String flowId) throws ExecutorManagerException {
+ public int fetchNumExecutableFlows(int projectId, String flowId)
+ throws ExecutorManagerException {
QueryRunner runner = createQueryRunner();
IntHandler intHandler = new IntHandler();
try {
- int count = runner.query(IntHandler.NUM_FLOW_EXECUTIONS, intHandler, projectId, flowId);
+ int count = runner.query(
+ IntHandler.NUM_FLOW_EXECUTIONS, intHandler, projectId, flowId);
return count;
} catch (SQLException e) {
throw new ExecutorManagerException("Error fetching num executions", e);
@@ -193,12 +195,14 @@ public class JdbcExecutorLoader extends AbstractJdbcLoader implements ExecutorLo
}
@Override
- public int fetchNumExecutableNodes(int projectId, String jobId) throws ExecutorManagerException {
+ public int fetchNumExecutableNodes(int projectId, String jobId)
+ throws ExecutorManagerException {
QueryRunner runner = createQueryRunner();
IntHandler intHandler = new IntHandler();
try {
- int count = runner.query(IntHandler.NUM_JOB_EXECUTIONS, intHandler, projectId, jobId);
+ int count = runner.query(
+ IntHandler.NUM_JOB_EXECUTIONS, intHandler, projectId, jobId);
return count;
} catch (SQLException e) {
throw new ExecutorManagerException("Error fetching num executions", e);
@@ -206,12 +210,19 @@ public class JdbcExecutorLoader extends AbstractJdbcLoader implements ExecutorLo
}
@Override
- public List<ExecutableFlow> fetchFlowHistory(int projectId, String flowId, int skip, int num) throws ExecutorManagerException {
+ public List<ExecutableFlow> fetchFlowHistory(int projectId, String flowId,
+ int skip, int num) throws ExecutorManagerException {
QueryRunner runner = createQueryRunner();
FetchExecutableFlows flowHandler = new FetchExecutableFlows();
try {
- List<ExecutableFlow> properties = runner.query(FetchExecutableFlows.FETCH_EXECUTABLE_FLOW_HISTORY, flowHandler, projectId, flowId, skip, num);
+ List<ExecutableFlow> properties = runner.query(
+ FetchExecutableFlows.FETCH_EXECUTABLE_FLOW_HISTORY,
+ flowHandler,
+ projectId,
+ flowId,
+ skip,
+ num);
return properties;
} catch (SQLException e) {
throw new ExecutorManagerException("Error fetching active flows", e);
@@ -219,12 +230,21 @@ public class JdbcExecutorLoader extends AbstractJdbcLoader implements ExecutorLo
}
@Override
- public List<ExecutableFlow> fetchFlowHistory(int projectId, String flowId, int skip, int num, Status status) throws ExecutorManagerException {
+ public List<ExecutableFlow> fetchFlowHistory(
+ int projectId, String flowId, int skip, int num, Status status)
+ throws ExecutorManagerException {
QueryRunner runner = createQueryRunner();
FetchExecutableFlows flowHandler = new FetchExecutableFlows();
try {
- List<ExecutableFlow> properties = runner.query(FetchExecutableFlows.FETCH_EXECUTABLE_FLOW_BY_STATUS, flowHandler, projectId, flowId, status.getNumVal(), skip, num);
+ List<ExecutableFlow> properties = runner.query(
+ FetchExecutableFlows.FETCH_EXECUTABLE_FLOW_BY_STATUS,
+ flowHandler,
+ projectId,
+ flowId,
+ status.getNumVal(),
+ skip,
+ num);
return properties;
} catch (SQLException e) {
throw new ExecutorManagerException("Error fetching active flows", e);
@@ -232,13 +252,18 @@ public class JdbcExecutorLoader extends AbstractJdbcLoader implements ExecutorLo
}
@Override
- public List<ExecutableFlow> fetchFlowHistory(int skip, int num) throws ExecutorManagerException {
+ public List<ExecutableFlow> fetchFlowHistory(int skip, int num)
+ throws ExecutorManagerException {
QueryRunner runner = createQueryRunner();
FetchExecutableFlows flowHandler = new FetchExecutableFlows();
try {
- List<ExecutableFlow> properties = runner.query(FetchExecutableFlows.FETCH_ALL_EXECUTABLE_FLOW_HISTORY, flowHandler, skip, num);
+ List<ExecutableFlow> properties = runner.query(
+ FetchExecutableFlows.FETCH_ALL_EXECUTABLE_FLOW_HISTORY,
+ flowHandler,
+ skip,
+ num);
return properties;
} catch (SQLException e) {
throw new ExecutorManagerException("Error fetching active flows", e);
@@ -247,7 +272,15 @@ public class JdbcExecutorLoader extends AbstractJdbcLoader implements ExecutorLo
@Override
- public List<ExecutableFlow> fetchFlowHistory(String projContain, String flowContains, String userNameContains, int status, long startTime, long endTime, int skip, int num) throws ExecutorManagerException {
+ public List<ExecutableFlow> fetchFlowHistory(
+ String projContain,
+ String flowContains,
+ String userNameContains,
+ int status,
+ long startTime,
+ long endTime,
+ int skip,
+ int num) throws ExecutorManagerException {
String query = FetchExecutableFlows.FETCH_BASE_EXECUTABLE_FLOW_QUERY;
ArrayList<Object> params = new ArrayList<Object>();
@@ -329,7 +362,8 @@ public class JdbcExecutorLoader extends AbstractJdbcLoader implements ExecutorLo
FetchExecutableFlows flowHandler = new FetchExecutableFlows();
try {
- List<ExecutableFlow> properties = runner.query(query, flowHandler, params.toArray());
+ List<ExecutableFlow> properties = runner.query(
+ query, flowHandler, params.toArray());
return properties;
} catch (SQLException e) {
throw new ExecutorManagerException("Error fetching active flows", e);
@@ -839,13 +873,28 @@ public class JdbcExecutorLoader extends AbstractJdbcLoader implements ExecutorLo
}
- private static class FetchExecutableFlows implements ResultSetHandler<List<ExecutableFlow>> {
- private static String FETCH_BASE_EXECUTABLE_FLOW_QUERY = "SELECT exec_id, enc_type, flow_data FROM execution_flows ";
- private static String FETCH_EXECUTABLE_FLOW = "SELECT exec_id, enc_type, flow_data FROM execution_flows WHERE exec_id=?";
- //private static String FETCH_ACTIVE_EXECUTABLE_FLOW = "SELECT ex.exec_id exec_id, ex.enc_type enc_type, ex.flow_data flow_data FROM execution_flows ex INNER JOIN active_executing_flows ax ON ex.exec_id = ax.exec_id";
- private static String FETCH_ALL_EXECUTABLE_FLOW_HISTORY = "SELECT exec_id, enc_type, flow_data FROM execution_flows ORDER BY exec_id DESC LIMIT ?, ?";
- private static String FETCH_EXECUTABLE_FLOW_HISTORY = "SELECT exec_id, enc_type, flow_data FROM execution_flows WHERE project_id=? AND flow_id=? ORDER BY exec_id DESC LIMIT ?, ?";
- private static String FETCH_EXECUTABLE_FLOW_BY_STATUS = "SELECT exec_id, enc_type, flow_data FROM execution_flows WHERE project_id=? AND flow_id=? AND status=? ORDER BY exec_id DESC LIMIT ?, ?";
+ private static class FetchExecutableFlows
+ implements ResultSetHandler<List<ExecutableFlow>> {
+ private static String FETCH_BASE_EXECUTABLE_FLOW_QUERY =
+ "SELECT exec_id, enc_type, flow_data FROM execution_flows ";
+ private static String FETCH_EXECUTABLE_FLOW =
+ "SELECT exec_id, enc_type, flow_data FROM execution_flows " +
+ "WHERE exec_id=?";
+ //private static String FETCH_ACTIVE_EXECUTABLE_FLOW =
+ // "SELECT ex.exec_id exec_id, ex.enc_type enc_type, ex.flow_data flow_data " +
+ // "FROM execution_flows ex " +
+ // "INNER JOIN active_executing_flows ax ON ex.exec_id = ax.exec_id";
+ private static String FETCH_ALL_EXECUTABLE_FLOW_HISTORY =
+ "SELECT exec_id, enc_type, flow_data FROM execution_flows " +
+ "ORDER BY exec_id DESC LIMIT ?, ?";
+ private static String FETCH_EXECUTABLE_FLOW_HISTORY =
+ "SELECT exec_id, enc_type, flow_data FROM execution_flows " +
+ "WHERE project_id=? AND flow_id=? " +
+ "ORDER BY exec_id DESC LIMIT ?, ?";
+ private static String FETCH_EXECUTABLE_FLOW_BY_STATUS =
+ "SELECT exec_id, enc_type, flow_data FROM execution_flows " +
+ "WHERE project_id=? AND flow_id=? AND status=? " +
+ "ORDER BY exec_id DESC LIMIT ?, ?";
@Override
public List<ExecutableFlow> handle(ResultSet rs) throws SQLException {
@@ -863,7 +912,8 @@ public class JdbcExecutorLoader extends AbstractJdbcLoader implements ExecutorLo
EncodingType encType = EncodingType.fromInteger(encodingType);
Object flowObj;
try {
- // Convoluted way to inflate strings. Should find common package or helper function.
+ // 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");
@@ -874,7 +924,8 @@ public class JdbcExecutorLoader extends AbstractJdbcLoader implements ExecutorLo
flowObj = JSONUtils.parseJSONFromString(jsonString);
}
- ExecutableFlow exFlow = ExecutableFlow.createExecutableFlowFromObject(flowObj);
+ ExecutableFlow exFlow =
+ ExecutableFlow.createExecutableFlowFromObject(flowObj);
execFlows.add(exFlow);
} catch (IOException e) {
throw new SQLException("Error retrieving flow data " + id, e);
diff --git a/src/java/azkaban/jobExecutor/AbstractProcessJob.java b/src/java/azkaban/jobExecutor/AbstractProcessJob.java
index df9e1d1..d106ea0 100644
--- a/src/java/azkaban/jobExecutor/AbstractProcessJob.java
+++ b/src/java/azkaban/jobExecutor/AbstractProcessJob.java
@@ -162,7 +162,8 @@ public abstract class AbstractProcessJob extends AbstractJob {
File directory = new File(workingDir);
File tempFile = null;
try {
- tempFile = File.createTempFile(getId() + "_", "_tmp", directory);
+ // The temp file prefix must be at least 3 characters.
+ tempFile = File.createTempFile(getId() + "_props_", "_tmp", directory);
jobProps.storeFlattened(tempFile);
} catch (IOException e) {
throw new RuntimeException("Failed to create temp property file ", e);
diff --git a/src/java/azkaban/webapp/servlet/ExecutorServlet.java b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
index 6cc091b..55f5d5b 100644
--- a/src/java/azkaban/webapp/servlet/ExecutorServlet.java
+++ b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
@@ -17,6 +17,7 @@
package azkaban.webapp.servlet;
import java.io.IOException;
+import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -44,7 +45,7 @@ import azkaban.user.Permission;
import azkaban.user.User;
import azkaban.user.Permission.Type;
import azkaban.utils.FileIOUtils.LogData;
-import azkaban.utils.LogSummary;
+import azkaban.utils.JSONUtils;
import azkaban.webapp.AzkabanWebServer;
import azkaban.webapp.session.Session;
@@ -55,6 +56,8 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
private ScheduleManager scheduleManager;
private ExecutorVelocityHelper velocityHelper;
+ private String statsDir;
+
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
@@ -63,6 +66,7 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
executorManager = server.getExecutorManager();
scheduleManager = server.getScheduleManager();
velocityHelper = new ExecutorVelocityHelper();
+ statsDir = server.getServerProps().getString("azkaban.stats.dir");
}
@Override
@@ -128,9 +132,9 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
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("fetchExecJobStats")) {
+ ajaxFetchJobStats(req, resp, ret, session.getUser(), exFlow);
+ }
else if (ajaxName.equals("retryFailedJobs")) {
ajaxRestartFailed(req, resp, ret, session.getUser(), exFlow);
}
@@ -451,52 +455,41 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
}
}
- /**
- * Gets the job summary.
- *
- * @param req
- * @param resp
- * @param user
- * @param 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);
+ private void ajaxFetchJobStats(
+ 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;
}
- String jobId = this.getParam(req, "jobId");
+ String jobId = this.getParam(req, "jobid");
resp.setCharacterEncoding("utf-8");
-
+ String statsFilePath = null;
try {
ExecutableNode node = exFlow.getExecutableNode(jobId);
if (node == null) {
- ret.put("error", "Job " + jobId + " doesn't exist in " + exFlow.getExecutionId());
+ ret.put("error", "Job " + jobId + " doesn't exist in " +
+ exFlow.getExecutionId());
return;
}
-
- int attempt = this.getIntParam(req, "attempt", node.getAttempt());
- 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);
- }
- }
+
+ statsFilePath = statsDir + "/" + exFlow.getExecutionId() + "-" +
+ jobId + "-stats.json";
+ File statsFile = new File(statsFilePath);
+ List<Object> jsonObj =
+ (ArrayList<Object>) JSONUtils.parseJSONFromFile(statsFile);
+ ret.put("jobStats", jsonObj);
+ }
+ catch (IOException e) {
+ ret.put("error", "Cannot open stats file: " + statsFilePath);
+ return;
+ }
+ }
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);
diff --git a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index 662bad0..4fdcb5c 100644
--- a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -47,6 +47,7 @@ import azkaban.executor.ExecutableFlow;
import azkaban.executor.ExecutableJobInfo;
import azkaban.executor.ExecutorManagerAdapter;
import azkaban.executor.ExecutorManagerException;
+import azkaban.executor.Status;
import azkaban.flow.Edge;
import azkaban.flow.Flow;
import azkaban.flow.FlowProps;
@@ -248,6 +249,11 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
ajaxFetchFlowExecutions(project, ret, req);
}
}
+ else if (ajaxName.equals("fetchLastSuccessfulFlowExecution")) {
+ if (handleAjaxPermission(project, user, Type.READ, ret)) {
+ ajaxFetchLastSuccessfulFlowExecution(project, ret, req);
+ }
+ }
else if (ajaxName.equals("fetchJobInfo")) {
if (handleAjaxPermission(project, user, Type.READ, ret)) {
ajaxFetchJobInfo(project, ret, req);
@@ -339,7 +345,34 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
}
}
- private void ajaxFetchFlowExecutions(Project project, HashMap<String, Object> ret, HttpServletRequest req) throws ServletException {
+ private void ajaxFetchLastSuccessfulFlowExecution(Project project,
+ HashMap<String, Object> ret, HttpServletRequest req)
+ throws ServletException {
+ String flowId = getParam(req, "flow");
+ List<ExecutableFlow> exFlows = null;
+ try {
+ exFlows = executorManager.getExecutableFlows(
+ project.getId(), flowId, 0, 1, Status.SUCCEEDED);
+ }
+ catch (ExecutorManagerException e) {
+ ret.put("error", "Error retrieving executable flows");
+ return;
+ }
+
+ if (exFlows.size() == 0) {
+ ret.put("success", "false");
+ ret.put("message", "This flow has no successful run.");
+ return;
+ }
+
+ ret.put("success", "true");
+ ret.put("message", "");
+ ret.put("execId", exFlows.get(0).getExecutionId());
+ }
+
+ 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"));
int length = Integer.valueOf(getParam(req, "length"));
@@ -347,8 +380,10 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
ArrayList<ExecutableFlow> exFlows = new ArrayList<ExecutableFlow>();
int total = 0;
try {
- total = executorManager.getExecutableFlows(project.getId(), flowId, from, length, exFlows);
- } catch (ExecutorManagerException e) {
+ total = executorManager.getExecutableFlows(
+ project.getId(), flowId, from, length, exFlows);
+ }
+ catch (ExecutorManagerException e) {
ret.put("error", "Error retrieving executable flows");
}
diff --git a/src/java/azkaban/webapp/servlet/ProjectServlet.java b/src/java/azkaban/webapp/servlet/ProjectServlet.java
index 68afdd7..72f065f 100644
--- a/src/java/azkaban/webapp/servlet/ProjectServlet.java
+++ b/src/java/azkaban/webapp/servlet/ProjectServlet.java
@@ -39,8 +39,10 @@ import azkaban.webapp.session.Session;
* The main page
*/
public class ProjectServlet extends LoginAbstractAzkabanServlet {
- private static final Logger logger = Logger.getLogger(ProjectServlet.class.getName());
- private static final String LOCKDOWN_CREATE_PROJECTS_KEY = "lockdown.create.projects";
+ private static final Logger logger =
+ Logger.getLogger(ProjectServlet.class.getName());
+ private static final String LOCKDOWN_CREATE_PROJECTS_KEY =
+ "lockdown.create.projects";
private static final long serialVersionUID = -1;
private UserManager userManager;
@@ -50,24 +52,23 @@ public class ProjectServlet extends LoginAbstractAzkabanServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
-
AzkabanWebServer server = (AzkabanWebServer)getApplication();
userManager = server.getUserManager();
- lockdownCreateProjects = server.getServerProps().getBoolean(LOCKDOWN_CREATE_PROJECTS_KEY, false);
+ lockdownCreateProjects = server.getServerProps().getBoolean(
+ LOCKDOWN_CREATE_PROJECTS_KEY, false);
if (lockdownCreateProjects) {
logger.info("Creation of projects is locked down");
}
}
@Override
- protected void handleGet(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
-
- if(hasParam(req, "doaction")) {
- if(getParam(req, "doaction").equals("search")) {
+ protected void handleGet(HttpServletRequest req, HttpServletResponse resp,
+ Session session) throws ServletException, IOException {
+ if (hasParam(req, "doaction")) {
+ if (getParam(req, "doaction").equals("search")) {
String searchTerm = getParam(req, "searchterm");
-
- if(!searchTerm.equals("") && !searchTerm.equals(".*")) {
+ if (!searchTerm.equals("") && !searchTerm.equals(".*")) {
handleFilter(req, resp, session, searchTerm);
return;
}
@@ -76,8 +77,10 @@ public class ProjectServlet extends LoginAbstractAzkabanServlet {
User user = session.getUser();
- ProjectManager manager = ((AzkabanWebServer)getApplication()).getProjectManager();
- Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/index.vm");
+ ProjectManager manager =
+ ((AzkabanWebServer)getApplication()).getProjectManager();
+ Page page = newPage(
+ req, resp, session, "azkaban/webapp/servlet/velocity/index.vm");
if (lockdownCreateProjects && !hasPermissionToCreateProject(user)) {
page.add("hideCreateProject", true);
@@ -85,21 +88,30 @@ public class ProjectServlet extends LoginAbstractAzkabanServlet {
if (hasParam(req, "all")) {
List<Project> projects = manager.getProjects();
- page.add("allProjects", "true");
+ page.add("viewProjects", "all");
+ page.add("projects", projects);
+ }
+ else if (hasParam(req, "group")) {
+ List<Project> projects = manager.getUserProjects(user);
+ page.add("viewProjects", "group");
page.add("projects", projects);
}
else {
List<Project> projects = manager.getUserProjects(user);
+ page.add("viewProjects", "personal");
page.add("projects", projects);
}
page.render();
}
- private void handleFilter(HttpServletRequest req, HttpServletResponse resp, Session session, String searchTerm) {
+ private void handleFilter(HttpServletRequest req, HttpServletResponse resp,
+ Session session, String searchTerm) {
User user = session.getUser();
- ProjectManager manager = ((AzkabanWebServer)getApplication()).getProjectManager();
- Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/index.vm");
+ ProjectManager manager =
+ ((AzkabanWebServer)getApplication()).getProjectManager();
+ Page page = newPage(
+ req, resp, session, "azkaban/webapp/servlet/velocity/index.vm");
if (hasParam(req, "all")) {
//do nothing special if one asks for 'ALL' projects
List<Project> projects = manager.getProjectsByRegex(searchTerm);
@@ -120,14 +132,14 @@ public class ProjectServlet extends LoginAbstractAzkabanServlet {
protected void handlePost(HttpServletRequest req, HttpServletResponse resp,
Session session) throws ServletException, IOException {
// TODO Auto-generated method stub
-
}
private boolean hasPermissionToCreateProject(User user) {
- for(String roleName: user.getRoles()) {
+ for (String roleName: user.getRoles()) {
Role role = userManager.getRole(roleName);
Permission perm = role.getPermission();
- if (perm.isPermissionSet(Permission.Type.ADMIN) || perm.isPermissionSet(Permission.Type.CREATEPROJECTS)) {
+ if (perm.isPermissionSet(Permission.Type.ADMIN) ||
+ perm.isPermissionSet(Permission.Type.CREATEPROJECTS)) {
return true;
}
}
diff --git a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
index 693a707..cb6fe85 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -22,17 +22,23 @@
#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.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/svgNavigate.js"></script>
+ <script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
+
+ <script type="text/javascript" src="${context}/js/dust-full-2.2.3.min.js"></script>
+ <script type="text/javascript" src="${context}/js/flowstats.js"></script>
+ <script type="text/javascript" src="${context}/js/flowstats-no-data.js"></script>
+
+ <script type="text/javascript" src="${context}/js/azkaban/util/common.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/date.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/context-menu.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/ajax.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/job-status.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/layout.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/exflow.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/flow-stats.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/flow-job.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/svg-graph.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/svg-navigate.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -62,20 +68,20 @@
<div class="az-page-header">
<div class="container-full" id="flow-status">
<div class="row">
- <div class="col-lg-7">
+ <div class="header-title">
<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="header-control">
<div class="az-exflow-stats">
- <div class="col-md-5">
+ <div class="col-xs-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">
+ <div class="col-xs-7">
<p><strong>Start Time</strong> <span id="startTime">-</span></p>
<p><strong>End Time</strong> <span id="endTime">-</span></p>
</div>
@@ -104,6 +110,7 @@
<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 id="statsViewLink"><a href="#stats">Stats</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>
@@ -120,7 +127,7 @@
<div class="container-full" id="jobListView">
<div class="row">
- <div class="col-lg-12">
+ <div class="col-xs-12">
<table class="table table-striped table-bordered table-condensed table-hover executions-table">
<thead>
<tr>
@@ -136,7 +143,7 @@
<tbody id="executableBody">
</tbody>
</table>
- </div><!-- /.col-lg-12 -->
+ </div><!-- /.col-xs-12 -->
</div><!-- /.row -->
</div><!-- /.container-full -->
@@ -144,7 +151,7 @@
<div class="container-full container-fill" id="flowLogView">
<div class="row">
- <div class="col-lg-12 col-content">
+ <div class="col-xs-12 col-content">
<div class="log-viewer">
<div class="panel panel-default">
<div class="panel-heading">
@@ -158,9 +165,24 @@
</div>
</div><!-- /.panel -->
</div><!-- /.log-viewer -->
- </div><!-- /.col-lg-12 -->
+ </div><!-- /.col-xs-12 -->
</div><!-- /.row -->
- </div><!-- /. -->
+ </div><!-- /.container-full -->
+
+ ## Stats view.
+
+ <div class="container-full" id="statsView">
+ <div id="flow-stats-container">
+ <div class="row">
+ <div class="col-lg-12">
+ <div class="alert alert-default">
+ <h4>No stats available</h4>
+ <p>Stats for this flow execution are not available.</p>
+ </div>
+ </div>
+ </div>
+ </div><!-- /.row -->
+ </div><!-- /.container-fill -->
## Error message message dialog.
diff --git a/src/java/azkaban/webapp/servlet/velocity/executionspage.vm b/src/java/azkaban/webapp/servlet/velocity/executionspage.vm
index 91d1d0c..e089e75 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executionspage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executionspage.vm
@@ -21,7 +21,7 @@
#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" src="${context}/js/azkaban/view/executions.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -55,7 +55,7 @@
</ul>
<div class="row" id="currently-running-view">
- <div class="col-lg-12">
+ <div class="col-xs-12">
<table id="executingJobs" class="table table-striped table-bordered table-hover table-condensed executions-table">
<thead>
<tr>
@@ -98,11 +98,11 @@
#end
</tbody>
</table>
- </div><!-- /col-lg-12 -->
+ </div><!-- /col-xs-12 -->
</div><!-- /row -->
<div class="row" id="recently-finished-view">
- <div class="col-lg-12">
+ <div class="col-xs-12">
<table id="recentlyFinished" class="table table-striped table-bordered table-hover table-condensed executions-table">
<thead>
<tr>
@@ -145,7 +145,7 @@
#end
</tbody>
</table>
- </div><!-- /col-lg-12 -->
+ </div><!-- /col-xs-12 -->
</div><!-- /row -->
</div><!-- /container-full -->
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
index cda497a..5c04043 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
@@ -14,12 +14,12 @@
* 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>
+ <script type="text/javascript" src="${context}/js/azkaban/util/layout.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/svg-navigate.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/context-menu.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/common.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/svg-graph.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/flow-execute.js"></script>
<div class="modal modal-wide" id="execute-flow-panel">
<div class="modal-dialog">
@@ -29,7 +29,7 @@
<h4 class="modal-title" id="execute-flow-panel-title"></h4>
</div><!-- /modal-header -->
<div class="modal-body row">
- <div class="col-md-4">
+ <div class="col-xs-4">
<ul class="nav nav-pills nav-stacked" id="graph-options">
<li id="flow-option" viewpanel="svg-div-custom">
<a href="#">Flow View</a>
@@ -52,8 +52,8 @@
<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><!-- /col-xs-4 -->
+ <div class="col-xs-8">
<div id="execution-graph-options-panel">
## SVG graph panel.
@@ -181,7 +181,7 @@
</div>
</div><!-- /execution-graph-options-panel -->
- </div><!-- /col-md-8 -->
+ </div><!-- /col-xs-8 -->
</div><!-- /modal-body -->
<div class="modal-footer">
@@ -199,7 +199,7 @@
#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>
+ <button type="button" class="btn btn-primary" id="execute-btn">Execute</button>
</div><!-- /modal-footer -->
</div><!-- /modal-content -->
</div><!-- /modal-dialog -->
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index 83dfff0..3ae46d6 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -23,18 +23,23 @@
<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/d3.v3.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/dust-full-2.2.3.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.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/svgNavigate.js"></script>
+ <script type="text/javascript" src="${context}/js/flowstats-no-data.js"></script>
+ <script type="text/javascript" src="${context}/js/flowstats.js"></script>
+
+ <script type="text/javascript" src="${context}/js/azkaban/util/date.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/ajax.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/common.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/layout.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/time-graph.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/flow.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/flow-stats.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/flow-job.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/flow-graph.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/svg-navigate.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -47,6 +52,24 @@
var flowId = "${flowid}";
var execId = null;
</script>
+ <style>
+ .axis path,
+ .axis line {
+ fill: none;
+ stroke: #000;
+ shape-rendering: crispEdges;
+ }
+
+ .x.axis path {
+ display: none;
+ }
+
+ .line {
+ fill: none;
+ stroke: steelblue;
+ stroke-width: 1.5px;
+ }
+ </style>
<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>
@@ -64,10 +87,10 @@
<div class="az-page-header">
<div class="container-full">
<div class="row">
- <div class="col-lg-6">
+ <div class="col-xs-6">
<h1><a href="${context}/manager?project=${project.name}&flow=${flowid}">Flow <small>$flowid</small></a></h1>
</div>
- <div class="col-lg-6">
+ <div class="col-xs-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>
@@ -105,7 +128,11 @@
<div class="container-full" id="executionsView">
<div class="row">
- <div class="col-lg-12">
+ <div class="col-xs-12">
+ <div class="well well-clear well-sm">
+ <div id="timeGraph"></div>
+ </div>
+
<table class="table table-striped table-bordered table-condensed table-hover" id="execTable">
<thead>
<tr>
@@ -137,10 +164,25 @@
## Summary view.
<div class="container-full" id="summaryView">
- <div class="row" id="summary-view-content">
- </div><!-- /.row -->
+ <div id="summary-view-content">
+ </div>
+ <div id="flow-stats-container">
+ <div class="row">
+ <div class="col-xs-12">
+ <div class="alert alert-info">
+ <h4>Analyze last run</h4>
+ <p>Analyze the last run for aggregate performance statistics. <strong>Note:</strong> this may take a few minutes, especially if your flow is large.</p>
+ <p>
+ <button type="button" id="analyze-btn" class="btn btn-primary">Analyze</button>
+ </p>
+ </div>
+ </div>
+ </div><!-- /.col-lg-12 -->
+ </div>
</div><!-- /.container-fill -->
+ ## Context menu and the rest of the page.
+
<div class="container-full">
<div id="contextMenu">
</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/historypage.vm b/src/java/azkaban/webapp/servlet/velocity/historypage.vm
index 63db454..3df93ad 100644
--- a/src/java/azkaban/webapp/servlet/velocity/historypage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/historypage.vm
@@ -25,7 +25,7 @@
<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" src="${context}/js/azkaban/view/history.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -46,10 +46,10 @@
<div class="az-page-header">
<div class="container-full">
<div class="row">
- <div class="col-lg-6">
+ <div class="header-title">
<h1><a href="${context}/history">History</a></h1>
</div>
- <div class="col-lg-6">
+ <div class="header-control">
<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">
@@ -72,7 +72,7 @@
#parse ("azkaban/webapp/servlet/velocity/alerts.vm")
<div class="row">
- <div class="col-lg-12">
+ <div class="col-xs-12">
<table id="executingJobs" class="table table-striped table-bordered table-hover table-condensed executions-table">
<thead>
<tr>
@@ -144,7 +144,7 @@
<li id="next"><a href="${context}/history?page=${next.page}&size=${next.size}">Next<span class="arrow">→</span></a></li>
#end
</ul>
- </div><!-- /col-lg-12 -->
+ </div><!-- /col-xs-12 -->
</div><!-- /row -->
## Advanced Filter Modal.
src/java/azkaban/webapp/servlet/velocity/index.vm 100(+48 -52)
diff --git a/src/java/azkaban/webapp/servlet/velocity/index.vm b/src/java/azkaban/webapp/servlet/velocity/index.vm
index 159bd1a..9cbe273 100644
--- a/src/java/azkaban/webapp/servlet/velocity/index.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/index.vm
@@ -21,8 +21,8 @@
#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.main.view.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/table-sort.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/main.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -41,20 +41,16 @@
<div class="az-page-header">
<div class="container-full">
<div class="row">
- <div class="col-lg-6">
-#if ($allProjects)
- <h1><a href="${context}/index">All Projects</a></h1>
-#else
- <h1><a href="${context}/index">My Projects</a></h1>
-#end
+ <div class="header-title">
+ <h1><a href="${context}/index">Projects</a></h1>
</div>
- <div class="col-lg-6">
+ <div class="header-control">
<form id="search-form" method="get" class="form-inline az-page-header-form" role="form">
<input type="hidden" name="doaction" value="search">
-#if ($allProjects)
+#if ($viewProjects == 'all')
<input type="hidden" name="all" value="true">
#end
- <div class="form-group col-md-9">
+ <div class="form-group col-xs-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">
@@ -63,7 +59,7 @@
</div>
</div>
#if (!$hideCreateProject)
- <div class="form-group col-md-3" id="create-project">
+ <div class="form-group col-xs-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">
@@ -83,50 +79,50 @@
## 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>
+ <div class="row row-offcanvas row-offcanvas-left">
+ <div class="col-xs-6 col-sm-3 sidebar-offcanvas graph-sidebar">
+ <ul class="nav nav-pills nav-stacked">
+ <li#if ($viewProjects == 'personal') class="active"#end><a href="${context}/index">Personal</a></li>
+ <li#if ($viewProjects == 'group') class="active"#end><a href="${context}/index?group">Group</a></li>
+ <li#if ($viewProjects == 'all') class="active"#end><a href="${context}/index?all">All</a></li>
+ </ul>
+ </div>
+ <div class="col-xs-12 col-sm-9 col-content">
#if (!$projects.isEmpty())
+ <ul id="project-list">
#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
+ <li>
+ <div class="project-info">
+ <h4><a href="${context}/manager?project=${project.name}">$project.name</a></h4>
+ <p class="project-description">$project.description</p>
+ <p class="project-last-modified">Last modified on <strong>$utils.formatDate($project.lastModifiedTimestamp)</strong> by <strong>$project.lastModifiedUser</strong>.</p>
+ </div>
+ <div class="project-expander" id="${project.name}">
+ <span class="glyphicon glyphicon-chevron-down project-expander-icon"></span>
+ </div>
+ <div class="clearfix"></div>
+ <div class="project-flows" id="${project.name}-child">
+ <table class="table">
+ <thead>
+ <tr>
+ <th class="tb-name">Flows</th>
+ </tr>
+ </thead>
+ <tbody id="${project.name}-tbody">
+ </tbody>
+ </table>
+ </div>
+ </li>
+ #end
+ </ul>
#else
- <tr>
- <td colspan="3">No viewable projects found.</td>
- </tr>
+ <div class="alert alert-default">
+ <h4>No Viewable Projects</h4>
+ <p>Click Create Project to create a new project.</p>
+ </div>
#end
- </tbody>
- </table>
- </div>
- </div>
+ </div>
+ </div>
## Modal dialog to be displayed to create a new project.
diff --git a/src/java/azkaban/webapp/servlet/velocity/javascript.vm b/src/java/azkaban/webapp/servlet/velocity/javascript.vm
index 2b2c316..2ebb28b 100644
--- a/src/java/azkaban/webapp/servlet/velocity/javascript.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/javascript.vm
@@ -17,5 +17,5 @@
<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/azkaban/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 30c8f74..03a1806 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm
@@ -21,7 +21,7 @@
#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" src="${context}/js/azkaban/view/jmx.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -51,7 +51,7 @@
## Web Client JMX
<div class="row">
- <div class="col-lg-12">
+ <div class="col-xs-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">
@@ -101,7 +101,7 @@
#foreach ($executor in $executorRemoteMBeans.entrySet())
<div class="row">
- <div class="col-lg-12">
+ <div class="col-xs-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">
@@ -150,7 +150,7 @@
#foreach ($triggerserver in $triggerserverRemoteMBeans.entrySet())
<div class="row">
- <div class="col-lg-12">
+ <div class="col-xs-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">
diff --git a/src/java/azkaban/webapp/servlet/velocity/jobdetailspage.vm b/src/java/azkaban/webapp/servlet/velocity/jobdetailspage.vm
index a8df9c2..0a88c63 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jobdetailspage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jobdetailspage.vm
@@ -21,8 +21,9 @@
#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" src="${context}/js/azkaban/util/ajax.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/model/log-data.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/job-details.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -51,10 +52,10 @@
<div class="az-page-header">
<div class="container-full">
<div class="row">
- <div class="col-lg-6">
+ <div class="col-xs-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="col-xs-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>
@@ -89,7 +90,7 @@
<div class="container-full container-fill" id="jobLogView">
<div class="row">
- <div class="col-lg-12 col-content">
+ <div class="col-xs-12 col-content">
<div class="log-viewer">
<div class="panel panel-default">
<div class="panel-heading">
@@ -110,44 +111,54 @@
## 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 class="row">
+ <div class="col-lg-12">
+ <h3>
+ Job Summary
+ <div class="pull-right">
+ <button type="button" id="updateSummaryBtn" class="btn btn-xs btn-default">Refresh</button>
</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>
+ </h3>
+
+ <div id="command-summary">
+ <h4>Command Summary</h4>
+ <table id="commandTable" class="table table-striped table-bordered table-hover">
+ </table>
+ </div>
+
+ <div id="pigJobSummary">
+ <h4>Pig Job Summary</h4>
+ <table class="table table-striped table-bordered table-hover">
+ <thead id="summaryHeader">
+ </thead>
+ <tbody id="summaryBody">
+ </tbody>
+ </table>
+ </div>
+
+ <div id="pigJobStats">
+ <h4>Pig Job Stats</h4>
+ <div class="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 id="hiveJobSummary">
+ <h4>Hive Job Summary</h4>
+ <table class="table table-striped table-bordered table-hover" id="hiveTable">
+ <thead id="hiveTableHeader">
+ </thead>
+ <tbody id="hiveTableBody">
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
</div>
## Error message message dialog.
diff --git a/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm b/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm
index 0177383..48b7394 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm
@@ -22,8 +22,9 @@
#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" src="${context}/js/azkaban/util/date.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/time-graph.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/job-history.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -86,7 +87,7 @@
## Time graph and job history table.
<div class="row">
- <div class="col-lg-12">
+ <div class="col-xs-12">
<div class="well well-clear well-sm">
<div id="timeGraph"></div>
</div>
@@ -152,7 +153,7 @@
<li id="next"><a href="${context}/manager?project=${projectName}&job=${jobid}&history&page=${next.page}&size=${next.size}">Next<span class="arrow">→</span></a></li>
</ul>
- </div><!-- /.col-lg-12 -->
+ </div><!-- /.col-xs-12 -->
</div><!-- /.row -->
</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/jobpage.vm b/src/java/azkaban/webapp/servlet/velocity/jobpage.vm
index 5dbec1f..e212556 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jobpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jobpage.vm
@@ -21,7 +21,7 @@
#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>
+ <script type="text/javascript" src="${context}/js/azkaban/view/job-edit.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -47,10 +47,10 @@
<div class="az-page-header">
<div class="container-full">
<div class="row">
- <div class="col-lg-6">
+ <div class="col-xs-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="col-xs-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>
@@ -102,7 +102,7 @@
</tbody>
</table>
</div>
- </div><!-- /col-lg-8 -->
+ </div><!-- /col-xs-8 -->
<div class="col-xs-6 col-sm-3 sidebar-offcanvas">
<div class="well" id="job-summary">
<h3>Job <small>$jobid</small></h3>
@@ -158,7 +158,7 @@
#end
</ul>
</div><!-- /panel -->
- </div><!-- /col-lg-4 -->
+ </div><!-- /col-xs-4 -->
</div><!-- /row -->
## Edit job modal.
diff --git a/src/java/azkaban/webapp/servlet/velocity/login.vm b/src/java/azkaban/webapp/servlet/velocity/login.vm
index bba573e..0067a02 100644
--- a/src/java/azkaban/webapp/servlet/velocity/login.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/login.vm
@@ -21,7 +21,7 @@
#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>
+ <script type="text/javascript" src="${context}/js/azkaban/view/login.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
</script>
diff --git a/src/java/azkaban/webapp/servlet/velocity/messagedialog.vm b/src/java/azkaban/webapp/servlet/velocity/messagedialog.vm
index 3068df5..ecf86e0 100644
--- a/src/java/azkaban/webapp/servlet/velocity/messagedialog.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/messagedialog.vm
@@ -14,7 +14,7 @@
* the License.
*#
- <script type="text/javascript" src="${context}/js/azkaban.message.dialog.view.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/message-dialog.js"></script>
<div class="modal" id="azkaban-message-dialog">
<div class="modal-dialog">
diff --git a/src/java/azkaban/webapp/servlet/velocity/nav.vm b/src/java/azkaban/webapp/servlet/velocity/nav.vm
index f709390..91e7e66 100644
--- a/src/java/azkaban/webapp/servlet/velocity/nav.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/nav.vm
@@ -22,13 +22,6 @@
<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>
diff --git a/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm b/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
index 43d29c4..067b89c 100644
--- a/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
@@ -21,8 +21,8 @@
#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" src="${context}/js/azkaban/view/project-permissions.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/project-modals.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -232,10 +232,10 @@
</table>
</div>
- </div><!-- /col-lg-8 -->
+ </div><!-- /col-xs-8 -->
<div class="col-xs-6 col-sm-3 sidebar-offcanvas">
#parse ("azkaban/webapp/servlet/velocity/projectsidebar.vm")
- </div><!-- /col-lg-4 -->
+ </div><!-- /col-xs-4 -->
</div><!-- /row -->
## Remove proxy user modal dialog.
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectlogpage.vm b/src/java/azkaban/webapp/servlet/velocity/projectlogpage.vm
index dbb998a..08dbb88 100644
--- a/src/java/azkaban/webapp/servlet/velocity/projectlogpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/projectlogpage.vm
@@ -21,10 +21,10 @@
#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.projectlog.view.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.projectmodals.view.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/date.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/ajax.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/project-logs.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/project-modals.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectpage.vm b/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
index 8d6e865..e6b5314 100644
--- a/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
@@ -25,9 +25,9 @@
<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" src="${context}/js/azkaban/util/ajax.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/project.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/project-modals.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -85,17 +85,17 @@
</div>
#end
#else
- <div class="alert alert-info">
+ <div class="alert alert-default">
<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><!-- /col-xs-8 -->
<div class="col-xs-6 col-sm-3 sidebar-offcanvas">
#parse ("azkaban/webapp/servlet/velocity/projectsidebar.vm")
- </div><!-- /col-lg-4 -->
+ </div><!-- /col-xs-4 -->
</div><!-- /row -->
#parse ("azkaban/webapp/servlet/velocity/projectmodals.vm")
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectpageheader.vm b/src/java/azkaban/webapp/servlet/velocity/projectpageheader.vm
index 0936995..6bb0858 100644
--- a/src/java/azkaban/webapp/servlet/velocity/projectpageheader.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/projectpageheader.vm
@@ -17,7 +17,7 @@
<div class="az-page-header">
<div class="container-full">
<div class="row">
- <div class="col-lg-6" id="project-page-header">
+ <div class="header-title" 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">
@@ -29,7 +29,7 @@
</div>
</div>
</div>
- <div class="col-lg-6">
+ <div class="header-control">
<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
diff --git a/src/java/azkaban/webapp/servlet/velocity/propertypage.vm b/src/java/azkaban/webapp/servlet/velocity/propertypage.vm
index c1d3097..9ddfeb5 100644
--- a/src/java/azkaban/webapp/servlet/velocity/propertypage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/propertypage.vm
@@ -86,7 +86,7 @@
</tbody>
</table>
</div>
- </div><!-- /col-lg-8 -->
+ </div><!-- /col-xs-8 -->
<div class="col-xs-6 col-sm-3 sidebar-offcanvas">
<div class="well" id="job-summary">
<h4>Properties <small>$property</small></h4>
diff --git a/src/java/azkaban/webapp/servlet/velocity/scheduledflowcalendarpage.vm b/src/java/azkaban/webapp/servlet/velocity/scheduledflowcalendarpage.vm
index 761350d..d5a37ca 100644
--- a/src/java/azkaban/webapp/servlet/velocity/scheduledflowcalendarpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/scheduledflowcalendarpage.vm
@@ -28,10 +28,11 @@
<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.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>
+
+ <script type="text/javascript" src="${context}/js/azkaban/view/table-sort.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/schedule-svg.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/context-menu.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/svg-navigate.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -60,7 +61,7 @@
<div class="container-full">
<div class="row">
- <div class="col-lg-12">
+ <div class="col-xs-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>
diff --git a/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm
index 87285cf..1aa2610 100644
--- a/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm
@@ -25,8 +25,8 @@
<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.scheduled.view.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/table-sort.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/scheduled.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -59,7 +59,7 @@
#parse ("azkaban/webapp/servlet/velocity/alerts.vm")
<div class="row">
- <div class="col-lg-12">
+ <div class="col-xs-12">
<table id="scheduledFlowsTbl" class="table table-striped table-condensed table-bordered table-hover">
<thead>
<tr>
@@ -102,7 +102,7 @@
#end
</tbody>
</table>
- </div><!-- /col-lg-12 -->
+ </div><!-- /col-xs-12 -->
</div><!-- /row -->
## Set SLA modal.
diff --git a/src/java/azkaban/webapp/servlet/velocity/schedulepanel.vm b/src/java/azkaban/webapp/servlet/velocity/schedulepanel.vm
index 3381d91..3e39a5c 100644
--- a/src/java/azkaban/webapp/servlet/velocity/schedulepanel.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/schedulepanel.vm
@@ -14,8 +14,8 @@
* the License.
*#
- <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>
+ <script type="text/javascript" src="${context}/js/azkaban/util/date.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/schedule-panel.js"></script>
<div class="modal" id="schedule-modal">
<div class="modal-dialog">
diff --git a/src/java/azkaban/webapp/servlet/velocity/triggerspage.vm b/src/java/azkaban/webapp/servlet/velocity/triggerspage.vm
index 542e699..c48ec9e 100644
--- a/src/java/azkaban/webapp/servlet/velocity/triggerspage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/triggerspage.vm
@@ -27,8 +27,9 @@
<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" src="${context}/js/azkaban/view/table-sort.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/triggers.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -57,7 +58,7 @@
#parse ("azkaban/webapp/servlet/velocity/alerts.vm")
<div class="row">
- <div class="col-lg-12">
+ <div class="col-xs-12">
<table id="triggersTbl" class="table table-striped table-bordered table-condensed table-hover">
<thead>
<tr>
src/less/.gitignore 1(+1 -0)
diff --git a/src/less/.gitignore b/src/less/.gitignore
new file mode 100644
index 0000000..2416a67
--- /dev/null
+++ b/src/less/.gitignore
@@ -0,0 +1 @@
+obj/
src/less/azkaban.less 6(+4 -2)
diff --git a/src/less/azkaban.less b/src/less/azkaban.less
index da43720..54ba380 100644
--- a/src/less/azkaban.less
+++ b/src/less/azkaban.less
@@ -1,10 +1,12 @@
+@import "non-responsive.less";
+
@import "base.less";
-@import "offcanvas.less";
+@import "off-canvas.less";
@import "navbar.less";
@import "header.less";
-@import "contextmenu.less";
+@import "context-menu.less";
@import "tables.less";
@import "login.less";
src/less/base.less 16(+16 -0)
diff --git a/src/less/base.less b/src/less/base.less
index 573ccc1..cb7a0da 100644
--- a/src/less/base.less
+++ b/src/less/base.less
@@ -2,6 +2,8 @@
padding: 0 105px;
margin: 0 auto;
width: 100%;
+ max-width: none;
+ min-width: 1075px;
}
.container-fill {
@@ -22,6 +24,20 @@
}
}
+.alert-default {
+ color: #a0a0a0;
+ background-color: #f5f5f5;
+ border-color: #dddddd;
+
+ hr {
+ border-top-color: #cccccc;
+ }
+
+ .alert-link {
+ color: #a0a0a0;
+ }
+}
+
// Wide modal used for certain panels such as executing flow panel.
.modal-wide .modal-dialog {
width: 80%;
src/less/flow.less 36(+27 -9)
diff --git a/src/less/flow.less b/src/less/flow.less
index 931f47c..4c23e39 100644
--- a/src/less/flow.less
+++ b/src/less/flow.less
@@ -25,7 +25,7 @@
.flow-progress-bar {
height: 100%;
background-color: #ccc;
- border-radius: 4px;
+ border-radius: 5px;
-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;
@@ -84,7 +84,8 @@ td {
background-color: #c82123;
}
- &.READY {
+ &.READY,
+ &.UNKNOWN {
background-color: #ccc;
}
@@ -96,10 +97,7 @@ td {
background-color: #f19153;
}
- &.DISABLED {
- background-color: #aaa;
- }
-
+ &.DISABLED,
&.SKIPPED {
background-color: #aaa;
}
@@ -107,10 +105,30 @@ td {
&.KILLED {
background-color: #d9534f;
}
+ }
+}
- &.UNKNOWN {
- background-color: #ccc;
- }
+#flowStatus {
+ &.SKIPPED {
+ color: #aaa;
+ }
+
+ &.SUCCEEDED {
+ color: #4e911e;
+ }
+
+ &.RUNNING {
+ color: #009fc9;
+ }
+
+ &.PAUSED {
+ color: #c92123;
+ }
+
+ &.FAILED,
+ &.FAILED_FINISHING,
+ &.KILLED {
+ color: #cc0000;
}
}
src/less/header.less 16(+16 -0)
diff --git a/src/less/header.less b/src/less/header.less
index 3ca6d00..9ec9c27 100644
--- a/src/less/header.less
+++ b/src/less/header.less
@@ -35,4 +35,20 @@
.editable-form {
display: none;
}
+
+ .header-title {
+ padding-left: 15px;
+ float: left;
+ width: 40%;
+ }
+
+ .header-control {
+ float: right;
+ padding-right: 15px;
+ width: 500px;
+
+ .form-group {
+ padding: 0;
+ }
+ }
}
src/less/Makefile 16(+16 -0)
diff --git a/src/less/Makefile b/src/less/Makefile
new file mode 100644
index 0000000..48f04c6
--- /dev/null
+++ b/src/less/Makefile
@@ -0,0 +1,16 @@
+OBJ_DIR = obj
+OBJ = \
+ $(OBJ_DIR)/azkaban.css \
+ $(OBJ_DIR)/azkaban-svg.css
+
+all: $(OBJ)
+
+$(OBJ_DIR)/%.css: %.less
+ lessc $< $@
+
+clean:
+ rm -rf $(OBJ_DIR)
+
+.SUFFIXES: .less .css
+
+.PHONY: all clean
src/less/non-responsive.less 88(+88 -0)
diff --git a/src/less/non-responsive.less b/src/less/non-responsive.less
new file mode 100644
index 0000000..7fa282c
--- /dev/null
+++ b/src/less/non-responsive.less
@@ -0,0 +1,88 @@
+/* Non-responsive overrides
+ *
+ * Utilitze the following CSS to disable the responsive-ness of the container,
+ * grid system, and navbar.
+ */
+
+/* Reset the container */
+.container {
+ max-width: none !important;
+ width: 970px;
+}
+
+.container .navbar-header,
+.container .navbar-collapse {
+ margin-right: 0;
+ margin-left: 0;
+}
+
+/* Always float the navbar header */
+.navbar-header {
+ float: left;
+}
+
+/* Undo the collapsing navbar */
+.navbar-collapse {
+ display: block !important;
+ height: auto !important;
+ padding-bottom: 0;
+ overflow: visible !important;
+}
+
+.navbar-toggle {
+ display: none;
+}
+.navbar-collapse {
+ border-top: 0;
+}
+
+.navbar-brand {
+ margin-left: -15px;
+}
+
+/* Always apply the floated nav */
+.navbar-nav {
+ float: left;
+ margin: 0;
+}
+.navbar-nav > li {
+ float: left;
+}
+.navbar-nav > li > a {
+ padding: 15px;
+}
+
+/* Redeclare since we override the float above */
+.navbar-nav.navbar-right {
+ float: right;
+}
+
+/* Undo custom dropdowns */
+.navbar .navbar-nav .open .dropdown-menu {
+ position: absolute;
+ float: left;
+ background-color: #fff;
+ border: 1px solid #cccccc;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-width: 0 1px 1px;
+ border-radius: 0 0 4px 4px;
+ -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+}
+.navbar-default .navbar-nav .open .dropdown-menu > li > a {
+ color: #333;
+}
+.navbar .navbar-nav .open .dropdown-menu > li > a:hover,
+.navbar .navbar-nav .open .dropdown-menu > li > a:focus,
+.navbar .navbar-nav .open .dropdown-menu > .active > a,
+.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
+.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
+ color: #fff !important;
+ background-color: #428bca !important;
+}
+.navbar .navbar-nav .open .dropdown-menu > .disabled > a,
+.navbar .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+.navbar .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+ color: #999 !important;
+ background-color: transparent !important;
+}
src/less/project.less 54(+52 -2)
diff --git a/src/less/project.less b/src/less/project.less
index b3ae43f..d7e1fb9 100644
--- a/src/less/project.less
+++ b/src/less/project.less
@@ -1,5 +1,55 @@
-.az-project-row {
- cursor: pointer;
+#project-list {
+ padding: 0;
+ margin: 0;
+
+ li {
+ list-style: none;
+ border-bottom: 1px solid #cccccc;
+ padding-top: 14px;
+ padding-bottom: 0px;
+ &:first-child {
+ border-top: 1px solid #cccccc;
+ }
+ }
+
+ .project-expander {
+ float: right;
+ cursor: pointer;
+ &:hover {
+ color: #2a6496;
+ }
+ }
+
+ .project-info {
+ float: left;
+ h4 {
+ margin-top: 0;
+ margin-bottom: 4px;
+ }
+
+ .project-description {
+ margin-bottom: 4px;
+ }
+
+ .project-last-modified {
+ color: #a0a0a0;
+ margin-bottom: 16px;
+ strong {
+ font-weight: normal;
+ color: #000000;
+ }
+ }
+ }
+
+ .project-flows {
+ display: none;
+ background-color: #f9f9f9;
+ table {
+ background: transparent;
+ margin-bottom: 0;
+ border-top: 1px solid #dddddd;
+ }
+ }
}
// Flow panel heading.
diff --git a/src/package/soloserver/conf/azkaban.properties b/src/package/soloserver/conf/azkaban.properties
index 7524a14..d600ed9 100644
--- a/src/package/soloserver/conf/azkaban.properties
+++ b/src/package/soloserver/conf/azkaban.properties
@@ -20,6 +20,9 @@ database.type=h2
h2.path=data/azkaban
h2.create.tables=true
+# Stats
+azkaban.stats.dir=
+
# Velocity dev mode
velocity.dev.mode=false
diff --git a/src/package/webserver/conf/azkaban.properties b/src/package/webserver/conf/azkaban.properties
index 3ccb2f3..3fe43a0 100644
--- a/src/package/webserver/conf/azkaban.properties
+++ b/src/package/webserver/conf/azkaban.properties
@@ -22,6 +22,8 @@ mysql.user=azkaban
mysql.password=azkaban
mysql.numconnections=100
+azkaban.stats.dir=
+
# Velocity dev mode
velocity.dev.mode=false
src/tl/.gitignore 1(+1 -0)
diff --git a/src/tl/.gitignore b/src/tl/.gitignore
new file mode 100644
index 0000000..2416a67
--- /dev/null
+++ b/src/tl/.gitignore
@@ -0,0 +1 @@
+obj/
src/tl/flowstats.tl 133(+133 -0)
diff --git a/src/tl/flowstats.tl b/src/tl/flowstats.tl
new file mode 100644
index 0000000..8fffebe
--- /dev/null
+++ b/src/tl/flowstats.tl
@@ -0,0 +1,133 @@
+ <div class="row">
+ <div class="col-xs-12">
+ <h4>Resources</h4>
+ <table class="table table-bordered table-condensed table-striped">
+ <thead>
+ <tr>
+ <th class="property-key">Resource</th>
+ <th class="property-key">Value</th>
+ <th>Job Name</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="property-key">Max Map Slots</td>
+ <td>{stats.mapSlots.max}</td>
+ <td>{stats.mapSlots.job}</td>
+ </tr>
+ <tr>
+ <td class="property-key">Max Reduce Slots</td>
+ <td>{stats.reduceSlots.max}</td>
+ <td>{stats.reduceSlots.job}</td>
+ </tr>
+ <tr>
+ <td class="property-key">Total Map Slots</td>
+ <td colspan="2">{stats.totalMapSlots}</td>
+ </tr>
+ <tr>
+ <td class="property-key">Total Reduce Slots</td>
+ <td colspan="2">{stats.totalReduceSlots}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-xs-12">
+ <h4>Parameters</h4>
+ <table class="table table-bordered table-condensed table-striped">
+ <thead>
+ <tr>
+ <th class="property-key">Parameter</th>
+ <th class="property-key">Value</th>
+ <th>Job Name</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="property-key">Max <code>-Xmx</code></td>
+ <td>{stats.xmx.str}</td>
+ <td>{stats.xmx.job}</td>
+ </tr>
+ <tr>
+ <td class="property-key">Max <code>-Xms</code></td>
+ {?stats.xms.set}
+ <td>
+ {stats.xms.str}
+ </td>
+ <td>
+ {stats.xms.job}
+ </td>
+ {:else}
+ <td colspan="2">
+ Not set.
+ </td>
+ {/stats.xms.set}
+ </tr>
+ <tr>
+ <td class="property-key">Max <code>mapred.job.map.memory.mb</code></td>
+ <td>{stats.jobMapMemoryMb.max}</td>
+ <td>{stats.jobMapMemoryMb.job}</td>
+ </tr>
+ <tr>
+ <td class="property-key">Max <code>mapred.job.reduce.memory.mb</code></td>
+ <td>{stats.jobReduceMemoryMb.max}</td>
+ <td>{stats.jobReduceMemoryMb.job}</td>
+ </tr>
+ <tr>
+ <td class="property-key">Max Distributed Cache</td>
+ {?stats.distributedCache.using}
+ <td>
+ {stats.distributedCache.max}
+ </td>
+ <td>
+ {stats.distributedCache.job}
+ </td>
+ {:else}
+ <td colspan="2">
+ Not used.
+ </td>
+ {/stats.distributedCache.using}
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-xs-12">
+ <h4>Counters</h4>
+ <table class="table table-bordered table-condensed">
+ <thead>
+ <tr>
+ <th class="property-key">Parameter</th>
+ <th class="property-key">Value</th>
+ <th>Job Name</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="property-key">Max <code>FILE_BYTES_READ</code></td>
+ <td>{stats.fileBytesRead.max}</td>
+ <td>{stats.fileBytesRead.job}</td>
+ </tr>
+ <tr>
+ <td class="property-key">Max <code>HDFS_BYTES_READ</code></td>
+ <td>{stats.hdfsBytesRead.max}</td>
+ <td>{stats.hdfsBytesRead.job}</td>
+ </tr>
+ <tr>
+ <td class="property-key">Max <code>FILE_BYTES_WRITTEN</code></td>
+ <td>{stats.fileBytesWritten.max}</td>
+ <td>{stats.fileBytesWritten.job}</td>
+ </tr>
+ <tr>
+ <td class="property-key">Max <code>HDFS_BYTES_WRITTEN</code></td>
+ <td>{stats.hdfsBytesWritten.max}</td>
+ <td>{stats.hdfsBytesWritten.job}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
src/tl/flowstats-no-data.tl 8(+8 -0)
diff --git a/src/tl/flowstats-no-data.tl b/src/tl/flowstats-no-data.tl
new file mode 100644
index 0000000..2f3605a
--- /dev/null
+++ b/src/tl/flowstats-no-data.tl
@@ -0,0 +1,8 @@
+ <div class="row">
+ <div class="col-xs-12">
+ <div class="alert alert-default">
+ <h4>No Flow Stats Available</h4>
+ <p>{message}</p>
+ </div>
+ </div>
+ </div>
src/tl/flowsummary.tl 160(+59 -101)
diff --git a/src/tl/flowsummary.tl b/src/tl/flowsummary.tl
index 10a0680..1c9154f 100644
--- a/src/tl/flowsummary.tl
+++ b/src/tl/flowsummary.tl
@@ -1,112 +1,70 @@
- <div class="col-lg-12">
- <table class="table table-bordered table-condensed table-striped">
+ <div class="row">
+ <div class="col-xs-12">
+ <table class="table table-bordered table-condensed">
<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>
+ <td>{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>
+ <td>{#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>
+ </div>
+ <hr>
+
+ <div class="row">
+ <div class="col-xs-12">
+ <h3>
+ Scheduling
+ {?schedule}
+ <div class="pull-right">
+ <button type="button" id="removeSchedBtn" class="btn btn-sm btn-danger" onclick="removeSched({schedule.scheduleId})" >Remove Schedule</button>
</div>
- {/lastRun}
- </div>
- </div><!-- /.col-lg-12 -->
-
+ {/schedule}
+ </h3>
+ {?schedule}
+ <table class="table table-condensed table-bordered">
+ <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="alert alert-default">
+ <h4>None</h4>
+ <p>This flow has not been scheduled.</p>
+ </div>
+ {/schedule}
+ <h3>Last Run Stats</h3>
+ </div>
+ </div>
src/tl/Makefile 17(+17 -0)
diff --git a/src/tl/Makefile b/src/tl/Makefile
new file mode 100644
index 0000000..a9c8028
--- /dev/null
+++ b/src/tl/Makefile
@@ -0,0 +1,17 @@
+OBJ_DIR = obj
+OBJ = \
+ $(OBJ_DIR)/flowsummary.js \
+ $(OBJ_DIR)/flowstats.js \
+ $(OBJ_DIR)/flowstats-no-data.js
+
+all: $(OBJ)
+
+$(OBJ_DIR)/%.js: %.tl
+ mkdir -p $(OBJ_DIR) && dustc --name=$(basename $<) $< $@
+
+clean:
+ rm -rf $(OBJ_DIR)
+
+.SUFFIXES: .tl .js
+
+.PHONY: all clean
src/web/js/azkaban/model/log-data.js 302(+302 -0)
diff --git a/src/web/js/azkaban/model/log-data.js b/src/web/js/azkaban/model/log-data.js
new file mode 100644
index 0000000..2e21386
--- /dev/null
+++ b/src/web/js/azkaban/model/log-data.js
@@ -0,0 +1,302 @@
+$.namespace('azkaban');
+
+azkaban.LogDataModel = Backbone.Model.extend({
+ TIMESTAMP_REGEX: /^.*? - /gm,
+
+ JOB_TRACKER_URL_REGEX: /https?:\/\/[-\w\.]+(?::\d+)?\/[\w\/\.]*\?\S+(job_\d{12}_\d{4,})\S*/,
+
+ // Command properties
+ COMMAND_START: "Command: ",
+ CLASSPATH_REGEX: /(?:-cp|-classpath)\s+(\S+)/g,
+ ENVIRONMENT_VARIABLES_REGEX: /-D(\S+)/g,
+ JVM_MEMORY_REGEX: /(-Xm\S+)/g,
+ PIG_PARAMS_REGEX: /-param\s+(\S+)/g,
+
+ JOB_TYPE_REGEX: /Building (\S+) job executor/,
+
+ PIG_JOB_SUMMARY_START: "HadoopVersion",
+ PIG_JOB_STATS_START: "Job Stats (time in seconds):",
+
+ HIVE_PARSING_START: "Parsing command: ",
+ HIVE_PARSING_END: "Parse Completed",
+ HIVE_NUM_MAP_REDUCE_JOBS_STRING: "Total MapReduce jobs = ",
+ HIVE_MAP_REDUCE_JOB_START: "Starting Job",
+ HIVE_MAP_REDUCE_JOBS_SUMMARY: "MapReduce Jobs Launched:",
+ HIVE_MAP_REDUCE_SUMMARY_REGEX: /Job (\d+): Map: (\d+) Reduce: (\d+) HDFS Read: (\d+) HDFS Write: (\d+)/,
+
+ initialize: function() {
+ this.set("offset", 0 );
+ this.set("logData", "");
+ this.on("change:logData", this.parseLogData);
+ },
+
+ refresh: function() {
+ var requestURL = contextURL + "/executor";
+ var finished = false;
+
+ var date = new Date();
+ var startTime = date.getTime();
+
+ while (!finished) {
+ var requestData = {
+ "execid": execId,
+ "jobId": jobId,
+ "ajax":"fetchExecJobLogs",
+ "offset": this.get("offset"),
+ "length": 50000,
+ "attempt": attempt
+ };
+
+ var self = this;
+
+ 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.");
+ }
+
+ self.set("offset", data.offset + data.length);
+ self.set("logData", self.get("logData") + data.data);
+ }
+ }
+
+ $.ajax({
+ url: requestURL,
+ type: "get",
+ async: false,
+ data: requestData,
+ dataType: "json",
+ error: function(data) {
+ console.log(data);
+ finished = true;
+ },
+ success: successHandler
+ });
+ }
+ },
+
+ parseLogData: function() {
+ var data = this.get("logData").replace(this.TIMESTAMP_REGEX, "");
+ var lines = data.split("\n");
+
+ if (this.parseCommand(lines)) {
+ this.parseJobTrackerUrls(lines);
+
+ var jobType = this.parseJobType(lines);
+ if (jobType.indexOf("pig") !== -1) {
+ this.parsePigTable(lines, "pigSummary", this.PIG_JOB_SUMMARY_START, "", 0);
+ this.parsePigTable(lines, "pigStats", this.PIG_JOB_STATS_START, "", 1);
+ } else if (jobType.indexOf("hive") !== -1) {
+ this.parseHiveQueries(lines);
+ }
+ }
+ },
+
+ parseCommand: function(lines) {
+ var commandStartIndex = -1;
+ var numLines = lines.length;
+ for (var i = 0; i < numLines; i++) {
+ if (lines[i].indexOf(this.COMMAND_START) === 0) {
+ commandStartIndex = i;
+ break;
+ }
+ }
+
+ if (commandStartIndex != -1) {
+ var commandProperties = {};
+
+ var command = lines[commandStartIndex].substring(this.COMMAND_START.length);
+ commandProperties.Command = command;
+
+ this.parseCommandProperty(command, commandProperties, "Classpath", this.CLASSPATH_REGEX, ':');
+ this.parseCommandProperty(command, commandProperties, "-D", this.ENVIRONMENT_VARIABLES_REGEX);
+ this.parseCommandProperty(command, commandProperties, "Memory Settings", this.JVM_MEMORY_REGEX);
+ this.parseCommandProperty(command, commandProperties, "Params", this.PIG_PARAMS_REGEX);
+
+ this.set("commandProperties", commandProperties);
+
+ return true;
+ }
+
+ return false;
+ },
+
+ parseCommandProperty: function(command, commandProperties, propertyName, regex, split) {
+ var results = [];
+ var match;
+ while (match = regex.exec(command)) {
+ if (split) {
+ results = results.concat(match[1].split(split));
+ } else {
+ results.push(match[1]);
+ }
+ }
+
+ if (results.length > 0) {
+ commandProperties[propertyName] = results;
+ }
+ },
+
+ parseJobTrackerUrls: function(lines) {
+ var jobTrackerUrls = {};
+ var jobTrackerUrlsOrdered = [];
+ var numLines = lines.length;
+ var match;
+ for (var i = 0; i < numLines; i++) {
+ if ((match = this.JOB_TRACKER_URL_REGEX.exec(lines[i])) && !jobTrackerUrls[match[1]]) {
+ jobTrackerUrls[match[1]] = match[0];
+ jobTrackerUrlsOrdered.push(match[0]);
+ }
+ }
+ this.set("jobTrackerUrls", jobTrackerUrls);
+ this.set("jobTrackerUrlsOrdered", jobTrackerUrlsOrdered);
+ },
+
+ parseJobType: function(lines) {
+ var numLines = lines.length;
+ var match;
+ for (var i = 0; numLines; i++) {
+ if (match = this.JOB_TYPE_REGEX.exec(lines[i])) {
+ return match[1];
+ }
+ }
+
+ return null;
+ },
+
+ parsePigTable: function(lines, tableName, startPattern, endPattern, linesToSkipAfterStart) {
+ var index = -1;
+ var numLines = lines.length;
+ for (var i = 0; i < numLines; i++) {
+ if (lines[i].indexOf(startPattern) === 0) {
+ index = i + linesToSkipAfterStart;
+ break;
+ }
+ }
+
+ if (index != -1) {
+ var table = [];
+ var line;
+ while ((line = lines[index]) !== endPattern) {
+ var columns = line.split("\t");
+ // If first column is a job id, make it a link to the job tracker.
+ if (this.get("jobTrackerUrls")[columns[0]]) {
+ columns[0] = "<a href='" + this.get("jobTrackerUrls")[columns[0]] + "'>" + columns[0] + "</a>";
+ }
+ table.push(columns);
+ index++;
+ }
+
+ this.set(tableName, table);
+ }
+ },
+
+ parseHiveQueries: function(lines) {
+ var hiveQueries = [];
+ var hiveQueryJobs = [];
+
+ var currMapReduceJob = 0;
+ var numLines = lines.length;
+ for (var i = 0; i < numLines;) {
+ var line = lines[i];
+ var parsingCommandIndex = line.indexOf(this.HIVE_PARSING_START);
+ if (parsingCommandIndex === -1) {
+ i++;
+ continue;
+ }
+
+ // parse query text, which could span multiple lines
+ var queryStartIndex = parsingCommandIndex + this.HIVE_PARSING_START.length;
+ var query = line.substring(queryStartIndex) + "<br/>";
+
+ i++;
+ while (i < numLines && (line = lines[i]).indexOf(this.HIVE_PARSING_END) === -1) {
+ query += line + "<br/>";
+ i++;
+ }
+ hiveQueries.push(query);
+ i++;
+
+ // parse the query's Map-Reduce jobs, if any.
+ var numMRJobs = 0;
+ while (i < numLines) {
+ line = lines[i];
+ if (line.contains(this.HIVE_NUM_MAP_REDUCE_JOBS_STRING)) {
+ // query involves map reduce jobs
+ var numMRJobs = parseInt(line.substring(this.HIVE_NUM_MAP_REDUCE_JOBS_STRING.length),10);
+ i++;
+
+ // get the map reduce jobs summary
+ while (i < numLines) {
+ line = lines[i];
+ if (line.contains(this.HIVE_MAP_REDUCE_JOBS_SUMMARY)) {
+ // job summary table found
+ i++;
+
+ var queryJobs = [];
+
+ var previousJob = -1;
+ var numJobsSeen = 0;
+ while (numJobsSeen < numMRJobs && i < numLines) {
+ line = lines[i];
+ var match;
+ if (match = this.HIVE_MAP_REDUCE_SUMMARY_REGEX.exec(line)) {
+ var currJob = parseInt(match[1], 10);
+ if (currJob === previousJob) {
+ i++;
+ continue;
+ }
+
+ var job = [];
+ job.push("<a href='" + this.get("jobTrackerUrlsOrdered")[currMapReduceJob++] + "'>" + currJob + "</a>");
+ job.push(match[2]);
+ job.push(match[3]);
+ job.push(match[4]);
+ job.push(match[5]);
+ queryJobs.push(job);
+ previousJob = currJob;
+ numJobsSeen++;
+ }
+ i++;
+ }
+
+ if (numJobsSeen === numMRJobs) {
+ hiveQueryJobs.push(queryJobs);
+ }
+
+ break;
+ }
+ i++;
+ }
+ break;
+ }
+ else if (line.contains(this.HIVE_PARSING_START)) {
+ if (numMRJobs === 0) {
+ hiveQueryJobs.push(null);
+ }
+ break;
+ }
+ i++;
+ }
+ continue;
+ }
+
+ if (hiveQueries.length > 0) {
+ this.set("hiveSummary", {
+ hiveQueries: hiveQueries,
+ hiveQueryJobs: hiveQueryJobs
+ });
+ }
+ }
+});
src/web/js/azkaban/view/flow-stats.js 318(+318 -0)
diff --git a/src/web/js/azkaban/view/flow-stats.js b/src/web/js/azkaban/view/flow-stats.js
new file mode 100644
index 0000000..5f2919e
--- /dev/null
+++ b/src/web/js/azkaban/view/flow-stats.js
@@ -0,0 +1,318 @@
+/*
+ * 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.FlowStatsModel = Backbone.Model.extend({});
+azkaban.FlowStatsView = Backbone.View.extend({
+ events: {
+ },
+
+ initialize: function(settings) {
+ this.model.bind('change:view', this.handleChangeView, this);
+ this.model.bind('render', this.render, this);
+ },
+
+ render: function(evt) {
+ },
+
+ show: function(execId) {
+ this.analyzeExecution(execId);
+ },
+
+ fetchJobs: function(execId) {
+ var requestURL = contextURL + "/executor";
+ var requestData = {"execid": execId, "ajax":"fetchexecflow"};
+ var jobs = [];
+ var successHandler = function(data) {
+ for (var i = 0; i < data.nodes.length; ++i) {
+ var node = data.nodes[i];
+ jobs.push(node.id);
+ }
+ };
+ $.ajax({
+ url: requestURL,
+ data: requestData,
+ success: successHandler,
+ dataType: "json",
+ async: false
+ });
+ return jobs;
+ },
+
+ fetchJobStats: function(jobId, execId) {
+ var requestURL = contextURL + "/executor";
+ var requestData = {
+ "execid": execId,
+ "flowid": flowId,
+ "jobid": jobId,
+ "ajax": "fetchExecJobStats"
+ };
+ var stats = null;
+ var successHandler = function(data) {
+ stats = data;
+ };
+ $.ajax({
+ url: requestURL,
+ data: requestData,
+ success: successHandler,
+ dataType: "json",
+ async: false
+ });
+ return stats;
+ },
+
+ updateStatsMapred: function(state, data, job) {
+ var stats = data.stats;
+ var mappers = parseInt(state.totalMappers);
+ var reducers = parseInt(state.totalReducers);
+ if (mappers >= stats.mapSlots.max) {
+ stats.mapSlots.max = mappers;
+ stats.mapSlots.job = job;
+ }
+ if (reducers >= stats.reduceSlots.max) {
+ stats.reduceSlots.max = reducers;
+ stats.reduceSlots.job = job;
+ }
+ stats.totalMapSlots += mappers;
+ stats.totalReduceSlots += reducers;
+
+ },
+
+ updateStatsConf: function(conf, data, job) {
+ var stats = data.stats;
+ if (conf == null) {
+ data.warnings.push("No job conf available for job " + job);
+ return;
+ }
+
+ var jobMapMemoryMb = parseInt(conf['mapred.job.map.memory.mb']);
+ if (jobMapMemoryMb >= stats.jobMapMemoryMb.max) {
+ stats.jobMapMemoryMb.max = jobMapMemoryMb;
+ stats.jobMapMemoryMb.job = job;
+ }
+ var jobReduceMemoryMb = parseInt(conf['mapred.job.reduce.memory.mb']);
+ if (jobReduceMemoryMb >= stats.jobReduceMemoryMb.max) {
+ stats.jobReduceMemoryMb.max = jobReduceMemoryMb;
+ stats.jobReduceMemoryMb.job = job;
+ }
+
+ var childJavaOpts = conf['mapred.child.java.opts'];
+ var parts = childJavaOpts.split(" ");
+ for (var i = 0; i < parts.length; ++i) {
+ var str = parts[i];
+ if (str.indexOf('Xmx') > -1) {
+ if (str.length <= 4) {
+ continue;
+ }
+ var size = str.substring(4, str.length);
+ var val = sizeStrToBytes(size);
+ if (val >= stats.xmx.max) {
+ stats.xmx.max = val;
+ stats.xmx.str = size;
+ stats.xmx.job = job;
+ }
+ }
+ if (str.indexOf('Xms') > -1) {
+ if (str.length <= 4) {
+ continue;
+ }
+ var size = str.substring(4, str.length);
+ var val = sizeStrToBytes(size);
+ stats.xms.set = true;
+ if (val >= stats.xms.max) {
+ stats.xms.max = val;
+ stats.xms.str = size;
+ stats.xms.job = job;
+ }
+ }
+ }
+
+ var cacheFiles = conf['mapred.cache.files'];
+ var cacheFilesFilesizes = conf['mapred.cache.files.filesizes'];
+ if (cacheFiles != null && cacheFilesFilesizes != null) {
+ stats.distributedCache.using = true;
+ var parts = cacheFilesFilesizes.split(',');
+ var size = 0;
+ for (var i = 0; i < parts.length; ++i) {
+ size += parseInt(parts[i]);
+ }
+ if (size >= stats.distributedCache.max) {
+ stats.distributedCache.max = size;
+ stats.distributedCache.job = job;
+ }
+ }
+ },
+
+ updateStatsCounters: function(state, data, job) {
+ var stats = data.stats;
+ if (state.counters == null) {
+ data.warnings.push("No job counters available for job " + job);
+ return;
+ }
+ var fileSystemCounters = state.counters['FileSystemCounters'];
+ if (fileSystemCounters == null) {
+ data.warnings.push("No FileSystemCounters available for job " + job);
+ return;
+ }
+ var fileBytesRead = parseInt(fileSystemCounters['FILE_BYTES_READ']);
+ if (fileBytesRead >= stats.fileBytesRead.max) {
+ stats.fileBytesRead.max = fileBytesRead;
+ stats.fileBytesRead.job = job;
+ }
+
+ var fileBytesWritten = parseInt(fileSystemCounters['FILE_BYTES_WRITTEN']);
+ if (fileBytesWritten >= stats.fileBytesWritten.max) {
+ stats.fileBytesWritten.max = fileBytesWritten;
+ stats.fileBytesWritten.job = job;
+ }
+
+ var hdfsBytesRead = parseInt(fileSystemCounters['HDFS_BYTES_READ']);
+ if (hdfsBytesRead >= stats.hdfsBytesRead.max) {
+ stats.hdfsBytesRead.max = hdfsBytesRead;
+ stats.hdfsBytesRead.job = job;
+ }
+
+ var hdfsBytesWritten = parseInt(fileSystemCounters['HDFS_BYTES_WRITTEN']);
+ if (hdfsBytesWritten >= stats.hdfsBytesWritten.max) {
+ stats.hdfsBytesWritten.max = hdfsBytesWritten;
+ stats.hdfsBytesWritten.job = job;
+ }
+ },
+
+ updateStats: function(jobStats, data, job) {
+ var stats = data.stats;
+ var state = jobStats.state;
+ var conf = jobStats.conf;
+
+ this.updateStatsMapred(state, data, job);
+ this.updateStatsConf(conf, data, job);
+ this.updateStatsCounters(state, data, job);
+ },
+
+ finalizeStats: function(data) {
+ data.success = true;
+ },
+
+ analyzeExecution: function(execId) {
+ var jobs = this.fetchJobs(execId);
+ if (jobs == null) {
+ this.model.set({'data': null});
+ this.model.trigger('render');
+ return;
+ }
+
+ var data = {
+ success: false,
+ message: null,
+ warnings: [],
+ stats: {
+ mapSlots: {
+ max: 0,
+ job: null
+ },
+ reduceSlots: {
+ max: 0,
+ job: null
+ },
+ totalMapSlots: 0,
+ totalReduceSlots: 0,
+ numJobs: jobs.length,
+ longestTaskTime: 0,
+ jobMapMemoryMb: {
+ max: 0,
+ job: null
+ },
+ jobReduceMemoryMb: {
+ max: 0,
+ job: null
+ },
+ xmx: {
+ max: 0,
+ str: null,
+ job: null
+ },
+ xms: {
+ set: false,
+ max: 0,
+ str: null,
+ job: null
+ },
+ fileBytesRead: {
+ max: 0,
+ job: null
+ },
+ hdfsBytesRead: {
+ max: 0,
+ job: null
+ },
+ fileBytesWritten: {
+ max: 0,
+ job: null
+ },
+ hdfsBytesWritten: {
+ max: 0,
+ job: null
+ },
+ distributedCache: {
+ using: false,
+ max: 0,
+ job: null
+ },
+ }
+ };
+
+ for (var i = 0; i < jobs.length; ++i) {
+ var job = jobs[i];
+ var jobStats = this.fetchJobStats(job, execId);
+ if (jobStats.jobStats == null) {
+ data.warnings.push("No job stats available for job " + job.id);
+ continue;
+ }
+ for (var j = 0; j < jobStats.jobStats.length; ++j) {
+ this.updateStats(jobStats.jobStats[j], data, job);
+ }
+ }
+ this.finalizeStats(data);
+ this.model.set({'data': data});
+ this.model.trigger('render');
+ },
+
+ render: function(evt) {
+ var view = this;
+ var data = this.model.get('data');
+ if (data == null) {
+ var msg = { message: "Error retrieving flow stats."};
+ dust.render("flowstats-no-data", msg, function(err, out) {
+ view.display(out);
+ });
+ }
+ else if (data.success == "false") {
+ dust.render("flowstats-no-data", data, function(err, out) {
+ view.display(out);
+ });
+ }
+ else {
+ dust.render("flowstats", data, function(err, out) {
+ view.display(out);
+ });
+ }
+ },
+
+ display: function(out) {
+ $('#flow-stats-container').html(out);
+ },
+});
src/web/js/azkaban/view/job-details.js 276(+276 -0)
diff --git a/src/web/js/azkaban/view/job-details.js b/src/web/js/azkaban/view/job-details.js
new file mode 100644
index 0000000..f191f37
--- /dev/null
+++ b/src/web/js/azkaban/view/job-details.js
@@ -0,0 +1,276 @@
+/*
+ * 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 jobLogView;
+azkaban.JobLogView = Backbone.View.extend({
+ events: {
+ "click #updateLogBtn" : "refresh"
+ },
+
+ initialize: function() {
+ this.listenTo(this.model, "change:logData", this.render);
+ },
+
+ refresh: function() {
+ this.model.refresh();
+ },
+
+ render: function() {
+ var re = /(https?:\/\/(([-\w\.]+)+(:\d+)?(\/([\w/_\.]*(\?\S+)?)?)?))/g;
+ var log = this.model.get("logData");
+ log = log.replace(re, "<a href=\"$1\" title=\"\">$1</a>");
+ $("#logSection").html(log);
+ }
+});
+
+var jobSummaryView;
+azkaban.JobSummaryView = Backbone.View.extend({
+ events: {
+ "click #updateSummaryBtn" : "refresh"
+ },
+
+ initialize: function(settings) {
+ $("#commandSummary").hide();
+ $("#pigJobSummary").hide();
+ $("#pigJobStats").hide();
+ $("#hiveJobSummary").hide();
+
+ this.listenTo(this.model, "change:commandProperties", this.renderCommandTable);
+ this.listenTo(this.model, "change:pigSummary", this.renderPigSummaryTable);
+ this.listenTo(this.model, "change:pigStats", this.renderPigStatsTable);
+ this.listenTo(this.model, "change:hiveSummary", this.renderHiveTable);
+ },
+
+ refresh: function() {
+ this.model.refresh();
+ },
+
+ handleUpdate: function(evt) {
+ renderJobTable(jobSummary.summaryTableHeaders, jobSummary.summaryTableData, "summary");
+ renderJobTable(jobSummary.statTableHeaders, jobSummary.statTableData, "stats");
+ renderHiveTable(jobSummary.hiveQueries, jobSummary.hiveQueryJobs);
+ },
+ renderCommandTable: function() {
+ var commandTable = $("#commandTable");
+ var commandProperties = this.model.get("commandProperties");
+
+ for (var key in commandProperties) {
+ if (commandProperties.hasOwnProperty(key)) {
+ var value = commandProperties[key];
+ if (Array.isArray(value)) {
+ value = value.join("<br/>");
+ }
+ var tr = document.createElement("tr");
+ var keyTd = document.createElement("td");
+ var valueTd = document.createElement("td");
+ $(keyTd).html("<b>" + key + "</b>");
+ $(valueTd).html(value);
+ $(tr).append(keyTd);
+ $(tr).append(valueTd);
+ commandTable.append(tr);
+ }
+ }
+
+ $("#commandSummary").show();
+ },
+ renderPigTable: function(tableName, data) {
+ // Add table headers
+ var header = $("#" + tableName + "Header");
+ var tr = document.createElement("tr");
+ var i;
+ var headers = data[0];
+ var numColumns = headers.length;
+ for (i = 0; i < numColumns; i++) {
+ var th = document.createElement("th");
+ $(th).text(headers[i]);
+ $(tr).append(th);
+ }
+ header.append(tr);
+
+ // Add table body
+ var body = $("#" + tableName + "Body");
+ for (i = 1; i < data.length; i++) {
+ tr = document.createElement("tr");
+ var row = data[i];
+ for (var j = 0; j < numColumns; 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);
+ }
+
+ $("#pigJob" + tableName.charAt(0).toUpperCase() + tableName.substring(1)).show();
+ },
+ renderPigSummaryTable: function() {
+ this.renderPigTable("summary", this.model.get("pigSummary"));
+ },
+ renderPigStatsTable: function() {
+ this.renderPigTable("stats", this.model.get("pigStats"));
+ },
+ renderHiveTable: function() {
+ var hiveSummary = this.model.get("hiveSummary");
+ var queries = hiveSummary.hiveQueries;
+ var queryJobs = hiveSummary.hiveQueryJobs;
+
+ // 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.html(tr);
+
+ // Construct table body
+ var oldBody = $("#hiveTableBody");
+ var newBody = $(document.createElement("tbody")).attr("id", "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);
+ }
+ newBody.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(" ");
+ $(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);
+ }
+ newBody.append(tr);
+ }
+
+ } else {
+ newBody.append(tr);
+ }
+ }
+ oldBody.replaceWith(newBody);
+
+ $("#hiveJobSummary").show();
+ }
+});
+
+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();
+ }
+ },
+
+ 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 logDataModel = new azkaban.LogDataModel();
+
+ jobLogView = new azkaban.JobLogView({
+ el: $('#jobLogView'),
+ model: logDataModel
+ });
+
+ jobSummaryView = new azkaban.JobSummaryView({
+ el: $('#jobSummaryView'),
+ model: logDataModel
+ });
+
+ jobTabView = new azkaban.JobTabView({
+ el: $('#headertabs')
+ });
+
+ logDataModel.refresh();
+
+ if (window.location.hash) {
+ var hash = window.location.hash;
+ if (hash == '#joblog') {
+ jobTabView.handleJobLogViewLinkClick();
+ }
+ else if (hash == '#jobsummary') {
+ jobTabView.handleJobSummaryViewLinkClick();
+ }
+ }
+});
src/web/js/azkaban/view/job-history.js 38(+38 -0)
diff --git a/src/web/js/azkaban/view/job-history.js b/src/web/js/azkaban/view/job-history.js
new file mode 100644
index 0000000..a205d22
--- /dev/null
+++ b/src/web/js/azkaban/view/job-history.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.
+ */
+
+$.namespace('azkaban');
+
+var jobHistoryView;
+
+var dataModel;
+azkaban.DataModel = Backbone.Model.extend({});
+
+$(function() {
+ var selected;
+ var series = dataSeries;
+ dataModel = new azkaban.DataModel();
+ dataModel.set({
+ "data": series
+ });
+ dataModel.trigger('render');
+
+ jobHistoryView = new azkaban.TimeGraphView({
+ el: $('#timeGraph'),
+ model: dataModel,
+ modelField: "data"
+ });
+});
src/web/js/dust-full-2.2.3.min.js 5(+5 -0)
diff --git a/src/web/js/dust-full-2.2.3.min.js b/src/web/js/dust-full-2.2.3.min.js
new file mode 100644
index 0000000..8ba0201
--- /dev/null
+++ b/src/web/js/dust-full-2.2.3.min.js
@@ -0,0 +1,5 @@
+/*! Dust - Asynchronous Templating - v2.2.3
+* http://linkedin.github.io/dustjs/
+* Copyright (c) 2013 Aleksander Williams; Released under the MIT License */
+function getGlobal(){return function(){return this.dust}.call(null)}var dust={};!function(dust){function Context(a,b,c,d){this.stack=a,this.global=b,this.blocks=c,this.templateName=d}function Stack(a,b,c,d){this.tail=b,this.isObject=a&&"object"==typeof a,this.head=a,this.index=c,this.of=d}function Stub(a){this.head=new Chunk(this),this.callback=a,this.out=""}function Stream(){this.head=new Chunk(this)}function Chunk(a,b,c){this.root=a,this.next=b,this.data=[],this.flushable=!1,this.taps=c}function Tap(a,b){this.head=a,this.tail=b}if(dust){var ERROR="ERROR",WARN="WARN",INFO="INFO",DEBUG="DEBUG",levels=[DEBUG,INFO,WARN,ERROR],EMPTY_FUNC=function(){},logger=EMPTY_FUNC;dust.isDebug=!1,dust.debugLevel=INFO,"undefined"!=typeof window&&window&&window.console&&window.console.log?logger=window.console.log:"undefined"!=typeof console&&console&&console.log&&(logger=console.log),dust.log=function(a,b){b=b||INFO,dust.isDebug&&levels.indexOf(b)>=levels.indexOf(dust.debugLevel)&&(dust.logQueue||(dust.logQueue=[]),dust.logQueue.push({message:a,type:b}),logger.call(console||window.console,"[DUST "+b+"]: "+a))},dust.onError=function(a,b){if(dust.log(a.message||a,ERROR),dust.isDebug)throw a;return b},dust.helpers={},dust.cache={},dust.register=function(a,b){a&&(dust.cache[a]=b)},dust.render=function(a,b,c){var d=new Stub(c).head;try{dust.load(a,d,Context.wrap(b,a)).end()}catch(e){dust.onError(e,d)}},dust.stream=function(a,b){var c=new Stream;return dust.nextTick(function(){try{dust.load(a,c.head,Context.wrap(b,a)).end()}catch(d){dust.onError(d,c.head)}}),c},dust.renderSource=function(a,b,c){return dust.compileFn(a)(b,c)},dust.compileFn=function(a,b){var c=dust.loadSource(dust.compile(a,b));return function(a,d){var e=d?new Stub(d):new Stream;return dust.nextTick(function(){"function"==typeof c?c(e.head,Context.wrap(a,b)).end():dust.onError(new Error("Template ["+b+"] cannot be resolved to a Dust function"))}),e}},dust.load=function(a,b,c){var d=dust.cache[a];return d?d(b,c):dust.onLoad?b.map(function(b){dust.onLoad(a,function(d,e){return d?b.setError(d):(dust.cache[a]||dust.loadSource(dust.compile(e,a)),dust.cache[a](b,c).end(),void 0)})}):b.setError(new Error("Template Not Found: "+a))},dust.loadSource=function(source,path){return eval(source)},dust.isArray=Array.isArray?Array.isArray:function(a){return"[object Array]"===Object.prototype.toString.call(a)},dust.nextTick=function(){return"undefined"!=typeof process?process.nextTick:function(a){setTimeout(a,0)}}(),dust.isEmpty=function(a){return dust.isArray(a)&&!a.length?!0:0===a?!1:!a},dust.filter=function(a,b,c){if(c)for(var d=0,e=c.length;e>d;d++){var f=c[d];"s"===f?(b=null,dust.log("Using unescape filter on ["+a+"]",DEBUG)):"function"==typeof dust.filters[f]?a=dust.filters[f](a):dust.onError(new Error("Invalid filter ["+f+"]"))}return b&&(a=dust.filters[b](a)),a},dust.filters={h:function(a){return dust.escapeHtml(a)},j:function(a){return dust.escapeJs(a)},u:encodeURI,uc:encodeURIComponent,js:function(a){return JSON?JSON.stringify(a):(dust.log("JSON is undefined. JSON stringify has not been used on ["+a+"]",WARN),a)},jp:function(a){return JSON?JSON.parse(a):(dust.log("JSON is undefined. JSON parse has not been used on ["+a+"]",WARN),a)}},dust.makeBase=function(a){return new Context(new Stack,a)},Context.wrap=function(a,b){return a instanceof Context?a:new Context(new Stack(a),{},null,b)},Context.prototype.get=function(a,b){return"string"==typeof a&&("."===a[0]&&(b=!0,a=a.substr(1)),a=a.split(".")),this._get(b,a)},Context.prototype._get=function(a,b){var c,d,e,f,g=this.stack,h=1;if(dust.log("Searching for reference [{"+b.join(".")+"}] in template ["+this.getTemplateName()+"]",DEBUG),d=b[0],e=b.length,a&&0===e)f=g,g=g.head;else{if(a)g=g.head[d];else{for(;g&&(!g.isObject||(f=g.head,c=g.head[d],void 0===c));)g=g.tail;g=void 0!==c?c:this.global?this.global[d]:void 0}for(;g&&e>h;)f=g,g=g[b[h]],h++}if("function"==typeof g){var i=function(){return g.apply(f,arguments)};return i.isFunction=!0,i}return void 0===g&&dust.log("Cannot find the value for reference [{"+b.join(".")+"}] in template ["+this.getTemplateName()+"]"),g},Context.prototype.getPath=function(a,b){return this._get(a,b)},Context.prototype.push=function(a,b,c){return new Context(new Stack(a,this.stack,b,c),this.global,this.blocks,this.getTemplateName())},Context.prototype.rebase=function(a){return new Context(new Stack(a),this.global,this.blocks,this.getTemplateName())},Context.prototype.current=function(){return this.stack.head},Context.prototype.getBlock=function(a){if("function"==typeof a){var b=new Chunk;a=a(b,this).data.join("")}var c=this.blocks;if(!c)return dust.log("No blocks for context[{"+a+"}] in template ["+this.getTemplateName()+"]",DEBUG),void 0;for(var d,e=c.length;e--;)if(d=c[e][a])return d},Context.prototype.shiftBlocks=function(a){var b,c=this.blocks;return a?(b=c?c.concat([a]):[a],new Context(this.stack,this.global,b,this.getTemplateName())):this},Context.prototype.getTemplateName=function(){return this.templateName},Stub.prototype.flush=function(){for(var a=this.head;a;){if(!a.flushable)return a.error?(this.callback(a.error),dust.onError(new Error("Chunk error ["+a.error+"] thrown. Ceasing to render this template.")),this.flush=EMPTY_FUNC,void 0):void 0;this.out+=a.data.join(""),a=a.next,this.head=a}this.callback(null,this.out)},Stream.prototype.flush=function(){for(var a=this.head;a;){if(!a.flushable)return a.error?(this.emit("error",a.error),dust.onError(new Error("Chunk error ["+a.error+"] thrown. Ceasing to render this template.")),this.flush=EMPTY_FUNC,void 0):void 0;this.emit("data",a.data.join("")),a=a.next,this.head=a}this.emit("end")},Stream.prototype.emit=function(a,b){if(!this.events)return dust.log("No events to emit",INFO),!1;var c=this.events[a];if(!c)return dust.log("Event type ["+a+"] does not exist",WARN),!1;if("function"==typeof c)c(b);else if(dust.isArray(c))for(var d=c.slice(0),e=0,f=d.length;f>e;e++)d[e](b);else dust.onError(new Error("Event Handler ["+c+"] is not of a type that is handled by emit"))},Stream.prototype.on=function(a,b){return this.events||(this.events={}),this.events[a]?"function"==typeof this.events[a]?this.events[a]=[this.events[a],b]:this.events[a].push(b):(dust.log("Event type ["+a+"] does not exist. Using just the specified callback.",WARN),b?this.events[a]=b:dust.log("Callback for type ["+a+"] does not exist. Listener not registered.",WARN)),this},Stream.prototype.pipe=function(a){return this.on("data",function(b){try{a.write(b,"utf8")}catch(c){dust.onError(c,a.head)}}).on("end",function(){try{return a.end()}catch(b){dust.onError(b,a.head)}}).on("error",function(b){a.error(b)}),this},Chunk.prototype.write=function(a){var b=this.taps;return b&&(a=b.go(a)),this.data.push(a),this},Chunk.prototype.end=function(a){return a&&this.write(a),this.flushable=!0,this.root.flush(),this},Chunk.prototype.map=function(a){var b=new Chunk(this.root,this.next,this.taps),c=new Chunk(this.root,b,this.taps);return this.next=c,this.flushable=!0,a(c),b},Chunk.prototype.tap=function(a){var b=this.taps;return this.taps=b?b.push(a):new Tap(a),this},Chunk.prototype.untap=function(){return this.taps=this.taps.tail,this},Chunk.prototype.render=function(a,b){return a(this,b)},Chunk.prototype.reference=function(a,b,c,d){return"function"==typeof a&&(a.isFunction=!0,a=a.apply(b.current(),[this,b,null,{auto:c,filters:d}]),a instanceof Chunk)?a:dust.isEmpty(a)?this:this.write(dust.filter(a,c,d))},Chunk.prototype.section=function(a,b,c,d){if("function"==typeof a&&(a=a.apply(b.current(),[this,b,c,d]),a instanceof Chunk))return a;var e=c.block,f=c["else"];if(d&&(b=b.push(d)),dust.isArray(a)){if(e){var g=a.length,h=this;if(g>0){b.stack.head&&(b.stack.head.$len=g);for(var i=0;g>i;i++)b.stack.head&&(b.stack.head.$idx=i),h=e(h,b.push(a[i],i,g));return b.stack.head&&(b.stack.head.$idx=void 0,b.stack.head.$len=void 0),h}if(f)return f(this,b)}}else if(a===!0){if(e)return e(this,b)}else if(a||0===a){if(e)return e(this,b.push(a))}else if(f)return f(this,b);return dust.log("Not rendering section (#) block in template ["+b.getTemplateName()+"], because above key was not found",DEBUG),this},Chunk.prototype.exists=function(a,b,c){var d=c.block,e=c["else"];if(dust.isEmpty(a)){if(e)return e(this,b)}else if(d)return d(this,b);return dust.log("Not rendering exists (?) block in template ["+b.getTemplateName()+"], because above key was not found",DEBUG),this},Chunk.prototype.notexists=function(a,b,c){var d=c.block,e=c["else"];if(dust.isEmpty(a)){if(d)return d(this,b)}else if(e)return e(this,b);return dust.log("Not rendering not exists (^) block check in template ["+b.getTemplateName()+"], because above key was found",DEBUG),this},Chunk.prototype.block=function(a,b,c){var d=c.block;return a&&(d=a),d?d(this,b):this},Chunk.prototype.partial=function(a,b,c){var d;d=dust.makeBase(b.global),d.blocks=b.blocks,b.stack&&b.stack.tail&&(d.stack=b.stack.tail),c&&(d=d.push(c)),"string"==typeof a&&(d.templateName=a),d=d.push(b.stack.head);var e;return e="function"==typeof a?this.capture(a,d,function(a,b){d.templateName=d.templateName||a,dust.load(a,b,d).end()}):dust.load(a,this,d)},Chunk.prototype.helper=function(a,b,c,d){var e=this;try{return dust.helpers[a]?dust.helpers[a](e,b,c,d):dust.onError(new Error("Invalid helper ["+a+"]"),e)}catch(f){return dust.onError(f,e)}},Chunk.prototype.capture=function(a,b,c){return this.map(function(d){var e=new Stub(function(a,b){a?d.setError(a):c(b,d)});a(e.head,b).end()})},Chunk.prototype.setError=function(a){return this.error=a,this.root.flush(),this},Tap.prototype.push=function(a){return new Tap(a,this)},Tap.prototype.go=function(a){for(var b=this;b;)a=b.head(a),b=b.tail;return a};var HCHARS=new RegExp(/[&<>\"\']/),AMP=/&/g,LT=/</g,GT=/>/g,QUOT=/\"/g,SQUOT=/\'/g;dust.escapeHtml=function(a){return"string"==typeof a?HCHARS.test(a)?a.replace(AMP,"&").replace(LT,"<").replace(GT,">").replace(QUOT,""").replace(SQUOT,"'"):a:a};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(a){return"string"==typeof a?a.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"):a}}}(dust),"undefined"!=typeof exports&&("undefined"!=typeof process&&require("./server")(dust),module.exports=dust);var dustCompiler=function(dust){function a(a){var b={};return dust.filterNode(b,a)}function b(a,b){var c,d,e,f=[b[0]];for(c=1,d=b.length;d>c;c++)e=dust.filterNode(a,b[c]),e&&f.push(e);return f}function c(a,b){var c,d,e,f,g=[b[0]];for(d=1,e=b.length;e>d;d++)f=dust.filterNode(a,b[d]),f&&("buffer"===f[0]?c?c[1]+=f[1]:(c=f,g.push(f)):(c=null,g.push(f)));return g}function d(a,b){return["buffer",l[b[1]]]}function e(a,b){return b}function f(){}function g(a,b){var c={name:b,bodies:[],blocks:{},index:0,auto:"h"};return"(function(){dust.register("+(b?'"'+b+'"':"null")+","+dust.compileNode(c,a)+");"+h(c)+i(c)+"return body_0;})();"}function h(a){var b,c=[],d=a.blocks;for(b in d)c.push('"'+b+'":'+d[b]);return c.length?(a.blocks="ctx=ctx.shiftBlocks(blocks);","var blocks={"+c.join(",")+"};"):a.blocks=""}function i(a){var b,c,d=[],e=a.bodies,f=a.blocks;for(b=0,c=e.length;c>b;b++)d[b]="function body_"+b+"(chk,ctx){"+f+"return chk"+e[b]+";}";return d.join("")}function j(a,b){var c,d,e="";for(c=1,d=b.length;d>c;c++)e+=dust.compileNode(a,b[c]);return e}function k(a,b,c){return"."+c+"("+dust.compileNode(a,b[1])+","+dust.compileNode(a,b[2])+","+dust.compileNode(a,b[4])+","+dust.compileNode(a,b[3])+")"}dust.compile=function(b,c){try{var d=a(dust.parse(b));return g(d,c)}catch(e){if(!e.line||!e.column)throw e;throw new SyntaxError(e.message+" At line : "+e.line+", column : "+e.column)}},dust.filterNode=function(a,b){return dust.optimizers[b[0]](a,b)},dust.optimizers={body:c,buffer:e,special:d,format:f,reference:b,"#":b,"?":b,"^":b,"<":b,"+":b,"@":b,"%":b,partial:b,context:b,params:b,bodies:b,param:b,filters:e,key:e,path:e,literal:e,comment:f,line:f,col:f},dust.pragmas={esc:function(a,b,c){var d,e=a.auto;return b||(b="h"),a.auto="s"===b?"":b,d=j(a,c.block),a.auto=e,d}};var l={s:" ",n:"\n",r:"\r",lb:"{",rb:"}"};dust.compileNode=function(a,b){return dust.nodes[b[0]](a,b)},dust.nodes={body:function(a,b){var c=a.index++,d="body_"+c;return a.bodies[c]=j(a,b),d},buffer:function(a,b){return".write("+m(b[1])+")"},format:function(a,b){return".write("+m(b[1]+b[2])+")"},reference:function(a,b){return".reference("+dust.compileNode(a,b[1])+",ctx,"+dust.compileNode(a,b[2])+")"},"#":function(a,b){return k(a,b,"section")},"?":function(a,b){return k(a,b,"exists")},"^":function(a,b){return k(a,b,"notexists")},"<":function(a,b){for(var c=b[4],d=1,e=c.length;e>d;d++){var f=c[d],g=f[1][1];if("block"===g)return a.blocks[b[1].text]=dust.compileNode(a,f[2]),""}return""},"+":function(a,b){return"undefined"==typeof b[1].text&&"undefined"==typeof b[4]?".block(ctx.getBlock("+dust.compileNode(a,b[1])+",chk, ctx),"+dust.compileNode(a,b[2])+", {},"+dust.compileNode(a,b[3])+")":".block(ctx.getBlock("+m(b[1].text)+"),"+dust.compileNode(a,b[2])+","+dust.compileNode(a,b[4])+","+dust.compileNode(a,b[3])+")"},"@":function(a,b){return".helper("+m(b[1].text)+","+dust.compileNode(a,b[2])+","+dust.compileNode(a,b[4])+","+dust.compileNode(a,b[3])+")"},"%":function(a,b){var c,d,e,f,g,h,i,j,k,l=b[1][1];if(!dust.pragmas[l])return"";for(c=b[4],d={},j=1,k=c.length;k>j;j++)h=c[j],d[h[1][1]]=h[2];for(e=b[3],f={},j=1,k=e.length;k>j;j++)i=e[j],f[i[1][1]]=i[2][1];return g=b[2][1]?b[2][1].text:null,dust.pragmas[l](a,g,d,f)},partial:function(a,b){return".partial("+dust.compileNode(a,b[1])+","+dust.compileNode(a,b[2])+","+dust.compileNode(a,b[3])+")"},context:function(a,b){return b[1]?"ctx.rebase("+dust.compileNode(a,b[1])+")":"ctx"},params:function(a,b){for(var c=[],d=1,e=b.length;e>d;d++)c.push(dust.compileNode(a,b[d]));return c.length?"{"+c.join(",")+"}":"null"},bodies:function(a,b){for(var c=[],d=1,e=b.length;e>d;d++)c.push(dust.compileNode(a,b[d]));return"{"+c.join(",")+"}"},param:function(a,b){return dust.compileNode(a,b[1])+":"+dust.compileNode(a,b[2])},filters:function(a,b){for(var c=[],d=1,e=b.length;e>d;d++){var f=b[d];c.push('"'+f+'"')}return'"'+a.auto+'"'+(c.length?",["+c.join(",")+"]":"")},key:function(a,b){return'ctx._get(false, ["'+b[1]+'"])'},path:function(a,b){for(var c=b[1],d=b[2],e=[],f=0,g=d.length;g>f;f++)dust.isArray(d[f])?e.push(dust.compileNode(a,d[f])):e.push('"'+d[f]+'"');return"ctx._get("+c+",["+e.join(",")+"])"},literal:function(a,b){return m(b[1])}};var m="undefined"==typeof JSON?function(a){return'"'+dust.escapeJs(a)+'"'}:JSON.stringify;return dust};"undefined"!=typeof exports?module.exports=dustCompiler:dustCompiler(getGlobal()),function(dust){var a=function(){function b(a){return'"'+a.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\x08/g,"\\b").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\f/g,"\\f").replace(/\r/g,"\\r").replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g,escape)+'"'}var c={parse:function(c,d){function e(a){var b={};for(var c in a)b[c]=a[c];return b}function f(a,b){for(var d=a.offset+b,e=a.offset;d>e;e++){var f=c.charAt(e);"\n"===f?(a.seenCR||a.line++,a.column=1,a.seenCR=!1):"\r"===f||"\u2028"===f||"\u2029"===f?(a.line++,a.column=1,a.seenCR=!0):(a.column++,a.seenCR=!1)}a.offset+=b}function g(a){Q.offset<S.offset||(Q.offset>S.offset&&(S=e(Q),T=[]),T.push(a))}function h(){var a,b,c;for(c=e(Q),a=[],b=i();null!==b;)a.push(b),b=i();return null!==a&&(a=function(a,b,c,d){return["body"].concat(d).concat([["line",b],["col",c]])}(c.offset,c.line,c.column,a)),null===a&&(Q=e(c)),a}function i(){var a;return a=G(),null===a&&(a=j(),null===a&&(a=q(),null===a&&(a=s(),null===a&&(a=p(),null===a&&(a=D()))))),a}function j(){var a,b,d,i,j,m,n,p,q;if(R++,p=e(Q),q=e(Q),a=k(),null!==a){for(b=[],d=N();null!==d;)b.push(d),d=N();null!==b?(d=J(),null!==d?(i=h(),null!==i?(j=o(),null!==j?(m=l(),m=null!==m?m:"",null!==m?(n=function(a,b,c,d,e,f,g){if(!g||d[1].text!==g.text)throw new Error("Expected end tag for "+d[1].text+" but it was not found. At line : "+b+", column : "+c);return!0}(Q.offset,Q.line,Q.column,a,i,j,m)?"":null,null!==n?a=[a,b,d,i,j,m,n]:(a=null,Q=e(q))):(a=null,Q=e(q))):(a=null,Q=e(q))):(a=null,Q=e(q))):(a=null,Q=e(q))):(a=null,Q=e(q))}else a=null,Q=e(q);if(null!==a&&(a=function(a,b,c,d,e,f){return f.push(["param",["literal","block"],e]),d.push(f),d.concat([["line",b],["col",c]])}(p.offset,p.line,p.column,a[0],a[3],a[4],a[5])),null===a&&(Q=e(p)),null===a){if(p=e(Q),q=e(Q),a=k(),null!==a){for(b=[],d=N();null!==d;)b.push(d),d=N();null!==b?(47===c.charCodeAt(Q.offset)?(d="/",f(Q,1)):(d=null,0===R&&g('"/"')),null!==d?(i=J(),null!==i?a=[a,b,d,i]:(a=null,Q=e(q))):(a=null,Q=e(q))):(a=null,Q=e(q))}else a=null,Q=e(q);null!==a&&(a=function(a,b,c,d){return d.push(["bodies"]),d.concat([["line",b],["col",c]])}(p.offset,p.line,p.column,a[0])),null===a&&(Q=e(p))}return R--,0===R&&null===a&&g("section"),a}function k(){var a,b,d,h,i,j,k,l;if(k=e(Q),l=e(Q),a=I(),null!==a)if(/^[#?^<+@%]/.test(c.charAt(Q.offset))?(b=c.charAt(Q.offset),f(Q,1)):(b=null,0===R&&g("[#?^<+@%]")),null!==b){for(d=[],h=N();null!==h;)d.push(h),h=N();null!==d?(h=t(),null!==h?(i=m(),null!==i?(j=n(),null!==j?a=[a,b,d,h,i,j]:(a=null,Q=e(l))):(a=null,Q=e(l))):(a=null,Q=e(l))):(a=null,Q=e(l))}else a=null,Q=e(l);else a=null,Q=e(l);return null!==a&&(a=function(a,b,c,d,e,f,g){return[d,e,f,g]}(k.offset,k.line,k.column,a[1],a[3],a[4],a[5])),null===a&&(Q=e(k)),a}function l(){var a,b,d,h,i,j,k,l;if(R++,k=e(Q),l=e(Q),a=I(),null!==a)if(47===c.charCodeAt(Q.offset)?(b="/",f(Q,1)):(b=null,0===R&&g('"/"')),null!==b){for(d=[],h=N();null!==h;)d.push(h),h=N();if(null!==d)if(h=t(),null!==h){for(i=[],j=N();null!==j;)i.push(j),j=N();null!==i?(j=J(),null!==j?a=[a,b,d,h,i,j]:(a=null,Q=e(l))):(a=null,Q=e(l))}else a=null,Q=e(l);else a=null,Q=e(l)}else a=null,Q=e(l);else a=null,Q=e(l);return null!==a&&(a=function(a,b,c,d){return d}(k.offset,k.line,k.column,a[3])),null===a&&(Q=e(k)),R--,0===R&&null===a&&g("end tag"),a}function m(){var a,b,d,h,i;return d=e(Q),h=e(Q),i=e(Q),58===c.charCodeAt(Q.offset)?(a=":",f(Q,1)):(a=null,0===R&&g('":"')),null!==a?(b=t(),null!==b?a=[a,b]:(a=null,Q=e(i))):(a=null,Q=e(i)),null!==a&&(a=function(a,b,c,d){return d}(h.offset,h.line,h.column,a[1])),null===a&&(Q=e(h)),a=null!==a?a:"",null!==a&&(a=function(a,b,c,d){return d?["context",d]:["context"]}(d.offset,d.line,d.column,a)),null===a&&(Q=e(d)),a}function n(){var a,b,d,h,i,j,k,l;if(R++,j=e(Q),a=[],k=e(Q),l=e(Q),d=N(),null!==d)for(b=[];null!==d;)b.push(d),d=N();else b=null;for(null!==b?(d=y(),null!==d?(61===c.charCodeAt(Q.offset)?(h="=",f(Q,1)):(h=null,0===R&&g('"="')),null!==h?(i=u(),null===i&&(i=t(),null===i&&(i=B())),null!==i?b=[b,d,h,i]:(b=null,Q=e(l))):(b=null,Q=e(l))):(b=null,Q=e(l))):(b=null,Q=e(l)),null!==b&&(b=function(a,b,c,d,e){return["param",["literal",d],e]}(k.offset,k.line,k.column,b[1],b[3])),null===b&&(Q=e(k));null!==b;){if(a.push(b),k=e(Q),l=e(Q),d=N(),null!==d)for(b=[];null!==d;)b.push(d),d=N();else b=null;null!==b?(d=y(),null!==d?(61===c.charCodeAt(Q.offset)?(h="=",f(Q,1)):(h=null,0===R&&g('"="')),null!==h?(i=u(),null===i&&(i=t(),null===i&&(i=B())),null!==i?b=[b,d,h,i]:(b=null,Q=e(l))):(b=null,Q=e(l))):(b=null,Q=e(l))):(b=null,Q=e(l)),null!==b&&(b=function(a,b,c,d,e){return["param",["literal",d],e]}(k.offset,k.line,k.column,b[1],b[3])),null===b&&(Q=e(k))}return null!==a&&(a=function(a,b,c,d){return["params"].concat(d)}(j.offset,j.line,j.column,a)),null===a&&(Q=e(j)),R--,0===R&&null===a&&g("params"),a}function o(){var a,b,d,i,j,k,l,m,n;for(R++,l=e(Q),a=[],m=e(Q),n=e(Q),b=I(),null!==b?(58===c.charCodeAt(Q.offset)?(d=":",f(Q,1)):(d=null,0===R&&g('":"')),null!==d?(i=y(),null!==i?(j=J(),null!==j?(k=h(),null!==k?b=[b,d,i,j,k]:(b=null,Q=e(n))):(b=null,Q=e(n))):(b=null,Q=e(n))):(b=null,Q=e(n))):(b=null,Q=e(n)),null!==b&&(b=function(a,b,c,d,e){return["param",["literal",d],e]}(m.offset,m.line,m.column,b[2],b[4])),null===b&&(Q=e(m));null!==b;)a.push(b),m=e(Q),n=e(Q),b=I(),null!==b?(58===c.charCodeAt(Q.offset)?(d=":",f(Q,1)):(d=null,0===R&&g('":"')),null!==d?(i=y(),null!==i?(j=J(),null!==j?(k=h(),null!==k?b=[b,d,i,j,k]:(b=null,Q=e(n))):(b=null,Q=e(n))):(b=null,Q=e(n))):(b=null,Q=e(n))):(b=null,Q=e(n)),null!==b&&(b=function(a,b,c,d,e){return["param",["literal",d],e]}(m.offset,m.line,m.column,b[2],b[4])),null===b&&(Q=e(m));return null!==a&&(a=function(a,b,c,d){return["bodies"].concat(d)}(l.offset,l.line,l.column,a)),null===a&&(Q=e(l)),R--,0===R&&null===a&&g("bodies"),a}function p(){var a,b,c,d,f,h;return R++,f=e(Q),h=e(Q),a=I(),null!==a?(b=t(),null!==b?(c=r(),null!==c?(d=J(),null!==d?a=[a,b,c,d]:(a=null,Q=e(h))):(a=null,Q=e(h))):(a=null,Q=e(h))):(a=null,Q=e(h)),null!==a&&(a=function(a,b,c,d,e){return["reference",d,e].concat([["line",b],["col",c]])}(f.offset,f.line,f.column,a[1],a[2])),null===a&&(Q=e(f)),R--,0===R&&null===a&&g("reference"),a}function q(){var a,b,d,h,i,j,k,l,o,p,q,r;if(R++,p=e(Q),q=e(Q),a=I(),null!==a)if(62===c.charCodeAt(Q.offset)?(b=">",f(Q,1)):(b=null,0===R&&g('">"')),null===b&&(43===c.charCodeAt(Q.offset)?(b="+",f(Q,1)):(b=null,0===R&&g('"+"'))),null!==b){for(d=[],h=N();null!==h;)d.push(h),h=N();if(null!==d)if(r=e(Q),h=y(),null!==h&&(h=function(a,b,c,d){return["literal",d]}(r.offset,r.line,r.column,h)),null===h&&(Q=e(r)),null===h&&(h=B()),null!==h)if(i=m(),null!==i)if(j=n(),null!==j){for(k=[],l=N();null!==l;)k.push(l),l=N();null!==k?(47===c.charCodeAt(Q.offset)?(l="/",f(Q,1)):(l=null,0===R&&g('"/"')),null!==l?(o=J(),null!==o?a=[a,b,d,h,i,j,k,l,o]:(a=null,Q=e(q))):(a=null,Q=e(q))):(a=null,Q=e(q))}else a=null,Q=e(q);else a=null,Q=e(q);else a=null,Q=e(q);else a=null,Q=e(q)}else a=null,Q=e(q);else a=null,Q=e(q);return null!==a&&(a=function(a,b,c,d,e,f,g){var h=">"===d?"partial":d;return[h,e,f,g].concat([["line",b],["col",c]])}(p.offset,p.line,p.column,a[1],a[3],a[4],a[5])),null===a&&(Q=e(p)),R--,0===R&&null===a&&g("partial"),a}function r(){var a,b,d,h,i,j;for(R++,h=e(Q),a=[],i=e(Q),j=e(Q),124===c.charCodeAt(Q.offset)?(b="|",f(Q,1)):(b=null,0===R&&g('"|"')),null!==b?(d=y(),null!==d?b=[b,d]:(b=null,Q=e(j))):(b=null,Q=e(j)),null!==b&&(b=function(a,b,c,d){return d}(i.offset,i.line,i.column,b[1])),null===b&&(Q=e(i));null!==b;)a.push(b),i=e(Q),j=e(Q),124===c.charCodeAt(Q.offset)?(b="|",f(Q,1)):(b=null,0===R&&g('"|"')),null!==b?(d=y(),null!==d?b=[b,d]:(b=null,Q=e(j))):(b=null,Q=e(j)),null!==b&&(b=function(a,b,c,d){return d}(i.offset,i.line,i.column,b[1])),null===b&&(Q=e(i));return null!==a&&(a=function(a,b,c,d){return["filters"].concat(d)}(h.offset,h.line,h.column,a)),null===a&&(Q=e(h)),R--,0===R&&null===a&&g("filters"),a}function s(){var a,b,d,h,i,j;return R++,i=e(Q),j=e(Q),a=I(),null!==a?(126===c.charCodeAt(Q.offset)?(b="~",f(Q,1)):(b=null,0===R&&g('"~"')),null!==b?(d=y(),null!==d?(h=J(),null!==h?a=[a,b,d,h]:(a=null,Q=e(j))):(a=null,Q=e(j))):(a=null,Q=e(j))):(a=null,Q=e(j)),null!==a&&(a=function(a,b,c,d){return["special",d].concat([["line",b],["col",c]])}(i.offset,i.line,i.column,a[2])),null===a&&(Q=e(i)),R--,0===R&&null===a&&g("special"),a}function t(){var a,b;return R++,b=e(Q),a=x(),null!==a&&(a=function(a,b,c,d){var e=["path"].concat(d);return e.text=d[1].join("."),e}(b.offset,b.line,b.column,a)),null===a&&(Q=e(b)),null===a&&(b=e(Q),a=y(),null!==a&&(a=function(a,b,c,d){var e=["key",d];return e.text=d,e}(b.offset,b.line,b.column,a)),null===a&&(Q=e(b))),R--,0===R&&null===a&&g("identifier"),a}function u(){var a,b;return R++,b=e(Q),a=v(),null===a&&(a=w()),null!==a&&(a=function(a,b,c,d){return["literal",d]}(b.offset,b.line,b.column,a)),null===a&&(Q=e(b)),R--,0===R&&null===a&&g("number"),a}function v(){var a,b,d,h,i,j;if(R++,i=e(Q),j=e(Q),a=w(),null!==a)if(46===c.charCodeAt(Q.offset)?(b=".",f(Q,1)):(b=null,0===R&&g('"."')),null!==b){if(h=w(),null!==h)for(d=[];null!==h;)d.push(h),h=w();else d=null;null!==d?a=[a,b,d]:(a=null,Q=e(j))}else a=null,Q=e(j);else a=null,Q=e(j);return null!==a&&(a=function(a,b,c,d,e){return parseFloat(d+"."+e.join(""))}(i.offset,i.line,i.column,a[0],a[2])),null===a&&(Q=e(i)),R--,0===R&&null===a&&g("float"),a}function w(){var a,b,d;if(R++,d=e(Q),/^[0-9]/.test(c.charAt(Q.offset))?(b=c.charAt(Q.offset),f(Q,1)):(b=null,0===R&&g("[0-9]")),null!==b)for(a=[];null!==b;)a.push(b),/^[0-9]/.test(c.charAt(Q.offset))?(b=c.charAt(Q.offset),f(Q,1)):(b=null,0===R&&g("[0-9]"));else a=null;return null!==a&&(a=function(a,b,c,d){return parseInt(d.join(""),10)}(d.offset,d.line,d.column,a)),null===a&&(Q=e(d)),R--,0===R&&null===a&&g("integer"),a}function x(){var a,b,d,h,i;if(R++,h=e(Q),i=e(Q),a=y(),a=null!==a?a:"",null!==a){if(d=A(),null===d&&(d=z()),null!==d)for(b=[];null!==d;)b.push(d),d=A(),null===d&&(d=z());else b=null;null!==b?a=[a,b]:(a=null,Q=e(i))}else a=null,Q=e(i);if(null!==a&&(a=function(a,b,c,d,e){return e=e[0],d&&e?(e.unshift(d),[!1,e].concat([["line",b],["col",c]])):[!0,e].concat([["line",b],["col",c]])}(h.offset,h.line,h.column,a[0],a[1])),null===a&&(Q=e(h)),null===a){if(h=e(Q),i=e(Q),46===c.charCodeAt(Q.offset)?(a=".",f(Q,1)):(a=null,0===R&&g('"."')),null!==a){for(b=[],d=A(),null===d&&(d=z());null!==d;)b.push(d),d=A(),null===d&&(d=z());null!==b?a=[a,b]:(a=null,Q=e(i))}else a=null,Q=e(i);null!==a&&(a=function(a,b,c,d){return d.length>0?[!0,d[0]].concat([["line",b],["col",c]]):[!0,[]].concat([["line",b],["col",c]])}(h.offset,h.line,h.column,a[1])),null===a&&(Q=e(h))}return R--,0===R&&null===a&&g("path"),a}function y(){var a,b,d,h,i;if(R++,h=e(Q),i=e(Q),/^[a-zA-Z_$]/.test(c.charAt(Q.offset))?(a=c.charAt(Q.offset),f(Q,1)):(a=null,0===R&&g("[a-zA-Z_$]")),null!==a){for(b=[],/^[0-9a-zA-Z_$\-]/.test(c.charAt(Q.offset))?(d=c.charAt(Q.offset),f(Q,1)):(d=null,0===R&&g("[0-9a-zA-Z_$\\-]"));null!==d;)b.push(d),/^[0-9a-zA-Z_$\-]/.test(c.charAt(Q.offset))?(d=c.charAt(Q.offset),f(Q,1)):(d=null,0===R&&g("[0-9a-zA-Z_$\\-]"));null!==b?a=[a,b]:(a=null,Q=e(i))}else a=null,Q=e(i);return null!==a&&(a=function(a,b,c,d,e){return d+e.join("")}(h.offset,h.line,h.column,a[0],a[1])),null===a&&(Q=e(h)),R--,0===R&&null===a&&g("key"),a}function z(){var a,b,d,h,i,j,k,l;if(R++,h=e(Q),i=e(Q),j=e(Q),k=e(Q),a=K(),null!==a){if(l=e(Q),/^[0-9]/.test(c.charAt(Q.offset))?(d=c.charAt(Q.offset),f(Q,1)):(d=null,0===R&&g("[0-9]")),null!==d)for(b=[];null!==d;)b.push(d),/^[0-9]/.test(c.charAt(Q.offset))?(d=c.charAt(Q.offset),f(Q,1)):(d=null,0===R&&g("[0-9]"));else b=null;null!==b&&(b=function(a,b,c,d){return d.join("")}(l.offset,l.line,l.column,b)),null===b&&(Q=e(l)),null===b&&(b=t()),null!==b?(d=L(),null!==d?a=[a,b,d]:(a=null,Q=e(k))):(a=null,Q=e(k))}else a=null,Q=e(k);return null!==a&&(a=function(a,b,c,d){return d}(j.offset,j.line,j.column,a[1])),null===a&&(Q=e(j)),null!==a?(b=A(),b=null!==b?b:"",null!==b?a=[a,b]:(a=null,Q=e(i))):(a=null,Q=e(i)),null!==a&&(a=function(a,b,c,d,e){return e?e.unshift(d):e=[d],e}(h.offset,h.line,h.column,a[0],a[1])),null===a&&(Q=e(h)),R--,0===R&&null===a&&g("array"),a}function A(){var a,b,d,h,i,j,k;if(R++,h=e(Q),i=e(Q),j=e(Q),k=e(Q),46===c.charCodeAt(Q.offset)?(b=".",f(Q,1)):(b=null,0===R&&g('"."')),null!==b?(d=y(),null!==d?b=[b,d]:(b=null,Q=e(k))):(b=null,Q=e(k)),null!==b&&(b=function(a,b,c,d){return d}(j.offset,j.line,j.column,b[1])),null===b&&(Q=e(j)),null!==b)for(a=[];null!==b;)a.push(b),j=e(Q),k=e(Q),46===c.charCodeAt(Q.offset)?(b=".",f(Q,1)):(b=null,0===R&&g('"."')),null!==b?(d=y(),null!==d?b=[b,d]:(b=null,Q=e(k))):(b=null,Q=e(k)),null!==b&&(b=function(a,b,c,d){return d}(j.offset,j.line,j.column,b[1])),null===b&&(Q=e(j));else a=null;return null!==a?(b=z(),b=null!==b?b:"",null!==b?a=[a,b]:(a=null,Q=e(i))):(a=null,Q=e(i)),null!==a&&(a=function(a,b,c,d,e){return e?d.concat(e):d}(h.offset,h.line,h.column,a[0],a[1])),null===a&&(Q=e(h)),R--,0===R&&null===a&&g("array_part"),a}function B(){var a,b,d,h,i;if(R++,h=e(Q),i=e(Q),34===c.charCodeAt(Q.offset)?(a='"',f(Q,1)):(a=null,0===R&&g('"\\""')),null!==a?(34===c.charCodeAt(Q.offset)?(b='"',f(Q,1)):(b=null,0===R&&g('"\\""')),null!==b?a=[a,b]:(a=null,Q=e(i))):(a=null,Q=e(i)),null!==a&&(a=function(a,b,c){return["literal",""].concat([["line",b],["col",c]])}(h.offset,h.line,h.column)),null===a&&(Q=e(h)),null===a&&(h=e(Q),i=e(Q),34===c.charCodeAt(Q.offset)?(a='"',f(Q,1)):(a=null,0===R&&g('"\\""')),null!==a?(b=E(),null!==b?(34===c.charCodeAt(Q.offset)?(d='"',f(Q,1)):(d=null,0===R&&g('"\\""')),null!==d?a=[a,b,d]:(a=null,Q=e(i))):(a=null,Q=e(i))):(a=null,Q=e(i)),null!==a&&(a=function(a,b,c,d){return["literal",d].concat([["line",b],["col",c]])}(h.offset,h.line,h.column,a[1])),null===a&&(Q=e(h)),null===a)){if(h=e(Q),i=e(Q),34===c.charCodeAt(Q.offset)?(a='"',f(Q,1)):(a=null,0===R&&g('"\\""')),null!==a){if(d=C(),null!==d)for(b=[];null!==d;)b.push(d),d=C();else b=null;null!==b?(34===c.charCodeAt(Q.offset)?(d='"',f(Q,1)):(d=null,0===R&&g('"\\""')),null!==d?a=[a,b,d]:(a=null,Q=e(i))):(a=null,Q=e(i))}else a=null,Q=e(i);null!==a&&(a=function(a,b,c,d){return["body"].concat(d).concat([["line",b],["col",c]])}(h.offset,h.line,h.column,a[1])),null===a&&(Q=e(h))}return R--,0===R&&null===a&&g("inline"),a}function C(){var a,b;return a=s(),null===a&&(a=p(),null===a&&(b=e(Q),a=E(),null!==a&&(a=function(a,b,c,d){return["buffer",d]}(b.offset,b.line,b.column,a)),null===a&&(Q=e(b)))),a}function D(){var a,b,d,h,i,j,k,l,m;if(R++,j=e(Q),k=e(Q),a=M(),null!==a){for(b=[],d=N();null!==d;)b.push(d),d=N();null!==b?a=[a,b]:(a=null,Q=e(k))}else a=null,Q=e(k);if(null!==a&&(a=function(a,b,c,d,e){return["format",d,e.join("")].concat([["line",b],["col",c]])}(j.offset,j.line,j.column,a[0],a[1])),null===a&&(Q=e(j)),null===a){if(j=e(Q),k=e(Q),l=e(Q),m=e(Q),R++,b=H(),R--,null===b?b="":(b=null,Q=e(m)),null!==b?(m=e(Q),R++,d=G(),R--,null===d?d="":(d=null,Q=e(m)),null!==d?(m=e(Q),R++,h=M(),R--,null===h?h="":(h=null,Q=e(m)),null!==h?(c.length>Q.offset?(i=c.charAt(Q.offset),f(Q,1)):(i=null,0===R&&g("any character")),null!==i?b=[b,d,h,i]:(b=null,Q=e(l))):(b=null,Q=e(l))):(b=null,Q=e(l))):(b=null,Q=e(l)),null!==b&&(b=function(a,b,c,d){return d}(k.offset,k.line,k.column,b[3])),null===b&&(Q=e(k)),null!==b)for(a=[];null!==b;)a.push(b),k=e(Q),l=e(Q),m=e(Q),R++,b=H(),R--,null===b?b="":(b=null,Q=e(m)),null!==b?(m=e(Q),R++,d=G(),R--,null===d?d="":(d=null,Q=e(m)),null!==d?(m=e(Q),R++,h=M(),R--,null===h?h="":(h=null,Q=e(m)),null!==h?(c.length>Q.offset?(i=c.charAt(Q.offset),f(Q,1)):(i=null,0===R&&g("any character")),null!==i?b=[b,d,h,i]:(b=null,Q=e(l))):(b=null,Q=e(l))):(b=null,Q=e(l))):(b=null,Q=e(l)),null!==b&&(b=function(a,b,c,d){return d}(k.offset,k.line,k.column,b[3])),null===b&&(Q=e(k));else a=null;null!==a&&(a=function(a,b,c,d){return["buffer",d.join("")].concat([["line",b],["col",c]])}(j.offset,j.line,j.column,a)),null===a&&(Q=e(j))}return R--,0===R&&null===a&&g("buffer"),a}function E(){var a,b,d,h,i,j,k;if(R++,h=e(Q),i=e(Q),j=e(Q),k=e(Q),R++,b=H(),R--,null===b?b="":(b=null,Q=e(k)),null!==b?(d=F(),null===d&&(/^[^"]/.test(c.charAt(Q.offset))?(d=c.charAt(Q.offset),f(Q,1)):(d=null,0===R&&g('[^"]'))),null!==d?b=[b,d]:(b=null,Q=e(j))):(b=null,Q=e(j)),null!==b&&(b=function(a,b,c,d){return d}(i.offset,i.line,i.column,b[1])),null===b&&(Q=e(i)),null!==b)for(a=[];null!==b;)a.push(b),i=e(Q),j=e(Q),k=e(Q),R++,b=H(),R--,null===b?b="":(b=null,Q=e(k)),null!==b?(d=F(),null===d&&(/^[^"]/.test(c.charAt(Q.offset))?(d=c.charAt(Q.offset),f(Q,1)):(d=null,0===R&&g('[^"]'))),null!==d?b=[b,d]:(b=null,Q=e(j))):(b=null,Q=e(j)),null!==b&&(b=function(a,b,c,d){return d}(i.offset,i.line,i.column,b[1])),null===b&&(Q=e(i));else a=null;return null!==a&&(a=function(a,b,c,d){return d.join("")}(h.offset,h.line,h.column,a)),null===a&&(Q=e(h)),R--,0===R&&null===a&&g("literal"),a}function F(){var a,b;return b=e(Q),'\\"'===c.substr(Q.offset,2)?(a='\\"',f(Q,2)):(a=null,0===R&&g('"\\\\\\""')),null!==a&&(a=function(){return'"'}(b.offset,b.line,b.column)),null===a&&(Q=e(b)),a}function G(){var a,b,d,h,i,j,k,l,m;if(R++,i=e(Q),j=e(Q),"{!"===c.substr(Q.offset,2)?(a="{!",f(Q,2)):(a=null,0===R&&g('"{!"')),null!==a){for(b=[],k=e(Q),l=e(Q),m=e(Q),R++,"!}"===c.substr(Q.offset,2)?(d="!}",f(Q,2)):(d=null,0===R&&g('"!}"')),R--,null===d?d="":(d=null,Q=e(m)),null!==d?(c.length>Q.offset?(h=c.charAt(Q.offset),f(Q,1)):(h=null,0===R&&g("any character")),null!==h?d=[d,h]:(d=null,Q=e(l))):(d=null,Q=e(l)),null!==d&&(d=function(a,b,c,d){return d
+}(k.offset,k.line,k.column,d[1])),null===d&&(Q=e(k));null!==d;)b.push(d),k=e(Q),l=e(Q),m=e(Q),R++,"!}"===c.substr(Q.offset,2)?(d="!}",f(Q,2)):(d=null,0===R&&g('"!}"')),R--,null===d?d="":(d=null,Q=e(m)),null!==d?(c.length>Q.offset?(h=c.charAt(Q.offset),f(Q,1)):(h=null,0===R&&g("any character")),null!==h?d=[d,h]:(d=null,Q=e(l))):(d=null,Q=e(l)),null!==d&&(d=function(a,b,c,d){return d}(k.offset,k.line,k.column,d[1])),null===d&&(Q=e(k));null!==b?("!}"===c.substr(Q.offset,2)?(d="!}",f(Q,2)):(d=null,0===R&&g('"!}"')),null!==d?a=[a,b,d]:(a=null,Q=e(j))):(a=null,Q=e(j))}else a=null,Q=e(j);return null!==a&&(a=function(a,b,c,d){return["comment",d.join("")].concat([["line",b],["col",c]])}(i.offset,i.line,i.column,a[1])),null===a&&(Q=e(i)),R--,0===R&&null===a&&g("comment"),a}function H(){var a,b,d,h,i,j,k,l,m,n,o;if(m=e(Q),a=I(),null!==a){for(b=[],d=N();null!==d;)b.push(d),d=N();if(null!==b)if(/^[#?^><+%:@\/~%]/.test(c.charAt(Q.offset))?(d=c.charAt(Q.offset),f(Q,1)):(d=null,0===R&&g("[#?^><+%:@\\/~%]")),null!==d){for(h=[],i=N();null!==i;)h.push(i),i=N();if(null!==h){if(n=e(Q),o=e(Q),R++,j=J(),R--,null===j?j="":(j=null,Q=e(o)),null!==j?(o=e(Q),R++,k=M(),R--,null===k?k="":(k=null,Q=e(o)),null!==k?(c.length>Q.offset?(l=c.charAt(Q.offset),f(Q,1)):(l=null,0===R&&g("any character")),null!==l?j=[j,k,l]:(j=null,Q=e(n))):(j=null,Q=e(n))):(j=null,Q=e(n)),null!==j)for(i=[];null!==j;)i.push(j),n=e(Q),o=e(Q),R++,j=J(),R--,null===j?j="":(j=null,Q=e(o)),null!==j?(o=e(Q),R++,k=M(),R--,null===k?k="":(k=null,Q=e(o)),null!==k?(c.length>Q.offset?(l=c.charAt(Q.offset),f(Q,1)):(l=null,0===R&&g("any character")),null!==l?j=[j,k,l]:(j=null,Q=e(n))):(j=null,Q=e(n))):(j=null,Q=e(n));else i=null;if(null!==i){for(j=[],k=N();null!==k;)j.push(k),k=N();null!==j?(k=J(),null!==k?a=[a,b,d,h,i,j,k]:(a=null,Q=e(m))):(a=null,Q=e(m))}else a=null,Q=e(m)}else a=null,Q=e(m)}else a=null,Q=e(m);else a=null,Q=e(m)}else a=null,Q=e(m);return null===a&&(a=p()),a}function I(){var a;return 123===c.charCodeAt(Q.offset)?(a="{",f(Q,1)):(a=null,0===R&&g('"{"')),a}function J(){var a;return 125===c.charCodeAt(Q.offset)?(a="}",f(Q,1)):(a=null,0===R&&g('"}"')),a}function K(){var a;return 91===c.charCodeAt(Q.offset)?(a="[",f(Q,1)):(a=null,0===R&&g('"["')),a}function L(){var a;return 93===c.charCodeAt(Q.offset)?(a="]",f(Q,1)):(a=null,0===R&&g('"]"')),a}function M(){var a;return 10===c.charCodeAt(Q.offset)?(a="\n",f(Q,1)):(a=null,0===R&&g('"\\n"')),null===a&&("\r\n"===c.substr(Q.offset,2)?(a="\r\n",f(Q,2)):(a=null,0===R&&g('"\\r\\n"')),null===a&&(13===c.charCodeAt(Q.offset)?(a="\r",f(Q,1)):(a=null,0===R&&g('"\\r"')),null===a&&(8232===c.charCodeAt(Q.offset)?(a="\u2028",f(Q,1)):(a=null,0===R&&g('"\\u2028"')),null===a&&(8233===c.charCodeAt(Q.offset)?(a="\u2029",f(Q,1)):(a=null,0===R&&g('"\\u2029"')))))),a}function N(){var a;return/^[\t\x0B\f \xA0\uFEFF]/.test(c.charAt(Q.offset))?(a=c.charAt(Q.offset),f(Q,1)):(a=null,0===R&&g("[\\t\\x0B\\f \\xA0\\uFEFF]")),null===a&&(a=M()),a}function O(a){a.sort();for(var b=null,c=[],d=0;d<a.length;d++)a[d]!==b&&(c.push(a[d]),b=a[d]);return c}var P={body:h,part:i,section:j,sec_tag_start:k,end_tag:l,context:m,params:n,bodies:o,reference:p,partial:q,filters:r,special:s,identifier:t,number:u,"float":v,integer:w,path:x,key:y,array:z,array_part:A,inline:B,inline_part:C,buffer:D,literal:E,esc:F,comment:G,tag:H,ld:I,rd:J,lb:K,rb:L,eol:M,ws:N};if(void 0!==d){if(void 0===P[d])throw new Error("Invalid rule name: "+b(d)+".")}else d="body";var Q={offset:0,line:1,column:1,seenCR:!1},R=0,S={offset:0,line:1,column:1,seenCR:!1},T=[],U=P[d]();if(null===U||Q.offset!==c.length){var V=Math.max(Q.offset,S.offset),W=V<c.length?c.charAt(V):null,X=Q.offset>S.offset?Q:S;throw new a.SyntaxError(O(T),W,V,X.line,X.column)}return U},toSource:function(){return this._source}};return c.SyntaxError=function(a,c,d,e,f){function g(a,c){var d,e;switch(a.length){case 0:d="end of input";break;case 1:d=a[0];break;default:d=a.slice(0,a.length-1).join(", ")+" or "+a[a.length-1]}return e=c?b(c):"end of input","Expected "+d+" but "+e+" found."}this.name="SyntaxError",this.expected=a,this.found=c,this.message=g(a,c),this.offset=d,this.line=e,this.column=f},c.SyntaxError.prototype=Error.prototype,c}();dust.parse=a.parse}("undefined"!=typeof exports?exports:getGlobal());
\ No newline at end of file