azkaban-aplcache
Changes
build.xml 54(+22 -32)
src/java/azkaban/executor/ExecutorManager.java 47(+36 -11)
src/java/azkaban/migration/scheduler/Schedule.java 363(+363 -0)
src/java/azkaban/migration/sla/SLA.java 252(+252 -0)
src/java/azkaban/project/ProjectManager.java 10(+10 -0)
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/css/morris.css 25(+25 -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/util/svgutils.js 0(+0 -0)
src/web/js/azkaban/view/exflow.js 129(+84 -45)
src/web/js/azkaban/view/flow.js 44(+38 -6)
src/web/js/azkaban/view/flow-execute.js 36(+23 -13)
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 39(+39 -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 7(+3 -4)
src/web/js/azkaban/view/time-graph.js 99(+99 -0)
src/web/js/azkaban/view/triggers.js 0(+0 -0)
src/web/js/dust-full-2.2.3.min.js 5(+5 -0)
src/web/js/morris.min.js 1(+1 -0)
src/web/js/raphael.min.js 11(+11 -0)
Details
build.xml 54(+22 -32)
diff --git a/build.xml b/build.xml
index 9ccb34e..63152e5 100644
--- a/build.xml
+++ b/build.xml
@@ -42,52 +42,45 @@
<!-- 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="dust" description="Compile Less css files.">
<!-- 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>
+ <delete dir="${dist.dust.dir}" />
+ <mkdir dir="${dist.dust.dir}" />
+ <exec dir="${dust.src.dir}" executable="make" failonerror="true"/>
+ <copy todir="${dist.dust.dir}">
+ <fileset dir="${dust.src.dir}/obj" includes="*.js" />
+ </copy>
</target>
<target name="less" description="Compile Less css files.">
<!-- 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>
+ <delete dir="${dist.less.dir}" />
+ <mkdir dir="${dist.less.dir}" />
+ <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="build" description="Compile main source tree java files">
<delete dir="${dist.classes.dir}" />
<mkdir dir="${dist.classes.dir}" />
- <delete dir="${dist.dust.dir}" />
- <mkdir dir="${dist.dust.dir}" />
- <delete dir="${dist.less.dir}" />
- <mkdir dir="${dist.less.dir}" />
<!-- copy non-java files to classes dir to load from classpath -->
<copy todir="${dist.classes.dir}">
@@ -325,10 +318,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 408ef2e..cb763f5 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;
}
@@ -1098,15 +1114,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 b0eab38..37de61a 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);
@@ -847,13 +881,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 {
@@ -871,7 +920,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");
@@ -882,7 +932,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 491db7f..25f825b 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/jobExecutor/utils/process/AzkabanProcess.java b/src/java/azkaban/jobExecutor/utils/process/AzkabanProcess.java
index 54179fd..2e5de64 100644
--- a/src/java/azkaban/jobExecutor/utils/process/AzkabanProcess.java
+++ b/src/java/azkaban/jobExecutor/utils/process/AzkabanProcess.java
@@ -162,6 +162,13 @@ public class AzkabanProcess {
public void hardKill() {
checkStarted();
if (isRunning()) {
+ if (processId != 0 ) {
+ try {
+ Runtime.getRuntime().exec("kill -9 " + processId);
+ } catch (IOException e) {
+ logger.error("Kill attempt failed.", e);
+ }
+ }
process.destroy();
}
}
diff --git a/src/java/azkaban/migration/schedule2trigger/CommonParams.java b/src/java/azkaban/migration/schedule2trigger/CommonParams.java
new file mode 100644
index 0000000..0408f51
--- /dev/null
+++ b/src/java/azkaban/migration/schedule2trigger/CommonParams.java
@@ -0,0 +1,22 @@
+package azkaban.migration.schedule2trigger;
+
+public class CommonParams {
+ public static final String TYPE_FLOW_FINISH = "FlowFinish";
+ public static final String TYPE_FLOW_SUCCEED = "FlowSucceed";
+ public static final String TYPE_FLOW_PROGRESS = "FlowProgress";
+
+ public static final String TYPE_JOB_FINISH = "JobFinish";
+ public static final String TYPE_JOB_SUCCEED = "JobSucceed";
+ public static final String TYPE_JOB_PROGRESS = "JobProgress";
+
+ public static final String INFO_DURATION = "Duration";
+ public static final String INFO_FLOW_NAME = "FlowName";
+ public static final String INFO_JOB_NAME = "JobName";
+ public static final String INFO_PROGRESS_PERCENT = "ProgressPercent";
+ public static final String INFO_EMAIL_LIST = "EmailList";
+
+ // always alert
+ public static final String ALERT_TYPE = "SlaAlertType";
+ public static final String ACTION_CANCEL_FLOW = "SlaCancelFlow";
+ public static final String ACTION_ALERT = "SlaAlert";
+}
diff --git a/src/java/azkaban/migration/schedule2trigger/Schedule2Trigger.java b/src/java/azkaban/migration/schedule2trigger/Schedule2Trigger.java
new file mode 100644
index 0000000..6d1bd39
--- /dev/null
+++ b/src/java/azkaban/migration/schedule2trigger/Schedule2Trigger.java
@@ -0,0 +1,256 @@
+package azkaban.migration.schedule2trigger;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
+import azkaban.executor.ExecutionOptions;
+import static azkaban.migration.schedule2trigger.CommonParams.*;
+import azkaban.utils.JSONUtils;
+import azkaban.utils.Props;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.ReadablePeriod;
+import azkaban.trigger.Condition;
+import azkaban.trigger.ConditionChecker;
+import azkaban.trigger.JdbcTriggerLoader;
+import azkaban.trigger.Trigger;
+import azkaban.trigger.TriggerAction;
+import azkaban.trigger.TriggerLoader;
+import azkaban.trigger.builtin.BasicTimeChecker;
+import azkaban.trigger.builtin.ExecuteFlowAction;
+import azkaban.utils.Utils;
+
+public class Schedule2Trigger {
+
+ private static final Logger logger = Logger.getLogger(Schedule2Trigger.class);
+ private static Props props;
+ private static File outputDir;
+
+ public static void main(String[] args) throws Exception{
+ if(args.length < 1) {
+ printUsage();
+ }
+
+ File confFile = new File(args[0]);
+ try {
+ logger.info("Trying to load config from " + confFile.getAbsolutePath());
+ props = loadAzkabanConfig(confFile);
+ } catch (Exception e) {
+ e.printStackTrace();
+ logger.error(e);
+ return;
+ }
+
+ try {
+ outputDir = File.createTempFile("schedules", null);
+ logger.info("Creating temp dir for dumping existing schedules.");
+ outputDir.delete();
+ outputDir.mkdir();
+ } catch (Exception e) {
+ e.printStackTrace();
+ logger.error(e);
+ return;
+ }
+
+ try {
+ schedule2File();
+ } catch (Exception e) {
+ e.printStackTrace();
+ logger.error(e);
+ return;
+ }
+
+ try {
+ file2ScheduleTrigger();
+ } catch (Exception e) {
+ e.printStackTrace();
+ logger.error(e);
+ return;
+ }
+
+ logger.info("Uploaded all schedules. Removing temp dir.");
+ FileUtils.deleteDirectory(outputDir);
+ System.exit(0);
+ }
+
+ private static Props loadAzkabanConfig(File confFile) throws IOException {
+ return new Props(null, confFile);
+ }
+
+ private static void printUsage() {
+ System.out.println("Usage: schedule2Trigger PATH_TO_CONFIG_FILE");
+ }
+
+ private static void schedule2File() throws Exception {
+ azkaban.migration.scheduler.ScheduleLoader scheduleLoader = new azkaban.migration.scheduler.JdbcScheduleLoader(props);
+ logger.info("Loading old schedule info from DB.");
+ List<azkaban.migration.scheduler.Schedule> schedules = scheduleLoader.loadSchedules();
+ for(azkaban.migration.scheduler.Schedule sched : schedules) {
+ writeScheduleFile(sched, outputDir);
+ }
+ }
+
+ private static void writeScheduleFile(azkaban.migration.scheduler.Schedule sched, File outputDir) throws IOException {
+ String scheduleFileName = sched.getProjectName()+"-"+sched.getFlowName();
+ File outputFile = new File(outputDir, scheduleFileName);
+ outputFile.createNewFile();
+ Props props = new Props();
+ props.put("flowName", sched.getFlowName());
+ props.put("projectName", sched.getProjectName());
+ props.put("projectId", String.valueOf(sched.getProjectId()));
+ props.put("period", azkaban.migration.scheduler.Schedule.createPeriodString(sched.getPeriod()));
+ props.put("firstScheduleTimeLong", sched.getFirstSchedTime());
+ props.put("timezone", sched.getTimezone().getID());
+ props.put("submitUser", sched.getSubmitUser());
+ props.put("submitTimeLong", sched.getSubmitTime());
+ props.put("nextExecTimeLong", sched.getNextExecTime());
+
+ ExecutionOptions executionOptions = sched.getExecutionOptions();
+ if(executionOptions != null) {
+ props.put("executionOptionsObj", JSONUtils.toJSON(executionOptions.toObject()));
+ }
+
+ azkaban.migration.sla.SlaOptions slaOptions = sched.getSlaOptions();
+ if(slaOptions != null) {
+
+ List<Map<String, Object>> settingsObj = new ArrayList<Map<String,Object>>();
+ List<azkaban.migration.sla.SLA.SlaSetting> settings = slaOptions.getSettings();
+ for(azkaban.migration.sla.SLA.SlaSetting set : settings) {
+ Map<String, Object> setObj = new HashMap<String, Object>();
+ String setId = set.getId();
+ azkaban.migration.sla.SLA.SlaRule rule = set.getRule();
+ Map<String, Object> info = new HashMap<String, Object>();
+ info.put(INFO_DURATION, azkaban.migration.scheduler.Schedule.createPeriodString(set.getDuration()));
+ info.put(INFO_EMAIL_LIST, slaOptions.getSlaEmails());
+ List<String> actionsList = new ArrayList<String>();
+ for(azkaban.migration.sla.SLA.SlaAction act : set.getActions()) {
+ if(act.equals(azkaban.migration.sla.SLA.SlaAction.EMAIL)) {
+ actionsList.add(ACTION_ALERT);
+ info.put(ALERT_TYPE, "email");
+ } else if(act.equals(azkaban.migration.sla.SLA.SlaAction.KILL)) {
+ actionsList.add(ACTION_CANCEL_FLOW);
+ }
+ }
+ setObj.put("actions", actionsList);
+ if(setId.equals("")) {
+ info.put(INFO_FLOW_NAME, sched.getFlowName());
+ if(rule.equals(azkaban.migration.sla.SLA.SlaRule.FINISH)) {
+ setObj.put("type", TYPE_FLOW_FINISH);
+ } else if(rule.equals(azkaban.migration.sla.SLA.SlaRule.SUCCESS)) {
+ setObj.put("type", TYPE_FLOW_SUCCEED);
+ }
+ } else {
+ info.put(INFO_JOB_NAME, setId);
+ if(rule.equals(azkaban.migration.sla.SLA.SlaRule.FINISH)) {
+ setObj.put("type", TYPE_JOB_FINISH);
+ } else if(rule.equals(azkaban.migration.sla.SLA.SlaRule.SUCCESS)) {
+ setObj.put("type", TYPE_JOB_SUCCEED);
+ }
+ }
+ setObj.put("info", info);
+ settingsObj.add(setObj);
+ }
+
+ props.put("slaOptionsObj", JSONUtils.toJSON(settingsObj));
+ }
+ props.storeLocal(outputFile);
+ }
+
+ private static void file2ScheduleTrigger() throws Exception {
+
+ TriggerLoader triggerLoader = new JdbcTriggerLoader(props);
+ for(File scheduleFile : outputDir.listFiles()) {
+ logger.info("Trying to load schedule from " + scheduleFile.getAbsolutePath());
+ if(scheduleFile.isFile()) {
+ Props schedProps = new Props(null, scheduleFile);
+ String flowName = schedProps.getString("flowName");
+ String projectName = schedProps.getString("projectName");
+ int projectId = schedProps.getInt("projectId");
+ long firstSchedTimeLong = schedProps.getLong("firstScheduleTimeLong");
+// DateTime firstSchedTime = new DateTime(firstSchedTimeLong);
+ String timezoneId = schedProps.getString("timezone");
+ DateTimeZone timezone = DateTimeZone.forID(timezoneId);
+ ReadablePeriod period = Utils.parsePeriodString(schedProps.getString("period"));
+// DateTime lastModifyTime = DateTime.now();
+ long nextExecTimeLong = schedProps.getLong("nextExecTimeLong");
+// DateTime nextExecTime = new DateTime(nextExecTimeLong);
+ long submitTimeLong = schedProps.getLong("submitTimeLong");
+// DateTime submitTime = new DateTime(submitTimeLong);
+ String submitUser = schedProps.getString("submitUser");
+ ExecutionOptions executionOptions = null;
+ if(schedProps.containsKey("executionOptionsObj")) {
+ String executionOptionsObj = schedProps.getString("executionOptionsObj");
+ executionOptions = ExecutionOptions.createFromObject(JSONUtils.parseJSONFromString(executionOptionsObj));
+ } else {
+ executionOptions = new ExecutionOptions();
+ }
+ List<azkaban.sla.SlaOption> slaOptions = null;
+ if(schedProps.containsKey("slaOptionsObj")) {
+ slaOptions = new ArrayList<azkaban.sla.SlaOption>();
+ List<Map<String, Object>> settingsObj = (List<Map<String, Object>>) JSONUtils.parseJSONFromString(schedProps.getString("slaOptionsObj"));
+ for(Map<String, Object> sla : settingsObj) {
+ String type = (String) sla.get("type");
+ Map<String, Object> info = (Map<String, Object>) sla.get("info");
+ List<String> actions = (List<String>) sla.get("actions");
+ azkaban.sla.SlaOption slaOption = new azkaban.sla.SlaOption(type, actions, info);
+ slaOptions.add(slaOption);
+ }
+ }
+
+ azkaban.scheduler.Schedule schedule = new azkaban.scheduler.Schedule(-1, projectId, projectName, flowName, "ready", firstSchedTimeLong, timezone, period, DateTime.now().getMillis(), nextExecTimeLong, submitTimeLong, submitUser, executionOptions, slaOptions);
+ Trigger t = scheduleToTrigger(schedule);
+ logger.info("Ready to insert trigger " + t.getDescription());
+ triggerLoader.addTrigger(t);
+
+ }
+
+ }
+ }
+
+
+ private static Trigger scheduleToTrigger(azkaban.scheduler.Schedule s) {
+
+ Condition triggerCondition = createTimeTriggerCondition(s);
+ Condition expireCondition = createTimeExpireCondition(s);
+ List<TriggerAction> actions = createActions(s);
+ Trigger t = new Trigger(s.getScheduleId(), s.getLastModifyTime(), s.getSubmitTime(), s.getSubmitUser(), azkaban.scheduler.ScheduleManager.triggerSource, triggerCondition, expireCondition, actions);
+ if(s.isRecurring()) {
+ t.setResetOnTrigger(true);
+ }
+ return t;
+ }
+
+ private static List<TriggerAction> createActions (azkaban.scheduler.Schedule s) {
+ List<TriggerAction> actions = new ArrayList<TriggerAction>();
+ ExecuteFlowAction executeAct = new ExecuteFlowAction("executeFlowAction", s.getProjectId(), s.getProjectName(), s.getFlowName(), s.getSubmitUser(), s.getExecutionOptions(), s.getSlaOptions());
+ actions.add(executeAct);
+
+ return actions;
+ }
+
+ private static Condition createTimeTriggerCondition (azkaban.scheduler.Schedule s) {
+ Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
+ ConditionChecker checker = new BasicTimeChecker("BasicTimeChecker_1", s.getFirstSchedTime(), s.getTimezone(), s.isRecurring(), s.skipPastOccurrences(), s.getPeriod());
+ checkers.put(checker.getId(), checker);
+ String expr = checker.getId() + ".eval()";
+ Condition cond = new Condition(checkers, expr);
+ return cond;
+ }
+
+ // if failed to trigger, auto expire?
+ private static Condition createTimeExpireCondition (azkaban.scheduler.Schedule s) {
+ Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
+ ConditionChecker checker = new BasicTimeChecker("BasicTimeChecker_2", s.getFirstSchedTime(), s.getTimezone(), s.isRecurring(), s.skipPastOccurrences(), s.getPeriod());
+ checkers.put(checker.getId(), checker);
+ String expr = checker.getId() + ".eval()";
+ Condition cond = new Condition(checkers, expr);
+ return cond;
+ }
+
+}
diff --git a/src/java/azkaban/migration/scheduler/JdbcScheduleLoader.java b/src/java/azkaban/migration/scheduler/JdbcScheduleLoader.java
new file mode 100644
index 0000000..bcef168
--- /dev/null
+++ b/src/java/azkaban/migration/scheduler/JdbcScheduleLoader.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright 2012 LinkedIn, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.migration.scheduler;
+
+
+import azkaban.database.DataSourceUtils;
+import azkaban.utils.GZIPUtils;
+import azkaban.utils.JSONUtils;
+import azkaban.utils.Props;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import javax.sql.DataSource;
+import org.apache.commons.dbutils.DbUtils;
+import org.apache.commons.dbutils.QueryRunner;
+import org.apache.commons.dbutils.ResultSetHandler;
+import org.apache.log4j.Logger;
+import org.joda.time.DateTimeZone;
+import org.joda.time.ReadablePeriod;
+
+@Deprecated
+public class JdbcScheduleLoader implements ScheduleLoader {
+
+ private static Logger logger = Logger.getLogger(JdbcScheduleLoader.class);
+
+ public static enum EncodingType {
+ PLAIN(1), GZIP(2);
+
+ private int numVal;
+
+ EncodingType(int numVal) {
+ this.numVal = numVal;
+ }
+
+ public int getNumVal() {
+ return numVal;
+ }
+
+ public static EncodingType fromInteger(int x) {
+ switch (x) {
+ case 1:
+ return PLAIN;
+ case 2:
+ return GZIP;
+ default:
+ return PLAIN;
+ }
+ }
+ }
+
+ private DataSource dataSource;
+ private EncodingType defaultEncodingType = EncodingType.GZIP;
+
+ private static final String scheduleTableName = "schedules";
+
+ private static String SELECT_ALL_SCHEDULES =
+ "SELECT project_id, project_name, flow_name, status, first_sched_time, timezone, period, last_modify_time, next_exec_time, submit_time, submit_user, enc_type, schedule_options FROM " + scheduleTableName;
+
+ private static String INSERT_SCHEDULE =
+ "INSERT INTO " + scheduleTableName + " ( project_id, project_name, flow_name, status, first_sched_time, timezone, period, last_modify_time, next_exec_time, submit_time, submit_user, enc_type, schedule_options) values (?,?,?,?,?,?,?,?,?,?,?,?,?)";
+
+ private static String REMOVE_SCHEDULE_BY_KEY =
+ "DELETE FROM " + scheduleTableName + " WHERE project_id=? AND flow_name=?";
+
+ private static String UPDATE_SCHEDULE_BY_KEY =
+ "UPDATE " + scheduleTableName + " SET status=?, first_sched_time=?, timezone=?, period=?, last_modify_time=?, next_exec_time=?, submit_time=?, submit_user=?, enc_type=?, schedule_options=? WHERE project_id=? AND flow_name=?";
+
+ private static String UPDATE_NEXT_EXEC_TIME =
+ "UPDATE " + scheduleTableName + " SET next_exec_time=? WHERE project_id=? AND flow_name=?";
+
+ private Connection getConnection() throws ScheduleManagerException {
+ Connection connection = null;
+ try {
+ connection = dataSource.getConnection();
+ } catch (Exception e) {
+ DbUtils.closeQuietly(connection);
+ throw new ScheduleManagerException("Error getting DB connection.", e);
+ }
+
+ return connection;
+ }
+
+ public EncodingType getDefaultEncodingType() {
+ return defaultEncodingType;
+ }
+
+ public void setDefaultEncodingType(EncodingType defaultEncodingType) {
+ this.defaultEncodingType = defaultEncodingType;
+ }
+
+ public JdbcScheduleLoader(Props props) {
+ String databaseType = props.getString("database.type");
+
+ if (databaseType.equals("mysql")) {
+ int port = props.getInt("mysql.port");
+ String host = props.getString("mysql.host");
+ String database = props.getString("mysql.database");
+ String user = props.getString("mysql.user");
+ String password = props.getString("mysql.password");
+ int numConnections = props.getInt("mysql.numconnections");
+
+ dataSource = DataSourceUtils.getMySQLDataSource(host, port, database, user, password, numConnections);
+ }
+ }
+
+ @Override
+ public List<Schedule> loadSchedules() throws ScheduleManagerException {
+ logger.info("Loading all schedules from db.");
+ Connection connection = getConnection();
+
+ QueryRunner runner = new QueryRunner();
+ ResultSetHandler<List<Schedule>> handler = new ScheduleResultHandler();
+
+ List<Schedule> schedules;
+
+ try {
+ schedules = runner.query(connection, SELECT_ALL_SCHEDULES, handler);
+ } catch (SQLException e) {
+ logger.error(SELECT_ALL_SCHEDULES + " failed.");
+
+ DbUtils.closeQuietly(connection);
+ throw new ScheduleManagerException("Loading schedules from db failed. ", e);
+ } finally {
+ DbUtils.closeQuietly(connection);
+ }
+
+ logger.info("Now trying to update the schedules");
+
+ // filter the schedules
+ Iterator<Schedule> scheduleIterator = schedules.iterator();
+ while (scheduleIterator.hasNext()) {
+ Schedule sched = scheduleIterator.next();
+ if(!sched.updateTime()) {
+ logger.info("Schedule " + sched.getScheduleName() + " was scheduled before azkaban start, skipping it.");
+ scheduleIterator.remove();
+ removeSchedule(sched);
+ }
+ else {
+ logger.info("Recurring schedule, need to update next exec time");
+ try {
+ updateNextExecTime(sched);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new ScheduleManagerException("Update next execution time failed.", e);
+ }
+ logger.info("Schedule " + sched.getScheduleName() + " loaded and updated.");
+ }
+ }
+
+
+
+ logger.info("Loaded " + schedules.size() + " schedules.");
+
+ return schedules;
+ }
+
+ @Override
+ public void removeSchedule(Schedule s) throws ScheduleManagerException {
+ logger.info("Removing schedule " + s.getScheduleName() + " from db.");
+
+ QueryRunner runner = new QueryRunner(dataSource);
+
+ try {
+ int removes = runner.update(REMOVE_SCHEDULE_BY_KEY, s.getProjectId(), s.getFlowName());
+ if (removes == 0) {
+ throw new ScheduleManagerException("No schedule has been removed.");
+ }
+ } catch (SQLException e) {
+ logger.error(REMOVE_SCHEDULE_BY_KEY + " failed.");
+ throw new ScheduleManagerException("Remove schedule " + s.getScheduleName() + " from db failed. ", e);
+ }
+ }
+
+
+ public void insertSchedule(Schedule s) throws ScheduleManagerException {
+ logger.info("Inserting schedule " + s.getScheduleName() + " into db.");
+ insertSchedule(s, defaultEncodingType);
+ }
+
+ public void insertSchedule(Schedule s, EncodingType encType) throws ScheduleManagerException {
+
+ String json = JSONUtils.toJSON(s.optionsToObject());
+ byte[] data = null;
+ try {
+ byte[] stringData = json.getBytes("UTF-8");
+ data = stringData;
+
+ if (encType == EncodingType.GZIP) {
+ data = GZIPUtils.gzipBytes(stringData);
+ }
+ logger.debug("NumChars: " + json.length() + " UTF-8:" + stringData.length + " Gzip:"+ data.length);
+ }
+ catch (IOException e) {
+ throw new ScheduleManagerException("Error encoding the schedule options. " + s.getScheduleName());
+ }
+
+ QueryRunner runner = new QueryRunner(dataSource);
+ try {
+ int inserts = runner.update(
+ INSERT_SCHEDULE,
+ s.getProjectId(),
+ s.getProjectName(),
+ s.getFlowName(),
+ s.getStatus(),
+ s.getFirstSchedTime(),
+ s.getTimezone().getID(),
+ Schedule.createPeriodString(s.getPeriod()),
+ s.getLastModifyTime(),
+ s.getNextExecTime(),
+ s.getSubmitTime(),
+ s.getSubmitUser(),
+ encType.getNumVal(),
+ data);
+ if (inserts == 0) {
+ throw new ScheduleManagerException("No schedule has been inserted.");
+ }
+ } catch (SQLException e) {
+ logger.error(INSERT_SCHEDULE + " failed.");
+ throw new ScheduleManagerException("Insert schedule " + s.getScheduleName() + " into db failed. ", e);
+ }
+ }
+
+ @Override
+ public void updateNextExecTime(Schedule s) throws ScheduleManagerException
+ {
+ logger.info("Update schedule " + s.getScheduleName() + " into db. ");
+ Connection connection = getConnection();
+ QueryRunner runner = new QueryRunner();
+ try {
+
+ runner.update(connection, UPDATE_NEXT_EXEC_TIME, s.getNextExecTime(), s.getProjectId(), s.getFlowName());
+ } catch (SQLException e) {
+ e.printStackTrace();
+ logger.error(UPDATE_NEXT_EXEC_TIME + " failed.", e);
+ throw new ScheduleManagerException("Update schedule " + s.getScheduleName() + " into db failed. ", e);
+ } finally {
+ DbUtils.closeQuietly(connection);
+ }
+ }
+
+ @Override
+ public void updateSchedule(Schedule s) throws ScheduleManagerException {
+ logger.info("Updating schedule " + s.getScheduleName() + " into db.");
+ updateSchedule(s, defaultEncodingType);
+ }
+
+ public void updateSchedule(Schedule s, EncodingType encType) throws ScheduleManagerException {
+
+ String json = JSONUtils.toJSON(s.optionsToObject());
+ byte[] data = null;
+ try {
+ byte[] stringData = json.getBytes("UTF-8");
+ data = stringData;
+
+ if (encType == EncodingType.GZIP) {
+ data = GZIPUtils.gzipBytes(stringData);
+ }
+ logger.debug("NumChars: " + json.length() + " UTF-8:" + stringData.length + " Gzip:"+ data.length);
+ }
+ catch (IOException e) {
+ throw new ScheduleManagerException("Error encoding the schedule options " + s.getScheduleName());
+ }
+
+ QueryRunner runner = new QueryRunner(dataSource);
+
+ try {
+ int updates = runner.update(
+ UPDATE_SCHEDULE_BY_KEY,
+ s.getStatus(),
+ s.getFirstSchedTime(),
+ s.getTimezone().getID(),
+ Schedule.createPeriodString(s.getPeriod()),
+ s.getLastModifyTime(),
+ s.getNextExecTime(),
+ s.getSubmitTime(),
+ s.getSubmitUser(),
+ encType.getNumVal(),
+ data,
+ s.getProjectId(),
+ s.getFlowName());
+ if (updates == 0) {
+ throw new ScheduleManagerException("No schedule has been updated.");
+ }
+ } catch (SQLException e) {
+ logger.error(UPDATE_SCHEDULE_BY_KEY + " failed.");
+ throw new ScheduleManagerException("Update schedule " + s.getScheduleName() + " into db failed. ", e);
+ }
+ }
+
+ public class ScheduleResultHandler implements ResultSetHandler<List<Schedule>> {
+ @Override
+ public List<Schedule> handle(ResultSet rs) throws SQLException {
+ if (!rs.next()) {
+ return Collections.<Schedule>emptyList();
+ }
+
+ ArrayList<Schedule> schedules = new ArrayList<Schedule>();
+ do {
+ int projectId = rs.getInt(1);
+ String projectName = rs.getString(2);
+ String flowName = rs.getString(3);
+ String status = rs.getString(4);
+ long firstSchedTime = rs.getLong(5);
+ DateTimeZone timezone = DateTimeZone.forID(rs.getString(6));
+ ReadablePeriod period = Schedule.parsePeriodString(rs.getString(7));
+ long lastModifyTime = rs.getLong(8);
+ long nextExecTime = rs.getLong(9);
+ long submitTime = rs.getLong(10);
+ String submitUser = rs.getString(11);
+ int encodingType = rs.getInt(12);
+ byte[] data = rs.getBytes(13);
+
+ Object optsObj = null;
+ if (data != null) {
+ EncodingType encType = EncodingType.fromInteger(encodingType);
+
+ try {
+ // Convoluted way to inflate strings. Should find common package or helper function.
+ if (encType == EncodingType.GZIP) {
+ // Decompress the sucker.
+ String jsonString = GZIPUtils.unGzipString(data, "UTF-8");
+ optsObj = JSONUtils.parseJSONFromString(jsonString);
+ }
+ else {
+ String jsonString = new String(data, "UTF-8");
+ optsObj = JSONUtils.parseJSONFromString(jsonString);
+ }
+ } catch (IOException e) {
+ throw new SQLException("Error reconstructing schedule options " + projectName + "." + flowName);
+ }
+ }
+
+ Schedule s = new Schedule(projectId, projectName, flowName, status, firstSchedTime, timezone, period, lastModifyTime, nextExecTime, submitTime, submitUser);
+ if (optsObj != null) {
+ s.createAndSetScheduleOptions(optsObj);
+ }
+
+ schedules.add(s);
+ } while (rs.next());
+
+ return schedules;
+ }
+
+ }
+}
\ No newline at end of file
src/java/azkaban/migration/scheduler/Schedule.java 363(+363 -0)
diff --git a/src/java/azkaban/migration/scheduler/Schedule.java b/src/java/azkaban/migration/scheduler/Schedule.java
new file mode 100644
index 0000000..9490243
--- /dev/null
+++ b/src/java/azkaban/migration/scheduler/Schedule.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2012 LinkedIn, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.migration.scheduler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.Days;
+import org.joda.time.DurationFieldType;
+import org.joda.time.Hours;
+import org.joda.time.Minutes;
+import org.joda.time.Months;
+import org.joda.time.ReadablePeriod;
+import org.joda.time.Seconds;
+import org.joda.time.Weeks;
+
+import azkaban.executor.ExecutionOptions;
+import azkaban.migration.sla.SlaOptions;
+import azkaban.utils.Pair;
+
+@Deprecated
+public class Schedule{
+
+// private long projectGuid;
+// private long flowGuid;
+
+// private String scheduleId;
+
+ private int projectId;
+ private String projectName;
+ private String flowName;
+ private long firstSchedTime;
+ private DateTimeZone timezone;
+ private long lastModifyTime;
+ private ReadablePeriod period;
+ private long nextExecTime;
+ private String submitUser;
+ private String status;
+ private long submitTime;
+
+ private ExecutionOptions executionOptions;
+ private SlaOptions slaOptions;
+
+ public Schedule(
+ int projectId,
+ String projectName,
+ String flowName,
+ String status,
+ long firstSchedTime,
+ DateTimeZone timezone,
+ ReadablePeriod period,
+ long lastModifyTime,
+ long nextExecTime,
+ long submitTime,
+ String submitUser
+ ) {
+ this.projectId = projectId;
+ this.projectName = projectName;
+ this.flowName = flowName;
+ this.firstSchedTime = firstSchedTime;
+ this.timezone = timezone;
+ this.lastModifyTime = lastModifyTime;
+ this.period = period;
+ this.nextExecTime = nextExecTime;
+ this.submitUser = submitUser;
+ this.status = status;
+ this.submitTime = submitTime;
+ this.executionOptions = null;
+ this.slaOptions = null;
+ }
+
+ public Schedule(
+ int projectId,
+ String projectName,
+ String flowName,
+ String status,
+ long firstSchedTime,
+ String timezoneId,
+ String period,
+ long lastModifyTime,
+ long nextExecTime,
+ long submitTime,
+ String submitUser,
+ ExecutionOptions executionOptions,
+ SlaOptions slaOptions
+ ) {
+ this.projectId = projectId;
+ this.projectName = projectName;
+ this.flowName = flowName;
+ this.firstSchedTime = firstSchedTime;
+ this.timezone = DateTimeZone.forID(timezoneId);
+ this.lastModifyTime = lastModifyTime;
+ this.period = parsePeriodString(period);
+ this.nextExecTime = nextExecTime;
+ this.submitUser = submitUser;
+ this.status = status;
+ this.submitTime = submitTime;
+ this.executionOptions = executionOptions;
+ this.slaOptions = slaOptions;
+ }
+
+ public Schedule(
+ int projectId,
+ String projectName,
+ String flowName,
+ String status,
+ long firstSchedTime,
+ DateTimeZone timezone,
+ ReadablePeriod period,
+ long lastModifyTime,
+ long nextExecTime,
+ long submitTime,
+ String submitUser,
+ ExecutionOptions executionOptions,
+ SlaOptions slaOptions
+ ) {
+ this.projectId = projectId;
+ this.projectName = projectName;
+ this.flowName = flowName;
+ this.firstSchedTime = firstSchedTime;
+ this.timezone = timezone;
+ this.lastModifyTime = lastModifyTime;
+ this.period = period;
+ this.nextExecTime = nextExecTime;
+ this.submitUser = submitUser;
+ this.status = status;
+ this.submitTime = submitTime;
+ this.executionOptions = executionOptions;
+ this.slaOptions = slaOptions;
+ }
+
+ public ExecutionOptions getExecutionOptions() {
+ return executionOptions;
+ }
+
+ public void setFlowOptions(ExecutionOptions executionOptions) {
+ this.executionOptions = executionOptions;
+ }
+
+ public SlaOptions getSlaOptions() {
+ return slaOptions;
+ }
+
+ public void setSlaOptions(SlaOptions slaOptions) {
+ this.slaOptions = slaOptions;
+ }
+
+ public String getScheduleName() {
+ return projectName + "." + flowName + " (" + projectId + ")";
+ }
+
+ public String toString() {
+ return projectName + "." + flowName + " (" + projectId + ")" + " to be run at (starting) " +
+ new DateTime(firstSchedTime).toDateTimeISO() + " with recurring period of " + (period == null ? "non-recurring" : createPeriodString(period));
+ }
+
+ public Pair<Integer, String> getScheduleId() {
+ return new Pair<Integer, String>(getProjectId(), getFlowName());
+ }
+
+ public int getProjectId() {
+ return projectId;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public String getFlowName() {
+ return flowName;
+ }
+
+ public long getFirstSchedTime() {
+ return firstSchedTime;
+ }
+
+ public DateTimeZone getTimezone() {
+ return timezone;
+ }
+
+ public long getLastModifyTime() {
+ return lastModifyTime;
+ }
+
+ public ReadablePeriod getPeriod() {
+ return period;
+ }
+
+ public long getNextExecTime() {
+ return nextExecTime;
+ }
+
+ public String getSubmitUser() {
+ return submitUser;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public long getSubmitTime() {
+ return submitTime;
+ }
+
+ public boolean updateTime() {
+ if (new DateTime(nextExecTime).isAfterNow()) {
+ return true;
+ }
+
+ if (period != null) {
+ DateTime nextTime = getNextRuntime(nextExecTime, timezone, period);
+
+ this.nextExecTime = nextTime.getMillis();
+ return true;
+ }
+
+ return false;
+ }
+
+ private DateTime getNextRuntime(long scheduleTime, DateTimeZone timezone, ReadablePeriod period) {
+ DateTime now = new DateTime();
+ DateTime date = new DateTime(scheduleTime).withZone(timezone);
+ int count = 0;
+ while (!now.isBefore(date)) {
+ if (count > 100000) {
+ throw new IllegalStateException(
+ "100000 increments of period did not get to present time.");
+ }
+
+ if (period == null) {
+ break;
+ } else {
+ date = date.plus(period);
+ }
+
+ count += 1;
+ }
+
+ return date;
+ }
+
+ public static ReadablePeriod parsePeriodString(String periodStr) {
+ ReadablePeriod period;
+ char periodUnit = periodStr.charAt(periodStr.length() - 1);
+ if (periodUnit == 'n') {
+ return null;
+ }
+
+ int periodInt = Integer.parseInt(periodStr.substring(0,
+ periodStr.length() - 1));
+ switch (periodUnit) {
+ case 'M':
+ period = Months.months(periodInt);
+ break;
+ case 'w':
+ period = Weeks.weeks(periodInt);
+ break;
+ case 'd':
+ period = Days.days(periodInt);
+ break;
+ case 'h':
+ period = Hours.hours(periodInt);
+ break;
+ case 'm':
+ period = Minutes.minutes(periodInt);
+ break;
+ case 's':
+ period = Seconds.seconds(periodInt);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid schedule period unit '"
+ + periodUnit);
+ }
+
+ return period;
+ }
+
+ public static String createPeriodString(ReadablePeriod period) {
+ String periodStr = "n";
+
+ if (period == null) {
+ return "n";
+ }
+
+ if (period.get(DurationFieldType.months()) > 0) {
+ int months = period.get(DurationFieldType.months());
+ periodStr = months + "M";
+ } else if (period.get(DurationFieldType.weeks()) > 0) {
+ int weeks = period.get(DurationFieldType.weeks());
+ periodStr = weeks + "w";
+ } else if (period.get(DurationFieldType.days()) > 0) {
+ int days = period.get(DurationFieldType.days());
+ periodStr = days + "d";
+ } else if (period.get(DurationFieldType.hours()) > 0) {
+ int hours = period.get(DurationFieldType.hours());
+ periodStr = hours + "h";
+ } else if (period.get(DurationFieldType.minutes()) > 0) {
+ int minutes = period.get(DurationFieldType.minutes());
+ periodStr = minutes + "m";
+ } else if (period.get(DurationFieldType.seconds()) > 0) {
+ int seconds = period.get(DurationFieldType.seconds());
+ periodStr = seconds + "s";
+ }
+
+ return periodStr;
+ }
+
+
+ public Map<String,Object> optionsToObject() {
+ if(executionOptions != null || slaOptions != null) {
+ HashMap<String, Object> schedObj = new HashMap<String, Object>();
+
+ if(executionOptions != null) {
+ schedObj.put("executionOptions", executionOptions.toObject());
+ }
+ if(slaOptions != null) {
+ schedObj.put("slaOptions", slaOptions.toObject());
+ }
+
+ return schedObj;
+ }
+ return null;
+ }
+
+ public void createAndSetScheduleOptions(Object obj) {
+ @SuppressWarnings("unchecked")
+ HashMap<String, Object> schedObj = (HashMap<String, Object>)obj;
+ if (schedObj.containsKey("executionOptions")) {
+ ExecutionOptions execOptions = ExecutionOptions.createFromObject(schedObj.get("executionOptions"));
+ this.executionOptions = execOptions;
+ }
+ else if (schedObj.containsKey("flowOptions")){
+ ExecutionOptions execOptions = ExecutionOptions.createFromObject(schedObj.get("flowOptions"));
+ this.executionOptions = execOptions;
+ execOptions.setConcurrentOption(ExecutionOptions.CONCURRENT_OPTION_SKIP);
+ }
+ else {
+ this.executionOptions = new ExecutionOptions();
+ this.executionOptions.setConcurrentOption(ExecutionOptions.CONCURRENT_OPTION_SKIP);
+ }
+
+ if (schedObj.containsKey("slaOptions")) {
+ SlaOptions slaOptions = SlaOptions.fromObject(schedObj.get("slaOptions"));
+ this.slaOptions = slaOptions;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/azkaban/migration/scheduler/ScheduleLoader.java b/src/java/azkaban/migration/scheduler/ScheduleLoader.java
new file mode 100644
index 0000000..6511d9c
--- /dev/null
+++ b/src/java/azkaban/migration/scheduler/ScheduleLoader.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 LinkedIn, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.migration.scheduler;
+
+import java.util.List;
+
+@Deprecated
+public interface ScheduleLoader {
+
+ public void insertSchedule(Schedule s) throws ScheduleManagerException;
+
+ public void updateSchedule(Schedule s) throws ScheduleManagerException;
+
+ public List<Schedule> loadSchedules() throws ScheduleManagerException;
+
+ public void removeSchedule(Schedule s) throws ScheduleManagerException;
+
+ public void updateNextExecTime(Schedule s) throws ScheduleManagerException;
+
+}
\ No newline at end of file
diff --git a/src/java/azkaban/migration/scheduler/ScheduleManagerException.java b/src/java/azkaban/migration/scheduler/ScheduleManagerException.java
new file mode 100644
index 0000000..f0f6705
--- /dev/null
+++ b/src/java/azkaban/migration/scheduler/ScheduleManagerException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 LinkedIn, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.migration.scheduler;
+
+@Deprecated
+public class ScheduleManagerException extends Exception{
+ private static final long serialVersionUID = 1L;
+
+ public ScheduleManagerException(String message) {
+ super(message);
+ }
+
+ public ScheduleManagerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
\ No newline at end of file
src/java/azkaban/migration/sla/SLA.java 252(+252 -0)
diff --git a/src/java/azkaban/migration/sla/SLA.java b/src/java/azkaban/migration/sla/SLA.java
new file mode 100644
index 0000000..0e75965
--- /dev/null
+++ b/src/java/azkaban/migration/sla/SLA.java
@@ -0,0 +1,252 @@
+package azkaban.migration.sla;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.joda.time.DateTime;
+import org.joda.time.ReadablePeriod;
+
+import azkaban.migration.scheduler.Schedule;
+
+@Deprecated
+public class SLA {
+
+ public static enum SlaRule {
+ SUCCESS(1), FINISH(2), WAITANDCHECKJOB(3);
+
+ private int numVal;
+
+ SlaRule(int numVal) {
+ this.numVal = numVal;
+ }
+
+ public int getNumVal() {
+ return numVal;
+ }
+
+ public static SlaRule fromInteger(int x) {
+ switch (x) {
+ case 1:
+ return SUCCESS;
+ case 2:
+ return FINISH;
+ case 3:
+ return WAITANDCHECKJOB;
+ default:
+ return SUCCESS;
+ }
+ }
+ }
+
+ public static enum SlaAction {
+ EMAIL(1), KILL(2);
+
+ private int numVal;
+
+ SlaAction(int numVal) {
+ this.numVal = numVal;
+ }
+
+ public int getNumVal() {
+ return numVal;
+ }
+
+ public static SlaAction fromInteger(int x) {
+ switch (x) {
+ case 1:
+ return EMAIL;
+ case 2:
+ return KILL;
+ default:
+ return EMAIL;
+ }
+ }
+ }
+
+ public static class SlaSetting {
+ public String getId() {
+ return id;
+ }
+ public void setId(String id) {
+ this.id = id;
+ }
+ public ReadablePeriod getDuration() {
+ return duration;
+ }
+ public void setDuration(ReadablePeriod duration) {
+ this.duration = duration;
+ }
+ public SlaRule getRule() {
+ return rule;
+ }
+ public void setRule(SlaRule rule) {
+ this.rule = rule;
+ }
+ public List<SlaAction> getActions() {
+ return actions;
+ }
+ public void setActions(List<SlaAction> actions) {
+ this.actions = actions;
+ }
+
+ public Object toObject() {
+ Map<String, Object> obj = new HashMap<String, Object>();
+ obj.put("id", id);
+ obj.put("duration", Schedule.createPeriodString(duration));
+// List<String> rulesObj = new ArrayList<String>();
+// for(SlaRule rule : rules) {
+// rulesObj.add(rule.toString());
+// }
+// obj.put("rules", rulesObj);
+ obj.put("rule", rule.toString());
+ List<String> actionsObj = new ArrayList<String>();
+ for(SlaAction act : actions) {
+ actionsObj.add(act.toString());
+ }
+ obj.put("actions", actionsObj);
+ return obj;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static SlaSetting fromObject(Object obj) {
+ Map<String, Object> slaObj = (HashMap<String, Object>) obj;
+ String subId = (String) slaObj.get("id");
+ ReadablePeriod dur = Schedule.parsePeriodString((String) slaObj.get("duration"));
+// List<String> rulesObj = (ArrayList<String>) slaObj.get("rules");
+// List<SlaRule> slaRules = new ArrayList<SLA.SlaRule>();
+// for(String rule : rulesObj) {
+// slaRules.add(SlaRule.valueOf(rule));
+// }
+ SlaRule slaRule = SlaRule.valueOf((String) slaObj.get("rule"));
+ List<String> actsObj = (ArrayList<String>) slaObj.get("actions");
+ List<SlaAction> slaActs = new ArrayList<SlaAction>();
+ for(String act : actsObj) {
+ slaActs.add(SlaAction.valueOf(act));
+ }
+
+ SlaSetting ret = new SlaSetting();
+ ret.setId(subId);
+ ret.setDuration(dur);
+ ret.setRule(slaRule);
+ ret.setActions(slaActs);
+ return ret;
+ }
+
+ private String id;
+ private ReadablePeriod duration;
+ private SlaRule rule = SlaRule.SUCCESS;
+ private List<SlaAction> actions;
+ }
+
+ private int execId;
+ private String jobName;
+ private DateTime checkTime;
+ private List<String> emails;
+ private List<SlaAction> actions;
+ private List<SlaSetting> jobSettings;
+ private SlaRule rule;
+
+ public SLA(
+ int execId,
+ String jobName,
+ DateTime checkTime,
+ List<String> emails,
+ List<SlaAction> slaActions,
+ List<SlaSetting> jobSettings,
+ SlaRule slaRule
+ ) {
+ this.execId = execId;
+ this.jobName = jobName;
+ this.checkTime = checkTime;
+ this.emails = emails;
+ this.actions = slaActions;
+ this.jobSettings = jobSettings;
+ this.rule = slaRule;
+ }
+
+ public int getExecId() {
+ return execId;
+ }
+
+ public String getJobName() {
+ return jobName;
+ }
+
+ public DateTime getCheckTime() {
+ return checkTime;
+ }
+
+ public List<String> getEmails() {
+ return emails;
+ }
+
+ public List<SlaAction> getActions() {
+ return actions;
+ }
+
+ public List<SlaSetting> getJobSettings() {
+ return jobSettings;
+ }
+
+ public SlaRule getRule() {
+ return rule;
+ }
+
+ public String toString() {
+ return execId + " " + jobName + " to be checked at " + checkTime.toDateTimeISO();
+ }
+
+ public Map<String,Object> optionToObject() {
+ HashMap<String, Object> slaObj = new HashMap<String, Object>();
+
+ slaObj.put("emails", emails);
+// slaObj.put("rule", rule.toString());
+
+ List<String> actionsObj = new ArrayList<String>();
+ for(SlaAction act : actions) {
+ actionsObj.add(act.toString());
+ }
+ slaObj.put("actions", actionsObj);
+
+ if(jobSettings != null && jobSettings.size() > 0) {
+ List<Object> settingsObj = new ArrayList<Object>();
+ for(SlaSetting set : jobSettings) {
+ settingsObj.add(set.toObject());
+ }
+ slaObj.put("jobSettings", settingsObj);
+ }
+
+ return slaObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static SLA createSlaFromObject(int execId, String jobName, DateTime checkTime, SlaRule rule, Object obj) {
+
+ HashMap<String, Object> slaObj = (HashMap<String,Object>)obj;
+
+ List<String> emails = (List<String>)slaObj.get("emails");
+// SlaRule rule = SlaRule.valueOf((String)slaObj.get("rule"));
+ List<String> actsObj = (ArrayList<String>) slaObj.get("actions");
+ List<SlaAction> slaActs = new ArrayList<SlaAction>();
+ for(String act : actsObj) {
+ slaActs.add(SlaAction.valueOf(act));
+ }
+ List<SlaSetting> jobSets = null;
+ if(slaObj.containsKey("jobSettings") && slaObj.get("jobSettings") != null) {
+ jobSets = new ArrayList<SLA.SlaSetting>();
+ for(Object set : (List<Object>)slaObj.get("jobSettings")) {
+ SlaSetting jobSet = SlaSetting.fromObject(set);
+ jobSets.add(jobSet);
+ }
+ }
+
+ return new SLA(execId, jobName, checkTime, emails, slaActs, jobSets, rule);
+ }
+
+ public void setCheckTime(DateTime time) {
+ this.checkTime = time;
+ }
+
+}
diff --git a/src/java/azkaban/migration/sla/SlaOptions.java b/src/java/azkaban/migration/sla/SlaOptions.java
new file mode 100644
index 0000000..f7b9d49
--- /dev/null
+++ b/src/java/azkaban/migration/sla/SlaOptions.java
@@ -0,0 +1,52 @@
+package azkaban.migration.sla;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import azkaban.migration.sla.SLA.SlaSetting;
+
+@Deprecated
+public class SlaOptions {
+
+ public List<String> getSlaEmails() {
+ return slaEmails;
+ }
+ public void setSlaEmails(List<String> slaEmails) {
+ this.slaEmails = slaEmails;
+ }
+ public List<SlaSetting> getSettings() {
+ return settings;
+ }
+ public void setSettings(List<SlaSetting> settings) {
+ this.settings = settings;
+ }
+ private List<String> slaEmails;
+ private List<SlaSetting> settings;
+ public Object toObject() {
+ Map<String, Object> obj = new HashMap<String, Object>();
+ obj.put("slaEmails", slaEmails);
+ List<Object> slaSettings = new ArrayList<Object>();
+ for(SlaSetting s : settings) {
+ slaSettings.add(s.toObject());
+ }
+ obj.put("settings", slaSettings);
+ return obj;
+ }
+ @SuppressWarnings("unchecked")
+ public static SlaOptions fromObject(Object object) {
+ if(object != null) {
+ SlaOptions slaOptions = new SlaOptions();
+ Map<String, Object> obj = (HashMap<String, Object>) object;
+ slaOptions.setSlaEmails((List<String>) obj.get("slaEmails"));
+ List<SlaSetting> slaSets = new ArrayList<SlaSetting>();
+ for(Object set: (List<Object>)obj.get("settings")) {
+ slaSets.add(SlaSetting.fromObject(set));
+ }
+ slaOptions.setSettings(slaSets);
+ return slaOptions;
+ }
+ return null;
+ }
+}
\ No newline at end of file
src/java/azkaban/project/ProjectManager.java 10(+10 -0)
diff --git a/src/java/azkaban/project/ProjectManager.java b/src/java/azkaban/project/ProjectManager.java
index a5be083..be494e4 100644
--- a/src/java/azkaban/project/ProjectManager.java
+++ b/src/java/azkaban/project/ProjectManager.java
@@ -114,6 +114,16 @@ public class ProjectManager {
return array;
}
+ public List<Project> getGroupProjects(User user) {
+ List<Project> array = new ArrayList<Project>();
+ for (Project project : projectsById.values()) {
+ if (project.hasGroupPermission(user, Type.READ)) {
+ array.add(project);
+ }
+ }
+ return array;
+ }
+
public List<Project> getUserProjectsByRegex(User user, String regexPattern) {
List<Project> array = new ArrayList<Project>();
Pattern pattern;
diff --git a/src/java/azkaban/webapp/servlet/ExecutorServlet.java b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
index 808ba7b..188454b 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;
@@ -46,7 +47,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;
@@ -57,6 +58,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);
@@ -65,6 +68,7 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
executorManager = server.getExecutorManager();
scheduleManager = server.getScheduleManager();
velocityHelper = new ExecutorVelocityHelper();
+ statsDir = server.getServerProps().getString("azkaban.stats.dir");
}
@Override
@@ -127,9 +131,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);
}
@@ -450,52 +454,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 e998b00..d173bd0 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;
@@ -254,6 +255,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);
@@ -345,7 +351,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"));
@@ -353,8 +386,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..ba263d6 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.getGroupProjects(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 edc4c5b..4e6f5cc 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -20,26 +20,15 @@
#parse("azkaban/webapp/servlet/velocity/style.vm")
#parse("azkaban/webapp/servlet/velocity/javascript.vm")
+#parse("azkaban/webapp/servlet/velocity/svgflowincludes.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/jquery.svg.min.js"></script>
- <script type="text/javascript" src="${context}/js/jquery.svganim.min.js"></script>
- <script type="text/javascript" src="${context}/js/jquery.svgfilter.min.js"></script>
-
- <script type="text/javascript" src="${context}/js/svgutils.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.context.menu.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.flow.execute.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.flow.loader.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/azkaban.exflow.view.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/view/exflow.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -70,20 +59,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>
@@ -112,6 +101,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>
@@ -128,7 +118,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>
@@ -144,7 +134,7 @@
<tbody id="executableBody">
</tbody>
</table>
- </div><!-- /.col-lg-12 -->
+ </div><!-- /.col-xs-12 -->
</div><!-- /.row -->
</div><!-- /.container-full -->
@@ -152,7 +142,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">
@@ -166,9 +156,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 01f328c..6895f48 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
@@ -22,7 +22,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>
@@ -45,8 +45,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.
@@ -174,7 +174,7 @@
</div>
</div><!-- /execution-graph-options-panel -->
- </div><!-- /col-md-8 -->
+ </div><!-- /col-xs-8 -->
</div><!-- /modal-body -->
<div class="modal-footer">
@@ -192,7 +192,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 6c6aa06..2873437 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -20,29 +20,19 @@
#parse("azkaban/webapp/servlet/velocity/style.vm")
#parse("azkaban/webapp/servlet/velocity/javascript.vm")
-
+#parse("azkaban/webapp/servlet/velocity/svgflowincludes.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/bootstrap-datetimepicker.min.js"></script>
+ <script type="text/javascript" src="${context}/js/raphael.min.js"></script>
+ <script type="text/javascript" src="${context}/js/morris.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/flowstats-no-data.js"></script>
+ <script type="text/javascript" src="${context}/js/flowstats.js"></script>
- <script type="text/javascript" src="${context}/js/dust-core-2.2.2.min.js"></script>
- <script type="text/javascript" src="${context}/js/flowsummary.js"></script>
-
- <script type="text/javascript" src="${context}/js/jquery.svg.min.js"></script>
- <script type="text/javascript" src="${context}/js/jquery.svganim.min.js"></script>
- <script type="text/javascript" src="${context}/js/jquery.svgfilter.min.js"></script>
-
- <script type="text/javascript" src="${context}/js/svgutils.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.context.menu.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.flow.execute.view.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.svg.flow.loader.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/azkaban/view/time-graph.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/flow.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -55,7 +45,8 @@
var flowId = "${flowid}";
var execId = null;
</script>
- <link rel="stylesheet" type="text/css" href="${context}/css/flow.css" />
+ <link rel="stylesheet" type="text/css" href="${context}/css/morris.css" />
+ <link rel="stylesheet" type="text/css" href="${context}/css/azkaban-svg.css" />
<link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
<link rel="stylesheet" type="text/css" href="${context}/css/azkaban-graph.css" />
</head>
@@ -73,10 +64,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>
@@ -114,7 +105,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>
@@ -146,10 +141,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..19fcac4 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm
@@ -21,9 +21,11 @@
#parse("azkaban/webapp/servlet/velocity/style.vm")
#parse("azkaban/webapp/servlet/velocity/javascript.vm")
- <script type="text/javascript" src="${context}/js/d3.v3.min.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.date.utils.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.jobhistory.view.js"></script>
+ <script type="text/javascript" src="${context}/js/raphael.min.js"></script>
+ <script type="text/javascript" src="${context}/js/morris.min.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};
@@ -36,24 +38,7 @@
var jobName = "$jobid";
var dataSeries = $dataSeries;
</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/morris.css" />
</head>
<body>
@@ -86,7 +71,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 +137,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..4f5fd38 100644
--- a/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
@@ -20,14 +20,13 @@
#parse ("azkaban/webapp/servlet/velocity/style.vm")
#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
-
+#parse ("azkaban/webapp/servlet/velocity/svgflowincludes.vm")
<link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
<link rel="stylesheet" type="text/css" href="${context}/css/azkaban-svg.css" />
<script type="text/javascript" src="${context}/js/moment.min.js"></script>
<script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.project.view.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.projectmodals.view.js"></script>
+ <script type="text/javascript" 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 +84,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/svgflowincludes.vm b/src/java/azkaban/webapp/servlet/velocity/svgflowincludes.vm
new file mode 100644
index 0000000..364a968
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/svgflowincludes.vm
@@ -0,0 +1,37 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+ <script type="text/javascript" src="${context}/js/jquery.svg.min.js"></script>
+ <script type="text/javascript" src="${context}/js/jquery.svganim.min.js"></script>
+ <script type="text/javascript" src="${context}/js/jquery.svgfilter.min.js"></script>
+
+ <script type="text/javascript" src="${context}/js/azkaban/util/svgutils.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/view/flow-execute.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/flow-loader.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/util/common.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/util/date.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>
+
+ <link rel="stylesheet" type="text/css" href="${context}/css/azkaban-graph.css" />
\ No newline at end of file
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 5408f07..e08f0af 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;
@@ -92,7 +92,8 @@ td {
background-color: #c82123;
}
- &.READY {
+ &.READY,
+ &.UNKNOWN {
background-color: #ccc;
}
@@ -104,10 +105,7 @@ td {
background-color: #f19153;
}
- &.DISABLED {
- background-color: #aaa;
- }
-
+ &.DISABLED,
&.SKIPPED {
background-color: #aaa;
}
@@ -115,10 +113,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/execserver/conf/azkaban.properties b/src/package/execserver/conf/azkaban.properties
index b54b0f5..3f9beda 100644
--- a/src/package/execserver/conf/azkaban.properties
+++ b/src/package/execserver/conf/azkaban.properties
@@ -8,6 +8,8 @@ azkaban.jobtype.plugin.dir=plugins/jobtypes
executor.global.properties=conf/global.properties
azkaban.project.dir=projects
+azkaban.stats.dir=
+
database.type=mysql
mysql.port=3306
mysql.host=localhost
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/bin/schedule2trigger.sh b/src/package/webserver/bin/schedule2trigger.sh
new file mode 100644
index 0000000..1178cf3
--- /dev/null
+++ b/src/package/webserver/bin/schedule2trigger.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+java -cp "lib/*:extlib/*" azkaban.migration.schedule2trigger.Schedule2Trigger conf/azkaban.properties
+
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/css/morris.css 25(+25 -0)
diff --git a/src/web/css/morris.css b/src/web/css/morris.css
new file mode 100644
index 0000000..5b313c2
--- /dev/null
+++ b/src/web/css/morris.css
@@ -0,0 +1,25 @@
+.morris-hover {
+ position: absolute;
+ z-index: 1000;
+}
+
+.morris-hover.morris-default-style {
+ border-radius: 10px;
+ padding: 6px;
+ color: #666;
+ background: rgba(255, 255, 255, 0.8);
+ border: solid 2px rgba(230, 230, 230, 0.8);
+ font-family: sans-serif;
+ font-size: 12px;
+ text-align: center;
+}
+
+.morris-hover.morris-default-style .morris-hover-row-label {
+ font-weight: bold;
+ margin: 0.25em 0;
+}
+
+.morris-hover.morris-default-style .morris-hover-point {
+ white-space: nowrap;
+ margin: 0.1em 0;
+}
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 39(+39 -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..b07c27e
--- /dev/null
+++ b/src/web/js/azkaban/view/job-history.js
@@ -0,0 +1,39 @@
+/*
+ * 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"
+ });
+});
\ No newline at end of file
src/web/js/azkaban/view/time-graph.js 99(+99 -0)
diff --git a/src/web/js/azkaban/view/time-graph.js b/src/web/js/azkaban/view/time-graph.js
new file mode 100644
index 0000000..5466db3
--- /dev/null
+++ b/src/web/js/azkaban/view/time-graph.js
@@ -0,0 +1,99 @@
+/*
+ * 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.TimeGraphView = Backbone.View.extend({
+ events: {
+ },
+
+ initialize: function(settings) {
+ this.model.bind('render', this.render, this);
+ this.model.bind('change:page', this.render, this);
+ this.modelField = settings.modelField;
+ this.element = settings.el;
+ this.render();
+ },
+
+ render: function(self) {
+ var series = this.model.get(this.modelField);
+ if (series == null) {
+ return;
+ }
+
+ // Array of points to be passed to Morris.
+ var data = [];
+
+ // Map of y value to index for faster look-up in the lineColorsCallback to
+ // get the status for each point.
+ var indexMap = {};
+ for (var i = 0; i < series.length; ++i) {
+ if (series[i].startTime == null || series[i].endTime == null) {
+ console.log("Each element in series must have startTime and endTime");
+ return;
+ }
+ var startTime = series[i].startTime;
+ var endTime = series[i].endTime;
+ if (endTime == -1) {
+ endTime = new Date().getTime();
+ }
+ var duration = endTime - startTime;
+ data.push({
+ time: endTime,
+ duration: duration
+ });
+
+ indexMap[endTime.toString()] = i;
+ }
+
+ var lineColorsCallback = function(row, sidx, type) {
+ if (type != 'point') {
+ return "#000000";
+ }
+ var i = indexMap[row.x.toString()];
+ var status = series[i].status;
+ if (status == 'SKIPPED') {
+ return '#aaa';
+ }
+ else if (status == 'SUCCEEDED') {
+ return '#4e911e';
+ }
+ else if (status == 'RUNNING') {
+ return '#009fc9';
+ }
+ else if (status == 'PAUSED') {
+ return '#c92123';
+ }
+ else if (status == 'FAILED' ||
+ status == 'FAILED_FINISHING' ||
+ status == 'KILLED') {
+ return '#cc0000';
+ }
+ else {
+ return '#ccc';
+ }
+ };
+
+ Morris.Line({
+ element: this.element,
+ data: data,
+ xkey: 'time',
+ ykeys: ['duration'],
+ labels: ['Duration'],
+ lineColors: lineColorsCallback
+ });
+ }
+});
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
src/web/js/morris.min.js 1(+1 -0)
diff --git a/src/web/js/morris.min.js b/src/web/js/morris.min.js
new file mode 100644
index 0000000..edc5d7b
--- /dev/null
+++ b/src/web/js/morris.min.js
@@ -0,0 +1 @@
+(function(){var e,t,n,r,i=[].slice,s={}.hasOwnProperty,o=function(e,t){function r(){this.constructor=e}for(var n in t)s.call(t,n)&&(e[n]=t[n]);return r.prototype=t.prototype,e.prototype=new r,e.__super__=t.prototype,e},u=function(e,t){return function(){return e.apply(t,arguments)}},a=[].indexOf||function(e){for(var t=0,n=this.length;t<n;t++)if(t in this&&this[t]===e)return t;return-1};t=window.Morris={},e=jQuery,t.EventEmitter=function(){function e(){}return e.prototype.on=function(e,t){return this.handlers==null&&(this.handlers={}),this.handlers[e]==null&&(this.handlers[e]=[]),this.handlers[e].push(t),this},e.prototype.fire=function(){var e,t,n,r,s,o,u;n=arguments[0],e=2<=arguments.length?i.call(arguments,1):[];if(this.handlers!=null&&this.handlers[n]!=null){o=this.handlers[n],u=[];for(r=0,s=o.length;r<s;r++)t=o[r],u.push(t.apply(null,e));return u}},e}(),t.commas=function(e){var t,n,r,i;return e!=null?(r=e<0?"-":"",t=Math.abs(e),n=Math.floor(t).toFixed(0),r+=n.replace(/(?=(?:\d{3})+$)(?!^)/g,","),i=t.toString(),i.length>n.length&&(r+=i.slice(n.length)),r):"-"},t.pad2=function(e){return(e<10?"0":"")+e},t.Grid=function(n){function r(t){var n=this;typeof t.element=="string"?this.el=e(document.getElementById(t.element)):this.el=e(t.element);if(this.el==null||this.el.length===0)throw new Error("Graph container element not found");this.el.css("position")==="static"&&this.el.css("position","relative"),this.options=e.extend({},this.gridDefaults,this.defaults||{},t),typeof this.options.units=="string"&&(this.options.postUnits=t.units),this.raphael=new Raphael(this.el[0]),this.elementWidth=null,this.elementHeight=null,this.dirty=!1,this.init&&this.init(),this.setData(this.options.data),this.el.bind("mousemove",function(e){var t;return t=n.el.offset(),n.fire("hovermove",e.pageX-t.left,e.pageY-t.top)}),this.el.bind("mouseout",function(e){return n.fire("hoverout")}),this.el.bind("touchstart touchmove touchend",function(e){var t,r;return r=e.originalEvent.touches[0]||e.originalEvent.changedTouches[0],t=n.el.offset(),n.fire("hover",r.pageX-t.left,r.pageY-t.top),r}),this.el.bind("click",function(e){var t;return t=n.el.offset(),n.fire("gridclick",e.pageX-t.left,e.pageY-t.top)}),this.postInit&&this.postInit()}return o(r,n),r.prototype.gridDefaults={dateFormat:null,axes:!0,grid:!0,gridLineColor:"#aaa",gridStrokeWidth:.5,gridTextColor:"#888",gridTextSize:12,gridTextFamily:"sans-serif",gridTextWeight:"normal",hideHover:!1,yLabelFormat:null,xLabelAngle:0,numLines:5,padding:25,parseTime:!0,postUnits:"",preUnits:"",ymax:"auto",ymin:"auto 0",goals:[],goalStrokeWidth:1,goalLineColors:["#666633","#999966","#cc6666","#663333"],events:[],eventStrokeWidth:1,eventLineColors:["#005a04","#ccffbb","#3a5f0b","#005502"]},r.prototype.setData=function(e,n){var r,i,s,o,u,a,f,l,c,h,p,d,v,m;n==null&&(n=!0),this.options.data=e;if(e==null||e.length===0){this.data=[],this.raphael.clear(),this.hover!=null&&this.hover.hide();return}d=this.cumulative?0:null,v=this.cumulative?0:null,this.options.goals.length>0&&(u=Math.min.apply(null,this.options.goals),o=Math.max.apply(null,this.options.goals),v=v!=null?Math.min(v,u):u,d=d!=null?Math.max(d,o):o),this.data=function(){var n,r,o;o=[];for(s=n=0,r=e.length;n<r;s=++n)f=e[s],a={},a.label=f[this.options.xkey],this.options.parseTime?(a.x=t.parseDate(a.label),this.options.dateFormat?a.label=this.options.dateFormat(a.x):typeof a.label=="number"&&(a.label=(new Date(a.label)).toString())):(a.x=s,this.options.xLabelFormat&&(a.label=this.options.xLabelFormat(a))),c=0,a.y=function(){var e,t,n,r;n=this.options.ykeys,r=[];for(i=e=0,t=n.length;e<t;i=++e)p=n[i],m=f[p],typeof m=="string"&&(m=parseFloat(m)),m!=null&&typeof m!="number"&&(m=null),m!=null&&(this.cumulative?c+=m:d!=null?(d=Math.max(m,d),v=Math.min(m,v)):d=v=m),this.cumulative&&c!=null&&(d=Math.max(c,d),v=Math.min(c,v)),r.push(m);return r}.call(this),o.push(a);return o}.call(this),this.options.parseTime&&(this.data=this.data.sort(function(e,t){return(e.x>t.x)-(t.x>e.x)})),this.xmin=this.data[0].x,this.xmax=this.data[this.data.length-1].x,this.events=[],this.options.parseTime&&this.options.events.length>0&&(this.events=function(){var e,n,i,s;i=this.options.events,s=[];for(e=0,n=i.length;e<n;e++)r=i[e],s.push(t.parseDate(r));return s}.call(this),this.xmax=Math.max(this.xmax,Math.max.apply(null,this.events)),this.xmin=Math.min(this.xmin,Math.min.apply(null,this.events))),this.xmin===this.xmax&&(this.xmin-=1,this.xmax+=1),this.ymin=this.yboundary("min",v),this.ymax=this.yboundary("max",d),this.ymin===this.ymax&&(v&&(this.ymin-=1),this.ymax+=1);if(this.options.axes===!0||this.options.grid===!0)this.options.ymax===this.gridDefaults.ymax&&this.options.ymin===this.gridDefaults.ymin?(this.grid=this.autoGridLines(this.ymin,this.ymax,this.options.numLines),this.ymin=Math.min(this.ymin,this.grid[0]),this.ymax=Math.max(this.ymax,this.grid[this.grid.length-1])):(l=(this.ymax-this.ymin)/(this.options.numLines-1),this.grid=function(){var e,t,n,r;r=[];for(h=e=t=this.ymin,n=this.ymax;t<=n?e<=n:e>=n;h=e+=l)r.push(h);return r}.call(this));this.dirty=!0;if(n)return this.redraw()},r.prototype.yboundary=function(e,t){var n,r;return n=this.options["y"+e],typeof n=="string"?n.slice(0,4)==="auto"?n.length>5?(r=parseInt(n.slice(5),10),t==null?r:Math[e](t,r)):t!=null?t:0:parseInt(n,10):n},r.prototype.autoGridLines=function(e,t,n){var r,i,s,o,u,a,f,l,c;return u=t-e,c=Math.floor(Math.log(u)/Math.log(10)),f=Math.pow(10,c),i=Math.floor(e/f)*f,r=Math.ceil(t/f)*f,a=(r-i)/(n-1),f===1&&a>1&&Math.ceil(a)!==a&&(a=Math.ceil(a),r=i+a*(n-1)),i<0&&r>0&&(i=Math.floor(e/a)*a,r=Math.ceil(t/a)*a),a<1?(o=Math.floor(Math.log(a)/Math.log(10)),s=function(){var e,t;t=[];for(l=e=i;i<=r?e<=r:e>=r;l=e+=a)t.push(parseFloat(l.toFixed(1-o)));return t}()):s=function(){var e,t;t=[];for(l=e=i;i<=r?e<=r:e>=r;l=e+=a)t.push(l);return t}(),s},r.prototype._calc=function(){var e,t,n,r,i,s;i=this.el.width(),n=this.el.height();if(this.elementWidth!==i||this.elementHeight!==n||this.dirty){this.elementWidth=i,this.elementHeight=n,this.dirty=!1,this.left=this.options.padding,this.right=this.elementWidth-this.options.padding,this.top=this.options.padding,this.bottom=this.elementHeight-this.options.padding,this.options.axes&&(s=function(){var e,n,r,i;r=this.grid,i=[];for(e=0,n=r.length;e<n;e++)t=r[e],i.push(this.measureText(this.yAxisFormat(t)).width);return i}.call(this),this.left+=Math.max.apply(Math,s),e=function(){var e,t,n;n=[];for(r=e=0,t=this.data.length;0<=t?e<t:e>t;r=0<=t?++e:--e)n.push(this.measureText(this.data[r].text,-this.options.xLabelAngle).height);return n}.call(this),this.bottom-=Math.max.apply(Math,e)),this.width=Math.max(1,this.right-this.left),this.height=Math.max(1,this.bottom-this.top),this.dx=this.width/(this.xmax-this.xmin),this.dy=this.height/(this.ymax-this.ymin);if(this.calc)return this.calc()}},r.prototype.transY=function(e){return this.bottom-(e-this.ymin)*this.dy},r.prototype.transX=function(e){return this.data.length===1?(this.left+this.right)/2:this.left+(e-this.xmin)*this.dx},r.prototype.redraw=function(){this.raphael.clear(),this._calc(),this.drawGrid(),this.drawGoals(),this.drawEvents();if(this.draw)return this.draw()},r.prototype.measureText=function(e,t){var n,r;return t==null&&(t=0),r=this.raphael.text(100,100,e).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).rotate(t),n=r.getBBox(),r.remove(),n},r.prototype.yAxisFormat=function(e){return this.yLabelFormat(e)},r.prototype.yLabelFormat=function(e){return typeof this.options.yLabelFormat=="function"?this.options.yLabelFormat(e):""+this.options.preUnits+t.commas(e)+this.options.postUnits},r.prototype.updateHover=function(e,t){var n,r;n=this.hitTest(e,t);if(n!=null)return(r=this.hover).update.apply(r,n)},r.prototype.drawGrid=function(){var e,t,n,r,i,s;if(this.options.grid===!1&&this.options.axes===!1)return;i=this.grid,s=[];for(n=0,r=i.length;n<r;n++)e=i[n],t=this.transY(e),this.options.axes&&this.drawYAxisLabel(this.left-this.options.padding/2,t,this.yAxisFormat(e)),this.options.grid?s.push(this.drawGridLine("M"+this.left+","+t+"H"+(this.left+this.width))):s.push(void 0);return s},r.prototype.drawGoals=function(){var e,t,n,r,i,s,o;s=this.options.goals,o=[];for(n=r=0,i=s.length;r<i;n=++r)t=s[n],e=this.options.goalLineColors[n%this.options.goalLineColors.length],o.push(this.drawGoal(t,e));return o},r.prototype.drawEvents=function(){var e,t,n,r,i,s,o;s=this.events,o=[];for(n=r=0,i=s.length;r<i;n=++r)t=s[n],e=this.options.eventLineColors[n%this.options.eventLineColors.length],o.push(this.drawEvent(t,e));return o},r.prototype.drawGoal=function(e,t){return this.raphael.path("M"+this.left+","+this.transY(e)+"H"+this.right).attr("stroke",t).attr("stroke-width",this.options.goalStrokeWidth)},r.prototype.drawEvent=function(e,t){return this.raphael.path("M"+this.transX(e)+","+this.bottom+"V"+this.top).attr("stroke",t).attr("stroke-width",this.options.eventStrokeWidth)},r.prototype.drawYAxisLabel=function(e,t,n){return this.raphael.text(e,t,n).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).attr("fill",this.options.gridTextColor).attr("text-anchor","end")},r.prototype.drawGridLine=function(e){return this.raphael.path(e).attr("stroke",this.options.gridLineColor).attr("stroke-width",this.options.gridStrokeWidth)},r}(t.EventEmitter),t.parseDate=function(e){var t,n,r,i,s,o,u,a,f,l,c;return typeof e=="number"?e:(n=e.match(/^(\d+) Q(\d)$/),i=e.match(/^(\d+)-(\d+)$/),s=e.match(/^(\d+)-(\d+)-(\d+)$/),u=e.match(/^(\d+) W(\d+)$/),a=e.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+)(Z|([+-])(\d\d):?(\d\d))?$/),f=e.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+):(\d+(\.\d+)?)(Z|([+-])(\d\d):?(\d\d))?$/),n?(new Date(parseInt(n[1],10),parseInt(n[2],10)*3-1,1)).getTime():i?(new Date(parseInt(i[1],10),parseInt(i[2],10)-1,1)).getTime():s?(new Date(parseInt(s[1],10),parseInt(s[2],10)-1,parseInt(s[3],10))).getTime():u?(l=new Date(parseInt(u[1],10),0,1),l.getDay()!==4&&l.setMonth(0,1+(4-l.getDay()+7)%7),l.getTime()+parseInt(u[2],10)*6048e5):a?a[6]?(o=0,a[6]!=="Z"&&(o=parseInt(a[8],10)*60+parseInt(a[9],10),a[7]==="+"&&(o=0-o)),Date.UTC(parseInt(a[1],10),parseInt(a[2],10)-1,parseInt(a[3],10),parseInt(a[4],10),parseInt(a[5],10)+o)):(new Date(parseInt(a[1],10),parseInt(a[2],10)-1,parseInt(a[3],10),parseInt(a[4],10),parseInt(a[5],10))).getTime():f?(c=parseFloat(f[6]),t=Math.floor(c),r=Math.round((c-t)*1e3),f[8]?(o=0,f[8]!=="Z"&&(o=parseInt(f[10],10)*60+parseInt(f[11],10),f[9]==="+"&&(o=0-o)),Date.UTC(parseInt(f[1],10),parseInt(f[2],10)-1,parseInt(f[3],10),parseInt(f[4],10),parseInt(f[5],10)+o,t,r)):(new Date(parseInt(f[1],10),parseInt(f[2],10)-1,parseInt(f[3],10),parseInt(f[4],10),parseInt(f[5],10),t,r)).getTime()):(new Date(parseInt(e,10),0,1)).getTime())},t.Hover=function(){function n(n){n==null&&(n={}),this.options=e.extend({},t.Hover.defaults,n),this.el=e("<div class='"+this.options["class"]+"'></div>"),this.el.hide(),this.options.parent.append(this.el)}return n.defaults={"class":"morris-hover morris-default-style"},n.prototype.update=function(e,t,n){return this.html(e),this.show(),this.moveTo(t,n)},n.prototype.html=function(e){return this.el.html(e)},n.prototype.moveTo=function(e,t){var n,r,i,s,o,u;return o=this.options.parent.innerWidth(),s=this.options.parent.innerHeight(),r=this.el.outerWidth(),n=this.el.outerHeight(),i=Math.min(Math.max(0,e-r/2),o-r),t!=null?(u=t-n-10,u<0&&(u=t+10,u+n>s&&(u=s/2-n/2))):u=s/2-n/2,this.el.css({left:i+"px",top:parseInt(u)+"px"})},n.prototype.show=function(){return this.el.show()},n.prototype.hide=function(){return this.el.hide()},n}(),t.Line=function(e){function n(e){this.hilight=u(this.hilight,this),this.onHoverOut=u(this.onHoverOut,this),this.onHoverMove=u(this.onHoverMove,this),this.onGridClick=u(this.onGridClick,this);if(!(this instanceof t.Line))return new t.Line(e);n.__super__.constructor.call(this,e)}return o(n,e),n.prototype.init=function(){this.pointGrow=Raphael.animation({r:this.options.pointSize+3},25,"linear"),this.pointShrink=Raphael.animation({r:this.options.pointSize},25,"linear");if(this.options.hideHover!=="always")return this.hover=new t.Hover({parent:this.el}),this.on("hovermove",this.onHoverMove),this.on("hoverout",this.onHoverOut),this.on("gridclick",this.onGridClick)},n.prototype.defaults={lineWidth:3,pointSize:4,lineColors:["#0b62a4","#7A92A3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],pointWidths:[1],pointStrokeColors:["#ffffff"],pointFillColors:[],smooth:!0,xLabels:"auto",xLabelFormat:null,xLabelMargin:24,continuousLine:!0,hideHover:!1},n.prototype.calc=function(){return this.calcPoints(),this.generatePaths()},n.prototype.calcPoints=function(){var e,t,n,r,i,s;i=this.data,s=[];for(n=0,r=i.length;n<r;n++)e=i[n],e._x=this.transX(e.x),e._y=function(){var n,r,i,s;i=e.y,s=[];for(n=0,r=i.length;n<r;n++)t=i[n],t!=null?s.push(this.transY(t)):s.push(t);return s}.call(this),s.push(e._ymax=Math.min.apply(null,[this.bottom].concat(function(){var n,r,i,s;i=e._y,s=[];for(n=0,r=i.length;n<r;n++)t=i[n],t!=null&&s.push(t);return s}())));return s},n.prototype.hitTest=function(e,t){var n,r,i,s,o;if(this.data.length===0)return null;o=this.data.slice(1);for(n=i=0,s=o.length;i<s;n=++i){r=o[n];if(e<(r._x+this.data[n]._x)/2)break}return n},n.prototype.onGridClick=function(e,t){var n;return n=this.hitTest(e,t),this.fire("click",n,this.options.data[n],e,t)},n.prototype.onHoverMove=function(e,t){var n;return n=this.hitTest(e,t),this.displayHoverForRow(n)},n.prototype.onHoverOut=function(){if(this.options.hideHover!==!1)return this.displayHoverForRow(null)},n.prototype.displayHoverForRow=function(e){var t;return e!=null?((t=this.hover).update.apply(t,this.hoverContentForRow(e)),this.hilight(e)):(this.hover.hide(),this.hilight())},n.prototype.hoverContentForRow=function(e){var t,n,r,i,s,o,u;r=this.data[e],t="<div class='morris-hover-row-label'>"+r.label+"</div>",u=r.y;for(n=s=0,o=u.length;s<o;n=++s)i=u[n],t+="<div class='morris-hover-point' style='color: "+this.colorFor(r,n,"label")+"'>\n "+this.options.labels[n]+":\n "+this.yLabelFormat(i)+"\n</div>";return typeof this.options.hoverCallback=="function"&&(t=this.options.hoverCallback(e,this.options,t)),[t,r._x,r._ymax]},n.prototype.generatePaths=function(){var e,n,r,i,s;return this.paths=function(){var o,u,f,l;l=[];for(r=o=0,u=this.options.ykeys.length;0<=u?o<u:o>u;r=0<=u?++o:--o)s=this.options.smooth===!0||(f=this.options.ykeys[r],a.call(this.options.smooth,f)>=0),n=function(){var e,t,n,s;n=this.data,s=[];for(e=0,t=n.length;e<t;e++)i=n[e],i._y[r]!==void 0&&s.push({x:i._x,y:i._y[r]});return s}.call(this),this.options.continuousLine&&(n=function(){var t,r,i;i=[];for(t=0,r=n.length;t<r;t++)e=n[t],e.y!==null&&i.push(e);return i}()),n.length>1?l.push(t.Line.createPath(n,s,this.bottom)):l.push(null);return l}.call(this)},n.prototype.draw=function(){this.options.axes&&this.drawXAxis(),this.drawSeries();if(this.options.hideHover===!1)return this.displayHoverForRow(this.data.length-1)},n.prototype.drawXAxis=function(){var e,n,r,i,s,o,u,a,f,l,c=this;u=this.bottom+this.options.padding/2,s=null,i=null,e=function(e,t){var n,r,o,a,f;return n=c.drawXAxisLabel(c.transX(t),u,e),f=n.getBBox(),n.transform("r"+ -c.options.xLabelAngle),r=n.getBBox(),n.transform("t0,"+r.height/2+"..."),c.options.xLabelAngle!==0&&(a=-0.5*f.width*Math.cos(c.options.xLabelAngle*Math.PI/180),n.transform("t"+a+",0...")),r=n.getBBox(),(s==null||s>=r.x+r.width||i!=null&&i>=r.x)&&r.x>=0&&r.x+r.width<c.el.width()?(c.options.xLabelAngle!==0&&(o=1.25*c.options.gridTextSize/Math.sin(c.options.xLabelAngle*Math.PI/180),i=r.x-o),s=r.x-c.options.xLabelMargin):n.remove()},this.options.parseTime?this.data.length===1&&this.options.xLabels==="auto"?r=[[this.data[0].label,this.data[0].x]]:r=t.labelSeries(this.xmin,this.xmax,this.width,this.options.xLabels,this.options.xLabelFormat):r=function(){var e,t,n,r;n=this.data,r=[];for(e=0,t=n.length;e<t;e++)o=n[e],r.push([o.label,o.x]);return r}.call(this),r.reverse(),l=[];for(a=0,f=r.length;a<f;a++)n=r[a],l.push(e(n[0],n[1]));return l},n.prototype.drawSeries=function(){var e,t,n,r,i,s;this.seriesPoints=[];for(e=t=r=this.options.ykeys.length-1;r<=0?t<=0:t>=0;e=r<=0?++t:--t)this._drawLineFor(e);s=[];for(e=n=i=this.options.ykeys.length-1;i<=0?n<=0:n>=0;e=i<=0?++n:--n)s.push(this._drawPointFor(e));return s},n.prototype._drawPointFor=function(e){var t,n,r,i,s,o;this.seriesPoints[e]=[],s=this.data,o=[];for(r=0,i=s.length;r<i;r++)n=s[r],t=null,n._y[e]!=null&&(t=this.drawLinePoint(n._x,n._y[e],this.options.pointSize,this.colorFor(n,e,"point"),e)),o.push(this.seriesPoints[e].push(t));return o},n.prototype._drawLineFor=function(e){var t;t=this.paths[e];if(t!==null)return this.drawLinePath(t,this.colorFor(null,e,"line"))},n.createPath=function(e,n,r){var i,s,o,u,a,f,l,c,h,p,d,v,m,g;l="",n&&(o=t.Line.gradients(e)),c={y:null};for(u=m=0,g=e.length;m<g;u=++m){i=e[u];if(i.y!=null)if(c.y!=null)n?(s=o[u],f=o[u-1],a=(i.x-c.x)/4,h=c.x+a,d=Math.min(r,c.y+a*f),p=i.x-a,v=Math.min(r,i.y-a*s),l+="C"+h+","+d+","+p+","+v+","+i.x+","+i.y):l+="L"+i.x+","+i.y;else if(!n||o[u]!=null)l+="M"+i.x+","+i.y;c=i}return l},n.gradients=function(e){var t,n,r,i,s,o,u,a;n=function(e,t){return(e.y-t.y)/(e.x-t.x)},a=[];for(r=o=0,u=e.length;o<u;r=++o)t=e[r],t.y!=null?(i=e[r+1]||{y:null},s=e[r-1]||{y:null},s.y!=null&&i.y!=null?a.push(n(s,i)):s.y!=null?a.push(n(s,t)):i.y!=null?a.push(n(t,i)):a.push(null)):a.push(null);return a},n.prototype.hilight=function(e){var t,n,r,i,s;if(this.prevHilight!==null&&this.prevHilight!==e)for(t=n=0,i=this.seriesPoints.length-1;0<=i?n<=i:n>=i;t=0<=i?++n:--n)this.seriesPoints[t][this.prevHilight]&&this.seriesPoints[t][this.prevHilight].animate(this.pointShrink);if(e!==null&&this.prevHilight!==e)for(t=r=0,s=this.seriesPoints.length-1;0<=s?r<=s:r>=s;t=0<=s?++r:--r)this.seriesPoints[t][e]&&this.seriesPoints[t][e].animate(this.pointGrow);return this.prevHilight=e},n.prototype.colorFor=function(e,t,n){return typeof this.options.lineColors=="function"?this.options.lineColors.call(this,e,t,n):n==="point"?this.options.pointFillColors[t%this.options.pointFillColors.length]||this.options.lineColors[t%this.options.lineColors.length]:this.options.lineColors[t%this.options.lineColors.length]},n.prototype.drawXAxisLabel=function(e,t,n){return this.raphael.text(e,t,n).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).attr("fill",this.options.gridTextColor)},n.prototype.drawLinePath=function(e,t){return this.raphael.path(e).attr("stroke",t).attr("stroke-width",this.options.lineWidth)},n.prototype.drawLinePoint=function(e,t,n,r,i){return this.raphael.circle(e,t,n).attr("fill",r).attr("stroke-width",this.strokeWidthForSeries(i)).attr("stroke",this.strokeForSeries(i))},n.prototype.strokeWidthForSeries=function(e){return this.options.pointWidths[e%this.options.pointWidths.length]},n.prototype.strokeForSeries=function(e){return this.options.pointStrokeColors[e%this.options.pointStrokeColors.length]},n}(t.Grid),t.labelSeries=function(n,r,i,s,o){var u,a,f,l,c,h,p,d,v,m,g;f=200*(r-n)/i,a=new Date(n),p=t.LABEL_SPECS[s];if(p===void 0){g=t.AUTO_LABEL_ORDER;for(v=0,m=g.length;v<m;v++){l=g[v],h=t.LABEL_SPECS[l];if(f>=h.span){p=h;break}}}p===void 0&&(p=t.LABEL_SPECS.second),o&&(p=e.extend({},p,{fmt:o})),u=p.start(a),c=[];while((d=u.getTime())<=r)d>=n&&c.push([p.fmt(u),d]),p.incr(u);return c},n=function(e){return{span:e*60*1e3,start:function(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours())},fmt:function(e){return""+t.pad2(e.getHours())+":"+t.pad2(e.getMinutes())},incr:function(t){return t.setUTCMinutes(t.getUTCMinutes()+e)}}},r=function(e){return{span:e*1e3,start:function(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes())},fmt:function(e){return""+t.pad2(e.getHours())+":"+t.pad2(e.getMinutes())+":"+t.pad2(e.getSeconds())},incr:function(t){return t.setUTCSeconds(t.getUTCSeconds()+e)}}},t.LABEL_SPECS={decade:{span:1728e8,start:function(e){return new Date(e.getFullYear()-e.getFullYear()%10,0,1)},fmt:function(e){return""+e.getFullYear()},incr:function(e){return e.setFullYear(e.getFullYear()+10)}},year:{span:1728e7,start:function(e){return new Date(e.getFullYear(),0,1)},fmt:function(e){return""+e.getFullYear()},incr:function(e){return e.setFullYear(e.getFullYear()+1)}},month:{span:24192e5,start:function(e){return new Date(e.getFullYear(),e.getMonth(),1)},fmt:function(e){return""+e.getFullYear()+"-"+t.pad2(e.getMonth()+1)},incr:function(e){return e.setMonth(e.getMonth()+1)}},day:{span:864e5,start:function(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate())},fmt:function(e){return""+e.getFullYear()+"-"+t.pad2(e.getMonth()+1)+"-"+t.pad2(e.getDate())},incr:function(e){return e.setDate(e.getDate()+1)}},hour:n(60),"30min":n(30),"15min":n(15),"10min":n(10),"5min":n(5),minute:n(1),"30sec":r(30),"15sec":r(15),"10sec":r(10),"5sec":r(5),second:r(1)},t.AUTO_LABEL_ORDER=["decade","year","month","day","hour","30min","15min","10min","5min","minute","30sec","15sec","10sec","5sec","second"],t.Area=function(n){function i(n){var s;if(!(this instanceof t.Area))return new t.Area(n);s=e.extend({},r,n),this.cumulative=!s.behaveLikeLine,s.fillOpacity==="auto"&&(s.fillOpacity=s.behaveLikeLine?.8:1),i.__super__.constructor.call(this,s)}var r;return o(i,n),r={fillOpacity:"auto",behaveLikeLine:!1},i.prototype.calcPoints=function(){var e,t,n,r,i,s,o;s=this.data,o=[];for(r=0,i=s.length;r<i;r++)e=s[r],e._x=this.transX(e.x),t=0,e._y=function(){var r,i,s,o;s=e.y,o=[];for(r=0,i=s.length;r<i;r++)n=s[r],this.options.behaveLikeLine?o.push(this.transY(n)):(t+=n||0,o.push(this.transY(t)));return o}.call(this),o.push(e._ymax=Math.max.apply(Math,e._y));return o},i.prototype.drawSeries=function(){var e,t,n,r,i,s,o,u,a,f,l;this.seriesPoints=[],this.options.behaveLikeLine?t=function(){a=[];for(var e=0,t=this.options.ykeys.length-1;0<=t?e<=t:e>=t;0<=t?e++:e--)a.push(e);return a}.apply(this):t=function(){f=[];for(var e=u=this.options.ykeys.length-1;u<=0?e<=0:e>=0;u<=0?e++:e--)f.push(e);return f}.apply(this),l=[];for(i=0,s=t.length;i<s;i++)e=t[i],this._drawFillFor(e),this._drawLineFor(e),l.push(this._drawPointFor(e));return l},i.prototype._drawFillFor=function(e){var t;t=this.paths[e];if(t!==null)return t+="L"+this.transX(this.xmax)+","+this.bottom+"L"+this.transX(this.xmin)+","+this.bottom+"Z",this.drawFilledPath(t,this.fillForSeries(e))},i.prototype.fillForSeries=function(e){var t;return t=Raphael.rgb2hsl(this.colorFor(this.data[e],e,"line")),Raphael.hsl(t.h,this.options.behaveLikeLine?t.s*.9:t.s*.75,Math.min(.98,this.options.behaveLikeLine?t.l*1.2:t.l*1.25))},i.prototype.drawFilledPath=function(e,t){return this.raphael.path(e).attr("fill",t).attr("fill-opacity",this.options.fillOpacity).attr("stroke-width",0)},i}(t.Line),t.Bar=function(n){function r(n){this.onHoverOut=u(this.onHoverOut,this),this.onHoverMove=u(this.onHoverMove,this),this.onGridClick=u(this.onGridClick,this);if(!(this instanceof t.Bar))return new t.Bar(n);r.__super__.constructor.call(this,e.extend({},n,{parseTime:!1}))}return o(r,n),r.prototype.init=function(){this.cumulative=this.options.stacked;if(this.options.hideHover!=="always")return this.hover=new t.Hover({parent:this.el}),this.on("hovermove",this.onHoverMove),this.on("hoverout",this.onHoverOut),this.on("gridclick",this.onGridClick)},r.prototype.defaults={barSizeRatio:.75,barGap:3,barColors:["#0b62a4","#7a92a3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],xLabelMargin:50},r.prototype.calc=function(){var e;this.calcBars();if(this.options.hideHover===!1)return(e=this.hover).update.apply(e,this.hoverContentForRow(this.data.length-1))},r.prototype.calcBars=function(){var e,t,n,r,i,s,o;s=this.data,o=[];for(e=r=0,i=s.length;r<i;e=++r)t=s[e],t._x=this.left+this.width*(e+.5)/this.data.length,o.push(t._y=function(){var e,r,i,s;i=t.y,s=[];for(e=0,r=i.length;e<r;e++)n=i[e],n!=null?s.push(this.transY(n)):s.push(null);return s}.call(this));return o},r.prototype.draw=function(){return this.options.axes&&this.drawXAxis(),this.drawSeries()},r.prototype.drawXAxis=function(){var e,t,n,r,i,s,o,u,a,f,l,c,h;f=this.bottom+this.options.padding/2,o=null,s=null,h=[];for(e=l=0,c=this.data.length;0<=c?l<c:l>c;e=0<=c?++l:--l)u=this.data[this.data.length-1-e],t=this.drawXAxisLabel(u._x,f,u.label),a=t.getBBox(),t.transform("r"+ -this.options.xLabelAngle),n=t.getBBox(),t.transform("t0,"+n.height/2+"..."),this.options.xLabelAngle!==0&&(i=-0.5*a.width*Math.cos(this.options.xLabelAngle*Math.PI/180),t.transform("t"+i+",0...")),(o==null||o>=n.x+n.width||s!=null&&s>=n.x)&&n.x>=0&&n.x+n.width<this.el.width()?(this.options.xLabelAngle!==0&&(r=1.25*this.options.gridTextSize/Math.sin(this.options.xLabelAngle*Math.PI/180),s=n.x-r),h.push(o=n.x-this.options.xLabelMargin)):h.push(t.remove());return h},r.prototype.drawSeries=function(){var e,t,n,r,i,s,o,u,a,f,l,c,h,p;return n=this.width/this.options.data.length,u=this.options.stacked!=null?1:this.options.ykeys.length,e=(n*this.options.barSizeRatio-this.options.barGap*(u-1))/u,o=n*(1-this.options.barSizeRatio)/2,p=this.ymin<=0&&this.ymax>=0?this.transY(0):null,this.bars=function(){var u,d,v,m;v=this.data,m=[];for(r=u=0,d=v.length;u<d;r=++u)a=v[r],i=0,m.push(function(){var u,d,v,m;v=a._y,m=[];for(f=u=0,d=v.length;u<d;f=++u)h=v[f],h!==null?(p?(c=Math.min(h,p),t=Math.max(h,p)):(c=h,t=this.bottom),s=this.left+r*n+o,this.options.stacked||(s+=f*(e+this.options.barGap)),l=t-c,this.options.stacked&&(c-=i),this.drawBar(s,c,e,l,this.colorFor(a,f,"bar")),m.push(i+=l)):m.push(null);return m}.call(this));return m}.call(this)},r.prototype.colorFor=function(e,t,n){var r,i;return typeof this.options.barColors=="function"?(r={x:e.x,y:e.y[t],label:e.label},i={index:t,key:this.options.ykeys[t],label:this.options.labels[t]},this.options.barColors.call(this,r,i,n)):this.options.barColors[t%this.options.barColors.length]},r.prototype.hitTest=function(e,t){return this.data.length===0?null:(e=Math.max(Math.min(e,this.right),this.left),Math.min(this.data.length-1,Math.floor((e-this.left)/(this.width/this.data.length))))},r.prototype.onGridClick=function(e,t){var n;return n=this.hitTest(e,t),this.fire("click",n,this.options.data[n],e,t)},r.prototype.onHoverMove=function(e,t){var n,r;return n=this.hitTest(e,t),(r=this.hover).update.apply(r,this.hoverContentForRow(n))},r.prototype.onHoverOut=function(){if(this.options.hideHover!==!1)return this.hover.hide()},r.prototype.hoverContentForRow=function(e){var t,n,r,i,s,o,u,a;r=this.data[e],t="<div class='morris-hover-row-label'>"+r.label+"</div>",a=r.y;for(n=o=0,u=a.length;o<u;n=++o)s=a[n],t+="<div class='morris-hover-point' style='color: "+this.colorFor(r,n,"label")+"'>\n "+this.options.labels[n]+":\n "+this.yLabelFormat(s)+"\n</div>";return typeof this.options.hoverCallback=="function"&&(t=this.options.hoverCallback(e,this.options,t)),i=this.left+(e+.5)*this.width/this.data.length,[t,i]},r.prototype.drawXAxisLabel=function(e,t,n){var r;return r=this.raphael.text(e,t,n).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).attr("fill",this.options.gridTextColor)},r.prototype.drawBar=function(e,t,n,r,i){return this.raphael.rect(e,t,n,r).attr("fill",i).attr("stroke-width",0)},r}(t.Grid),t.Donut=function(n){function r(n){this.select=u(this.select,this),this.click=u(this.click,this);var r;if(!(this instanceof t.Donut))return new t.Donut(n);typeof n.element=="string"?this.el=e(document.getElementById(n.element)):this.el=e(n.element),this.options=e.extend({},this.defaults,n);if(this.el===null||this.el.length===0)throw new Error("Graph placeholder not found.");if(n.data===void 0||n.data.length===0)return;this.data=n.data,this.values=function(){var e,t,n,i;n=this.data,i=[];for(e=0,t=n.length;e<t;e++)r=n[e],i.push(parseFloat(r.value));return i}.call(this),this.redraw()}return o(r,n),r.prototype.defaults={colors:["#0B62A4","#3980B5","#679DC6","#95BBD7","#B0CCE1","#095791","#095085","#083E67","#052C48","#042135"],backgroundColor:"#FFFFFF",labelColor:"#000000",formatter:t.commas},r.prototype.redraw=function(){var e,n,r,i,s,o,u,a,f,l,c,h,p,d,v,m,g,y,b,w,E,S,x;this.el.empty(),this.raphael=new Raphael(this.el[0]),n=this.el.width()/2,r=this.el.height()/2,p=(Math.min(n,r)-10)/3,c=0,w=this.values;for(d=0,g=w.length;d<g;d++)h=w[d],c+=h;a=5/(2*p),e=1.9999*Math.PI-a*this.data.length,o=0,s=0,this.segments=[],E=this.values;for(i=v=0,y=E.length;v<y;i=++v)h=E[i],f=o+a+e*(h/c),l=new t.DonutSegment(n,r,p*2,p,o,f,this.options.colors[s%this.options.colors.length],this.options.backgroundColor,s,this.raphael),l.render(),this.segments.push(l),l.on("hover",this.select),l.on("click",this.click),o=f,s+=1;this.text1=this.drawEmptyDonutLabel(n,r-10,this.options.labelColor,15,800),this.text2=this.drawEmptyDonutLabel(n,r+10,this.options.labelColor,14),u=Math.max.apply(null,function(){var e,t,n,r;n=this.values,r=[];for(e=0,t=n.length;e<t;e++)h=n[e],r.push(h);return r}.call(this)),s=0,S=this.values,x=[];for(m=0,b=S.length;m<b;m++){h=S[m];if(h===u){this.select(s);break}x.push(s+=1)}return x},r.prototype.click=function(e){return this.fire("click",e,this.data[e])},r.prototype.select=function(e){var t,n,r,i,s,o;o=this.segments;for(i=0,s=o.length;i<s;i++)n=o[i],n.deselect();return r=this.segments[e],r.select(),t=this.data[e],this.setLabels(t.label,this.options.formatter(t.value,t))},r.prototype.setLabels=function(e,t){var n,r,i,s,o,u,a,f;return n=(Math.min(this.el.width()/2,this.el.height()/2)-10)*2/3,s=1.8*n,i=n/2,r=n/3,this.text1.attr({text:e,transform:""}),o=this.text1.getBBox(),u=Math.min(s/o.width,i/o.height),this.text1.attr({transform:"S"+u+","+u+","+(o.x+o.width/2)+","+(o.y+o.height)}),this.text2.attr({text:t,transform:""}),a=this.text2.getBBox(),f=Math.min(s/a.width,r/a.height),this.text2.attr({transform:"S"+f+","+f+","+(a.x+a.width/2)+","+a.y})},r.prototype.drawEmptyDonutLabel=function(e,t,n,r,i){var s;return s=this.raphael.text(e,t,"").attr("font-size",r).attr("fill",n),i!=null&&s.attr("font-weight",i),s},r}(t.EventEmitter),t.DonutSegment=function(e){function t(e,t,n,r,i,s,o,a,f,l){this.cx=e,this.cy=t,this.inner=n,this.outer=r,this.color=o,this.backgroundColor=a,this.index=f,this.raphael=l,this.deselect=u(this.deselect,this),this.select=u(this.select,this),this.sin_p0=Math.sin(i),this.cos_p0=Math.cos(i),this.sin_p1=Math.sin(s),this.cos_p1=Math.cos(s),this.is_long=s-i>Math.PI?1:0,this.path=this.calcSegment(this.inner+3,this.inner+this.outer-5),this.selectedPath=this.calcSegment(this.inner+3,this.inner+this.outer),this.hilight=this.calcArc(this.inner)}return o(t,e),t.prototype.calcArcPoints=function(e){return[this.cx+e*this.sin_p0,this.cy+e*this.cos_p0,this.cx+e*this.sin_p1,this.cy+e*this.cos_p1]},t.prototype.calcSegment=function(e,t){var n,r,i,s,o,u,a,f,l,c;return l=this.calcArcPoints(e),n=l[0],i=l[1],r=l[2],s=l[3],c=this.calcArcPoints(t),o=c[0],a=c[1],u=c[2],f=c[3],"M"+n+","+i+("A"+e+","+e+",0,"+this.is_long+",0,"+r+","+s)+("L"+u+","+f)+("A"+t+","+t+",0,"+this.is_long+",1,"+o+","+a)+"Z"},t.prototype.calcArc=function(e){var t,n,r,i,s;return s=this.calcArcPoints(e),t=s[0],r=s[1],n=s[2],i=s[3],"M"+t+","+r+("A"+e+","+e+",0,"+this.is_long+",0,"+n+","+i)},t.prototype.render=function(){var e=this;return this.arc=this.drawDonutArc(this.hilight,this.color),this.seg=this.drawDonutSegment(this.path,this.color,this.backgroundColor,function(){return e.fire("hover",e.index)},function(){return e.fire("click",e.index)})},t.prototype.drawDonutArc=function(e,t){return this.raphael.path(e).attr({stroke:t,"stroke-width":2,opacity:0})},t.prototype.drawDonutSegment=function(e,t,n,r,i){return this.raphael.path(e).attr({fill:t,stroke:n,"stroke-width":3}).hover(r).click(i)},t.prototype.select=function(){if(!this.selected)return this.seg.animate({path:this.selectedPath},150,"<>"),this.arc.animate({opacity:1},150,"<>"),this.selected=!0},t.prototype.deselect=function(){if(this.selected)return this.seg.animate({path:this.path},150,"<>"),this.arc.animate({opacity:0},150,"<>"),this.selected=!1},t}(t.EventEmitter)}).call(this);
\ No newline at end of file
src/web/js/raphael.min.js 11(+11 -0)
diff --git a/src/web/js/raphael.min.js b/src/web/js/raphael.min.js
new file mode 100644
index 0000000..404f8b2
--- /dev/null
+++ b/src/web/js/raphael.min.js
@@ -0,0 +1,11 @@
+// ┌────────────────────────────────────────────────────────────────────┐ \\
+// │ Raphaël 2.1.2 - JavaScript Vector Library │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com) │ \\
+// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com) │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
+// └────────────────────────────────────────────────────────────────────┘ \\
+!function(a){var b,c,d="0.4.2",e="hasOwnProperty",f=/[\.\/]/,g="*",h=function(){},i=function(a,b){return a-b},j={n:{}},k=function(a,d){a=String(a);var e,f=c,g=Array.prototype.slice.call(arguments,2),h=k.listeners(a),j=0,l=[],m={},n=[],o=b;b=a,c=0;for(var p=0,q=h.length;q>p;p++)"zIndex"in h[p]&&(l.push(h[p].zIndex),h[p].zIndex<0&&(m[h[p].zIndex]=h[p]));for(l.sort(i);l[j]<0;)if(e=m[l[j++]],n.push(e.apply(d,g)),c)return c=f,n;for(p=0;q>p;p++)if(e=h[p],"zIndex"in e)if(e.zIndex==l[j]){if(n.push(e.apply(d,g)),c)break;do if(j++,e=m[l[j]],e&&n.push(e.apply(d,g)),c)break;while(e)}else m[e.zIndex]=e;else if(n.push(e.apply(d,g)),c)break;return c=f,b=o,n.length?n:null};k._events=j,k.listeners=function(a){var b,c,d,e,h,i,k,l,m=a.split(f),n=j,o=[n],p=[];for(e=0,h=m.length;h>e;e++){for(l=[],i=0,k=o.length;k>i;i++)for(n=o[i].n,c=[n[m[e]],n[g]],d=2;d--;)b=c[d],b&&(l.push(b),p=p.concat(b.f||[]));o=l}return p},k.on=function(a,b){if(a=String(a),"function"!=typeof b)return function(){};for(var c=a.split(f),d=j,e=0,g=c.length;g>e;e++)d=d.n,d=d.hasOwnProperty(c[e])&&d[c[e]]||(d[c[e]]={n:{}});for(d.f=d.f||[],e=0,g=d.f.length;g>e;e++)if(d.f[e]==b)return h;return d.f.push(b),function(a){+a==+a&&(b.zIndex=+a)}},k.f=function(a){var b=[].slice.call(arguments,1);return function(){k.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},k.stop=function(){c=1},k.nt=function(a){return a?new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)").test(b):b},k.nts=function(){return b.split(f)},k.off=k.unbind=function(a,b){if(!a)return k._events=j={n:{}},void 0;var c,d,h,i,l,m,n,o=a.split(f),p=[j];for(i=0,l=o.length;l>i;i++)for(m=0;m<p.length;m+=h.length-2){if(h=[m,1],c=p[m].n,o[i]!=g)c[o[i]]&&h.push(c[o[i]]);else for(d in c)c[e](d)&&h.push(c[d]);p.splice.apply(p,h)}for(i=0,l=p.length;l>i;i++)for(c=p[i];c.n;){if(b){if(c.f){for(m=0,n=c.f.length;n>m;m++)if(c.f[m]==b){c.f.splice(m,1);break}!c.f.length&&delete c.f}for(d in c.n)if(c.n[e](d)&&c.n[d].f){var q=c.n[d].f;for(m=0,n=q.length;n>m;m++)if(q[m]==b){q.splice(m,1);break}!q.length&&delete c.n[d].f}}else{delete c.f;for(d in c.n)c.n[e](d)&&c.n[d].f&&delete c.n[d].f}c=c.n}},k.once=function(a,b){var c=function(){return k.unbind(a,c),b.apply(this,arguments)};return k.on(a,c)},k.version=d,k.toString=function(){return"You are running Eve "+d},"undefined"!=typeof module&&module.exports?module.exports=k:"undefined"!=typeof define?define("eve",[],function(){return k}):a.eve=k}(this),function(a,b){"function"==typeof define&&define.amd?define(["eve"],function(c){return b(a,c)}):b(a,a.eve)}(this,function(a,b){function c(a){if(c.is(a,"function"))return u?a():b.on("raphael.DOMload",a);if(c.is(a,V))return c._engine.create[D](c,a.splice(0,3+c.is(a[0],T))).add(a);var d=Array.prototype.slice.call(arguments,0);if(c.is(d[d.length-1],"function")){var e=d.pop();return u?e.call(c._engine.create[D](c,d)):b.on("raphael.DOMload",function(){e.call(c._engine.create[D](c,d))})}return c._engine.create[D](c,arguments)}function d(a){if("function"==typeof a||Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[z](c)&&(b[c]=d(a[c]));return b}function e(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function f(a,b,c){function d(){var f=Array.prototype.slice.call(arguments,0),g=f.join("␀"),h=d.cache=d.cache||{},i=d.count=d.count||[];return h[z](g)?(e(i,g),c?c(h[g]):h[g]):(i.length>=1e3&&delete h[i.shift()],i.push(g),h[g]=a[D](b,f),c?c(h[g]):h[g])}return d}function g(){return this.hex}function h(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function i(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function j(a,b,c,d,e,f,g,h,j){null==j&&(j=1),j=j>1?1:0>j?0:j;for(var k=j/2,l=12,m=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],n=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],o=0,p=0;l>p;p++){var q=k*m[p]+k,r=i(q,a,c,e,g),s=i(q,b,d,f,h),t=r*r+s*s;o+=n[p]*N.sqrt(t)}return k*o}function k(a,b,c,d,e,f,g,h,i){if(!(0>i||j(a,b,c,d,e,f,g,h)<i)){var k,l=1,m=l/2,n=l-m,o=.01;for(k=j(a,b,c,d,e,f,g,h,n);Q(k-i)>o;)m/=2,n+=(i>k?1:-1)*m,k=j(a,b,c,d,e,f,g,h,n);return n}}function l(a,b,c,d,e,f,g,h){if(!(O(a,c)<P(e,g)||P(a,c)>O(e,g)||O(b,d)<P(f,h)||P(b,d)>O(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+P(a,c).toFixed(2)||n>+O(a,c).toFixed(2)||n<+P(e,g).toFixed(2)||n>+O(e,g).toFixed(2)||o<+P(b,d).toFixed(2)||o>+O(b,d).toFixed(2)||o<+P(f,h).toFixed(2)||o>+O(f,h).toFixed(2)))return{x:l,y:m}}}}function m(a,b,d){var e=c.bezierBBox(a),f=c.bezierBBox(b);if(!c.isBBoxIntersect(e,f))return d?0:[];for(var g=j.apply(0,a),h=j.apply(0,b),i=O(~~(g/5),1),k=O(~~(h/5),1),m=[],n=[],o={},p=d?0:[],q=0;i+1>q;q++){var r=c.findDotsAtSegment.apply(c,a.concat(q/i));m.push({x:r.x,y:r.y,t:q/i})}for(q=0;k+1>q;q++)r=c.findDotsAtSegment.apply(c,b.concat(q/k)),n.push({x:r.x,y:r.y,t:q/k});for(q=0;i>q;q++)for(var s=0;k>s;s++){var t=m[q],u=m[q+1],v=n[s],w=n[s+1],x=Q(u.x-t.x)<.001?"y":"x",y=Q(w.x-v.x)<.001?"y":"x",z=l(t.x,t.y,u.x,u.y,v.x,v.y,w.x,w.y);if(z){if(o[z.x.toFixed(4)]==z.y.toFixed(4))continue;o[z.x.toFixed(4)]=z.y.toFixed(4);var A=t.t+Q((z[x]-t[x])/(u[x]-t[x]))*(u.t-t.t),B=v.t+Q((z[y]-v[y])/(w[y]-v[y]))*(w.t-v.t);A>=0&&1.001>=A&&B>=0&&1.001>=B&&(d?p++:p.push({x:z.x,y:z.y,t1:P(A,1),t2:P(B,1)}))}}return p}function n(a,b,d){a=c._path2curve(a),b=c._path2curve(b);for(var e,f,g,h,i,j,k,l,n,o,p=d?0:[],q=0,r=a.length;r>q;q++){var s=a[q];if("M"==s[0])e=i=s[1],f=j=s[2];else{"C"==s[0]?(n=[e,f].concat(s.slice(1)),e=n[6],f=n[7]):(n=[e,f,e,f,i,j,i,j],e=i,f=j);for(var t=0,u=b.length;u>t;t++){var v=b[t];if("M"==v[0])g=k=v[1],h=l=v[2];else{"C"==v[0]?(o=[g,h].concat(v.slice(1)),g=o[6],h=o[7]):(o=[g,h,g,h,k,l,k,l],g=k,h=l);var w=m(n,o,d);if(d)p+=w;else{for(var x=0,y=w.length;y>x;x++)w[x].segment1=q,w[x].segment2=t,w[x].bez1=n,w[x].bez2=o;p=p.concat(w)}}}}}return p}function o(a,b,c,d,e,f){null!=a?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function p(){return this.x+H+this.y+H+this.width+" × "+this.height}function q(a,b,c,d,e,f){function g(a){return((l*a+k)*a+j)*a}function h(a,b){var c=i(a,b);return((o*c+n)*c+m)*c}function i(a,b){var c,d,e,f,h,i;for(e=a,i=0;8>i;i++){if(f=g(e)-a,Q(f)<b)return e;if(h=(3*l*e+2*k)*e+j,Q(h)<1e-6)break;e-=f/h}if(c=0,d=1,e=a,c>e)return c;if(e>d)return d;for(;d>c;){if(f=g(e),Q(f-a)<b)return e;a>f?c=e:d=e,e=(d-c)/2+c}return e}var j=3*b,k=3*(d-b)-j,l=1-j-k,m=3*c,n=3*(e-c)-m,o=1-m-n;return h(a,1/(200*f))}function r(a,b){var c=[],d={};if(this.ms=b,this.times=1,a){for(var e in a)a[z](e)&&(d[_(e)]=a[e],c.push(_(e)));c.sort(lb)}this.anim=d,this.top=c[c.length-1],this.percents=c}function s(a,d,e,f,g,h){e=_(e);var i,j,k,l,m,n,p=a.ms,r={},s={},t={};if(f)for(v=0,x=ic.length;x>v;v++){var u=ic[v];if(u.el.id==d.id&&u.anim==a){u.percent!=e?(ic.splice(v,1),k=1):j=u,d.attr(u.totalOrigin);break}}else f=+s;for(var v=0,x=a.percents.length;x>v;v++){if(a.percents[v]==e||a.percents[v]>f*a.top){e=a.percents[v],m=a.percents[v-1]||0,p=p/a.top*(e-m),l=a.percents[v+1],i=a.anim[e];break}f&&d.attr(a.anim[a.percents[v]])}if(i){if(j)j.initstatus=f,j.start=new Date-j.ms*f;else{for(var y in i)if(i[z](y)&&(db[z](y)||d.paper.customAttributes[z](y)))switch(r[y]=d.attr(y),null==r[y]&&(r[y]=cb[y]),s[y]=i[y],db[y]){case T:t[y]=(s[y]-r[y])/p;break;case"colour":r[y]=c.getRGB(r[y]);var A=c.getRGB(s[y]);t[y]={r:(A.r-r[y].r)/p,g:(A.g-r[y].g)/p,b:(A.b-r[y].b)/p};break;case"path":var B=Kb(r[y],s[y]),C=B[1];for(r[y]=B[0],t[y]=[],v=0,x=r[y].length;x>v;v++){t[y][v]=[0];for(var D=1,F=r[y][v].length;F>D;D++)t[y][v][D]=(C[v][D]-r[y][v][D])/p}break;case"transform":var G=d._,H=Pb(G[y],s[y]);if(H)for(r[y]=H.from,s[y]=H.to,t[y]=[],t[y].real=!0,v=0,x=r[y].length;x>v;v++)for(t[y][v]=[r[y][v][0]],D=1,F=r[y][v].length;F>D;D++)t[y][v][D]=(s[y][v][D]-r[y][v][D])/p;else{var K=d.matrix||new o,L={_:{transform:G.transform},getBBox:function(){return d.getBBox(1)}};r[y]=[K.a,K.b,K.c,K.d,K.e,K.f],Nb(L,s[y]),s[y]=L._.transform,t[y]=[(L.matrix.a-K.a)/p,(L.matrix.b-K.b)/p,(L.matrix.c-K.c)/p,(L.matrix.d-K.d)/p,(L.matrix.e-K.e)/p,(L.matrix.f-K.f)/p]}break;case"csv":var M=I(i[y])[J](w),N=I(r[y])[J](w);if("clip-rect"==y)for(r[y]=N,t[y]=[],v=N.length;v--;)t[y][v]=(M[v]-r[y][v])/p;s[y]=M;break;default:for(M=[][E](i[y]),N=[][E](r[y]),t[y]=[],v=d.paper.customAttributes[y].length;v--;)t[y][v]=((M[v]||0)-(N[v]||0))/p}var O=i.easing,P=c.easing_formulas[O];if(!P)if(P=I(O).match(Z),P&&5==P.length){var Q=P;P=function(a){return q(a,+Q[1],+Q[2],+Q[3],+Q[4],p)}}else P=nb;if(n=i.start||a.start||+new Date,u={anim:a,percent:e,timestamp:n,start:n+(a.del||0),status:0,initstatus:f||0,stop:!1,ms:p,easing:P,from:r,diff:t,to:s,el:d,callback:i.callback,prev:m,next:l,repeat:h||a.times,origin:d.attr(),totalOrigin:g},ic.push(u),f&&!j&&!k&&(u.stop=!0,u.start=new Date-p*f,1==ic.length))return kc();k&&(u.start=new Date-u.ms*f),1==ic.length&&jc(kc)}b("raphael.anim.start."+d.id,d,a)}}function t(a){for(var b=0;b<ic.length;b++)ic[b].el.paper==a&&ic.splice(b--,1)}c.version="2.1.2",c.eve=b;var u,v,w=/[, ]+/,x={circle:1,rect:1,path:1,ellipse:1,text:1,image:1},y=/\{(\d+)\}/g,z="hasOwnProperty",A={doc:document,win:a},B={was:Object.prototype[z].call(A.win,"Raphael"),is:A.win.Raphael},C=function(){this.ca=this.customAttributes={}},D="apply",E="concat",F="ontouchstart"in A.win||A.win.DocumentTouch&&A.doc instanceof DocumentTouch,G="",H=" ",I=String,J="split",K="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[J](H),L={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},M=I.prototype.toLowerCase,N=Math,O=N.max,P=N.min,Q=N.abs,R=N.pow,S=N.PI,T="number",U="string",V="array",W=Object.prototype.toString,X=(c._ISURL=/^url\(['"]?([^\)]+?)['"]?\)$/i,/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i),Y={NaN:1,Infinity:1,"-Infinity":1},Z=/^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,$=N.round,_=parseFloat,ab=parseInt,bb=I.prototype.toUpperCase,cb=c._availableAttrs={"arrow-end":"none","arrow-start":"none",blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:'10px "Arial"',"font-family":'"Arial"',"font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/","letter-spacing":0,opacity:1,path:"M0,0",r:0,rx:0,ry:0,src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",transform:"",width:0,x:0,y:0},db=c._availableAnimAttrs={blur:T,"clip-rect":"csv",cx:T,cy:T,fill:"colour","fill-opacity":T,"font-size":T,height:T,opacity:T,path:"path",r:T,rx:T,ry:T,stroke:"colour","stroke-opacity":T,"stroke-width":T,transform:"transform",width:T,x:T,y:T},eb=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,fb={hs:1,rg:1},gb=/,?([achlmqrstvxz]),?/gi,hb=/([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,ib=/([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,jb=/(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/gi,kb=(c._radial_gradient=/^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,{}),lb=function(a,b){return _(a)-_(b)},mb=function(){},nb=function(a){return a},ob=c._rectPath=function(a,b,c,d,e){return e?[["M",a+e,b],["l",c-2*e,0],["a",e,e,0,0,1,e,e],["l",0,d-2*e],["a",e,e,0,0,1,-e,e],["l",2*e-c,0],["a",e,e,0,0,1,-e,-e],["l",0,2*e-d],["a",e,e,0,0,1,e,-e],["z"]]:[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]]},pb=function(a,b,c,d){return null==d&&(d=c),[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]]},qb=c._getPath={path:function(a){return a.attr("path")},circle:function(a){var b=a.attrs;return pb(b.cx,b.cy,b.r)},ellipse:function(a){var b=a.attrs;return pb(b.cx,b.cy,b.rx,b.ry)},rect:function(a){var b=a.attrs;return ob(b.x,b.y,b.width,b.height,b.r)},image:function(a){var b=a.attrs;return ob(b.x,b.y,b.width,b.height)},text:function(a){var b=a._getBBox();return ob(b.x,b.y,b.width,b.height)},set:function(a){var b=a._getBBox();return ob(b.x,b.y,b.width,b.height)}},rb=c.mapPath=function(a,b){if(!b)return a;var c,d,e,f,g,h,i;for(a=Kb(a),e=0,g=a.length;g>e;e++)for(i=a[e],f=1,h=i.length;h>f;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a};if(c._g=A,c.type=A.win.SVGAngle||A.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML","VML"==c.type){var sb,tb=A.doc.createElement("div");if(tb.innerHTML='<v:shape adj="1"/>',sb=tb.firstChild,sb.style.behavior="url(#default#VML)",!sb||"object"!=typeof sb.adj)return c.type=G;tb=null}c.svg=!(c.vml="VML"==c.type),c._Paper=C,c.fn=v=C.prototype=c.prototype,c._id=0,c._oid=0,c.is=function(a,b){return b=M.call(b),"finite"==b?!Y[z](+a):"array"==b?a instanceof Array:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||"array"==b&&Array.isArray&&Array.isArray(a)||W.call(a).slice(8,-1).toLowerCase()==b},c.angle=function(a,b,d,e,f,g){if(null==f){var h=a-d,i=b-e;return h||i?(180+180*N.atan2(-i,-h)/S+360)%360:0}return c.angle(a,b,f,g)-c.angle(d,e,f,g)},c.rad=function(a){return a%360*S/180},c.deg=function(a){return 180*a/S%360},c.snapTo=function(a,b,d){if(d=c.is(d,"finite")?d:10,c.is(a,V)){for(var e=a.length;e--;)if(Q(a[e]-b)<=d)return a[e]}else{a=+a;var f=b%a;if(d>f)return b-f;if(f>a-d)return b-f+a}return b},c.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=0|16*N.random(),c="x"==a?b:8|3&b;return c.toString(16)}),c.setWindow=function(a){b("raphael.setWindow",c,A.win,a),A.win=a,A.doc=A.win.document,c._engine.initWin&&c._engine.initWin(A.win)};var ub=function(a){if(c.vml){var b,d=/^\s+|\s+$/g;try{var e=new ActiveXObject("htmlfile");e.write("<body>"),e.close(),b=e.body}catch(g){b=createPopup().document.body}var h=b.createTextRange();ub=f(function(a){try{b.style.color=I(a).replace(d,G);var c=h.queryCommandValue("ForeColor");return c=(255&c)<<16|65280&c|(16711680&c)>>>16,"#"+("000000"+c.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=A.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",A.doc.body.appendChild(i),ub=f(function(a){return i.style.color=a,A.doc.defaultView.getComputedStyle(i,G).getPropertyValue("color")})}return ub(a)},vb=function(){return"hsb("+[this.h,this.s,this.b]+")"},wb=function(){return"hsl("+[this.h,this.s,this.l]+")"},xb=function(){return this.hex},yb=function(a,b,d){if(null==b&&c.is(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,b=a.g,a=a.r),null==b&&c.is(a,U)){var e=c.getRGB(a);a=e.r,b=e.g,d=e.b}return(a>1||b>1||d>1)&&(a/=255,b/=255,d/=255),[a,b,d]},zb=function(a,b,d,e){a*=255,b*=255,d*=255;var f={r:a,g:b,b:d,hex:c.rgb(a,b,d),toString:xb};return c.is(e,"finite")&&(f.opacity=e),f};c.color=function(a){var b;return c.is(a,"object")&&"h"in a&&"s"in a&&"b"in a?(b=c.hsb2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.hex=b.hex):c.is(a,"object")&&"h"in a&&"s"in a&&"l"in a?(b=c.hsl2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.hex=b.hex):(c.is(a,"string")&&(a=c.getRGB(a)),c.is(a,"object")&&"r"in a&&"g"in a&&"b"in a?(b=c.rgb2hsl(a),a.h=b.h,a.s=b.s,a.l=b.l,b=c.rgb2hsb(a),a.v=b.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1)),a.toString=xb,a},c.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;return a=a%360/60,i=c*b,h=i*(1-Q(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],zb(e,f,g,d)},c.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var e,f,g,h,i;return a=a%360/60,i=2*b*(.5>c?c:1-c),h=i*(1-Q(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],zb(e,f,g,d)},c.rgb2hsb=function(a,b,c){c=yb(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=O(a,b,c),g=f-P(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=60*((d+360)%6)/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:vb}},c.rgb2hsl=function(a,b,c){c=yb(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=O(a,b,c),h=P(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=60*((d+360)%6)/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:wb}},c._path2string=function(){return this.join(",").replace(gb,"$1")},c._preload=function(a,b){var c=A.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,A.doc.body.removeChild(this)},c.onerror=function(){A.doc.body.removeChild(this)},A.doc.body.appendChild(c),c.src=a},c.getRGB=f(function(a){if(!a||(a=I(a)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:g};if("none"==a)return{r:-1,g:-1,b:-1,hex:"none",toString:g};!(fb[z](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=ub(a));var b,d,e,f,h,i,j=a.match(X);return j?(j[2]&&(e=ab(j[2].substring(5),16),d=ab(j[2].substring(3,5),16),b=ab(j[2].substring(1,3),16)),j[3]&&(e=ab((h=j[3].charAt(3))+h,16),d=ab((h=j[3].charAt(2))+h,16),b=ab((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100)),j[5]?(i=j[5][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100),c.hsb2rgb(b,d,e,f)):j[6]?(i=j[6][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100),c.hsl2rgb(b,d,e,f)):(j={r:b,g:d,b:e,toString:g},j.hex="#"+(16777216|e|d<<8|b<<16).toString(16).slice(1),c.is(f,"finite")&&(j.opacity=f),j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:g}},c),c.hsb=f(function(a,b,d){return c.hsb2rgb(a,b,d).hex}),c.hsl=f(function(a,b,d){return c.hsl2rgb(a,b,d).hex}),c.rgb=f(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),c.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);return b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b})),c.hex},c.getColor.reset=function(){delete this.start},c.parsePathString=function(a){if(!a)return null;var b=Ab(a);if(b.arr)return Cb(b.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];return c.is(a,V)&&c.is(a[0],V)&&(e=Cb(a)),e.length||I(a).replace(hb,function(a,b,c){var f=[],g=b.toLowerCase();if(c.replace(jb,function(a,b){b&&f.push(+b)}),"m"==g&&f.length>2&&(e.push([b][E](f.splice(0,2))),g="l",b="m"==b?"l":"L"),"r"==g)e.push([b][E](f));else for(;f.length>=d[g]&&(e.push([b][E](f.splice(0,d[g]))),d[g]););}),e.toString=c._path2string,b.arr=Cb(e),e},c.parseTransformString=f(function(a){if(!a)return null;var b=[];return c.is(a,V)&&c.is(a[0],V)&&(b=Cb(a)),b.length||I(a).replace(ib,function(a,c,d){var e=[];M.call(c),d.replace(jb,function(a,b){b&&e.push(+b)}),b.push([c][E](e))}),b.toString=c._path2string,b});var Ab=function(a){var b=Ab.ps=Ab.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[z](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]};c.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=R(j,3),l=R(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*N.atan2(q-s,r-t)/S;return(q>s||t>r)&&(y+=180),{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}},c.bezierBBox=function(a,b,d,e,f,g,h,i){c.is(a,"array")||(a=[a,b,d,e,f,g,h,i]);var j=Jb.apply(null,a);return{x:j.min.x,y:j.min.y,x2:j.max.x,y2:j.max.y,width:j.max.x-j.min.x,height:j.max.y-j.min.y}},c.isPointInsideBBox=function(a,b,c){return b>=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},c.isBBoxIntersect=function(a,b){var d=c.isPointInsideBBox;return d(b,a.x,a.y)||d(b,a.x2,a.y)||d(b,a.x,a.y2)||d(b,a.x2,a.y2)||d(a,b.x,b.y)||d(a,b.x2,b.y)||d(a,b.x,b.y2)||d(a,b.x2,b.y2)||(a.x<b.x2&&a.x>b.x||b.x<a.x2&&b.x>a.x)&&(a.y<b.y2&&a.y>b.y||b.y<a.y2&&b.y>a.y)},c.pathIntersection=function(a,b){return n(a,b)},c.pathIntersectionNumber=function(a,b){return n(a,b,1)},c.isPointInsidePath=function(a,b,d){var e=c.pathBBox(a);return c.isPointInsideBBox(e,b,d)&&1==n(a,[["M",b,d],["H",e.x2+10]],1)%2},c._removedFactory=function(a){return function(){b("raphael.log",null,"Raphaël: you are calling to method “"+a+"” of removed object",a)}};var Bb=c.pathBBox=function(a){var b=Ab(a);if(b.bbox)return d(b.bbox);if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=Kb(a);for(var c,e=0,f=0,g=[],h=[],i=0,j=a.length;j>i;i++)if(c=a[i],"M"==c[0])e=c[1],f=c[2],g.push(e),h.push(f);else{var k=Jb(e,f,c[1],c[2],c[3],c[4],c[5],c[6]);g=g[E](k.min.x,k.max.x),h=h[E](k.min.y,k.max.y),e=c[5],f=c[6]}var l=P[D](0,g),m=P[D](0,h),n=O[D](0,g),o=O[D](0,h),p=n-l,q=o-m,r={x:l,y:m,x2:n,y2:o,width:p,height:q,cx:l+p/2,cy:m+q/2};return b.bbox=d(r),r},Cb=function(a){var b=d(a);return b.toString=c._path2string,b},Db=c._pathToRelative=function(a){var b=Ab(a);if(b.rel)return Cb(b.rel);c.is(a,V)&&c.is(a&&a[0],V)||(a=c.parsePathString(a));var d=[],e=0,f=0,g=0,h=0,i=0;"M"==a[0][0]&&(e=a[0][1],f=a[0][2],g=e,h=f,i++,d.push(["M",e,f]));for(var j=i,k=a.length;k>j;j++){var l=d[j]=[],m=a[j];if(m[0]!=M.call(m[0]))switch(l[0]=M.call(m[0]),l[0]){case"a":l[1]=m[1],l[2]=m[2],l[3]=m[3],l[4]=m[4],l[5]=m[5],l[6]=+(m[6]-e).toFixed(3),l[7]=+(m[7]-f).toFixed(3);break;case"v":l[1]=+(m[1]-f).toFixed(3);break;case"m":g=m[1],h=m[2];default:for(var n=1,o=m.length;o>n;n++)l[n]=+(m[n]-(n%2?e:f)).toFixed(3)}else{l=d[j]=[],"m"==m[0]&&(g=m[1]+e,h=m[2]+f);for(var p=0,q=m.length;q>p;p++)d[j][p]=m[p]}var r=d[j].length;switch(d[j][0]){case"z":e=g,f=h;break;case"h":e+=+d[j][r-1];break;case"v":f+=+d[j][r-1];break;default:e+=+d[j][r-2],f+=+d[j][r-1]}}return d.toString=c._path2string,b.rel=Cb(d),d},Eb=c._pathToAbsolute=function(a){var b=Ab(a);if(b.abs)return Cb(b.abs);if(c.is(a,V)&&c.is(a&&a[0],V)||(a=c.parsePathString(a)),!a||!a.length)return[["M",0,0]];var d=[],e=0,f=0,g=0,i=0,j=0;"M"==a[0][0]&&(e=+a[0][1],f=+a[0][2],g=e,i=f,j++,d[0]=["M",e,f]);for(var k,l,m=3==a.length&&"M"==a[0][0]&&"R"==a[1][0].toUpperCase()&&"Z"==a[2][0].toUpperCase(),n=j,o=a.length;o>n;n++){if(d.push(k=[]),l=a[n],l[0]!=bb.call(l[0]))switch(k[0]=bb.call(l[0]),k[0]){case"A":k[1]=l[1],k[2]=l[2],k[3]=l[3],k[4]=l[4],k[5]=l[5],k[6]=+(l[6]+e),k[7]=+(l[7]+f);break;case"V":k[1]=+l[1]+f;break;case"H":k[1]=+l[1]+e;break;case"R":for(var p=[e,f][E](l.slice(1)),q=2,r=p.length;r>q;q++)p[q]=+p[q]+e,p[++q]=+p[q]+f;d.pop(),d=d[E](h(p,m));break;case"M":g=+l[1]+e,i=+l[2]+f;default:for(q=1,r=l.length;r>q;q++)k[q]=+l[q]+(q%2?e:f)}else if("R"==l[0])p=[e,f][E](l.slice(1)),d.pop(),d=d[E](h(p,m)),k=["R"][E](l.slice(-2));else for(var s=0,t=l.length;t>s;s++)k[s]=l[s];switch(k[0]){case"Z":e=g,f=i;break;case"H":e=k[1];break;case"V":f=k[1];break;case"M":g=k[k.length-2],i=k[k.length-1];default:e=k[k.length-2],f=k[k.length-1]}}return d.toString=c._path2string,b.abs=Cb(d),d},Fb=function(a,b,c,d){return[a,b,c,d,c,d]},Gb=function(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]},Hb=function(a,b,c,d,e,g,h,i,j,k){var l,m=120*S/180,n=S/180*(+e||0),o=[],p=f(function(a,b,c){var d=a*N.cos(c)-b*N.sin(c),e=a*N.sin(c)+b*N.cos(c);return{x:d,y:e}});if(k)y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(a,b,-n),a=l.x,b=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(N.cos(S/180*e),N.sin(S/180*e),(a-i)/2),r=(b-j)/2,s=q*q/(c*c)+r*r/(d*d);s>1&&(s=N.sqrt(s),c=s*c,d=s*d);var t=c*c,u=d*d,v=(g==h?-1:1)*N.sqrt(Q((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*c*r/d+(a+i)/2,x=v*-d*q/c+(b+j)/2,y=N.asin(((b-x)/d).toFixed(9)),z=N.asin(((j-x)/d).toFixed(9));y=w>a?S-y:y,z=w>i?S-z:z,0>y&&(y=2*S+y),0>z&&(z=2*S+z),h&&y>z&&(y-=2*S),!h&&z>y&&(z-=2*S)}var A=z-y;if(Q(A)>m){var B=z,C=i,D=j;z=y+m*(h&&z>y?1:-1),i=w+c*N.cos(z),j=x+d*N.sin(z),o=Hb(i,j,c,d,e,0,h,C,D,[z,B,w,x])}A=z-y;var F=N.cos(y),G=N.sin(y),H=N.cos(z),I=N.sin(z),K=N.tan(A/4),L=4/3*c*K,M=4/3*d*K,O=[a,b],P=[a+L*G,b-M*F],R=[i+L*I,j-M*H],T=[i,j];if(P[0]=2*O[0]-P[0],P[1]=2*O[1]-P[1],k)return[P,R,T][E](o);o=[P,R,T][E](o).join()[J](",");for(var U=[],V=0,W=o.length;W>V;V++)U[V]=V%2?p(o[V-1],o[V],n).y:p(o[V],o[V+1],n).x;return U},Ib=function(a,b,c,d,e,f,g,h,i){var j=1-i;return{x:R(j,3)*a+3*R(j,2)*i*c+3*j*i*i*e+R(i,3)*g,y:R(j,3)*b+3*R(j,2)*i*d+3*j*i*i*f+R(i,3)*h}},Jb=f(function(a,b,c,d,e,f,g,h){var i,j=e-2*c+a-(g-2*e+c),k=2*(c-a)-2*(e-c),l=a-c,m=(-k+N.sqrt(k*k-4*j*l))/2/j,n=(-k-N.sqrt(k*k-4*j*l))/2/j,o=[b,h],p=[a,g];return Q(m)>"1e12"&&(m=.5),Q(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ib(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ib(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),j=f-2*d+b-(h-2*f+d),k=2*(d-b)-2*(f-d),l=b-d,m=(-k+N.sqrt(k*k-4*j*l))/2/j,n=(-k-N.sqrt(k*k-4*j*l))/2/j,Q(m)>"1e12"&&(m=.5),Q(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ib(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ib(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),{min:{x:P[D](0,p),y:P[D](0,o)},max:{x:O[D](0,p),y:O[D](0,o)}}}),Kb=c._path2curve=f(function(a,b){var c=!b&&Ab(a);if(!b&&c.curve)return Cb(c.curve);for(var d=Eb(a),e=b&&Eb(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=(function(a,b,c){var d,e;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][E](Hb[D](0,[b.x,b.y][E](a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e][E](a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"][E](Gb(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][E](Gb(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][E](Fb(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][E](Fb(b.x,b.y,a[1],b.y));break;case"V":a=["C"][E](Fb(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][E](Fb(b.x,b.y,b.X,b.Y))}return a}),i=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)a.splice(b++,0,["C"][E](c.splice(0,6)));a.splice(b,1),l=O(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&"M"==a[g][0]&&"M"!=b[g][0]&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],l=O(d.length,e&&e.length||0))},k=0,l=O(d.length,e&&e.length||0);l>k;k++){d[k]=h(d[k],f),i(d,k),e&&(e[k]=h(e[k],g)),e&&i(e,k),j(d,e,f,g,k),j(e,d,g,f,k);var m=d[k],n=e&&e[k],o=m.length,p=e&&n.length;f.x=m[o-2],f.y=m[o-1],f.bx=_(m[o-4])||f.x,f.by=_(m[o-3])||f.y,g.bx=e&&(_(n[p-4])||g.x),g.by=e&&(_(n[p-3])||g.y),g.x=e&&n[p-2],g.y=e&&n[p-1]}return e||(c.curve=Cb(d)),e?[d,e]:d},null,Cb),Lb=(c._parseDots=f(function(a){for(var b=[],d=0,e=a.length;e>d;d++){var f={},g=a[d].match(/^([^:]*):?([\d\.]*)/);if(f.color=c.getRGB(g[1]),f.color.error)return null;f.color=f.color.hex,g[2]&&(f.offset=g[2]+"%"),b.push(f)}for(d=1,e=b.length-1;e>d;d++)if(!b[d].offset){for(var h=_(b[d-1].offset||0),i=0,j=d+1;e>j;j++)if(b[j].offset){i=b[j].offset;break}i||(i=100,j=e),i=_(i);for(var k=(i-h)/(j-d+1);j>d;d++)h+=k,b[d].offset=h+"%"}return b}),c._tear=function(a,b){a==b.top&&(b.top=a.prev),a==b.bottom&&(b.bottom=a.next),a.next&&(a.next.prev=a.prev),a.prev&&(a.prev.next=a.next)}),Mb=(c._tofront=function(a,b){b.top!==a&&(Lb(a,b),a.next=null,a.prev=b.top,b.top.next=a,b.top=a)},c._toback=function(a,b){b.bottom!==a&&(Lb(a,b),a.next=b.bottom,a.prev=null,b.bottom.prev=a,b.bottom=a)},c._insertafter=function(a,b,c){Lb(a,c),b==c.top&&(c.top=a),b.next&&(b.next.prev=a),a.next=b.next,a.prev=b,b.next=a},c._insertbefore=function(a,b,c){Lb(a,c),b==c.bottom&&(c.bottom=a),b.prev&&(b.prev.next=a),a.prev=b.prev,b.prev=a,a.next=b},c.toMatrix=function(a,b){var c=Bb(a),d={_:{transform:G},getBBox:function(){return c}};return Nb(d,b),d.matrix}),Nb=(c.transformPath=function(a,b){return rb(a,Mb(a,b))},c._extractTransform=function(a,b){if(null==b)return a._.transform;b=I(b).replace(/\.{3}|\u2026/g,a._.transform||G);var d=c.parseTransformString(b),e=0,f=0,g=0,h=1,i=1,j=a._,k=new o;if(j.transform=d||[],d)for(var l=0,m=d.length;m>l;l++){var n,p,q,r,s,t=d[l],u=t.length,v=I(t[0]).toLowerCase(),w=t[0]!=v,x=w?k.invert():0;"t"==v&&3==u?w?(n=x.x(0,0),p=x.y(0,0),q=x.x(t[1],t[2]),r=x.y(t[1],t[2]),k.translate(q-n,r-p)):k.translate(t[1],t[2]):"r"==v?2==u?(s=s||a.getBBox(1),k.rotate(t[1],s.x+s.width/2,s.y+s.height/2),e+=t[1]):4==u&&(w?(q=x.x(t[2],t[3]),r=x.y(t[2],t[3]),k.rotate(t[1],q,r)):k.rotate(t[1],t[2],t[3]),e+=t[1]):"s"==v?2==u||3==u?(s=s||a.getBBox(1),k.scale(t[1],t[u-1],s.x+s.width/2,s.y+s.height/2),h*=t[1],i*=t[u-1]):5==u&&(w?(q=x.x(t[3],t[4]),r=x.y(t[3],t[4]),k.scale(t[1],t[2],q,r)):k.scale(t[1],t[2],t[3],t[4]),h*=t[1],i*=t[2]):"m"==v&&7==u&&k.add(t[1],t[2],t[3],t[4],t[5],t[6]),j.dirtyT=1,a.matrix=k}a.matrix=k,j.sx=h,j.sy=i,j.deg=e,j.dx=f=k.e,j.dy=g=k.f,1==h&&1==i&&!e&&j.bbox?(j.bbox.x+=+f,j.bbox.y+=+g):j.dirtyT=1}),Ob=function(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}},Pb=c._equaliseTransform=function(a,b){b=I(b).replace(/\.{3}|\u2026/g,a),a=c.parseTransformString(a)||[],b=c.parseTransformString(b)||[];for(var d,e,f,g,h=O(a.length,b.length),i=[],j=[],k=0;h>k;k++){if(f=a[k]||Ob(b[k]),g=b[k]||Ob(f),f[0]!=g[0]||"r"==f[0].toLowerCase()&&(f[2]!=g[2]||f[3]!=g[3])||"s"==f[0].toLowerCase()&&(f[3]!=g[3]||f[4]!=g[4]))return;for(i[k]=[],j[k]=[],d=0,e=O(f.length,g.length);e>d;d++)d in f&&(i[k][d]=f[d]),d in g&&(j[k][d]=g[d])
+}return{from:i,to:j}};c._getContainer=function(a,b,d,e){var f;return f=null!=e||c.is(a,"object")?a:A.doc.getElementById(a),null!=f?f.tagName?null==b?{container:f,width:f.style.pixelWidth||f.offsetWidth,height:f.style.pixelHeight||f.offsetHeight}:{container:f,width:b,height:d}:{container:1,x:a,y:b,width:d,height:e}:void 0},c.pathToRelative=Db,c._engine={},c.path2curve=Kb,c.matrix=function(a,b,c,d,e,f){return new o(a,b,c,d,e,f)},function(a){function b(a){return a[0]*a[0]+a[1]*a[1]}function d(a){var c=N.sqrt(b(a));a[0]&&(a[0]/=c),a[1]&&(a[1]/=c)}a.add=function(a,b,c,d,e,f){var g,h,i,j,k=[[],[],[]],l=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],m=[[a,c,e],[b,d,f],[0,0,1]];for(a&&a instanceof o&&(m=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]),g=0;3>g;g++)for(h=0;3>h;h++){for(j=0,i=0;3>i;i++)j+=l[g][i]*m[i][h];k[g][h]=j}this.a=k[0][0],this.b=k[1][0],this.c=k[0][1],this.d=k[1][1],this.e=k[0][2],this.f=k[1][2]},a.invert=function(){var a=this,b=a.a*a.d-a.b*a.c;return new o(a.d/b,-a.b/b,-a.c/b,a.a/b,(a.c*a.f-a.d*a.e)/b,(a.b*a.e-a.a*a.f)/b)},a.clone=function(){return new o(this.a,this.b,this.c,this.d,this.e,this.f)},a.translate=function(a,b){this.add(1,0,0,1,a,b)},a.scale=function(a,b,c,d){null==b&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d)},a.rotate=function(a,b,d){a=c.rad(a),b=b||0,d=d||0;var e=+N.cos(a).toFixed(9),f=+N.sin(a).toFixed(9);this.add(e,f,-f,e,b,d),this.add(1,0,0,1,-b,-d)},a.x=function(a,b){return a*this.a+b*this.c+this.e},a.y=function(a,b){return a*this.b+b*this.d+this.f},a.get=function(a){return+this[I.fromCharCode(97+a)].toFixed(4)},a.toString=function(){return c.svg?"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")":[this.get(0),this.get(2),this.get(1),this.get(3),0,0].join()},a.toFilter=function(){return"progid:DXImageTransform.Microsoft.Matrix(M11="+this.get(0)+", M12="+this.get(2)+", M21="+this.get(1)+", M22="+this.get(3)+", Dx="+this.get(4)+", Dy="+this.get(5)+", sizingmethod='auto expand')"},a.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},a.split=function(){var a={};a.dx=this.e,a.dy=this.f;var e=[[this.a,this.c],[this.b,this.d]];a.scalex=N.sqrt(b(e[0])),d(e[0]),a.shear=e[0][0]*e[1][0]+e[0][1]*e[1][1],e[1]=[e[1][0]-e[0][0]*a.shear,e[1][1]-e[0][1]*a.shear],a.scaley=N.sqrt(b(e[1])),d(e[1]),a.shear/=a.scaley;var f=-e[0][1],g=e[1][1];return 0>g?(a.rotate=c.deg(N.acos(g)),0>f&&(a.rotate=360-a.rotate)):a.rotate=c.deg(N.asin(f)),a.isSimple=!(+a.shear.toFixed(9)||a.scalex.toFixed(9)!=a.scaley.toFixed(9)&&a.rotate),a.isSuperSimple=!+a.shear.toFixed(9)&&a.scalex.toFixed(9)==a.scaley.toFixed(9)&&!a.rotate,a.noRotation=!+a.shear.toFixed(9)&&!a.rotate,a},a.toTransformString=function(a){var b=a||this[J]();return b.isSimple?(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[b.dx,b.dy]:G)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:G)+(b.rotate?"r"+[b.rotate,0,0]:G)):"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]}}(o.prototype);var Qb=navigator.userAgent.match(/Version\/(.*?)\s/)||navigator.userAgent.match(/Chrome\/(\d+)/);v.safari="Apple Computer, Inc."==navigator.vendor&&(Qb&&Qb[1]<4||"iP"==navigator.platform.slice(0,2))||"Google Inc."==navigator.vendor&&Qb&&Qb[1]<8?function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});setTimeout(function(){a.remove()})}:mb;for(var Rb=function(){this.returnValue=!1},Sb=function(){return this.originalEvent.preventDefault()},Tb=function(){this.cancelBubble=!0},Ub=function(){return this.originalEvent.stopPropagation()},Vb=function(a){var b=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,c=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft;return{x:a.clientX+c,y:a.clientY+b}},Wb=function(){return A.doc.addEventListener?function(a,b,c,d){var e=function(a){var b=Vb(a);return c.call(d,a,b.x,b.y)};if(a.addEventListener(b,e,!1),F&&L[b]){var f=function(b){for(var e=Vb(b),f=b,g=0,h=b.targetTouches&&b.targetTouches.length;h>g;g++)if(b.targetTouches[g].target==a){b=b.targetTouches[g],b.originalEvent=f,b.preventDefault=Sb,b.stopPropagation=Ub;break}return c.call(d,b,e.x,e.y)};a.addEventListener(L[b],f,!1)}return function(){return a.removeEventListener(b,e,!1),F&&L[b]&&a.removeEventListener(L[b],e,!1),!0}}:A.doc.attachEvent?function(a,b,c,d){var e=function(a){a=a||A.win.event;var b=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,e=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft,f=a.clientX+e,g=a.clientY+b;return a.preventDefault=a.preventDefault||Rb,a.stopPropagation=a.stopPropagation||Tb,c.call(d,a,f,g)};a.attachEvent("on"+b,e);var f=function(){return a.detachEvent("on"+b,e),!0};return f}:void 0}(),Xb=[],Yb=function(a){for(var c,d=a.clientX,e=a.clientY,f=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,g=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft,h=Xb.length;h--;){if(c=Xb[h],F&&a.touches){for(var i,j=a.touches.length;j--;)if(i=a.touches[j],i.identifier==c.el._drag.id){d=i.clientX,e=i.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}else a.preventDefault();var k,l=c.el.node,m=l.nextSibling,n=l.parentNode,o=l.style.display;A.win.opera&&n.removeChild(l),l.style.display="none",k=c.el.paper.getElementByPoint(d,e),l.style.display=o,A.win.opera&&(m?n.insertBefore(l,m):n.appendChild(l)),k&&b("raphael.drag.over."+c.el.id,c.el,k),d+=g,e+=f,b("raphael.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,a)}},Zb=function(a){c.unmousemove(Yb).unmouseup(Zb);for(var d,e=Xb.length;e--;)d=Xb[e],d.el._drag={},b("raphael.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,a);Xb=[]},$b=c.el={},_b=K.length;_b--;)!function(a){c[a]=$b[a]=function(b,d){return c.is(b,"function")&&(this.events=this.events||[],this.events.push({name:a,f:b,unbind:Wb(this.shape||this.node||A.doc,a,b,d||this)})),this},c["un"+a]=$b["un"+a]=function(b){for(var d=this.events||[],e=d.length;e--;)d[e].name!=a||!c.is(b,"undefined")&&d[e].f!=b||(d[e].unbind(),d.splice(e,1),!d.length&&delete this.events);return this}}(K[_b]);$b.data=function(a,d){var e=kb[this.id]=kb[this.id]||{};if(0==arguments.length)return e;if(1==arguments.length){if(c.is(a,"object")){for(var f in a)a[z](f)&&this.data(f,a[f]);return this}return b("raphael.data.get."+this.id,this,e[a],a),e[a]}return e[a]=d,b("raphael.data.set."+this.id,this,d,a),this},$b.removeData=function(a){return null==a?kb[this.id]={}:kb[this.id]&&delete kb[this.id][a],this},$b.getData=function(){return d(kb[this.id]||{})},$b.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},$b.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var ac=[];$b.drag=function(a,d,e,f,g,h){function i(i){(i.originalEvent||i).preventDefault();var j=i.clientX,k=i.clientY,l=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,m=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft;if(this._drag.id=i.identifier,F&&i.touches)for(var n,o=i.touches.length;o--;)if(n=i.touches[o],this._drag.id=n.identifier,n.identifier==this._drag.id){j=n.clientX,k=n.clientY;break}this._drag.x=j+m,this._drag.y=k+l,!Xb.length&&c.mousemove(Yb).mouseup(Zb),Xb.push({el:this,move_scope:f,start_scope:g,end_scope:h}),d&&b.on("raphael.drag.start."+this.id,d),a&&b.on("raphael.drag.move."+this.id,a),e&&b.on("raphael.drag.end."+this.id,e),b("raphael.drag.start."+this.id,g||f||this,i.clientX+m,i.clientY+l,i)}return this._drag={},ac.push({el:this,start:i}),this.mousedown(i),this},$b.onDragOver=function(a){a?b.on("raphael.drag.over."+this.id,a):b.unbind("raphael.drag.over."+this.id)},$b.undrag=function(){for(var a=ac.length;a--;)ac[a].el==this&&(this.unmousedown(ac[a].start),ac.splice(a,1),b.unbind("raphael.drag.*."+this.id));!ac.length&&c.unmousemove(Yb).unmouseup(Zb),Xb=[]},v.circle=function(a,b,d){var e=c._engine.circle(this,a||0,b||0,d||0);return this.__set__&&this.__set__.push(e),e},v.rect=function(a,b,d,e,f){var g=c._engine.rect(this,a||0,b||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},v.ellipse=function(a,b,d,e){var f=c._engine.ellipse(this,a||0,b||0,d||0,e||0);return this.__set__&&this.__set__.push(f),f},v.path=function(a){a&&!c.is(a,U)&&!c.is(a[0],V)&&(a+=G);var b=c._engine.path(c.format[D](c,arguments),this);return this.__set__&&this.__set__.push(b),b},v.image=function(a,b,d,e,f){var g=c._engine.image(this,a||"about:blank",b||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},v.text=function(a,b,d){var e=c._engine.text(this,a||0,b||0,I(d));return this.__set__&&this.__set__.push(e),e},v.set=function(a){!c.is(a,"array")&&(a=Array.prototype.splice.call(arguments,0,arguments.length));var b=new mc(a);return this.__set__&&this.__set__.push(b),b.paper=this,b.type="set",b},v.setStart=function(a){this.__set__=a||this.set()},v.setFinish=function(){var a=this.__set__;return delete this.__set__,a},v.setSize=function(a,b){return c._engine.setSize.call(this,a,b)},v.setViewBox=function(a,b,d,e,f){return c._engine.setViewBox.call(this,a,b,d,e,f)},v.top=v.bottom=null,v.raphael=c;var bc=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,g=e.clientLeft||d.clientLeft||0,h=b.top+(A.win.pageYOffset||e.scrollTop||d.scrollTop)-f,i=b.left+(A.win.pageXOffset||e.scrollLeft||d.scrollLeft)-g;return{y:h,x:i}};v.getElementByPoint=function(a,b){var c=this,d=c.canvas,e=A.doc.elementFromPoint(a,b);if(A.win.opera&&"svg"==e.tagName){var f=bc(d),g=d.createSVGRect();g.x=a-f.x,g.y=b-f.y,g.width=g.height=1;var h=d.getIntersectionList(g,null);h.length&&(e=h[h.length-1])}if(!e)return null;for(;e.parentNode&&e!=d.parentNode&&!e.raphael;)e=e.parentNode;return e==c.canvas.parentNode&&(e=d),e=e&&e.raphael?c.getById(e.raphaelid):null},v.getElementsByBBox=function(a){var b=this.set();return this.forEach(function(d){c.isBBoxIntersect(d.getBBox(),a)&&b.push(d)}),b},v.getById=function(a){for(var b=this.bottom;b;){if(b.id==a)return b;b=b.next}return null},v.forEach=function(a,b){for(var c=this.bottom;c;){if(a.call(b,c)===!1)return this;c=c.next}return this},v.getElementsByPoint=function(a,b){var c=this.set();return this.forEach(function(d){d.isPointInside(a,b)&&c.push(d)}),c},$b.isPointInside=function(a,b){var d=this.realPath=qb[this.type](this);return this.attr("transform")&&this.attr("transform").length&&(d=c.transformPath(d,this.attr("transform"))),c.isPointInsidePath(d,a,b)},$b.getBBox=function(a){if(this.removed)return{};var b=this._;return a?((b.dirty||!b.bboxwt)&&(this.realPath=qb[this.type](this),b.bboxwt=Bb(this.realPath),b.bboxwt.toString=p,b.dirty=0),b.bboxwt):((b.dirty||b.dirtyT||!b.bbox)&&((b.dirty||!this.realPath)&&(b.bboxwt=0,this.realPath=qb[this.type](this)),b.bbox=Bb(rb(this.realPath,this.matrix)),b.bbox.toString=p,b.dirty=b.dirtyT=0),b.bbox)},$b.clone=function(){if(this.removed)return null;var a=this.paper[this.type]().attr(this.attr());return this.__set__&&this.__set__.push(a),a},$b.glow=function(a){if("text"==this.type)return null;a=a||{};var b={width:(a.width||10)+(+this.attr("stroke-width")||1),fill:a.fill||!1,opacity:a.opacity||.5,offsetx:a.offsetx||0,offsety:a.offsety||0,color:a.color||"#000"},c=b.width/2,d=this.paper,e=d.set(),f=this.realPath||qb[this.type](this);f=this.matrix?rb(f,this.matrix):f;for(var g=1;c+1>g;g++)e.push(d.path(f).attr({stroke:b.color,fill:b.fill?b.color:"none","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":+(b.width/c*g).toFixed(3),opacity:+(b.opacity/c).toFixed(3)}));return e.insertBefore(this).translate(b.offsetx,b.offsety)};var cc=function(a,b,d,e,f,g,h,i,l){return null==l?j(a,b,d,e,f,g,h,i):c.findDotsAtSegment(a,b,d,e,f,g,h,i,k(a,b,d,e,f,g,h,i,l))},dc=function(a,b){return function(d,e,f){d=Kb(d);for(var g,h,i,j,k,l="",m={},n=0,o=0,p=d.length;p>o;o++){if(i=d[o],"M"==i[0])g=+i[1],h=+i[2];else{if(j=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6]),n+j>e){if(b&&!m.start){if(k=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),l+=["C"+k.start.x,k.start.y,k.m.x,k.m.y,k.x,k.y],f)return l;m.start=l,l=["M"+k.x,k.y+"C"+k.n.x,k.n.y,k.end.x,k.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!a&&!b)return k=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),{x:k.x,y:k.y,alpha:k.alpha}}n+=j,g=+i[5],h=+i[6]}l+=i.shift()+i}return m.end=l,k=a?n:b?m:c.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),k.alpha&&(k={x:k.x,y:k.y,alpha:k.alpha}),k}},ec=dc(1),fc=dc(),gc=dc(0,1);c.getTotalLength=ec,c.getPointAtLength=fc,c.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return gc(a,b).end;var d=gc(a,c,1);return b?gc(d,b).end:d},$b.getTotalLength=function(){var a=this.getPath();if(a)return this.node.getTotalLength?this.node.getTotalLength():ec(a)},$b.getPointAtLength=function(a){var b=this.getPath();if(b)return fc(b,a)},$b.getPath=function(){var a,b=c._getPath[this.type];if("text"!=this.type&&"set"!=this.type)return b&&(a=b(this)),a},$b.getSubpath=function(a,b){var d=this.getPath();if(d)return c.getSubpath(d,a,b)};var hc=c.easing_formulas={linear:function(a){return a},"<":function(a){return R(a,1.7)},">":function(a){return R(a,.48)},"<>":function(a){var b=.48-a/1.04,c=N.sqrt(.1734+b*b),d=c-b,e=R(Q(d),1/3)*(0>d?-1:1),f=-c-b,g=R(Q(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){return a==!!a?a:R(2,-10*a)*N.sin((a-.075)*2*S/.3)+1},bounce:function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b}};hc.easeIn=hc["ease-in"]=hc["<"],hc.easeOut=hc["ease-out"]=hc[">"],hc.easeInOut=hc["ease-in-out"]=hc["<>"],hc["back-in"]=hc.backIn,hc["back-out"]=hc.backOut;var ic=[],jc=a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||function(a){setTimeout(a,16)},kc=function(){for(var a=+new Date,d=0;d<ic.length;d++){var e=ic[d];if(!e.el.removed&&!e.paused){var f,g,h=a-e.start,i=e.ms,j=e.easing,k=e.from,l=e.diff,m=e.to,n=(e.t,e.el),o={},p={};if(e.initstatus?(h=(e.initstatus*e.anim.top-e.prev)/(e.percent-e.prev)*i,e.status=e.initstatus,delete e.initstatus,e.stop&&ic.splice(d--,1)):e.status=(e.prev+(e.percent-e.prev)*(h/i))/e.anim.top,!(0>h))if(i>h){var q=j(h/i);for(var r in k)if(k[z](r)){switch(db[r]){case T:f=+k[r]+q*i*l[r];break;case"colour":f="rgb("+[lc($(k[r].r+q*i*l[r].r)),lc($(k[r].g+q*i*l[r].g)),lc($(k[r].b+q*i*l[r].b))].join(",")+")";break;case"path":f=[];for(var t=0,u=k[r].length;u>t;t++){f[t]=[k[r][t][0]];for(var v=1,w=k[r][t].length;w>v;v++)f[t][v]=+k[r][t][v]+q*i*l[r][t][v];f[t]=f[t].join(H)}f=f.join(H);break;case"transform":if(l[r].real)for(f=[],t=0,u=k[r].length;u>t;t++)for(f[t]=[k[r][t][0]],v=1,w=k[r][t].length;w>v;v++)f[t][v]=k[r][t][v]+q*i*l[r][t][v];else{var x=function(a){return+k[r][a]+q*i*l[r][a]};f=[["m",x(0),x(1),x(2),x(3),x(4),x(5)]]}break;case"csv":if("clip-rect"==r)for(f=[],t=4;t--;)f[t]=+k[r][t]+q*i*l[r][t];break;default:var y=[][E](k[r]);for(f=[],t=n.paper.customAttributes[r].length;t--;)f[t]=+y[t]+q*i*l[r][t]}o[r]=f}n.attr(o),function(a,c,d){setTimeout(function(){b("raphael.anim.frame."+a,c,d)})}(n.id,n,e.anim)}else{if(function(a,d,e){setTimeout(function(){b("raphael.anim.frame."+d.id,d,e),b("raphael.anim.finish."+d.id,d,e),c.is(a,"function")&&a.call(d)})}(e.callback,n,e.anim),n.attr(m),ic.splice(d--,1),e.repeat>1&&!e.next){for(g in m)m[z](g)&&(p[g]=e.totalOrigin[g]);e.el.attr(p),s(e.anim,e.el,e.anim.percents[0],null,e.totalOrigin,e.repeat-1)}e.next&&!e.stop&&s(e.anim,e.el,e.next,null,e.totalOrigin,e.repeat)}}}c.svg&&n&&n.paper&&n.paper.safari(),ic.length&&jc(kc)},lc=function(a){return a>255?255:0>a?0:a};$b.animateWith=function(a,b,d,e,f,g){var h=this;if(h.removed)return g&&g.call(h),h;var i=d instanceof r?d:c.animation(d,e,f,g);s(i,h,i.percents[0],null,h.attr());for(var j=0,k=ic.length;k>j;j++)if(ic[j].anim==b&&ic[j].el==a){ic[k-1].start=ic[j].start;break}return h},$b.onAnimation=function(a){return a?b.on("raphael.anim.frame."+this.id,a):b.unbind("raphael.anim.frame."+this.id),this},r.prototype.delay=function(a){var b=new r(this.anim,this.ms);return b.times=this.times,b.del=+a||0,b},r.prototype.repeat=function(a){var b=new r(this.anim,this.ms);return b.del=this.del,b.times=N.floor(O(a,0))||1,b},c.animation=function(a,b,d,e){if(a instanceof r)return a;(c.is(d,"function")||!d)&&(e=e||d||null,d=null),a=Object(a),b=+b||0;var f,g,h={};for(g in a)a[z](g)&&_(g)!=g&&_(g)+"%"!=g&&(f=!0,h[g]=a[g]);return f?(d&&(h.easing=d),e&&(h.callback=e),new r({100:h},b)):new r(a,b)},$b.animate=function(a,b,d,e){var f=this;if(f.removed)return e&&e.call(f),f;var g=a instanceof r?a:c.animation(a,b,d,e);return s(g,f,g.percents[0],null,f.attr()),f},$b.setTime=function(a,b){return a&&null!=b&&this.status(a,P(b,a.ms)/a.ms),this},$b.status=function(a,b){var c,d,e=[],f=0;if(null!=b)return s(a,this,-1,P(b,1)),this;for(c=ic.length;c>f;f++)if(d=ic[f],d.el.id==this.id&&(!a||d.anim==a)){if(a)return d.status;e.push({anim:d.anim,status:d.status})}return a?0:e},$b.pause=function(a){for(var c=0;c<ic.length;c++)ic[c].el.id!=this.id||a&&ic[c].anim!=a||b("raphael.anim.pause."+this.id,this,ic[c].anim)!==!1&&(ic[c].paused=!0);return this},$b.resume=function(a){for(var c=0;c<ic.length;c++)if(ic[c].el.id==this.id&&(!a||ic[c].anim==a)){var d=ic[c];b("raphael.anim.resume."+this.id,this,d.anim)!==!1&&(delete d.paused,this.status(d.anim,d.status))}return this},$b.stop=function(a){for(var c=0;c<ic.length;c++)ic[c].el.id!=this.id||a&&ic[c].anim!=a||b("raphael.anim.stop."+this.id,this,ic[c].anim)!==!1&&ic.splice(c--,1);return this},b.on("raphael.remove",t),b.on("raphael.clear",t),$b.toString=function(){return"Raphaël’s object"};var mc=function(a){if(this.items=[],this.length=0,this.type="set",a)for(var b=0,c=a.length;c>b;b++)!a[b]||a[b].constructor!=$b.constructor&&a[b].constructor!=mc||(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},nc=mc.prototype;nc.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)a=arguments[c],!a||a.constructor!=$b.constructor&&a.constructor!=mc||(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},nc.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},nc.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)if(a.call(b,this.items[c],c)===!1)return this;return this};for(var oc in $b)$b[z](oc)&&(nc[oc]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a][D](c,b)})}}(oc));return nc.attr=function(a,b){if(a&&c.is(a,V)&&c.is(a[0],"object"))for(var d=0,e=a.length;e>d;d++)this.items[d].attr(a[d]);else for(var f=0,g=this.items.length;g>f;f++)this.items[f].attr(a,b);return this},nc.clear=function(){for(;this.length;)this.pop()},nc.splice=function(a,b){a=0>a?O(this.length+a,0):a,b=O(0,P(this.length-a,b));var c,d=[],e=[],f=[];for(c=2;c<arguments.length;c++)f.push(arguments[c]);for(c=0;b>c;c++)e.push(this[a+c]);for(;c<this.length-a;c++)d.push(this[a+c]);var g=f.length;for(c=0;c<g+d.length;c++)this.items[a+c]=this[a+c]=g>c?f[c]:d[c-g];for(c=this.items.length=this.length-=b-g;this[c];)delete this[c++];return new mc(e)},nc.exclude=function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]==a)return this.splice(b,1),!0},nc.animate=function(a,b,d,e){(c.is(d,"function")||!d)&&(e=d||null);var f,g,h=this.items.length,i=h,j=this;if(!h)return this;e&&(g=function(){!--h&&e.call(j)}),d=c.is(d,U)?d:g;var k=c.animation(a,b,d,g);for(f=this.items[--i].animate(k);i--;)this.items[i]&&!this.items[i].removed&&this.items[i].animateWith(f,k,k),this.items[i]&&!this.items[i].removed||h--;return this},nc.insertAfter=function(a){for(var b=this.items.length;b--;)this.items[b].insertAfter(a);return this},nc.getBBox=function(){for(var a=[],b=[],c=[],d=[],e=this.items.length;e--;)if(!this.items[e].removed){var f=this.items[e].getBBox();a.push(f.x),b.push(f.y),c.push(f.x+f.width),d.push(f.y+f.height)}return a=P[D](0,a),b=P[D](0,b),c=O[D](0,c),d=O[D](0,d),{x:a,y:b,x2:c,y2:d,width:c-a,height:d-b}},nc.clone=function(a){a=this.paper.set();for(var b=0,c=this.items.length;c>b;b++)a.push(this.items[b].clone());return a},nc.toString=function(){return"Raphaël‘s set"},nc.glow=function(a){var b=this.paper.set();return this.forEach(function(c){var d=c.glow(a);null!=d&&d.forEach(function(a){b.push(a)})}),b},nc.isPointInside=function(a,b){var c=!1;return this.forEach(function(d){return d.isPointInside(a,b)?(console.log("runned"),c=!0,!1):void 0}),c},c.registerFont=function(a){if(!a.face)return a;this.fonts=this.fonts||{};var b={w:a.w,face:{},glyphs:{}},c=a.face["font-family"];for(var d in a.face)a.face[z](d)&&(b.face[d]=a.face[d]);if(this.fonts[c]?this.fonts[c].push(b):this.fonts[c]=[b],!a.svg){b.face["units-per-em"]=ab(a.face["units-per-em"],10);for(var e in a.glyphs)if(a.glyphs[z](e)){var f=a.glyphs[e];if(b.glyphs[e]={w:f.w,k:{},d:f.d&&"M"+f.d.replace(/[mlcxtrv]/g,function(a){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[a]||"M"})+"z"},f.k)for(var g in f.k)f[z](g)&&(b.glyphs[e].k[g]=f.k[g])}}return a},v.getFont=function(a,b,d,e){if(e=e||"normal",d=d||"normal",b=+b||{normal:400,bold:700,lighter:300,bolder:800}[b]||400,c.fonts){var f=c.fonts[a];if(!f){var g=new RegExp("(^|\\s)"+a.replace(/[^\w\d\s+!~.:_-]/g,G)+"(\\s|$)","i");for(var h in c.fonts)if(c.fonts[z](h)&&g.test(h)){f=c.fonts[h];break}}var i;if(f)for(var j=0,k=f.length;k>j&&(i=f[j],i.face["font-weight"]!=b||i.face["font-style"]!=d&&i.face["font-style"]||i.face["font-stretch"]!=e);j++);return i}},v.print=function(a,b,d,e,f,g,h,i){g=g||"middle",h=O(P(h||0,1),-1),i=O(P(i||1,3),1);var j,k=I(d)[J](G),l=0,m=0,n=G;if(c.is(e,"string")&&(e=this.getFont(e)),e){j=(f||16)/e.face["units-per-em"];for(var o=e.face.bbox[J](w),p=+o[0],q=o[3]-o[1],r=0,s=+o[1]+("baseline"==g?q+ +e.face.descent:q/2),t=0,u=k.length;u>t;t++){if("\n"==k[t])l=0,x=0,m=0,r+=q*i;else{var v=m&&e.glyphs[k[t-1]]||{},x=e.glyphs[k[t]];l+=m?(v.w||e.w)+(v.k&&v.k[k[t]]||0)+e.w*h:0,m=1}x&&x.d&&(n+=c.transformPath(x.d,["t",l*j,r*j,"s",j,j,p,s,"t",(a-p)/j,(b-s)/j]))}}return this.path(n).attr({fill:"#000",stroke:"none"})},v.add=function(a){if(c.is(a,"array"))for(var b,d=this.set(),e=0,f=a.length;f>e;e++)b=a[e]||{},x[z](b.type)&&d.push(this[b.type]().attr(b));return d},c.format=function(a,b){var d=c.is(b,V)?[0][E](b):arguments;return a&&c.is(a,U)&&d.length-1&&(a=a.replace(y,function(a,b){return null==d[++b]?G:d[b]})),a||G},c.fullfill=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return String(b).replace(a,function(a,b){return c(a,b,d)})}}(),c.ninja=function(){return B.was?A.win.Raphael=B.is:delete Raphael,c},c.st=nc,function(a,b,d){function e(){/in/.test(a.readyState)?setTimeout(e,9):c.eve("raphael.DOMload")}null==a.readyState&&a.addEventListener&&(a.addEventListener(b,d=function(){a.removeEventListener(b,d,!1),a.readyState="complete"},!1),a.readyState="loading"),e()}(document,"DOMContentLoaded"),b.on("raphael.DOMload",function(){u=!0}),function(){if(c.svg){var a="hasOwnProperty",b=String,d=parseFloat,e=parseInt,f=Math,g=f.max,h=f.abs,i=f.pow,j=/[, ]+/,k=c.eve,l="",m=" ",n="http://www.w3.org/1999/xlink",o={block:"M5,0 0,2.5 5,5z",classic:"M5,0 0,2.5 5,5 3.5,3 3.5,2z",diamond:"M2.5,0 5,2.5 2.5,5 0,2.5z",open:"M6,1 1,3.5 6,6",oval:"M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"},p={};c.toString=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var q=function(d,e){if(e){"string"==typeof d&&(d=q(d));for(var f in e)e[a](f)&&("xlink:"==f.substring(0,6)?d.setAttributeNS(n,f.substring(6),b(e[f])):d.setAttribute(f,b(e[f])))}else d=c._g.doc.createElementNS("http://www.w3.org/2000/svg",d),d.style&&(d.style.webkitTapHighlightColor="rgba(0,0,0,0)");return d},r=function(a,e){var j="linear",k=a.id+e,m=.5,n=.5,o=a.node,p=a.paper,r=o.style,s=c._g.doc.getElementById(k);if(!s){if(e=b(e).replace(c._radial_gradient,function(a,b,c){if(j="radial",b&&c){m=d(b),n=d(c);var e=2*(n>.5)-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&.5!=n&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/),"linear"==j){var t=e.shift();if(t=-d(t),isNaN(t))return null;var u=[0,0,f.cos(c.rad(t)),f.sin(c.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=c._parseDots(e);if(!w)return null;if(k=k.replace(/[\(\)\s,\xb0#]/g,"_"),a.gradient&&k!=a.gradient.id&&(p.defs.removeChild(a.gradient),delete a.gradient),!a.gradient){s=q(j+"Gradient",{id:k}),a.gradient=s,q(s,"radial"==j?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:a.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;y>x;x++)s.appendChild(q("stop",{offset:w[x].offset?w[x].offset:x?"100%":"0%","stop-color":w[x].color||"#fff"}))}}return q(o,{fill:"url(#"+k+")",opacity:1,"fill-opacity":1}),r.fill=l,r.opacity=1,r.fillOpacity=1,1},s=function(a){var b=a.getBBox(1);q(a.pattern,{patternTransform:a.matrix.invert()+" translate("+b.x+","+b.y+")"})},t=function(d,e,f){if("path"==d.type){for(var g,h,i,j,k,m=b(e).toLowerCase().split("-"),n=d.paper,r=f?"end":"start",s=d.node,t=d.attrs,u=t["stroke-width"],v=m.length,w="classic",x=3,y=3,z=5;v--;)switch(m[v]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":w=m[v];break;case"wide":y=5;break;case"narrow":y=2;break;case"long":x=5;break;case"short":x=2}if("open"==w?(x+=2,y+=2,z+=2,i=1,j=f?4:1,k={fill:"none",stroke:t.stroke}):(j=i=x/2,k={fill:t.stroke,stroke:"none"}),d._.arrows?f?(d._.arrows.endPath&&p[d._.arrows.endPath]--,d._.arrows.endMarker&&p[d._.arrows.endMarker]--):(d._.arrows.startPath&&p[d._.arrows.startPath]--,d._.arrows.startMarker&&p[d._.arrows.startMarker]--):d._.arrows={},"none"!=w){var A="raphael-marker-"+w,B="raphael-marker-"+r+w+x+y;c._g.doc.getElementById(A)?p[A]++:(n.defs.appendChild(q(q("path"),{"stroke-linecap":"round",d:o[w],id:A})),p[A]=1);var C,D=c._g.doc.getElementById(B);D?(p[B]++,C=D.getElementsByTagName("use")[0]):(D=q(q("marker"),{id:B,markerHeight:y,markerWidth:x,orient:"auto",refX:j,refY:y/2}),C=q(q("use"),{"xlink:href":"#"+A,transform:(f?"rotate(180 "+x/2+" "+y/2+") ":l)+"scale("+x/z+","+y/z+")","stroke-width":(1/((x/z+y/z)/2)).toFixed(4)}),D.appendChild(C),n.defs.appendChild(D),p[B]=1),q(C,k);var E=i*("diamond"!=w&&"oval"!=w);f?(g=d._.arrows.startdx*u||0,h=c.getTotalLength(t.path)-E*u):(g=E*u,h=c.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),k={},k["marker-"+r]="url(#"+B+")",(h||g)&&(k.d=c.getSubpath(t.path,g,h)),q(s,k),d._.arrows[r+"Path"]=A,d._.arrows[r+"Marker"]=B,d._.arrows[r+"dx"]=E,d._.arrows[r+"Type"]=w,d._.arrows[r+"String"]=e}else f?(g=d._.arrows.startdx*u||0,h=c.getTotalLength(t.path)-g):(g=0,h=c.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),d._.arrows[r+"Path"]&&q(s,{d:c.getSubpath(t.path,g,h)}),delete d._.arrows[r+"Path"],delete d._.arrows[r+"Marker"],delete d._.arrows[r+"dx"],delete d._.arrows[r+"Type"],delete d._.arrows[r+"String"];for(k in p)if(p[a](k)&&!p[k]){var F=c._g.doc.getElementById(k);F&&F.parentNode.removeChild(F)}}},u={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},v=function(a,c,d){if(c=u[b(c).toLowerCase()]){for(var e=a.attrs["stroke-width"]||"1",f={round:e,square:e,butt:0}[a.attrs["stroke-linecap"]||d["stroke-linecap"]]||0,g=[],h=c.length;h--;)g[h]=c[h]*e+(h%2?1:-1)*f;q(a.node,{"stroke-dasharray":g.join(",")})}},w=function(d,f){var i=d.node,k=d.attrs,m=i.style.visibility;i.style.visibility="hidden";for(var o in f)if(f[a](o)){if(!c._availableAttrs[a](o))continue;var p=f[o];switch(k[o]=p,o){case"blur":d.blur(p);break;case"href":case"title":var u=q("title"),w=c._g.doc.createTextNode(p);u.appendChild(w),i.appendChild(u);break;case"target":var x=i.parentNode;if("a"!=x.tagName.toLowerCase()){var u=q("a");x.insertBefore(u,i),u.appendChild(i),x=u}"target"==o?x.setAttributeNS(n,"show","blank"==p?"new":p):x.setAttributeNS(n,o,p);break;case"cursor":i.style.cursor=p;break;case"transform":d.transform(p);break;case"arrow-start":t(d,p);break;case"arrow-end":t(d,p,1);break;case"clip-rect":var z=b(p).split(j);if(4==z.length){d.clip&&d.clip.parentNode.parentNode.removeChild(d.clip.parentNode);var A=q("clipPath"),B=q("rect");A.id=c.createUUID(),q(B,{x:z[0],y:z[1],width:z[2],height:z[3]}),A.appendChild(B),d.paper.defs.appendChild(A),q(i,{"clip-path":"url(#"+A.id+")"}),d.clip=B}if(!p){var C=i.getAttribute("clip-path");if(C){var D=c._g.doc.getElementById(C.replace(/(^url\(#|\)$)/g,l));D&&D.parentNode.removeChild(D),q(i,{"clip-path":l}),delete d.clip}}break;case"path":"path"==d.type&&(q(i,{d:p?k.path=c._pathToAbsolute(p):"M0,0"}),d._.dirty=1,d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1)));break;case"width":if(i.setAttribute(o,p),d._.dirty=1,!k.fx)break;o="x",p=k.x;case"x":k.fx&&(p=-k.x-(k.width||0));case"rx":if("rx"==o&&"rect"==d.type)break;case"cx":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"height":if(i.setAttribute(o,p),d._.dirty=1,!k.fy)break;o="y",p=k.y;case"y":k.fy&&(p=-k.y-(k.height||0));case"ry":if("ry"==o&&"rect"==d.type)break;case"cy":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"r":"rect"==d.type?q(i,{rx:p,ry:p}):i.setAttribute(o,p),d._.dirty=1;break;case"src":"image"==d.type&&i.setAttributeNS(n,"href",p);break;case"stroke-width":(1!=d._.sx||1!=d._.sy)&&(p/=g(h(d._.sx),h(d._.sy))||1),d.paper._vbSize&&(p*=d.paper._vbSize),i.setAttribute(o,p),k["stroke-dasharray"]&&v(d,k["stroke-dasharray"],f),d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"stroke-dasharray":v(d,p,f);break;case"fill":var E=b(p).match(c._ISURL);if(E){A=q("pattern");var F=q("image");A.id=c.createUUID(),q(A,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1}),q(F,{x:0,y:0,"xlink:href":E[1]}),A.appendChild(F),function(a){c._preload(E[1],function(){var b=this.offsetWidth,c=this.offsetHeight;q(a,{width:b,height:c}),q(F,{width:b,height:c}),d.paper.safari()})}(A),d.paper.defs.appendChild(A),q(i,{fill:"url(#"+A.id+")"}),d.pattern=A,d.pattern&&s(d);break}var G=c.getRGB(p);if(G.error){if(("circle"==d.type||"ellipse"==d.type||"r"!=b(p).charAt())&&r(d,p)){if("opacity"in k||"fill-opacity"in k){var H=c._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l));if(H){var I=H.getElementsByTagName("stop");q(I[I.length-1],{"stop-opacity":("opacity"in k?k.opacity:1)*("fill-opacity"in k?k["fill-opacity"]:1)})}}k.gradient=p,k.fill="none";break}}else delete f.gradient,delete k.gradient,!c.is(k.opacity,"undefined")&&c.is(f.opacity,"undefined")&&q(i,{opacity:k.opacity}),!c.is(k["fill-opacity"],"undefined")&&c.is(f["fill-opacity"],"undefined")&&q(i,{"fill-opacity":k["fill-opacity"]});G[a]("opacity")&&q(i,{"fill-opacity":G.opacity>1?G.opacity/100:G.opacity});case"stroke":G=c.getRGB(p),i.setAttribute(o,G.hex),"stroke"==o&&G[a]("opacity")&&q(i,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity}),"stroke"==o&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":("circle"==d.type||"ellipse"==d.type||"r"!=b(p).charAt())&&r(d,p);break;case"opacity":k.gradient&&!k[a]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){H=c._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),H&&(I=H.getElementsByTagName("stop"),q(I[I.length-1],{"stop-opacity":p}));break}default:"font-size"==o&&(p=e(p,10)+"px");var J=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[J]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if("text"==d.type&&(f[a]("text")||f[a]("font")||f[a]("font-size")||f[a]("x")||f[a]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(c._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;
+if(f[a]("text")){for(g.text=f.text;h.firstChild;)h.removeChild(h.firstChild);for(var j,k=b(f.text).split("\n"),m=[],n=0,o=k.length;o>n;n++)j=q("tspan"),n&&q(j,{dy:i*x,x:g.x}),j.appendChild(c._g.doc.createTextNode(k[n])),h.appendChild(j),m[n]=j}else for(m=h.getElementsByTagName("tspan"),n=0,o=m.length;o>n;n++)n?q(m[n],{dy:i*x,x:g.x}):q(m[0],{dy:0});q(h,{x:g.x,y:g.y}),d._.dirty=1;var p=d._getBBox(),r=g.y-(p.y+p.height/2);r&&c.is(r,"finite")&&q(m[0],{dy:r})}},z=function(a,b){this[0]=this.node=a,a.raphael=!0,this.id=c._oid++,a.raphaelid=this.id,this.matrix=c.matrix(),this.realPath=null,this.paper=b,this.attrs=this.attrs||{},this._={transform:[],sx:1,sy:1,deg:0,dx:0,dy:0,dirty:1},!b.bottom&&(b.bottom=this),this.prev=b.top,b.top&&(b.top.next=this),b.top=this,this.next=null},A=c.el;z.prototype=A,A.constructor=z,c._engine.path=function(a,b){var c=q("path");b.canvas&&b.canvas.appendChild(c);var d=new z(c,b);return d.type="path",w(d,{fill:"none",stroke:"#000",path:a}),d},A.rotate=function(a,c,e){if(this.removed)return this;if(a=b(a).split(j),a.length-1&&(c=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(c=e),null==c||null==e){var f=this.getBBox(1);c=f.x+f.width/2,e=f.y+f.height/2}return this.transform(this._.transform.concat([["r",a,c,e]])),this},A.scale=function(a,c,e,f){if(this.removed)return this;if(a=b(a).split(j),a.length-1&&(c=d(a[1]),e=d(a[2]),f=d(a[3])),a=d(a[0]),null==c&&(c=a),null==f&&(e=f),null==e||null==f)var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,c,e,f]])),this},A.translate=function(a,c){return this.removed?this:(a=b(a).split(j),a.length-1&&(c=d(a[1])),a=d(a[0])||0,c=+c||0,this.transform(this._.transform.concat([["t",a,c]])),this)},A.transform=function(b){var d=this._;if(null==b)return d.transform;if(c._extractTransform(this,b),this.clip&&q(this.clip,{transform:this.matrix.invert()}),this.pattern&&s(this),this.node&&q(this.node,{transform:this.matrix}),1!=d.sx||1!=d.sy){var e=this.attrs[a]("stroke-width")?this.attrs["stroke-width"]:1;this.attr({"stroke-width":e})}return this},A.hide=function(){return!this.removed&&this.paper.safari(this.node.style.display="none"),this},A.show=function(){return!this.removed&&this.paper.safari(this.node.style.display=""),this},A.remove=function(){if(!this.removed&&this.node.parentNode){var a=this.paper;a.__set__&&a.__set__.exclude(this),k.unbind("raphael.*.*."+this.id),this.gradient&&a.defs.removeChild(this.gradient),c._tear(this,a),"a"==this.node.parentNode.tagName.toLowerCase()?this.node.parentNode.parentNode.removeChild(this.node.parentNode):this.node.parentNode.removeChild(this.node);for(var b in this)this[b]="function"==typeof this[b]?c._removedFactory(b):null;this.removed=!0}},A._getBBox=function(){if("none"==this.node.style.display){this.show();var a=!0}var b={};try{b=this.node.getBBox()}catch(c){}finally{b=b||{}}return a&&this.hide(),b},A.attr=function(b,d){if(this.removed)return this;if(null==b){var e={};for(var f in this.attrs)this.attrs[a](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}if(null==d&&c.is(b,"string")){if("fill"==b&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;if("transform"==b)return this._.transform;for(var g=b.split(j),h={},i=0,l=g.length;l>i;i++)b=g[i],h[b]=b in this.attrs?this.attrs[b]:c.is(this.paper.customAttributes[b],"function")?this.paper.customAttributes[b].def:c._availableAttrs[b];return l-1?h:h[g[0]]}if(null==d&&c.is(b,"array")){for(h={},i=0,l=b.length;l>i;i++)h[b[i]]=this.attr(b[i]);return h}if(null!=d){var m={};m[b]=d}else null!=b&&c.is(b,"object")&&(m=b);for(var n in m)k("raphael.attr."+n+"."+this.id,this,m[n]);for(n in this.paper.customAttributes)if(this.paper.customAttributes[a](n)&&m[a](n)&&c.is(this.paper.customAttributes[n],"function")){var o=this.paper.customAttributes[n].apply(this,[].concat(m[n]));this.attrs[n]=m[n];for(var p in o)o[a](p)&&(m[p]=o[p])}return w(this,m),this},A.toFront=function(){if(this.removed)return this;"a"==this.node.parentNode.tagName.toLowerCase()?this.node.parentNode.parentNode.appendChild(this.node.parentNode):this.node.parentNode.appendChild(this.node);var a=this.paper;return a.top!=this&&c._tofront(this,a),this},A.toBack=function(){if(this.removed)return this;var a=this.node.parentNode;return"a"==a.tagName.toLowerCase()?a.parentNode.insertBefore(this.node.parentNode,this.node.parentNode.parentNode.firstChild):a.firstChild!=this.node&&a.insertBefore(this.node,this.node.parentNode.firstChild),c._toback(this,this.paper),this.paper,this},A.insertAfter=function(a){if(this.removed)return this;var b=a.node||a[a.length-1].node;return b.nextSibling?b.parentNode.insertBefore(this.node,b.nextSibling):b.parentNode.appendChild(this.node),c._insertafter(this,a,this.paper),this},A.insertBefore=function(a){if(this.removed)return this;var b=a.node||a[0].node;return b.parentNode.insertBefore(this.node,b),c._insertbefore(this,a,this.paper),this},A.blur=function(a){var b=this;if(0!==+a){var d=q("filter"),e=q("feGaussianBlur");b.attrs.blur=a,d.id=c.createUUID(),q(e,{stdDeviation:+a||1.5}),d.appendChild(e),b.paper.defs.appendChild(d),b._blur=d,q(b.node,{filter:"url(#"+d.id+")"})}else b._blur&&(b._blur.parentNode.removeChild(b._blur),delete b._blur,delete b.attrs.blur),b.node.removeAttribute("filter");return b},c._engine.circle=function(a,b,c,d){var e=q("circle");a.canvas&&a.canvas.appendChild(e);var f=new z(e,a);return f.attrs={cx:b,cy:c,r:d,fill:"none",stroke:"#000"},f.type="circle",q(e,f.attrs),f},c._engine.rect=function(a,b,c,d,e,f){var g=q("rect");a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);return h.attrs={x:b,y:c,width:d,height:e,r:f||0,rx:f||0,ry:f||0,fill:"none",stroke:"#000"},h.type="rect",q(g,h.attrs),h},c._engine.ellipse=function(a,b,c,d,e){var f=q("ellipse");a.canvas&&a.canvas.appendChild(f);var g=new z(f,a);return g.attrs={cx:b,cy:c,rx:d,ry:e,fill:"none",stroke:"#000"},g.type="ellipse",q(f,g.attrs),g},c._engine.image=function(a,b,c,d,e,f){var g=q("image");q(g,{x:c,y:d,width:e,height:f,preserveAspectRatio:"none"}),g.setAttributeNS(n,"href",b),a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);return h.attrs={x:c,y:d,width:e,height:f,src:b},h.type="image",h},c._engine.text=function(a,b,d,e){var f=q("text");a.canvas&&a.canvas.appendChild(f);var g=new z(f,a);return g.attrs={x:b,y:d,"text-anchor":"middle",text:e,font:c._availableAttrs.font,stroke:"none",fill:"#000"},g.type="text",w(g,g.attrs),g},c._engine.setSize=function(a,b){return this.width=a||this.width,this.height=b||this.height,this.canvas.setAttribute("width",this.width),this.canvas.setAttribute("height",this.height),this._viewBox&&this.setViewBox.apply(this,this._viewBox),this},c._engine.create=function(){var a=c._getContainer.apply(0,arguments),b=a&&a.container,d=a.x,e=a.y,f=a.width,g=a.height;if(!b)throw new Error("SVG container not found.");var h,i=q("svg"),j="overflow:hidden;";return d=d||0,e=e||0,f=f||512,g=g||342,q(i,{height:g,version:1.1,width:f,xmlns:"http://www.w3.org/2000/svg"}),1==b?(i.style.cssText=j+"position:absolute;left:"+d+"px;top:"+e+"px",c._g.doc.body.appendChild(i),h=1):(i.style.cssText=j+"position:relative",b.firstChild?b.insertBefore(i,b.firstChild):b.appendChild(i)),b=new c._Paper,b.width=f,b.height=g,b.canvas=i,b.clear(),b._left=b._top=0,h&&(b.renderfix=function(){}),b.renderfix(),b},c._engine.setViewBox=function(a,b,c,d,e){k("raphael.setViewBox",this,this._viewBox,[a,b,c,d,e]);var f,h,i=g(c/this.width,d/this.height),j=this.top,l=e?"meet":"xMinYMin";for(null==a?(this._vbSize&&(i=1),delete this._vbSize,f="0 0 "+this.width+m+this.height):(this._vbSize=i,f=a+m+b+m+c+m+d),q(this.canvas,{viewBox:f,preserveAspectRatio:l});i&&j;)h="stroke-width"in j.attrs?j.attrs["stroke-width"]:1,j.attr({"stroke-width":h}),j._.dirty=1,j._.dirtyT=1,j=j.prev;return this._viewBox=[a,b,c,d,!!e],this},c.prototype.renderfix=function(){var a,b=this.canvas,c=b.style;try{a=b.getScreenCTM()||b.createSVGMatrix()}catch(d){a=b.createSVGMatrix()}var e=-a.e%1,f=-a.f%1;(e||f)&&(e&&(this._left=(this._left+e)%1,c.left=this._left+"px"),f&&(this._top=(this._top+f)%1,c.top=this._top+"px"))},c.prototype.clear=function(){c.eve("raphael.clear",this);for(var a=this.canvas;a.firstChild;)a.removeChild(a.firstChild);this.bottom=this.top=null,(this.desc=q("desc")).appendChild(c._g.doc.createTextNode("Created with Raphaël "+c.version)),a.appendChild(this.desc),a.appendChild(this.defs=q("defs"))},c.prototype.remove=function(){k("raphael.remove",this),this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null};var B=c.st;for(var C in A)A[a](C)&&!B[a](C)&&(B[C]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(C))}}(),function(){if(c.vml){var a="hasOwnProperty",b=String,d=parseFloat,e=Math,f=e.round,g=e.max,h=e.min,i=e.abs,j="fill",k=/[, ]+/,l=c.eve,m=" progid:DXImageTransform.Microsoft",n=" ",o="",p={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},q=/([clmz]),?([^clmz]*)/gi,r=/ progid:\S+Blur\([^\)]+\)/g,s=/-?[^,\s-]+/g,t="position:absolute;left:0;top:0;width:1px;height:1px",u=21600,v={path:1,rect:1,image:1},w={circle:1,ellipse:1},x=function(a){var d=/[ahqstv]/gi,e=c._pathToAbsolute;if(b(a).match(d)&&(e=c._path2curve),d=/[clmz]/g,e==c._pathToAbsolute&&!b(a).match(d)){var g=b(a).replace(q,function(a,b,c){var d=[],e="m"==b.toLowerCase(),g=p[b];return c.replace(s,function(a){e&&2==d.length&&(g+=d+p["m"==b?"l":"L"],d=[]),d.push(f(a*u))}),g+d});return g}var h,i,j=e(a);g=[];for(var k=0,l=j.length;l>k;k++){h=j[k],i=j[k][0].toLowerCase(),"z"==i&&(i="x");for(var m=1,r=h.length;r>m;m++)i+=f(h[m]*u)+(m!=r-1?",":o);g.push(i)}return g.join(n)},y=function(a,b,d){var e=c.matrix();return e.rotate(-a,.5,.5),{dx:e.x(b,d),dy:e.y(b,d)}},z=function(a,b,c,d,e,f){var g=a._,h=a.matrix,k=g.fillpos,l=a.node,m=l.style,o=1,p="",q=u/b,r=u/c;if(m.visibility="hidden",b&&c){if(l.coordsize=i(q)+n+i(r),m.rotation=f*(0>b*c?-1:1),f){var s=y(f,d,e);d=s.dx,e=s.dy}if(0>b&&(p+="x"),0>c&&(p+=" y")&&(o=-1),m.flip=p,l.coordorigin=d*-q+n+e*-r,k||g.fillsize){var t=l.getElementsByTagName(j);t=t&&t[0],l.removeChild(t),k&&(s=y(f,h.x(k[0],k[1]),h.y(k[0],k[1])),t.position=s.dx*o+n+s.dy*o),g.fillsize&&(t.size=g.fillsize[0]*i(b)+n+g.fillsize[1]*i(c)),l.appendChild(t)}m.visibility="visible"}};c.toString=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};var A=function(a,c,d){for(var e=b(c).toLowerCase().split("-"),f=d?"end":"start",g=e.length,h="classic",i="medium",j="medium";g--;)switch(e[g]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":h=e[g];break;case"wide":case"narrow":j=e[g];break;case"long":case"short":i=e[g]}var k=a.node.getElementsByTagName("stroke")[0];k[f+"arrow"]=h,k[f+"arrowlength"]=i,k[f+"arrowwidth"]=j},B=function(e,i){e.attrs=e.attrs||{};var l=e.node,m=e.attrs,p=l.style,q=v[e.type]&&(i.x!=m.x||i.y!=m.y||i.width!=m.width||i.height!=m.height||i.cx!=m.cx||i.cy!=m.cy||i.rx!=m.rx||i.ry!=m.ry||i.r!=m.r),r=w[e.type]&&(m.cx!=i.cx||m.cy!=i.cy||m.r!=i.r||m.rx!=i.rx||m.ry!=i.ry),s=e;for(var t in i)i[a](t)&&(m[t]=i[t]);if(q&&(m.path=c._getPath[e.type](e),e._.dirty=1),i.href&&(l.href=i.href),i.title&&(l.title=i.title),i.target&&(l.target=i.target),i.cursor&&(p.cursor=i.cursor),"blur"in i&&e.blur(i.blur),(i.path&&"path"==e.type||q)&&(l.path=x(~b(m.path).toLowerCase().indexOf("r")?c._pathToAbsolute(m.path):m.path),"image"==e.type&&(e._.fillpos=[m.x,m.y],e._.fillsize=[m.width,m.height],z(e,1,1,0,0,0))),"transform"in i&&e.transform(i.transform),r){var y=+m.cx,B=+m.cy,D=+m.rx||+m.r||0,E=+m.ry||+m.r||0;l.path=c.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x",f((y-D)*u),f((B-E)*u),f((y+D)*u),f((B+E)*u),f(y*u)),e._.dirty=1}if("clip-rect"in i){var G=b(i["clip-rect"]).split(k);if(4==G.length){G[2]=+G[2]+ +G[0],G[3]=+G[3]+ +G[1];var H=l.clipRect||c._g.doc.createElement("div"),I=H.style;I.clip=c.format("rect({1}px {2}px {3}px {0}px)",G),l.clipRect||(I.position="absolute",I.top=0,I.left=0,I.width=e.paper.width+"px",I.height=e.paper.height+"px",l.parentNode.insertBefore(H,l),H.appendChild(l),l.clipRect=H)}i["clip-rect"]||l.clipRect&&(l.clipRect.style.clip="auto")}if(e.textpath){var J=e.textpath.style;i.font&&(J.font=i.font),i["font-family"]&&(J.fontFamily='"'+i["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g,o)+'"'),i["font-size"]&&(J.fontSize=i["font-size"]),i["font-weight"]&&(J.fontWeight=i["font-weight"]),i["font-style"]&&(J.fontStyle=i["font-style"])}if("arrow-start"in i&&A(s,i["arrow-start"]),"arrow-end"in i&&A(s,i["arrow-end"],1),null!=i.opacity||null!=i["stroke-width"]||null!=i.fill||null!=i.src||null!=i.stroke||null!=i["stroke-width"]||null!=i["stroke-opacity"]||null!=i["fill-opacity"]||null!=i["stroke-dasharray"]||null!=i["stroke-miterlimit"]||null!=i["stroke-linejoin"]||null!=i["stroke-linecap"]){var K=l.getElementsByTagName(j),L=!1;if(K=K&&K[0],!K&&(L=K=F(j)),"image"==e.type&&i.src&&(K.src=i.src),i.fill&&(K.on=!0),(null==K.on||"none"==i.fill||null===i.fill)&&(K.on=!1),K.on&&i.fill){var M=b(i.fill).match(c._ISURL);if(M){K.parentNode==l&&l.removeChild(K),K.rotate=!0,K.src=M[1],K.type="tile";var N=e.getBBox(1);K.position=N.x+n+N.y,e._.fillpos=[N.x,N.y],c._preload(M[1],function(){e._.fillsize=[this.offsetWidth,this.offsetHeight]})}else K.color=c.getRGB(i.fill).hex,K.src=o,K.type="solid",c.getRGB(i.fill).error&&(s.type in{circle:1,ellipse:1}||"r"!=b(i.fill).charAt())&&C(s,i.fill,K)&&(m.fill="none",m.gradient=i.fill,K.rotate=!1)}if("fill-opacity"in i||"opacity"in i){var O=((+m["fill-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+c.getRGB(i.fill).o+1||2)-1);O=h(g(O,0),1),K.opacity=O,K.src&&(K.color="none")}l.appendChild(K);var P=l.getElementsByTagName("stroke")&&l.getElementsByTagName("stroke")[0],Q=!1;!P&&(Q=P=F("stroke")),(i.stroke&&"none"!=i.stroke||i["stroke-width"]||null!=i["stroke-opacity"]||i["stroke-dasharray"]||i["stroke-miterlimit"]||i["stroke-linejoin"]||i["stroke-linecap"])&&(P.on=!0),("none"==i.stroke||null===i.stroke||null==P.on||0==i.stroke||0==i["stroke-width"])&&(P.on=!1);var R=c.getRGB(i.stroke);P.on&&i.stroke&&(P.color=R.hex),O=((+m["stroke-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+R.o+1||2)-1);var S=.75*(d(i["stroke-width"])||1);if(O=h(g(O,0),1),null==i["stroke-width"]&&(S=m["stroke-width"]),i["stroke-width"]&&(P.weight=S),S&&1>S&&(O*=S)&&(P.weight=1),P.opacity=O,i["stroke-linejoin"]&&(P.joinstyle=i["stroke-linejoin"]||"miter"),P.miterlimit=i["stroke-miterlimit"]||8,i["stroke-linecap"]&&(P.endcap="butt"==i["stroke-linecap"]?"flat":"square"==i["stroke-linecap"]?"square":"round"),i["stroke-dasharray"]){var T={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};P.dashstyle=T[a](i["stroke-dasharray"])?T[i["stroke-dasharray"]]:o}Q&&l.appendChild(P)}if("text"==s.type){s.paper.canvas.style.display=o;var U=s.paper.span,V=100,W=m.font&&m.font.match(/\d+(?:\.\d*)?(?=px)/);p=U.style,m.font&&(p.font=m.font),m["font-family"]&&(p.fontFamily=m["font-family"]),m["font-weight"]&&(p.fontWeight=m["font-weight"]),m["font-style"]&&(p.fontStyle=m["font-style"]),W=d(m["font-size"]||W&&W[0])||10,p.fontSize=W*V+"px",s.textpath.string&&(U.innerHTML=b(s.textpath.string).replace(/</g,"<").replace(/&/g,"&").replace(/\n/g,"<br>"));var X=U.getBoundingClientRect();s.W=m.w=(X.right-X.left)/V,s.H=m.h=(X.bottom-X.top)/V,s.X=m.x,s.Y=m.y+s.H/2,("x"in i||"y"in i)&&(s.path.v=c.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));for(var Y=["x","y","text","font","font-family","font-weight","font-style","font-size"],Z=0,$=Y.length;$>Z;Z++)if(Y[Z]in i){s._.dirty=1;break}switch(m["text-anchor"]){case"start":s.textpath.style["v-text-align"]="left",s.bbx=s.W/2;break;case"end":s.textpath.style["v-text-align"]="right",s.bbx=-s.W/2;break;default:s.textpath.style["v-text-align"]="center",s.bbx=0}s.textpath.style["v-text-kern"]=!0}},C=function(a,f,g){a.attrs=a.attrs||{};var h=(a.attrs,Math.pow),i="linear",j=".5 .5";if(a.attrs.gradient=f,f=b(f).replace(c._radial_gradient,function(a,b,c){return i="radial",b&&c&&(b=d(b),c=d(c),h(b-.5,2)+h(c-.5,2)>.25&&(c=e.sqrt(.25-h(b-.5,2))*(2*(c>.5)-1)+.5),j=b+n+c),o}),f=f.split(/\s*\-\s*/),"linear"==i){var k=f.shift();if(k=-d(k),isNaN(k))return null}var l=c._parseDots(f);if(!l)return null;if(a=a.shape||a.node,l.length){a.removeChild(g),g.on=!0,g.method="none",g.color=l[0].color,g.color2=l[l.length-1].color;for(var m=[],p=0,q=l.length;q>p;p++)l[p].offset&&m.push(l[p].offset+n+l[p].color);g.colors=m.length?m.join():"0% "+g.color,"radial"==i?(g.type="gradientTitle",g.focus="100%",g.focussize="0 0",g.focusposition=j,g.angle=0):(g.type="gradient",g.angle=(270-k)%360),a.appendChild(g)}return 1},D=function(a,b){this[0]=this.node=a,a.raphael=!0,this.id=c._oid++,a.raphaelid=this.id,this.X=0,this.Y=0,this.attrs={},this.paper=b,this.matrix=c.matrix(),this._={transform:[],sx:1,sy:1,dx:0,dy:0,deg:0,dirty:1,dirtyT:1},!b.bottom&&(b.bottom=this),this.prev=b.top,b.top&&(b.top.next=this),b.top=this,this.next=null},E=c.el;D.prototype=E,E.constructor=D,E.transform=function(a){if(null==a)return this._.transform;var d,e=this.paper._viewBoxShift,f=e?"s"+[e.scale,e.scale]+"-1-1t"+[e.dx,e.dy]:o;e&&(d=a=b(a).replace(/\.{3}|\u2026/g,this._.transform||o)),c._extractTransform(this,f+a);var g,h=this.matrix.clone(),i=this.skew,j=this.node,k=~b(this.attrs.fill).indexOf("-"),l=!b(this.attrs.fill).indexOf("url(");if(h.translate(1,1),l||k||"image"==this.type)if(i.matrix="1 0 0 1",i.offset="0 0",g=h.split(),k&&g.noRotation||!g.isSimple){j.style.filter=h.toFilter();var m=this.getBBox(),p=this.getBBox(1),q=m.x-p.x,r=m.y-p.y;j.coordorigin=q*-u+n+r*-u,z(this,1,1,q,r,0)}else j.style.filter=o,z(this,g.scalex,g.scaley,g.dx,g.dy,g.rotate);else j.style.filter=o,i.matrix=b(h),i.offset=h.offset();return d&&(this._.transform=d),this},E.rotate=function(a,c,e){if(this.removed)return this;if(null!=a){if(a=b(a).split(k),a.length-1&&(c=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(c=e),null==c||null==e){var f=this.getBBox(1);c=f.x+f.width/2,e=f.y+f.height/2}return this._.dirtyT=1,this.transform(this._.transform.concat([["r",a,c,e]])),this}},E.translate=function(a,c){return this.removed?this:(a=b(a).split(k),a.length-1&&(c=d(a[1])),a=d(a[0])||0,c=+c||0,this._.bbox&&(this._.bbox.x+=a,this._.bbox.y+=c),this.transform(this._.transform.concat([["t",a,c]])),this)},E.scale=function(a,c,e,f){if(this.removed)return this;if(a=b(a).split(k),a.length-1&&(c=d(a[1]),e=d(a[2]),f=d(a[3]),isNaN(e)&&(e=null),isNaN(f)&&(f=null)),a=d(a[0]),null==c&&(c=a),null==f&&(e=f),null==e||null==f)var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,c,e,f]])),this._.dirtyT=1,this},E.hide=function(){return!this.removed&&(this.node.style.display="none"),this},E.show=function(){return!this.removed&&(this.node.style.display=o),this},E._getBBox=function(){return this.removed?{}:{x:this.X+(this.bbx||0)-this.W/2,y:this.Y-this.H,width:this.W,height:this.H}},E.remove=function(){if(!this.removed&&this.node.parentNode){this.paper.__set__&&this.paper.__set__.exclude(this),c.eve.unbind("raphael.*.*."+this.id),c._tear(this,this.paper),this.node.parentNode.removeChild(this.node),this.shape&&this.shape.parentNode.removeChild(this.shape);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null;this.removed=!0}},E.attr=function(b,d){if(this.removed)return this;if(null==b){var e={};for(var f in this.attrs)this.attrs[a](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}if(null==d&&c.is(b,"string")){if(b==j&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;for(var g=b.split(k),h={},i=0,m=g.length;m>i;i++)b=g[i],h[b]=b in this.attrs?this.attrs[b]:c.is(this.paper.customAttributes[b],"function")?this.paper.customAttributes[b].def:c._availableAttrs[b];return m-1?h:h[g[0]]}if(this.attrs&&null==d&&c.is(b,"array")){for(h={},i=0,m=b.length;m>i;i++)h[b[i]]=this.attr(b[i]);return h}var n;null!=d&&(n={},n[b]=d),null==d&&c.is(b,"object")&&(n=b);for(var o in n)l("raphael.attr."+o+"."+this.id,this,n[o]);if(n){for(o in this.paper.customAttributes)if(this.paper.customAttributes[a](o)&&n[a](o)&&c.is(this.paper.customAttributes[o],"function")){var p=this.paper.customAttributes[o].apply(this,[].concat(n[o]));this.attrs[o]=n[o];for(var q in p)p[a](q)&&(n[q]=p[q])}n.text&&"text"==this.type&&(this.textpath.string=n.text),B(this,n)}return this},E.toFront=function(){return!this.removed&&this.node.parentNode.appendChild(this.node),this.paper&&this.paper.top!=this&&c._tofront(this,this.paper),this},E.toBack=function(){return this.removed?this:(this.node.parentNode.firstChild!=this.node&&(this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild),c._toback(this,this.paper)),this)},E.insertAfter=function(a){return this.removed?this:(a.constructor==c.st.constructor&&(a=a[a.length-1]),a.node.nextSibling?a.node.parentNode.insertBefore(this.node,a.node.nextSibling):a.node.parentNode.appendChild(this.node),c._insertafter(this,a,this.paper),this)},E.insertBefore=function(a){return this.removed?this:(a.constructor==c.st.constructor&&(a=a[0]),a.node.parentNode.insertBefore(this.node,a.node),c._insertbefore(this,a,this.paper),this)},E.blur=function(a){var b=this.node.runtimeStyle,d=b.filter;return d=d.replace(r,o),0!==+a?(this.attrs.blur=a,b.filter=d+n+m+".Blur(pixelradius="+(+a||1.5)+")",b.margin=c.format("-{0}px 0 0 -{0}px",f(+a||1.5))):(b.filter=d,b.margin=0,delete this.attrs.blur),this},c._engine.path=function(a,b){var c=F("shape");c.style.cssText=t,c.coordsize=u+n+u,c.coordorigin=b.coordorigin;var d=new D(c,b),e={fill:"none",stroke:"#000"};a&&(e.path=a),d.type="path",d.path=[],d.Path=o,B(d,e),b.canvas.appendChild(c);var f=F("skew");return f.on=!0,c.appendChild(f),d.skew=f,d.transform(o),d},c._engine.rect=function(a,b,d,e,f,g){var h=c._rectPath(b,d,e,f,g),i=a.path(h),j=i.attrs;return i.X=j.x=b,i.Y=j.y=d,i.W=j.width=e,i.H=j.height=f,j.r=g,j.path=h,i.type="rect",i},c._engine.ellipse=function(a,b,c,d,e){var f=a.path();return f.attrs,f.X=b-d,f.Y=c-e,f.W=2*d,f.H=2*e,f.type="ellipse",B(f,{cx:b,cy:c,rx:d,ry:e}),f},c._engine.circle=function(a,b,c,d){var e=a.path();return e.attrs,e.X=b-d,e.Y=c-d,e.W=e.H=2*d,e.type="circle",B(e,{cx:b,cy:c,r:d}),e},c._engine.image=function(a,b,d,e,f,g){var h=c._rectPath(d,e,f,g),i=a.path(h).attr({stroke:"none"}),k=i.attrs,l=i.node,m=l.getElementsByTagName(j)[0];return k.src=b,i.X=k.x=d,i.Y=k.y=e,i.W=k.width=f,i.H=k.height=g,k.path=h,i.type="image",m.parentNode==l&&l.removeChild(m),m.rotate=!0,m.src=b,m.type="tile",i._.fillpos=[d,e],i._.fillsize=[f,g],l.appendChild(m),z(i,1,1,0,0,0),i},c._engine.text=function(a,d,e,g){var h=F("shape"),i=F("path"),j=F("textpath");d=d||0,e=e||0,g=g||"",i.v=c.format("m{0},{1}l{2},{1}",f(d*u),f(e*u),f(d*u)+1),i.textpathok=!0,j.string=b(g),j.on=!0,h.style.cssText=t,h.coordsize=u+n+u,h.coordorigin="0 0";var k=new D(h,a),l={fill:"#000",stroke:"none",font:c._availableAttrs.font,text:g};k.shape=h,k.path=i,k.textpath=j,k.type="text",k.attrs.text=b(g),k.attrs.x=d,k.attrs.y=e,k.attrs.w=1,k.attrs.h=1,B(k,l),h.appendChild(j),h.appendChild(i),a.canvas.appendChild(h);var m=F("skew");return m.on=!0,h.appendChild(m),k.skew=m,k.transform(o),k},c._engine.setSize=function(a,b){var d=this.canvas.style;return this.width=a,this.height=b,a==+a&&(a+="px"),b==+b&&(b+="px"),d.width=a,d.height=b,d.clip="rect(0 "+a+" "+b+" 0)",this._viewBox&&c._engine.setViewBox.apply(this,this._viewBox),this},c._engine.setViewBox=function(a,b,d,e,f){c.eve("raphael.setViewBox",this,this._viewBox,[a,b,d,e,f]);var h,i,j=this.width,k=this.height,l=1/g(d/j,e/k);return f&&(h=k/e,i=j/d,j>d*h&&(a-=(j-d*h)/2/h),k>e*i&&(b-=(k-e*i)/2/i)),this._viewBox=[a,b,d,e,!!f],this._viewBoxShift={dx:-a,dy:-b,scale:l},this.forEach(function(a){a.transform("...")}),this};var F;c._engine.initWin=function(a){var b=a.document;b.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!b.namespaces.rvml&&b.namespaces.add("rvml","urn:schemas-microsoft-com:vml"),F=function(a){return b.createElement("<rvml:"+a+' class="rvml">')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},c._engine.initWin(c._g.win),c._engine.create=function(){var a=c._getContainer.apply(0,arguments),b=a.container,d=a.height,e=a.width,f=a.x,g=a.y;if(!b)throw new Error("VML container not found.");var h=new c._Paper,i=h.canvas=c._g.doc.createElement("div"),j=i.style;return f=f||0,g=g||0,e=e||512,d=d||342,h.width=e,h.height=d,e==+e&&(e+="px"),d==+d&&(d+="px"),h.coordsize=1e3*u+n+1e3*u,h.coordorigin="0 0",h.span=c._g.doc.createElement("span"),h.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",i.appendChild(h.span),j.cssText=c.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",e,d),1==b?(c._g.doc.body.appendChild(i),j.left=f+"px",j.top=g+"px",j.position="absolute"):b.firstChild?b.insertBefore(i,b.firstChild):b.appendChild(i),h.renderfix=function(){},h},c.prototype.clear=function(){c.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=c._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},c.prototype.remove=function(){c.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null;return!0};var G=c.st;for(var H in E)E[a](H)&&!G[a](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}}(),B.was?A.win.Raphael=c:Raphael=c,c});
\ No newline at end of file