소스 검색

提交版本

李云瑞 1 년 전
부모
커밋
f0c9315dbb
15개의 변경된 파일3434개의 추가작업 그리고 0개의 파일을 삭제
  1. 96 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobCodeController.java
  2. 181 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobGroupController.java
  3. 166 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobInfoController.java
  4. 232 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobLogController.java
  5. 110 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
  6. 97 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/jobcode.index.1.js
  7. 359 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/jobgroup.index.1.js
  8. 682 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/jobinfo.index.1.js
  9. 91 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/joblog.detail.1.js
  10. 391 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/joblog.index.1.js
  11. 164 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/jobcode/jobcode.index.ftl
  12. 172 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/jobgroup/jobgroup.index.ftl
  13. 440 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/jobinfo/jobinfo.index.ftl
  14. 73 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/joblog/joblog.detail.ftl
  15. 180 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/joblog/joblog.index.ftl

+ 96 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobCodeController.java

@@ -0,0 +1,96 @@
+package com.xxl.job.admin.controller;
+
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.model.XxlJobLogGlue;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.admin.dao.XxlJobInfoDao;
+import com.xxl.job.admin.dao.XxlJobLogGlueDao;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.glue.GlueTypeEnum;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * job code controller
+ * @author xuxueli 2015-12-19 16:13:16
+ */
+@Controller
+@RequestMapping("/jobcode")
+public class JobCodeController {
+	
+	@Resource
+	private XxlJobInfoDao xxlJobInfoDao;
+	@Resource
+	private XxlJobLogGlueDao xxlJobLogGlueDao;
+
+	@RequestMapping
+	public String index(HttpServletRequest request, Model model, int jobId) {
+		XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId);
+		List<XxlJobLogGlue> jobLogGlues = xxlJobLogGlueDao.findByJobId(jobId);
+
+		if (jobInfo == null) {
+			throw new RuntimeException(I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
+		}
+		if (GlueTypeEnum.BEAN == GlueTypeEnum.match(jobInfo.getGlueType())) {
+			throw new RuntimeException(I18nUtil.getString("jobinfo_glue_gluetype_unvalid"));
+		}
+
+		// valid permission
+		JobInfoController.validPermission(request, jobInfo.getJobGroup());
+
+		// Glue类型-字典
+		model.addAttribute("GlueTypeEnum", GlueTypeEnum.values());
+
+		model.addAttribute("jobInfo", jobInfo);
+		model.addAttribute("jobLogGlues", jobLogGlues);
+		return "jobcode/jobcode.index";
+	}
+	
+	@RequestMapping("/save")
+	@ResponseBody
+	public ReturnT<String> save(Model model, int id, String glueSource, String glueRemark) {
+		// valid
+		if (glueRemark==null) {
+			return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_glue_remark")) );
+		}
+		if (glueRemark.length()<4 || glueRemark.length()>100) {
+			return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_remark_limit"));
+		}
+		XxlJobInfo exists_jobInfo = xxlJobInfoDao.loadById(id);
+		if (exists_jobInfo == null) {
+			return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
+		}
+		
+		// update new code
+		exists_jobInfo.setGlueSource(glueSource);
+		exists_jobInfo.setGlueRemark(glueRemark);
+		exists_jobInfo.setGlueUpdatetime(new Date());
+
+		exists_jobInfo.setUpdateTime(new Date());
+		xxlJobInfoDao.update(exists_jobInfo);
+
+		// log old code
+		XxlJobLogGlue xxlJobLogGlue = new XxlJobLogGlue();
+		xxlJobLogGlue.setJobId(exists_jobInfo.getId());
+		xxlJobLogGlue.setGlueType(exists_jobInfo.getGlueType());
+		xxlJobLogGlue.setGlueSource(glueSource);
+		xxlJobLogGlue.setGlueRemark(glueRemark);
+
+		xxlJobLogGlue.setAddTime(new Date());
+		xxlJobLogGlue.setUpdateTime(new Date());
+		xxlJobLogGlueDao.save(xxlJobLogGlue);
+
+		// remove code backup more than 30
+		xxlJobLogGlueDao.removeOld(exists_jobInfo.getId(), 30);
+
+		return ReturnT.SUCCESS;
+	}
+	
+}

+ 181 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobGroupController.java

@@ -0,0 +1,181 @@
+package com.xxl.job.admin.controller;
+
+import com.xxl.job.admin.core.model.XxlJobGroup;
+import com.xxl.job.admin.core.model.XxlJobRegistry;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.admin.dao.XxlJobGroupDao;
+import com.xxl.job.admin.dao.XxlJobInfoDao;
+import com.xxl.job.admin.dao.XxlJobRegistryDao;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.enums.RegistryConfig;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.*;
+
+/**
+ * job group controller
+ * @author xuxueli 2016-10-02 20:52:56
+ */
+@Controller
+@RequestMapping("/jobgroup")
+public class JobGroupController {
+
+	@Resource
+	public XxlJobInfoDao xxlJobInfoDao;
+	@Resource
+	public XxlJobGroupDao xxlJobGroupDao;
+	@Resource
+	private XxlJobRegistryDao xxlJobRegistryDao;
+
+	@RequestMapping
+	public String index(Model model) {
+		return "jobgroup/jobgroup.index";
+	}
+
+	@RequestMapping("/pageList")
+	@ResponseBody
+	public Map<String, Object> pageList(HttpServletRequest request,
+										@RequestParam(required = false, defaultValue = "0") int start,
+										@RequestParam(required = false, defaultValue = "10") int length,
+										String appname, String title) {
+
+		// page query
+		List<XxlJobGroup> list = xxlJobGroupDao.pageList(start, length, appname, title);
+		int list_count = xxlJobGroupDao.pageListCount(start, length, appname, title);
+
+		// package result
+		Map<String, Object> maps = new HashMap<String, Object>();
+		maps.put("recordsTotal", list_count);		// 总记录数
+		maps.put("recordsFiltered", list_count);	// 过滤后的总记录数
+		maps.put("data", list);  					// 分页列表
+		return maps;
+	}
+
+	@RequestMapping("/save")
+	@ResponseBody
+	public ReturnT<String> save(XxlJobGroup xxlJobGroup){
+
+		// valid
+		if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) {
+			return new ReturnT<String>(500, (I18nUtil.getString("system_please_input")+"AppName") );
+		}
+		if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
+			return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_appname_length") );
+		}
+		if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
+			return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
+		}
+		if (xxlJobGroup.getAddressType()!=0) {
+			if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
+				return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
+			}
+			String[] addresss = xxlJobGroup.getAddressList().split(",");
+			for (String item: addresss) {
+				if (item==null || item.trim().length()==0) {
+					return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") );
+				}
+			}
+		}
+
+		int ret = xxlJobGroupDao.save(xxlJobGroup);
+		return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
+	}
+
+	@RequestMapping("/update")
+	@ResponseBody
+	public ReturnT<String> update(XxlJobGroup xxlJobGroup){
+		// valid
+		if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) {
+			return new ReturnT<String>(500, (I18nUtil.getString("system_please_input")+"AppName") );
+		}
+		if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
+			return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_appname_length") );
+		}
+		if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
+			return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
+		}
+		if (xxlJobGroup.getAddressType() == 0) {
+			// 0=自动注册
+			List<String> registryList = findRegistryByAppName(xxlJobGroup.getAppname());
+			String addressListStr = null;
+			if (registryList!=null && !registryList.isEmpty()) {
+				Collections.sort(registryList);
+				addressListStr = "";
+				for (String item:registryList) {
+					addressListStr += item + ",";
+				}
+				addressListStr = addressListStr.substring(0, addressListStr.length()-1);
+			}
+			xxlJobGroup.setAddressList(addressListStr);
+		} else {
+			// 1=手动录入
+			if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
+				return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
+			}
+			String[] addresss = xxlJobGroup.getAddressList().split(",");
+			for (String item: addresss) {
+				if (item==null || item.trim().length()==0) {
+					return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") );
+				}
+			}
+		}
+
+		int ret = xxlJobGroupDao.update(xxlJobGroup);
+		return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
+	}
+
+	private List<String> findRegistryByAppName(String appnameParam){
+		HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
+		List<XxlJobRegistry> list = xxlJobRegistryDao.findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
+		if (list != null) {
+			for (XxlJobRegistry item: list) {
+				if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
+					String appname = item.getRegistryKey();
+					List<String> registryList = appAddressMap.get(appname);
+					if (registryList == null) {
+						registryList = new ArrayList<String>();
+					}
+
+					if (!registryList.contains(item.getRegistryValue())) {
+						registryList.add(item.getRegistryValue());
+					}
+					appAddressMap.put(appname, registryList);
+				}
+			}
+		}
+		return appAddressMap.get(appnameParam);
+	}
+
+	@RequestMapping("/remove")
+	@ResponseBody
+	public ReturnT<String> remove(int id){
+
+		// valid
+		int count = xxlJobInfoDao.pageListCount(0, 10, id, -1,  null, null, null);
+		if (count > 0) {
+			return new ReturnT<String>(500, I18nUtil.getString("jobgroup_del_limit_0") );
+		}
+
+		List<XxlJobGroup> allList = xxlJobGroupDao.findAll();
+		if (allList.size() == 1) {
+			return new ReturnT<String>(500, I18nUtil.getString("jobgroup_del_limit_1") );
+		}
+
+		int ret = xxlJobGroupDao.remove(id);
+		return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
+	}
+
+	@RequestMapping("/loadById")
+	@ResponseBody
+	public ReturnT<XxlJobGroup> loadById(int id){
+		XxlJobGroup jobGroup = xxlJobGroupDao.load(id);
+		return jobGroup!=null?new ReturnT<XxlJobGroup>(jobGroup):new ReturnT<XxlJobGroup>(ReturnT.FAIL_CODE, null);
+	}
+
+}

+ 166 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobInfoController.java

@@ -0,0 +1,166 @@
+package com.xxl.job.admin.controller;
+
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.exception.XxlJobException;
+import com.xxl.job.admin.core.model.XxlJobGroup;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.model.XxlJobUser;
+import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.admin.dao.XxlJobGroupDao;
+import com.xxl.job.admin.service.LoginService;
+import com.xxl.job.admin.service.XxlJobService;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.job.core.glue.GlueTypeEnum;
+import com.xxl.job.core.util.DateUtil;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.text.ParseException;
+import java.util.*;
+
+/**
+ * index controller
+ * @author xuxueli 2015-12-19 16:13:16
+ */
+@Controller
+@RequestMapping("/jobinfo")
+public class JobInfoController {
+
+	@Resource
+	private XxlJobGroupDao xxlJobGroupDao;
+	@Resource
+	private XxlJobService xxlJobService;
+	
+	@RequestMapping
+	public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "-1") int jobGroup) {
+
+		// 枚举-字典
+		model.addAttribute("ExecutorRouteStrategyEnum", ExecutorRouteStrategyEnum.values());	    // 路由策略-列表
+		model.addAttribute("GlueTypeEnum", GlueTypeEnum.values());								// Glue类型-字典
+		model.addAttribute("ExecutorBlockStrategyEnum", ExecutorBlockStrategyEnum.values());	    // 阻塞处理策略-字典
+
+		// 执行器列表
+		List<XxlJobGroup> jobGroupList_all =  xxlJobGroupDao.findAll();
+
+		// filter group
+		List<XxlJobGroup> jobGroupList = filterJobGroupByRole(request, jobGroupList_all);
+		if (jobGroupList==null || jobGroupList.size()==0) {
+			throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
+		}
+
+		model.addAttribute("JobGroupList", jobGroupList);
+		model.addAttribute("jobGroup", jobGroup);
+
+		return "jobinfo/jobinfo.index";
+	}
+
+	public static List<XxlJobGroup> filterJobGroupByRole(HttpServletRequest request, List<XxlJobGroup> jobGroupList_all){
+		List<XxlJobGroup> jobGroupList = new ArrayList<>();
+		if (jobGroupList_all!=null && jobGroupList_all.size()>0) {
+			XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
+			if (loginUser.getRole() == 1) {
+				jobGroupList = jobGroupList_all;
+			} else {
+				List<String> groupIdStrs = new ArrayList<>();
+				if (loginUser.getPermission()!=null && loginUser.getPermission().trim().length()>0) {
+					groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(","));
+				}
+				for (XxlJobGroup groupItem:jobGroupList_all) {
+					if (groupIdStrs.contains(String.valueOf(groupItem.getId()))) {
+						jobGroupList.add(groupItem);
+					}
+				}
+			}
+		}
+		return jobGroupList;
+	}
+	public static void validPermission(HttpServletRequest request, int jobGroup) {
+		XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
+		if (!loginUser.validPermission(jobGroup)) {
+			throw new RuntimeException(I18nUtil.getString("system_permission_limit") + "[username="+ loginUser.getUsername() +"]");
+		}
+	}
+	
+	@RequestMapping("/pageList")
+	@ResponseBody
+	public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start,  
+			@RequestParam(required = false, defaultValue = "10") int length,
+			int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
+		
+		return xxlJobService.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author);
+	}
+	
+	@RequestMapping("/add")
+	@ResponseBody
+	public ReturnT<String> add(XxlJobInfo jobInfo) {
+		return xxlJobService.add(jobInfo);
+	}
+	
+	@RequestMapping("/update")
+	@ResponseBody
+	public ReturnT<String> update(XxlJobInfo jobInfo) {
+		return xxlJobService.update(jobInfo);
+	}
+	
+	@RequestMapping("/remove")
+	@ResponseBody
+	public ReturnT<String> remove(int id) {
+		return xxlJobService.remove(id);
+	}
+	
+	@RequestMapping("/stop")
+	@ResponseBody
+	public ReturnT<String> pause(int id) {
+		return xxlJobService.stop(id);
+	}
+	
+	@RequestMapping("/start")
+	@ResponseBody
+	public ReturnT<String> start(int id) {
+		return xxlJobService.start(id);
+	}
+	
+	@RequestMapping("/trigger")
+	@ResponseBody
+	//@PermissionLimit(limit = false)
+	public ReturnT<String> triggerJob(int id, String executorParam, String addressList) {
+		// force cover job param
+		if (executorParam == null) {
+			executorParam = "";
+		}
+
+		JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList);
+		return ReturnT.SUCCESS;
+	}
+
+	@RequestMapping("/nextTriggerTime")
+	@ResponseBody
+	public ReturnT<List<String>> nextTriggerTime(String cron) {
+		List<String> result = new ArrayList<>();
+		try {
+			CronExpression cronExpression = new CronExpression(cron);
+			Date lastTime = new Date();
+			for (int i = 0; i < 5; i++) {
+				lastTime = cronExpression.getNextValidTimeAfter(lastTime);
+				if (lastTime != null) {
+					result.add(DateUtil.formatDateTime(lastTime));
+				} else {
+					break;
+				}
+			}
+		} catch (ParseException e) {
+			return new ReturnT<List<String>>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid"));
+		}
+		return new ReturnT<List<String>>(result);
+	}
+	
+}

+ 232 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobLogController.java

@@ -0,0 +1,232 @@
+package com.xxl.job.admin.controller;
+
+import com.xxl.job.admin.core.exception.XxlJobException;
+import com.xxl.job.admin.core.model.XxlJobGroup;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.model.XxlJobLog;
+import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.admin.dao.XxlJobGroupDao;
+import com.xxl.job.admin.dao.XxlJobInfoDao;
+import com.xxl.job.admin.dao.XxlJobLogDao;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.biz.model.KillParam;
+import com.xxl.job.core.biz.model.LogParam;
+import com.xxl.job.core.biz.model.LogResult;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.util.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * index controller
+ * @author xuxueli 2015-12-19 16:13:16
+ */
+@Controller
+@RequestMapping("/joblog")
+public class JobLogController {
+	private static Logger logger = LoggerFactory.getLogger(JobLogController.class);
+
+	@Resource
+	private XxlJobGroupDao xxlJobGroupDao;
+	@Resource
+	public XxlJobInfoDao xxlJobInfoDao;
+	@Resource
+	public XxlJobLogDao xxlJobLogDao;
+
+	@RequestMapping
+	public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "0") Integer jobId) {
+
+		// 执行器列表
+		List<XxlJobGroup> jobGroupList_all =  xxlJobGroupDao.findAll();
+
+		// filter group
+		List<XxlJobGroup> jobGroupList = JobInfoController.filterJobGroupByRole(request, jobGroupList_all);
+		if (jobGroupList==null || jobGroupList.size()==0) {
+			throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
+		}
+
+		model.addAttribute("JobGroupList", jobGroupList);
+
+		// 任务
+		if (jobId > 0) {
+			XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId);
+			if (jobInfo == null) {
+				throw new RuntimeException(I18nUtil.getString("jobinfo_field_id") + I18nUtil.getString("system_unvalid"));
+			}
+
+			model.addAttribute("jobInfo", jobInfo);
+
+			// valid permission
+			JobInfoController.validPermission(request, jobInfo.getJobGroup());
+		}
+
+		return "joblog/joblog.index";
+	}
+
+	@RequestMapping("/getJobsByGroup")
+	@ResponseBody
+	public ReturnT<List<XxlJobInfo>> getJobsByGroup(int jobGroup){
+		List<XxlJobInfo> list = xxlJobInfoDao.getJobsByGroup(jobGroup);
+		return new ReturnT<List<XxlJobInfo>>(list);
+	}
+	
+	@RequestMapping("/pageList")
+	@ResponseBody
+	public Map<String, Object> pageList(HttpServletRequest request,
+										@RequestParam(required = false, defaultValue = "0") int start,
+										@RequestParam(required = false, defaultValue = "10") int length,
+										int jobGroup, int jobId, int logStatus, String filterTime) {
+
+		// valid permission
+		JobInfoController.validPermission(request, jobGroup);	// 仅管理员支持查询全部;普通用户仅支持查询有权限的 jobGroup
+		
+		// parse param
+		Date triggerTimeStart = null;
+		Date triggerTimeEnd = null;
+		if (filterTime!=null && filterTime.trim().length()>0) {
+			String[] temp = filterTime.split(" - ");
+			if (temp.length == 2) {
+				triggerTimeStart = DateUtil.parseDateTime(temp[0]);
+				triggerTimeEnd = DateUtil.parseDateTime(temp[1]);
+			}
+		}
+		
+		// page query
+		List<XxlJobLog> list = xxlJobLogDao.pageList(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
+		int list_count = xxlJobLogDao.pageListCount(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
+		
+		// package result
+		Map<String, Object> maps = new HashMap<String, Object>();
+	    maps.put("recordsTotal", list_count);		// 总记录数
+	    maps.put("recordsFiltered", list_count);	// 过滤后的总记录数
+	    maps.put("data", list);  					// 分页列表
+		return maps;
+	}
+
+	@RequestMapping("/logDetailPage")
+	public String logDetailPage(int id, Model model){
+
+		// base check
+		ReturnT<String> logStatue = ReturnT.SUCCESS;
+		XxlJobLog jobLog = xxlJobLogDao.load(id);
+		if (jobLog == null) {
+            throw new RuntimeException(I18nUtil.getString("joblog_logid_unvalid"));
+		}
+
+        model.addAttribute("triggerCode", jobLog.getTriggerCode());
+        model.addAttribute("handleCode", jobLog.getHandleCode());
+        model.addAttribute("executorAddress", jobLog.getExecutorAddress());
+        model.addAttribute("triggerTime", jobLog.getTriggerTime().getTime());
+        model.addAttribute("logId", jobLog.getId());
+		return "joblog/joblog.detail";
+	}
+
+	@RequestMapping("/logDetailCat")
+	@ResponseBody
+	public ReturnT<LogResult> logDetailCat(String executorAddress, long triggerTime, long logId, int fromLineNum){
+		try {
+			ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
+			ReturnT<LogResult> logResult = executorBiz.log(new LogParam(triggerTime, logId, fromLineNum));
+
+			// is end
+            if (logResult.getContent()!=null && logResult.getContent().getFromLineNum() > logResult.getContent().getToLineNum()) {
+                XxlJobLog jobLog = xxlJobLogDao.load(logId);
+                if (jobLog.getHandleCode() > 0) {
+                    logResult.getContent().setEnd(true);
+                }
+            }
+
+			return logResult;
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+			return new ReturnT<LogResult>(ReturnT.FAIL_CODE, e.getMessage());
+		}
+	}
+
+	@RequestMapping("/logKill")
+	@ResponseBody
+	public ReturnT<String> logKill(int id){
+		// base check
+		XxlJobLog log = xxlJobLogDao.load(id);
+		XxlJobInfo jobInfo = xxlJobInfoDao.loadById(log.getJobId());
+		if (jobInfo==null) {
+			return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
+		}
+		if (ReturnT.SUCCESS_CODE != log.getTriggerCode()) {
+			return new ReturnT<String>(500, I18nUtil.getString("joblog_kill_log_limit"));
+		}
+
+		// request of kill
+		ReturnT<String> runResult = null;
+		try {
+			ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
+			runResult = executorBiz.kill(new KillParam(jobInfo.getId()));
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+			runResult = new ReturnT<String>(500, e.getMessage());
+		}
+
+		if (ReturnT.SUCCESS_CODE == runResult.getCode()) {
+			log.setHandleCode(ReturnT.FAIL_CODE);
+			log.setHandleMsg( I18nUtil.getString("joblog_kill_log_byman")+":" + (runResult.getMsg()!=null?runResult.getMsg():""));
+			log.setHandleTime(new Date());
+			xxlJobLogDao.updateHandleInfo(log);
+			return new ReturnT<String>(runResult.getMsg());
+		} else {
+			return new ReturnT<String>(500, runResult.getMsg());
+		}
+	}
+
+	@RequestMapping("/clearLog")
+	@ResponseBody
+	public ReturnT<String> clearLog(int jobGroup, int jobId, int type){
+
+		Date clearBeforeTime = null;
+		int clearBeforeNum = 0;
+		if (type == 1) {
+			clearBeforeTime = DateUtil.addMonths(new Date(), -1);	// 清理一个月之前日志数据
+		} else if (type == 2) {
+			clearBeforeTime = DateUtil.addMonths(new Date(), -3);	// 清理三个月之前日志数据
+		} else if (type == 3) {
+			clearBeforeTime = DateUtil.addMonths(new Date(), -6);	// 清理六个月之前日志数据
+		} else if (type == 4) {
+			clearBeforeTime = DateUtil.addYears(new Date(), -1);	// 清理一年之前日志数据
+		} else if (type == 5) {
+			clearBeforeNum = 1000;		// 清理一千条以前日志数据
+		} else if (type == 6) {
+			clearBeforeNum = 10000;		// 清理一万条以前日志数据
+		} else if (type == 7) {
+			clearBeforeNum = 30000;		// 清理三万条以前日志数据
+		} else if (type == 8) {
+			clearBeforeNum = 100000;	// 清理十万条以前日志数据
+		} else if (type == 9) {
+			clearBeforeNum = 0;			// 清理所有日志数据
+		} else {
+			return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("joblog_clean_type_unvalid"));
+		}
+
+		List<Long> logIds = null;
+		do {
+			logIds = xxlJobLogDao.findClearLogIds(jobGroup, jobId, clearBeforeTime, clearBeforeNum, 1000);
+			if (logIds!=null && logIds.size()>0) {
+				xxlJobLogDao.clearLog(logIds);
+			}
+		} while (logIds!=null && logIds.size()>0);
+
+		return ReturnT.SUCCESS;
+	}
+
+}

+ 110 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java

@@ -0,0 +1,110 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.model.XxlJobLog;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import com.xxl.job.admin.core.util.I18nUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * job monitor instance
+ *
+ * @author xuxueli 2015-9-1 18:05:56
+ */
+public class JobFailMonitorHelper {
+	private static Logger logger = LoggerFactory.getLogger(JobFailMonitorHelper.class);
+	
+	private static JobFailMonitorHelper instance = new JobFailMonitorHelper();
+	public static JobFailMonitorHelper getInstance(){
+		return instance;
+	}
+
+	// ---------------------- monitor ----------------------
+
+	private Thread monitorThread;
+	private volatile boolean toStop = false;
+	public void start(){
+		monitorThread = new Thread(new Runnable() {
+
+			@Override
+			public void run() {
+
+				// monitor
+				while (!toStop) {
+					try {
+
+						List<Long> failLogIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findFailJobLogIds(1000);
+						if (failLogIds!=null && !failLogIds.isEmpty()) {
+							for (long failLogId: failLogIds) {
+
+								// lock log
+								int lockRet = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, 0, -1);
+								if (lockRet < 1) {
+									continue;
+								}
+								XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(failLogId);
+								XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(log.getJobId());
+
+								// 1、fail retry monitor
+								if (log.getExecutorFailRetryCount() > 0) {
+									JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), log.getExecutorParam(), null);
+									String retryMsg = "<br><br><span style=\"color:#F39C12;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<<<<<<<<< </span><br>";
+									log.setTriggerMsg(log.getTriggerMsg() + retryMsg);
+									XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(log);
+								}
+
+								// 2、fail alarm monitor
+								int newAlarmStatus = 0;		// 告警状态:0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败
+								if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
+									boolean alarmResult = XxlJobAdminConfig.getAdminConfig().getJobAlarmer().alarm(info, log);
+									newAlarmStatus = alarmResult?2:3;
+								} else {
+									newAlarmStatus = 1;
+								}
+
+								XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, -1, newAlarmStatus);
+							}
+						}
+
+					} catch (Exception e) {
+						if (!toStop) {
+							logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e);
+						}
+					}
+
+                    try {
+                        TimeUnit.SECONDS.sleep(10);
+                    } catch (Exception e) {
+                        if (!toStop) {
+                            logger.error(e.getMessage(), e);
+                        }
+                    }
+
+                }
+
+				logger.info(">>>>>>>>>>> xxl-job, job fail monitor thread stop");
+
+			}
+		});
+		monitorThread.setDaemon(true);
+		monitorThread.setName("xxl-job, admin JobFailMonitorHelper");
+		monitorThread.start();
+	}
+
+	public void toStop(){
+		toStop = true;
+		// interrupt and wait
+		monitorThread.interrupt();
+		try {
+			monitorThread.join();
+		} catch (InterruptedException e) {
+			logger.error(e.getMessage(), e);
+		}
+	}
+
+}

+ 97 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/jobcode.index.1.js

@@ -0,0 +1,97 @@
+$(function() {
+
+	// init code editor
+	var codeEditor;
+	function initIde(glueSource) {
+		if (codeEditor == null) {
+            codeEditor = CodeMirror(document.getElementById("ideWindow"), {
+                mode : ideMode,
+                lineNumbers : true,
+                matchBrackets : true,
+                value: glueSource
+            });
+		} else {
+            codeEditor.setValue(glueSource);
+		}
+	}
+
+	initIde($("#version_now").val());
+
+	// code change
+	$(".source_version").click(function(){
+		var sourceId = $(this).attr('version');
+		var temp = $( "#" + sourceId ).val();
+
+		//codeEditor.setValue('');
+		initIde(temp);
+	});
+
+	// code source save
+	$("#save").click(function() {
+		$('#saveModal').modal({backdrop: false, keyboard: false}).modal('show');
+	});
+
+	$("#saveModal .ok").click(function() {
+
+		var glueSource = codeEditor.getValue();
+		var glueRemark = $("#glueRemark").val();
+		
+		if (!glueRemark) {
+			layer.open({
+				title: I18n.system_tips,
+                btn: [ I18n.system_ok],
+				content: I18n.system_please_input + I18n.jobinfo_glue_remark ,
+				icon: '2'
+			});
+			return;
+		}
+		if (glueRemark.length <4 || glueRemark.length > 100) {
+			layer.open({
+				title: I18n.system_tips ,
+                btn: [ I18n.system_ok ],
+				content: I18n.jobinfo_glue_remark_limit ,
+				icon: '2'
+			});
+			return;
+		}
+
+		$.ajax({
+			type : 'POST',
+			url : base_url + '/jobcode/save',
+			data : {
+				'id' : id,
+				'glueSource' : glueSource,
+				'glueRemark' : glueRemark
+			},
+			dataType : "json",
+			success : function(data){
+				if (data.code == 200) {
+					layer.open({
+						title: I18n.system_tips,
+                        btn: [ I18n.system_ok ],
+						content: (I18n.system_save + I18n.system_success) ,
+						icon: '1',
+						end: function(layero, index){
+							//$(window).unbind('beforeunload');
+							window.location.reload();
+						}
+					});
+				} else {
+					layer.open({
+						title: I18n.system_tips,
+                        btn: [ I18n.system_ok ],
+						content: (data.msg || (I18n.system_save + I18n.system_fail) ),
+						icon: '2'
+					});
+				}
+			}
+		});
+
+	});
+	
+	// before upload
+	/*$(window).bind('beforeunload',function(){
+		return 'Glue尚未保存,确定离开Glue编辑器?';
+	});*/
+	
+});

+ 359 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/jobgroup.index.1.js

@@ -0,0 +1,359 @@
+$(function() {
+
+	// init date tables
+	var jobGroupTable = $("#jobgroup_list").dataTable({
+		"deferRender": true,
+		"processing" : true,
+		"serverSide": true,
+		"ajax": {
+			url: base_url + "/jobgroup/pageList",
+			type:"post",
+			data : function ( d ) {
+				var obj = {};
+				obj.appname = $('#appname').val();
+				obj.title = $('#title').val();
+				obj.start = d.start;
+				obj.length = d.length;
+				return obj;
+			}
+		},
+		"searching": false,
+		"ordering": false,
+		//"scrollX": true,	// scroll x,close self-adaption
+		"columns": [
+			{
+				"data": 'id',
+				"visible" : false
+			},
+			{
+				"data": 'appname',
+				"visible" : true,
+				"width":'30%'
+			},
+			{
+				"data": 'title',
+				"visible" : true,
+				"width":'30%'
+			},
+			{
+				"data": 'addressType',
+				"width":'10%',
+				"visible" : true,
+				"render": function ( data, type, row ) {
+					if (row.addressType == 0) {
+						return I18n.jobgroup_field_addressType_0;
+					} else {
+						return I18n.jobgroup_field_addressType_1;
+					}
+				}
+			},
+			{
+				"data": 'registryList',
+				"width":'15%',
+				"visible" : true,
+				"render": function ( data, type, row ) {
+					return row.registryList
+						?'<a class="show_registryList" href="javascript:;" _id="'+ row.id +'" >'
+							+ I18n.system_show +' ( ' + row.registryList.length+ ' )</a>'
+						:I18n.system_empty;
+				}
+			},
+			{
+				"data": I18n.system_opt ,
+				"width":'15%',
+				"render": function ( data, type, row ) {
+					return function(){
+						// data
+						tableData['key'+row.id] = row;
+
+						// opt
+						var html = '<div class="btn-group">\n' +
+							'     <button type="button" class="btn btn-primary btn-sm">'+ I18n.system_opt +'</button>\n' +
+							'     <button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown">\n' +
+							'       <span class="caret"></span>\n' +
+							'       <span class="sr-only">Toggle Dropdown</span>\n' +
+							'     </button>\n' +
+							'     <ul class="dropdown-menu" role="menu" _id="'+ row.id +'" >\n' +
+							'       <li><a href="javascript:void(0);" class="opt_edit" >'+ I18n.system_opt_edit +'</a></li>\n' +
+							'       <li><a href="javascript:void(0);" class="opt_del" >'+ I18n.system_opt_del +'</a></li>\n' +
+							'     </ul>\n' +
+							'   </div>';
+
+						return html;
+					};
+				}
+			}
+		],
+		"language" : {
+			"sProcessing" : I18n.dataTable_sProcessing ,
+			"sLengthMenu" : I18n.dataTable_sLengthMenu ,
+			"sZeroRecords" : I18n.dataTable_sZeroRecords ,
+			"sInfo" : I18n.dataTable_sInfo ,
+			"sInfoEmpty" : I18n.dataTable_sInfoEmpty ,
+			"sInfoFiltered" : I18n.dataTable_sInfoFiltered ,
+			"sInfoPostFix" : "",
+			"sSearch" : I18n.dataTable_sSearch ,
+			"sUrl" : "",
+			"sEmptyTable" : I18n.dataTable_sEmptyTable ,
+			"sLoadingRecords" : I18n.dataTable_sLoadingRecords ,
+			"sInfoThousands" : ",",
+			"oPaginate" : {
+				"sFirst" : I18n.dataTable_sFirst ,
+				"sPrevious" : I18n.dataTable_sPrevious ,
+				"sNext" : I18n.dataTable_sNext ,
+				"sLast" : I18n.dataTable_sLast
+			},
+			"oAria" : {
+				"sSortAscending" : I18n.dataTable_sSortAscending ,
+				"sSortDescending" : I18n.dataTable_sSortDescending
+			}
+		}
+	});
+
+	// table data
+	var tableData = {};
+
+	// search btn
+	$('#searchBtn').on('click', function(){
+		jobGroupTable.fnDraw();
+	});
+
+	// job registryinfo
+	$("#jobgroup_list").on('click', '.show_registryList',function() {
+		var id = $(this).attr("_id");
+		var row = tableData['key'+id];
+
+		var html = '<div>';
+		if (row.registryList) {
+			for (var index in row.registryList) {
+				html += (parseInt(index)+1) + '. <span class="badge bg-green" >' + row.registryList[index] + '</span><br>';
+			}
+		}
+		html += '</div>';
+
+		layer.open({
+			title: I18n.jobinfo_opt_registryinfo ,
+			btn: [ I18n.system_ok ],
+			content: html
+		});
+
+	});
+
+
+	// opt_del
+	$("#jobgroup_list").on('click', '.opt_del',function() {
+		var id = $(this).parents('ul').attr("_id");
+
+		layer.confirm( (I18n.system_ok + I18n.jobgroup_del + '?') , {
+			icon: 3,
+			title: I18n.system_tips ,
+			btn: [ I18n.system_ok, I18n.system_cancel ]
+		}, function(index){
+			layer.close(index);
+
+			$.ajax({
+				type : 'POST',
+				url : base_url + '/jobgroup/remove',
+				data : {"id":id},
+				dataType : "json",
+				success : function(data){
+					if (data.code == 200) {
+						layer.open({
+							title: I18n.system_tips ,
+							btn: [ I18n.system_ok ],
+							content: (I18n.jobgroup_del + I18n.system_success),
+							icon: '1',
+							end: function(layero, index){
+								jobGroupTable.fnDraw();
+							}
+						});
+					} else {
+						layer.open({
+							title: I18n.system_tips,
+							btn: [ I18n.system_ok ],
+							content: (data.msg || (I18n.jobgroup_del + I18n.system_fail)),
+							icon: '2'
+						});
+					}
+				},
+			});
+		});
+	});
+
+
+	// jquery.validate “low letters start, limit contants、 letters、numbers and line-through.”
+	jQuery.validator.addMethod("myValid01", function(value, element) {
+		var length = value.length;
+		var valid = /^[a-z][a-zA-Z0-9-]*$/;
+		return this.optional(element) || valid.test(value);
+	}, I18n.jobgroup_field_appname_limit );
+
+	$('.add').on('click', function(){
+		$('#addModal').modal({backdrop: false, keyboard: false}).modal('show');
+	});
+	var addModalValidate = $("#addModal .form").validate({
+		errorElement : 'span',
+		errorClass : 'help-block',
+		focusInvalid : true,
+		rules : {
+			appname : {
+				required : true,
+				rangelength:[4,64],
+				myValid01 : true
+			},
+			title : {
+				required : true,
+				rangelength:[4, 12]
+			}
+		},
+		messages : {
+			appname : {
+				required : I18n.system_please_input+"AppName",
+				rangelength: I18n.jobgroup_field_appname_length ,
+				myValid01: I18n.jobgroup_field_appname_limit
+			},
+			title : {
+				required : I18n.system_please_input + I18n.jobgroup_field_title ,
+				rangelength: I18n.jobgroup_field_title_length
+			}
+		},
+		highlight : function(element) {
+			$(element).closest('.form-group').addClass('has-error');
+		},
+		success : function(label) {
+			label.closest('.form-group').removeClass('has-error');
+			label.remove();
+		},
+		errorPlacement : function(error, element) {
+			element.parent('div').append(error);
+		},
+		submitHandler : function(form) {
+			$.post(base_url + "/jobgroup/save",  $("#addModal .form").serialize(), function(data, status) {
+				if (data.code == "200") {
+					$('#addModal').modal('hide');
+					layer.open({
+						title: I18n.system_tips ,
+                        btn: [ I18n.system_ok ],
+						content: I18n.system_add_suc ,
+						icon: '1',
+						end: function(layero, index){
+							jobGroupTable.fnDraw();
+						}
+					});
+				} else {
+					layer.open({
+						title: I18n.system_tips,
+                        btn: [ I18n.system_ok ],
+						content: (data.msg || I18n.system_add_fail  ),
+						icon: '2'
+					});
+				}
+			});
+		}
+	});
+	$("#addModal").on('hide.bs.modal', function () {
+		$("#addModal .form")[0].reset();
+		addModalValidate.resetForm();
+		$("#addModal .form .form-group").removeClass("has-error");
+	});
+
+	// addressType change
+	$("#addModal input[name=addressType], #updateModal input[name=addressType]").click(function(){
+		var addressType = $(this).val();
+		var $addressList = $(this).parents("form").find("textarea[name=addressList]");
+		if (addressType == 0) {
+            $addressList.css("background-color", "#eee");	// 自动注册
+            $addressList.attr("readonly","readonly");
+			$addressList.val("");
+		} else {
+            $addressList.css("background-color", "white");
+			$addressList.removeAttr("readonly");
+		}
+	});
+
+	// opt_edit
+	$("#jobgroup_list").on('click', '.opt_edit',function() {
+		var id = $(this).parents('ul').attr("_id");
+		var row = tableData['key'+id];
+
+		$("#updateModal .form input[name='id']").val( row.id );
+		$("#updateModal .form input[name='appname']").val( row.appname );
+		$("#updateModal .form input[name='title']").val( row.title );
+
+		// 注册方式
+		$("#updateModal .form input[name='addressType']").removeAttr('checked');
+		$("#updateModal .form input[name='addressType'][value='"+ row.addressType +"']").click();
+		// 机器地址
+		$("#updateModal .form textarea[name='addressList']").val( row.addressList );
+
+		$('#updateModal').modal({backdrop: false, keyboard: false}).modal('show');
+	});
+	var updateModalValidate = $("#updateModal .form").validate({
+		errorElement : 'span',
+		errorClass : 'help-block',
+		focusInvalid : true,
+		rules : {
+			appname : {
+				required : true,
+				rangelength:[4,64],
+				myValid01 : true
+			},
+			title : {
+				required : true,
+				rangelength:[4, 12]
+			}
+		},
+		messages : {
+			appname : {
+                required : I18n.system_please_input+"AppName",
+                rangelength: I18n.jobgroup_field_appname_length ,
+                myValid01: I18n.jobgroup_field_appname_limit
+            },
+            title : {
+                required : I18n.system_please_input + I18n.jobgroup_field_title ,
+                rangelength: I18n.jobgroup_field_title_length
+            }
+		},
+		highlight : function(element) {
+			$(element).closest('.form-group').addClass('has-error');
+		},
+		success : function(label) {
+			label.closest('.form-group').removeClass('has-error');
+			label.remove();
+		},
+		errorPlacement : function(error, element) {
+			element.parent('div').append(error);
+		},
+		submitHandler : function(form) {
+			$.post(base_url + "/jobgroup/update",  $("#updateModal .form").serialize(), function(data, status) {
+				if (data.code == "200") {
+					$('#updateModal').modal('hide');
+
+					layer.open({
+						title: I18n.system_tips ,
+                        btn: [ I18n.system_ok ],
+						content: I18n.system_update_suc ,
+						icon: '1',
+						end: function(layero, index){
+							jobGroupTable.fnDraw();
+						}
+					});
+				} else {
+					layer.open({
+						title: I18n.system_tips,
+                        btn: [ I18n.system_ok ],
+						content: (data.msg || I18n.system_update_fail  ),
+						icon: '2'
+					});
+				}
+			});
+		}
+	});
+	$("#updateModal").on('hide.bs.modal', function () {
+		$("#updateModal .form")[0].reset();
+		addModalValidate.resetForm();
+		$("#updateModal .form .form-group").removeClass("has-error");
+	});
+
+	
+});

+ 682 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/jobinfo.index.1.js

@@ -0,0 +1,682 @@
+$(function() {
+
+	// init date tables
+	var jobTable = $("#job_list").dataTable({
+		"deferRender": true,
+		"processing" : true,
+	    "serverSide": true,
+		"ajax": {
+			url: base_url + "/jobinfo/pageList",
+			type:"post",
+	        data : function ( d ) {
+	        	var obj = {};
+	        	obj.jobGroup = $('#jobGroup').val();
+                obj.triggerStatus = $('#triggerStatus').val();
+                obj.jobDesc = $('#jobDesc').val();
+	        	obj.executorHandler = $('#executorHandler').val();
+                obj.author = $('#author').val();
+	        	obj.start = d.start;
+	        	obj.length = d.length;
+                return obj;
+            }
+	    },
+	    "searching": false,
+	    "ordering": false,
+	    //"scrollX": true,	// scroll x,close self-adaption
+	    "columns": [
+	                {
+	                	"data": 'id',
+						"bSortable": false,
+						"visible" : true,
+						"width":'7%'
+					},
+	                {
+	                	"data": 'jobGroup',
+	                	"visible" : false,
+	                	"render": function ( data, type, row ) {
+	            			var groupMenu = $("#jobGroup").find("option");
+	            			for ( var index in $("#jobGroup").find("option")) {
+	            				if ($(groupMenu[index]).attr('value') == data) {
+									return $(groupMenu[index]).html();
+								}
+							}
+	            			return data;
+	            		}
+            		},
+	                {
+	                	"data": 'jobDesc',
+						"visible" : true,
+						"width":'25%'
+					},
+					{
+						"data": 'glueType',
+						"width":'25%',
+						"visible" : true,
+						"render": function ( data, type, row ) {
+							var glueTypeTitle = findGlueTypeTitle(row.glueType);
+                            if (row.executorHandler) {
+                                return glueTypeTitle +":" + row.executorHandler;
+                            } else {
+                                return glueTypeTitle;
+                            }
+						}
+					},
+	                { "data": 'executorParam', "visible" : false},
+					{
+						"data": 'jobCron',
+						"visible" : true,
+						"width":'13%'
+					},
+	                {
+	                	"data": 'addTime',
+	                	"visible" : false,
+	                	"render": function ( data, type, row ) {
+	                		return data?moment(new Date(data)).format("YYYY-MM-DD HH:mm:ss"):"";
+	                	}
+	                },
+	                {
+	                	"data": 'updateTime',
+	                	"visible" : false,
+	                	"render": function ( data, type, row ) {
+	                		return data?moment(new Date(data)).format("YYYY-MM-DD HH:mm:ss"):"";
+	                	}
+	                },
+	                { "data": 'author', "visible" : true, "width":'10%'},
+	                { "data": 'alarmEmail', "visible" : false},
+	                {
+	                	"data": 'triggerStatus',
+						"width":'10%',
+	                	"visible" : true,
+	                	"render": function ( data, type, row ) {
+                            // status
+                            if (1 == data) {
+                                return '<small class="label label-success" >RUNNING</small>';
+                            } else {
+                                return '<small class="label label-default" >STOP</small>';
+                            }
+	                		return data;
+	                	}
+	                },
+	                {
+						"data": I18n.system_opt ,
+						"width":'10%',
+	                	"render": function ( data, type, row ) {
+	                		return function(){
+
+                                // status
+                                var start_stop_div = "";
+                                if (1 == row.triggerStatus ) {
+                                    start_stop_div = '<li><a href="javascript:void(0);" class="job_operate" _type="job_pause" >'+ I18n.jobinfo_opt_stop +'</a></li>\n';
+                                } else {
+                                    start_stop_div = '<li><a href="javascript:void(0);" class="job_operate" _type="job_resume" >'+ I18n.jobinfo_opt_start +'</a></li>\n';
+                                }
+
+                                // log url
+                                var logHref = base_url +'/joblog?jobId='+ row.id;
+
+                                // log url
+                                var codeBtn = "";
+                                if ('BEAN' != row.glueType) {
+                                    var codeUrl = base_url +'/jobcode?jobId='+ row.id;
+                                    codeBtn = '<li><a href="'+ codeUrl +'" target="_blank" >GLUE IDE</a></li>\n';
+                                    codeBtn += '<li class="divider"></li>\n';
+                                }
+
+                                // data
+                                tableData['key'+row.id] = row;
+
+                                // opt
+                                var html = '<div class="btn-group">\n' +
+                                    '     <button type="button" class="btn btn-primary btn-sm">'+ I18n.system_opt +'</button>\n' +
+                                    '     <button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown">\n' +
+                                    '       <span class="caret"></span>\n' +
+                                    '       <span class="sr-only">Toggle Dropdown</span>\n' +
+                                    '     </button>\n' +
+                                    '     <ul class="dropdown-menu" role="menu" _id="'+ row.id +'" >\n' +
+                                    '       <li><a href="javascript:void(0);" class="job_trigger" >'+ I18n.jobinfo_opt_run +'</a></li>\n' +
+                                    '       <li><a href="'+ logHref +'">'+ I18n.jobinfo_opt_log +'</a></li>\n' +
+                                    '       <li><a href="javascript:void(0);" class="job_registryinfo" >' + I18n.jobinfo_opt_registryinfo + '</a></li>\n' +
+                                    '       <li><a href="javascript:void(0);" class="job_next_time" >' + I18n.jobinfo_opt_next_time + '</a></li>\n' +
+                                    '       <li class="divider"></li>\n' +
+                                    codeBtn +
+                                    start_stop_div +
+                                    '       <li><a href="javascript:void(0);" class="update" >'+ I18n.system_opt_edit +'</a></li>\n' +
+                                    '       <li><a href="javascript:void(0);" class="job_operate" _type="job_del" >'+ I18n.system_opt_del +'</a></li>\n' +
+									'       <li><a href="javascript:void(0);" class="job_copy" >'+ I18n.system_opt_copy +'</a></li>\n' +
+                                    '     </ul>\n' +
+                                    '   </div>';
+
+	                			return html;
+							};
+	                	}
+	                }
+	            ],
+		"language" : {
+			"sProcessing" : I18n.dataTable_sProcessing ,
+			"sLengthMenu" : I18n.dataTable_sLengthMenu ,
+			"sZeroRecords" : I18n.dataTable_sZeroRecords ,
+			"sInfo" : I18n.dataTable_sInfo ,
+			"sInfoEmpty" : I18n.dataTable_sInfoEmpty ,
+			"sInfoFiltered" : I18n.dataTable_sInfoFiltered ,
+			"sInfoPostFix" : "",
+			"sSearch" : I18n.dataTable_sSearch ,
+			"sUrl" : "",
+			"sEmptyTable" : I18n.dataTable_sEmptyTable ,
+			"sLoadingRecords" : I18n.dataTable_sLoadingRecords ,
+			"sInfoThousands" : ",",
+			"oPaginate" : {
+				"sFirst" : I18n.dataTable_sFirst ,
+				"sPrevious" : I18n.dataTable_sPrevious ,
+				"sNext" : I18n.dataTable_sNext ,
+				"sLast" : I18n.dataTable_sLast
+			},
+			"oAria" : {
+				"sSortAscending" : I18n.dataTable_sSortAscending ,
+				"sSortDescending" : I18n.dataTable_sSortDescending
+			}
+		}
+	});
+
+    // table data
+    var tableData = {};
+
+	// search btn
+	$('#searchBtn').on('click', function(){
+		jobTable.fnDraw();
+	});
+
+	// jobGroup change
+	$('#jobGroup').on('change', function(){
+        //reload
+        var jobGroup = $('#jobGroup').val();
+        window.location.href = base_url + "/jobinfo?jobGroup=" + jobGroup;
+    });
+
+	// job operate
+	$("#job_list").on('click', '.job_operate',function() {
+		var typeName;
+		var url;
+		var needFresh = false;
+
+		var type = $(this).attr("_type");
+		if ("job_pause" == type) {
+			typeName = I18n.jobinfo_opt_stop ;
+			url = base_url + "/jobinfo/stop";
+			needFresh = true;
+		} else if ("job_resume" == type) {
+			typeName = I18n.jobinfo_opt_start ;
+			url = base_url + "/jobinfo/start";
+			needFresh = true;
+		} else if ("job_del" == type) {
+			typeName = I18n.system_opt_del ;
+			url = base_url + "/jobinfo/remove";
+			needFresh = true;
+		} else {
+			return;
+		}
+
+		var id = $(this).parents('ul').attr("_id");
+
+		layer.confirm( I18n.system_ok + typeName + '?', {
+			icon: 3,
+			title: I18n.system_tips ,
+            btn: [ I18n.system_ok, I18n.system_cancel ]
+		}, function(index){
+			layer.close(index);
+
+			$.ajax({
+				type : 'POST',
+				url : url,
+				data : {
+					"id" : id
+				},
+				dataType : "json",
+				success : function(data){
+					if (data.code == 200) {
+                        layer.msg( typeName + I18n.system_success );
+                        if (needFresh) {
+                            //window.location.reload();
+                            jobTable.fnDraw(false);
+                        }
+					} else {
+                        layer.msg( data.msg || typeName + I18n.system_fail );
+					}
+				}
+			});
+		});
+	});
+
+    // job trigger
+    $("#job_list").on('click', '.job_trigger',function() {
+        var id = $(this).parents('ul').attr("_id");
+        var row = tableData['key'+id];
+
+        $("#jobTriggerModal .form input[name='id']").val( row.id );
+        $("#jobTriggerModal .form textarea[name='executorParam']").val( row.executorParam );
+
+        $('#jobTriggerModal').modal({backdrop: false, keyboard: false}).modal('show');
+    });
+    $("#jobTriggerModal .ok").on('click',function() {
+        $.ajax({
+            type : 'POST',
+            url : base_url + "/jobinfo/trigger",
+            data : {
+                "id" : $("#jobTriggerModal .form input[name='id']").val(),
+                "executorParam" : $("#jobTriggerModal .textarea[name='executorParam']").val(),
+				"addressList" : $("#jobTriggerModal .textarea[name='addressList']").val()
+            },
+            dataType : "json",
+            success : function(data){
+                if (data.code == 200) {
+                    $('#jobTriggerModal').modal('hide');
+
+                    layer.msg( I18n.jobinfo_opt_run + I18n.system_success );
+                } else {
+                    layer.msg( data.msg || I18n.jobinfo_opt_run + I18n.system_fail );
+                }
+            }
+        });
+    });
+    $("#jobTriggerModal").on('hide.bs.modal', function () {
+        $("#jobTriggerModal .form")[0].reset();
+    });
+
+
+    // job registryinfo
+    $("#job_list").on('click', '.job_registryinfo',function() {
+        var id = $(this).parents('ul').attr("_id");
+        var row = tableData['key'+id];
+
+        var jobGroup = row.jobGroup;
+
+        $.ajax({
+            type : 'POST',
+            url : base_url + "/jobgroup/loadById",
+            data : {
+                "id" : jobGroup
+            },
+            dataType : "json",
+            success : function(data){
+
+                var html = '<div>';
+                if (data.code == 200 && data.content.registryList) {
+                    for (var index in data.content.registryList) {
+                        html += (parseInt(index)+1) + '. <span class="badge bg-green" >' + data.content.registryList[index] + '</span><br>';
+                    }
+                }
+                html += '</div>';
+
+                layer.open({
+                    title: I18n.jobinfo_opt_registryinfo ,
+                    btn: [ I18n.system_ok ],
+                    content: html
+                });
+
+            }
+        });
+
+    });
+
+    // job_next_time
+    $("#job_list").on('click', '.job_next_time',function() {
+        var id = $(this).parents('ul').attr("_id");
+        var row = tableData['key'+id];
+
+        var jobCron = row.jobCron;
+
+        $.ajax({
+            type : 'POST',
+            url : base_url + "/jobinfo/nextTriggerTime",
+            data : {
+                "cron" : jobCron
+            },
+            dataType : "json",
+            success : function(data){
+            	
+            	if (data.code != 200) {
+                    layer.open({
+                        title: I18n.jobinfo_opt_next_time ,
+                        btn: [ I18n.system_ok ],
+                        content: data.msg
+                    });
+				} else {
+                    var html = '<center>';
+                    if (data.code == 200 && data.content) {
+                        for (var index in data.content) {
+                            html += '<span>' + data.content[index] + '</span><br>';
+                        }
+                    }
+                    html += '</center>';
+
+                    layer.open({
+                        title: I18n.jobinfo_opt_next_time ,
+                        btn: [ I18n.system_ok ],
+                        content: html
+                    });
+				}
+
+            }
+        });
+
+    });
+
+	// add
+	$(".add").click(function(){
+
+		// init-cronGen
+        $("#addModal .form input[name='jobCron']").show().siblings().remove();
+        $("#addModal .form input[name='jobCron']").cronGen({});
+
+		$('#addModal').modal({backdrop: false, keyboard: false}).modal('show');
+	});
+	var addModalValidate = $("#addModal .form").validate({
+		errorElement : 'span',
+        errorClass : 'help-block',
+        focusInvalid : true,
+        rules : {
+			jobDesc : {
+				required : true,
+				maxlength: 50
+			},
+            jobCron : {
+            	required : true
+            },
+			author : {
+				required : true
+			},
+            executorTimeout : {
+                digits:true
+            },
+            executorFailRetryCount : {
+                digits:true
+            }
+        },
+        messages : {
+            jobDesc : {
+            	required : I18n.system_please_input + I18n.jobinfo_field_jobdesc
+            },
+            jobCron : {
+            	required : I18n.system_please_input + "Cron"
+            },
+            author : {
+            	required : I18n.system_please_input + I18n.jobinfo_field_author
+            },
+            executorTimeout : {
+                digits: I18n.system_please_input + I18n.system_digits
+            },
+            executorFailRetryCount : {
+                digits: I18n.system_please_input + I18n.system_digits
+            }
+        },
+		highlight : function(element) {
+            $(element).closest('.form-group').addClass('has-error');
+        },
+        success : function(label) {
+            label.closest('.form-group').removeClass('has-error');
+            label.remove();
+        },
+        errorPlacement : function(error, element) {
+            element.parent('div').append(error);
+        },
+        submitHandler : function(form) {
+
+			// process
+            var executorTimeout = $("#addModal .form input[name='executorTimeout']").val();
+            if(!/^\d+$/.test(executorTimeout)) {
+                executorTimeout = 0;
+			}
+            $("#addModal .form input[name='executorTimeout']").val(executorTimeout);
+            var executorFailRetryCount = $("#addModal .form input[name='executorFailRetryCount']").val();
+            if(!/^\d+$/.test(executorFailRetryCount)) {
+                executorFailRetryCount = 0;
+            }
+            $("#addModal .form input[name='executorFailRetryCount']").val(executorFailRetryCount);
+
+            // process-cronGen
+            $("#addModal .form input[name='jobCron']").val( $("#addModal .form input[name='cronGen_display']").val() );
+
+        	$.post(base_url + "/jobinfo/add",  $("#addModal .form").serialize(), function(data, status) {
+    			if (data.code == "200") {
+					$('#addModal').modal('hide');
+					layer.open({
+						title: I18n.system_tips ,
+                        btn: [ I18n.system_ok ],
+						content: I18n.system_add_suc ,
+						icon: '1',
+						end: function(layero, index){
+							jobTable.fnDraw();
+							//window.location.reload();
+						}
+					});
+    			} else {
+					layer.open({
+						title: I18n.system_tips ,
+                        btn: [ I18n.system_ok ],
+						content: (data.msg || I18n.system_add_fail),
+						icon: '2'
+					});
+    			}
+    		});
+		}
+	});
+	$("#addModal").on('hide.bs.modal', function () {
+        addModalValidate.resetForm();
+		$("#addModal .form")[0].reset();
+		$("#addModal .form .form-group").removeClass("has-error");
+		$(".remote_panel").show();	// remote
+
+		$("#addModal .form input[name='executorHandler']").removeAttr("readonly");
+	});
+
+
+    // glueType change
+    $(".glueType").change(function(){
+		// executorHandler
+        var $executorHandler = $(this).parents("form").find("input[name='executorHandler']");
+        var glueType = $(this).val();
+        if ('BEAN' != glueType) {
+            $executorHandler.val("");
+            $executorHandler.attr("readonly","readonly");
+        } else {
+            $executorHandler.removeAttr("readonly");
+        }
+    });
+
+	$("#addModal .glueType").change(function(){
+		// glueSource
+		var glueType = $(this).val();
+		if ('GLUE_GROOVY'==glueType){
+			$("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_java").val() );
+		} else if ('GLUE_SHELL'==glueType){
+			$("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_shell").val() );
+		} else if ('GLUE_PYTHON'==glueType){
+			$("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_python").val() );
+		} else if ('GLUE_PHP'==glueType){
+            $("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_php").val() );
+        } else if ('GLUE_NODEJS'==glueType){
+			$("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_nodejs").val() );
+		} else if ('GLUE_POWERSHELL'==glueType){
+            $("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_powershell").val() );
+        } else {
+            $("#addModal .form textarea[name='glueSource']").val("");
+		}
+	});
+
+	// update
+	$("#job_list").on('click', '.update',function() {
+
+        var id = $(this).parents('ul').attr("_id");
+        var row = tableData['key'+id];
+
+		// base data
+		$("#updateModal .form input[name='id']").val( row.id );
+		$('#updateModal .form select[name=jobGroup] option[value='+ row.jobGroup +']').prop('selected', true);
+		$("#updateModal .form input[name='jobDesc']").val( row.jobDesc );
+		$("#updateModal .form input[name='jobCron']").val( row.jobCron );
+		$("#updateModal .form input[name='author']").val( row.author );
+		$("#updateModal .form input[name='alarmEmail']").val( row.alarmEmail );
+		$("#updateModal .form input[name='executorTimeout']").val( row.executorTimeout );
+        $("#updateModal .form input[name='executorFailRetryCount']").val( row.executorFailRetryCount );
+		$('#updateModal .form select[name=executorRouteStrategy] option[value='+ row.executorRouteStrategy +']').prop('selected', true);
+		$("#updateModal .form input[name='executorHandler']").val( row.executorHandler );
+		$("#updateModal .form textarea[name='executorParam']").val( row.executorParam );
+        $("#updateModal .form input[name='childJobId']").val( row.childJobId );
+		$('#updateModal .form select[name=executorBlockStrategy] option[value='+ row.executorBlockStrategy +']').prop('selected', true);
+		$('#updateModal .form select[name=glueType] option[value='+ row.glueType +']').prop('selected', true);
+
+        $("#updateModal .form select[name=glueType]").change();
+
+        // init-cronGen
+        $("#updateModal .form input[name='jobCron']").show().siblings().remove();
+        $("#updateModal .form input[name='jobCron']").cronGen({});
+
+		// show
+		$('#updateModal').modal({backdrop: false, keyboard: false}).modal('show');
+	});
+	var updateModalValidate = $("#updateModal .form").validate({
+		errorElement : 'span',
+        errorClass : 'help-block',
+        focusInvalid : true,
+
+		rules : {
+			jobDesc : {
+				required : true,
+				maxlength: 50
+			},
+			jobCron : {
+				required : true
+			},
+			author : {
+				required : true
+			},
+            executorTimeout : {
+                digits:true
+            },
+            executorFailRetryCount : {
+                digits:true
+            }
+		},
+		messages : {
+			jobDesc : {
+                required : I18n.system_please_input + I18n.jobinfo_field_jobdesc
+			},
+			jobCron : {
+				required : I18n.system_please_input + "Cron"
+			},
+			author : {
+				required : I18n.system_please_input + I18n.jobinfo_field_author
+			},
+            executorTimeout : {
+                digits: I18n.system_please_input + I18n.system_digits
+            },
+            executorFailRetryCount : {
+                digits: I18n.system_please_input + I18n.system_digits
+            }
+		},
+		highlight : function(element) {
+            $(element).closest('.form-group').addClass('has-error');
+        },
+        success : function(label) {
+            label.closest('.form-group').removeClass('has-error');
+            label.remove();
+        },
+        errorPlacement : function(error, element) {
+            element.parent('div').append(error);
+        },
+        submitHandler : function(form) {
+
+            // process
+            var executorTimeout = $("#updateModal .form input[name='executorTimeout']").val();
+            if(!/^\d+$/.test(executorTimeout)) {
+                executorTimeout = 0;
+            }
+            $("#updateModal .form input[name='executorTimeout']").val(executorTimeout);
+            var executorFailRetryCount = $("#updateModal .form input[name='executorFailRetryCount']").val();
+            if(!/^\d+$/.test(executorFailRetryCount)) {
+                executorFailRetryCount = 0;
+            }
+            $("#updateModal .form input[name='executorFailRetryCount']").val(executorFailRetryCount);
+
+            // process-cronGen
+            $("#updateModal .form input[name='jobCron']").val( $("#updateModal .form input[name='cronGen_display']").val() );
+
+			// post
+    		$.post(base_url + "/jobinfo/update", $("#updateModal .form").serialize(), function(data, status) {
+    			if (data.code == "200") {
+					$('#updateModal').modal('hide');
+					layer.open({
+						title: I18n.system_tips ,
+                        btn: [ I18n.system_ok ],
+						content: I18n.system_update_suc ,
+						icon: '1',
+						end: function(layero, index){
+							//window.location.reload();
+							jobTable.fnDraw();
+						}
+					});
+    			} else {
+					layer.open({
+						title: I18n.system_tips ,
+                        btn: [ I18n.system_ok ],
+						content: (data.msg || I18n.system_update_fail ),
+						icon: '2'
+					});
+    			}
+    		});
+		}
+	});
+	$("#updateModal").on('hide.bs.modal', function () {
+        updateModalValidate.resetForm();
+        $("#updateModal .form")[0].reset();
+        $("#updateModal .form .form-group").removeClass("has-error");
+	});
+
+    /**
+	 * find title by name, GlueType
+     */
+	function findGlueTypeTitle(glueType) {
+		var glueTypeTitle;
+        $("#addModal .form select[name=glueType] option").each(function () {
+            var name = $(this).val();
+            var title = $(this).text();
+            if (glueType == name) {
+                glueTypeTitle = title;
+                return false
+            }
+        });
+        return glueTypeTitle;
+    }
+
+    // job_copy
+	$("#job_list").on('click', '.job_copy',function() {
+
+		var id = $(this).parents('ul').attr("_id");
+		var row = tableData['key'+id];
+
+		// base data
+		//$("#addModal .form input[name='id']").val( row.id );
+		$('#addModal .form select[name=jobGroup] option[value='+ row.jobGroup +']').prop('selected', true);
+		$("#addModal .form input[name='jobDesc']").val( row.jobDesc );
+		$("#addModal .form input[name='jobCron']").val( row.jobCron );
+		$("#addModal .form input[name='author']").val( row.author );
+		$("#addModal .form input[name='alarmEmail']").val( row.alarmEmail );
+		$("#addModal .form input[name='executorTimeout']").val( row.executorTimeout );
+		$("#addModal .form input[name='executorFailRetryCount']").val( row.executorFailRetryCount );
+		$('#addModal .form select[name=executorRouteStrategy] option[value='+ row.executorRouteStrategy +']').prop('selected', true);
+		$("#addModal .form input[name='executorHandler']").val( row.executorHandler );
+		$("#addModal .form textarea[name='executorParam']").val( row.executorParam );
+		$("#addModal .form input[name='childJobId']").val( row.childJobId );
+		$('#addModal .form select[name=executorBlockStrategy] option[value='+ row.executorBlockStrategy +']').prop('selected', true);
+		$('#addModal .form select[name=glueType] option[value='+ row.glueType +']').prop('selected', true);
+
+		$("#addModal .form select[name=glueType]").change();
+
+		// init-cronGen
+		$("#addModal .form input[name='jobCron']").show().siblings().remove();
+		$("#addModal .form input[name='jobCron']").cronGen({});
+
+		// show
+		$('#addModal').modal({backdrop: false, keyboard: false}).modal('show');
+	});
+
+});

+ 91 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/joblog.detail.1.js

@@ -0,0 +1,91 @@
+$(function() {
+
+    // trigger fail, end
+    if ( !(triggerCode == 200 || handleCode != 0) ) {
+        $('#logConsoleRunning').hide();
+        $('#logConsole').append('<span style="color: red;">'+ I18n.joblog_rolling_log_triggerfail +'</span>');
+        return;
+    }
+
+    // pull log
+    var fromLineNum = 1;    // [from, to], start as 1
+    var pullFailCount = 0;
+    function pullLog() {
+        // pullFailCount, max=20
+        if (pullFailCount++ > 20) {
+            logRunStop('<span style="color: red;">'+ I18n.joblog_rolling_log_failoften +'</span>');
+            return;
+        }
+
+        // load
+        console.log("pullLog, fromLineNum:" + fromLineNum);
+
+        $.ajax({
+            type : 'POST',
+            async: false,   // sync, make log ordered
+            url : base_url + '/joblog/logDetailCat',
+            data : {
+                "executorAddress":executorAddress,
+                "triggerTime":triggerTime,
+                "logId":logId,
+                "fromLineNum":fromLineNum
+            },
+            dataType : "json",
+            success : function(data){
+
+                if (data.code == 200) {
+                    if (!data.content) {
+                        console.log('pullLog fail');
+                        return;
+                    }
+                    if (fromLineNum != data.content.fromLineNum) {
+                        console.log('pullLog fromLineNum not match');
+                        return;
+                    }
+                    if (fromLineNum > data.content.toLineNum ) {
+                        console.log('pullLog already line-end');
+
+                        // valid end
+                        if (data.content.end) {
+                            logRunStop('<br><span style="color: green;">[Rolling Log Finish]</span>');
+                            return;
+                        }
+
+                        return;
+                    }
+
+                    // append content
+                    fromLineNum = data.content.toLineNum + 1;
+                    $('#logConsole').append(data.content.logContent);
+                    pullFailCount = 0;
+
+                    // scroll to bottom
+                    scrollTo(0, document.body.scrollHeight);        // $('#logConsolePre').scrollTop( document.body.scrollHeight + 300 );
+
+                } else {
+                    console.log('pullLog fail:'+data.msg);
+                }
+            }
+        });
+    }
+
+    // pull first page
+    pullLog();
+
+    // handler already callback, end
+    if (handleCode > 0) {
+        logRunStop('<br><span style="color: green;">[Load Log Finish]</span>');
+        return;
+    }
+
+    // round until end
+    var logRun = setInterval(function () {
+        pullLog()
+    }, 3000);
+    function logRunStop(content){
+        $('#logConsoleRunning').hide();
+        logRun = window.clearInterval(logRun);
+        $('#logConsole').append(content);
+    }
+
+});

+ 391 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/joblog.index.1.js

@@ -0,0 +1,391 @@
+$(function() {
+
+	// jobGroup change, job list init and select
+	$("#jobGroup").on("change", function () {
+		var jobGroup = $(this).children('option:selected').val();
+		$.ajax({
+			type : 'POST',
+            async: false,   // async, avoid js invoke pagelist before jobId data init
+			url : base_url + '/joblog/getJobsByGroup',
+			data : {"jobGroup":jobGroup},
+			dataType : "json",
+			success : function(data){
+				if (data.code == 200) {
+					$("#jobId").html( '<option value="0" >'+ I18n.system_all +'</option>' );
+					$.each(data.content, function (n, value) {
+                        $("#jobId").append('<option value="' + value.id + '" >' + value.jobDesc + '</option>');
+                    });
+                    if ($("#jobId").attr("paramVal")){
+                        $("#jobId").find("option[value='" + $("#jobId").attr("paramVal") + "']").attr("selected",true);
+                    }
+				} else {
+					layer.open({
+						title: I18n.system_tips ,
+                        btn: [ I18n.system_ok ],
+						content: (data.msg || I18n.system_api_error ),
+						icon: '2'
+					});
+				}
+			},
+		});
+	});
+	if ($("#jobGroup").attr("paramVal")){
+		$("#jobGroup").find("option[value='" + $("#jobGroup").attr("paramVal") + "']").attr("selected",true);
+        $("#jobGroup").change();
+	}
+
+	// filter Time
+    var rangesConf = {};
+    rangesConf[I18n.daterangepicker_ranges_recent_hour] = [moment().subtract(1, 'hours'), moment()];
+    rangesConf[I18n.daterangepicker_ranges_today] = [moment().startOf('day'), moment().endOf('day')];
+    rangesConf[I18n.daterangepicker_ranges_yesterday] = [moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').endOf('day')];
+    rangesConf[I18n.daterangepicker_ranges_this_month] = [moment().startOf('month'), moment().endOf('month')];
+    rangesConf[I18n.daterangepicker_ranges_last_month] = [moment().subtract(1, 'months').startOf('month'), moment().subtract(1, 'months').endOf('month')];
+    rangesConf[I18n.daterangepicker_ranges_recent_week] = [moment().subtract(1, 'weeks').startOf('day'), moment().endOf('day')];
+    rangesConf[I18n.daterangepicker_ranges_recent_month] = [moment().subtract(1, 'months').startOf('day'), moment().endOf('day')];
+
+	$('#filterTime').daterangepicker({
+        autoApply:false,
+        singleDatePicker:false,
+        showDropdowns:false,        // 是否显示年月选择条件
+		timePicker: true, 			// 是否显示小时和分钟选择条件
+		timePickerIncrement: 10, 	// 时间的增量,单位为分钟
+        timePicker24Hour : true,
+        opens : 'left', //日期选择框的弹出位置
+		ranges: rangesConf,
+        locale : {
+            format: 'YYYY-MM-DD HH:mm:ss',
+            separator : ' - ',
+            customRangeLabel : I18n.daterangepicker_custom_name ,
+            applyLabel : I18n.system_ok ,
+            cancelLabel : I18n.system_cancel ,
+            fromLabel : I18n.daterangepicker_custom_starttime ,
+            toLabel : I18n.daterangepicker_custom_endtime ,
+            daysOfWeek : I18n.daterangepicker_custom_daysofweek.split(',') ,        // '日', '一', '二', '三', '四', '五', '六'
+            monthNames : I18n.daterangepicker_custom_monthnames.split(',') ,        // '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'
+            firstDay : 1
+        },
+        startDate: rangesConf[I18n.daterangepicker_ranges_today][0],
+        endDate: rangesConf[I18n.daterangepicker_ranges_today][1]
+	});
+
+	// init date tables
+	var logTable = $("#joblog_list").dataTable({
+		"deferRender": true,
+		"processing" : true, 
+	    "serverSide": true,
+		"ajax": {
+	        url: base_url + "/joblog/pageList" ,
+            type:"post",
+	        data : function ( d ) {
+	        	var obj = {};
+	        	obj.jobGroup = $('#jobGroup').val();
+	        	obj.jobId = $('#jobId').val();
+                obj.logStatus = $('#logStatus').val();
+				obj.filterTime = $('#filterTime').val();
+	        	obj.start = d.start;
+	        	obj.length = d.length;
+                return obj;
+            }
+	    },
+	    "searching": false,
+	    "ordering": false,
+	    //"scrollX": false,
+	    "columns": [
+					{
+						"data": 'jobId',
+						"visible" : true,
+                        "width":'10%',
+						"render": function ( data, type, row ) {
+
+							var jobhandler = '';
+                            if (row.executorHandler) {
+                                jobhandler = "<br>JobHandler:" + row.executorHandler;
+                            }
+
+							var temp = '';
+							temp += I18n.joblog_field_executorAddress + ':' + (row.executorAddress?row.executorAddress:'');
+							temp += jobhandler;
+							temp += '<br>'+ I18n.jobinfo_field_executorparam +':' + row.executorParam;
+
+							return '<a class="logTips" href="javascript:;" >'+ row.jobId +'<span style="display:none;">'+ temp +'</span></a>';
+						}
+					},
+					{ "data": 'jobGroup', "visible" : false},
+					{
+						"data": 'triggerTime',
+                        "width":'20%',
+						"render": function ( data, type, row ) {
+							return data?moment(new Date(data)).format("YYYY-MM-DD HH:mm:ss"):"";
+						}
+					},
+					{
+						"data": 'triggerCode',
+                        "width":'10%',
+						"render": function ( data, type, row ) {
+							var html = data;
+							if (data == 200) {
+								html = '<span style="color: green">'+ I18n.system_success +'</span>';
+							} else if (data == 500) {
+								html = '<span style="color: red">'+ I18n.system_fail +'</span>';
+							} else if (data == 0) {
+                                html = '';
+							}
+                            return html;
+						}
+					},
+					{
+						"data": 'triggerMsg',
+                        "width":'10%',
+						"render": function ( data, type, row ) {
+							return data?'<a class="logTips" href="javascript:;" >'+ I18n.system_show +'<span style="display:none;">'+ data +'</span></a>':I18n.system_empty;
+						}
+					},
+	                { 
+	                	"data": 'handleTime',
+                        "width":'20%',
+	                	"render": function ( data, type, row ) {
+	                		return data?moment(new Date(data)).format("YYYY-MM-DD HH:mm:ss"):"";
+	                	}
+	                },
+	                {
+						"data": 'handleCode',
+                        "width":'10%',
+						"render": function ( data, type, row ) {
+                            var html = data;
+                            if (data == 200) {
+                                html = '<span style="color: green">'+ I18n.joblog_handleCode_200 +'</span>';
+                            } else if (data == 500) {
+                                html = '<span style="color: red">'+ I18n.joblog_handleCode_500 +'</span>';
+                            } else if (data == 502) {
+                                html = '<span style="color: red">'+ I18n.joblog_handleCode_502 +'</span>';
+                            } else if (data == 0) {
+                                html = '';
+                            }
+                            return html;
+						}
+	                },
+	                { 
+	                	"data": 'handleMsg',
+                        "width":'10%',
+	                	"render": function ( data, type, row ) {
+	                		return data?'<a class="logTips" href="javascript:;" >'+ I18n.system_show +'<span style="display:none;">'+ data +'</span></a>':I18n.system_empty;
+	                	}
+	                },
+	                {
+						"data": 'handleMsg' ,
+						"bSortable": false,
+                        "width":'10%',
+	                	"render": function ( data, type, row ) {
+	                		// better support expression or string, not function
+	                		return function () {
+		                		if (row.triggerCode == 200 || row.handleCode != 0){
+
+		                			/*var temp = '<a href="javascript:;" class="logDetail" _id="'+ row.id +'">'+ I18n.joblog_rolling_log +'</a>';
+		                			if(row.handleCode == 0){
+		                				temp += '<br><a href="javascript:;" class="logKill" _id="'+ row.id +'" style="color: red;" >'+ I18n.joblog_kill_log +'</a>';
+		                			}*/
+		                			//return temp;
+
+									var html = '<div class="btn-group">\n' +
+										'     <button type="button" class="btn btn-primary btn-sm">'+ I18n.system_opt +'</button>\n' +
+										'     <button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown">\n' +
+										'       <span class="caret"></span>\n' +
+										'       <span class="sr-only">Toggle Dropdown</span>\n' +
+										'     </button>\n' +
+										'     <ul class="dropdown-menu" role="menu" _id="'+ row.id +'" >\n' +
+										'       <li><a href="javascript:void(0);" class="logDetail" _id="'+ row.id +'" >'+ I18n.joblog_rolling_log +'</a></li>\n' +
+										'       <li class="divider"></li>\n' +
+										'       <li><a href="javascript:void(0);" class="logKill" _id="'+ row.id +'" >'+ I18n.joblog_kill_log +'</a></li>\n' +
+										'     </ul>\n' +
+										'   </div>';
+
+		                			return html;
+		                		}
+		                		return null;	
+	                		}
+	                	}
+	                }
+	            ],
+        "language" : {
+            "sProcessing" : I18n.dataTable_sProcessing ,
+            "sLengthMenu" : I18n.dataTable_sLengthMenu ,
+            "sZeroRecords" : I18n.dataTable_sZeroRecords ,
+            "sInfo" : I18n.dataTable_sInfo ,
+            "sInfoEmpty" : I18n.dataTable_sInfoEmpty ,
+            "sInfoFiltered" : I18n.dataTable_sInfoFiltered ,
+            "sInfoPostFix" : "",
+            "sSearch" : I18n.dataTable_sSearch ,
+            "sUrl" : "",
+            "sEmptyTable" : I18n.dataTable_sEmptyTable ,
+            "sLoadingRecords" : I18n.dataTable_sLoadingRecords ,
+            "sInfoThousands" : ",",
+            "oPaginate" : {
+                "sFirst" : I18n.dataTable_sFirst ,
+                "sPrevious" : I18n.dataTable_sPrevious ,
+                "sNext" : I18n.dataTable_sNext ,
+                "sLast" : I18n.dataTable_sLast
+            },
+            "oAria" : {
+                "sSortAscending" : I18n.dataTable_sSortAscending ,
+                "sSortDescending" : I18n.dataTable_sSortDescending
+            }
+        }
+	});
+    logTable.on('xhr.dt',function(e, settings, json, xhr) {
+        if (json.code && json.code != 200) {
+            layer.msg( json.msg || I18n.system_api_error );
+        }
+    });
+	
+	// logTips alert
+	$('#joblog_list').on('click', '.logTips', function(){
+		var msg = $(this).find('span').html();
+		ComAlertTec.show(msg);
+	});
+	
+	// search Btn
+	$('#searchBtn').on('click', function(){
+		logTable.fnDraw();
+	});
+	
+	// logDetail look
+	$('#joblog_list').on('click', '.logDetail', function(){
+		var _id = $(this).attr('_id');
+		
+		window.open(base_url + '/joblog/logDetailPage?id=' + _id);
+		return;
+	});
+
+	/**
+	 * log Kill
+	 */
+	$('#joblog_list').on('click', '.logKill', function(){
+		var _id = $(this).attr('_id');
+
+        layer.confirm( (I18n.system_ok + I18n.joblog_kill_log + '?'), {
+        	icon: 3,
+			title: I18n.system_tips ,
+            btn: [ I18n.system_ok, I18n.system_cancel ]
+		}, function(index){
+            layer.close(index);
+
+            $.ajax({
+                type : 'POST',
+                url : base_url + '/joblog/logKill',
+                data : {"id":_id},
+                dataType : "json",
+                success : function(data){
+                    if (data.code == 200) {
+                        layer.open({
+                            title: I18n.system_tips,
+                            btn: [ I18n.system_ok ],
+                            content: I18n.system_opt_suc ,
+                            icon: '1',
+                            end: function(layero, index){
+                                logTable.fnDraw();
+                            }
+                        });
+                    } else {
+                        layer.open({
+                            title: I18n.system_tips,
+                            btn: [ I18n.system_ok ],
+                            content: (data.msg || I18n.system_opt_fail ),
+                            icon: '2'
+                        });
+                    }
+                },
+            });
+        });
+
+	});
+
+	/**
+	 * clear Log
+	 */
+	$('#clearLog').on('click', function(){
+
+		var jobGroup = $('#jobGroup').val();
+		var jobId = $('#jobId').val();
+
+		var jobGroupText = $("#jobGroup").find("option:selected").text();
+		var jobIdText = $("#jobId").find("option:selected").text();
+
+		$('#clearLogModal input[name=jobGroup]').val(jobGroup);
+		$('#clearLogModal input[name=jobId]').val(jobId);
+
+		$('#clearLogModal .jobGroupText').val(jobGroupText);
+		$('#clearLogModal .jobIdText').val(jobIdText);
+
+		$('#clearLogModal').modal('show');
+
+	});
+	$("#clearLogModal .ok").on('click', function(){
+		$.post(base_url + "/joblog/clearLog",  $("#clearLogModal .form").serialize(), function(data, status) {
+			if (data.code == "200") {
+				$('#clearLogModal').modal('hide');
+				layer.open({
+					title: I18n.system_tips ,
+                    btn: [ I18n.system_ok ],
+					content: (I18n.joblog_clean_log + I18n.system_success) ,
+					icon: '1',
+					end: function(layero, index){
+						logTable.fnDraw();
+					}
+				});
+			} else {
+				layer.open({
+					title: I18n.system_tips ,
+                    btn: [ I18n.system_ok ],
+					content: (data.msg || (I18n.joblog_clean_log + I18n.system_fail) ),
+					icon: '2'
+				});
+			}
+		});
+	});
+	$("#clearLogModal").on('hide.bs.modal', function () {
+		$("#clearLogModal .form")[0].reset();
+	});
+
+});
+
+
+// Com Alert by Tec theme
+var ComAlertTec = {
+	html:function(){
+		var html =
+			'<div class="modal fade" id="ComAlertTec" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">' +
+			'	<div class="modal-dialog modal-lg-">' +
+			'		<div class="modal-content-tec">' +
+			'			<div class="modal-body">' +
+			'				<div class="alert" style="color:#fff;word-wrap: break-word;">' +
+			'				</div>' +
+			'			</div>' +
+			'				<div class="modal-footer">' +
+			'				<div class="text-center" >' +
+			'					<button type="button" class="btn btn-info ok" data-dismiss="modal" >'+ I18n.system_ok +'</button>' +
+			'				</div>' +
+			'			</div>' +
+			'		</div>' +
+			'	</div>' +
+			'</div>';
+		return html;
+	},
+	show:function(msg, callback){
+		// dom init
+		if ($('#ComAlertTec').length == 0){
+			$('body').append(ComAlertTec.html());
+		}
+
+		// init com alert
+		$('#ComAlertTec .alert').html(msg);
+		$('#ComAlertTec').modal('show');
+
+		$('#ComAlertTec .ok').click(function(){
+			$('#ComAlertTec').modal('hide');
+			if(typeof callback == 'function') {
+				callback();
+			}
+		});
+	}
+};

+ 164 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/jobcode/jobcode.index.ftl

@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+<head>
+  	<#import "../common/common.macro.ftl" as netCommon>
+	<@netCommon.commonStyle />
+	<link rel="stylesheet" href="${request.contextPath}/static/plugins/codemirror/lib/codemirror.css">
+	<link rel="stylesheet" href="${request.contextPath}/static/plugins/codemirror/addon/hint/show-hint.css">
+    <title>${I18n.admin_name}</title>
+	<style type="text/css">
+		.CodeMirror {
+      		font-size:16px;
+            width: 100%;
+      		height: 100%;
+            /*bottom: 0;
+            top: 0px;*/
+            position: absolute;
+		}
+    </style>
+</head>
+<body class="skin-blue fixed layout-top-nav">
+
+	<div class="wrapper">
+
+        <header class="main-header">
+            <nav class="navbar navbar-static-top">
+                <div class="container">
+					<#-- icon -->
+                    <div class="navbar-header">
+                        <a class="navbar-brand"><b>Web</b>IDE</a>
+                        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
+                            <i class="fa fa-bars"></i>
+                        </button>
+                    </div>
+
+                    <#-- left nav -->
+                    <div class="collapse navbar-collapse pull-left" id="navbar-collapse">
+                        <ul class="nav navbar-nav">
+                            <li class="active" ><a href="javascript:;">
+                                <span class="sr-only">(current)</span>
+                                【<#list GlueTypeEnum as item><#if item == jobInfo.glueType>${item.desc}</#if></#list>】
+                                ${jobInfo.jobDesc}
+                            </a></li>
+                        </ul>
+                    </div>
+
+					<#-- right nav -->
+                    <div class="navbar-custom-menu">
+                        <ul class="nav navbar-nav">
+                            <li class="dropdown">
+                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false">${I18n.jobinfo_glue_rollback} <span class="caret"></span></a>
+                                <ul class="dropdown-menu" role="menu">
+                                    <li <#if jobLogGlues?exists && jobLogGlues?size gt 0 >style="display: none;"</#if> >
+                                        <a href="javascript:;" class="source_version" version="version_now" glueType="${jobInfo.glueType}" >
+                                            <#list GlueTypeEnum as item><#if item == jobInfo.glueType>${item.desc}</#if></#list>: ${jobInfo.glueRemark}
+                                        </a>
+                                    </li>
+                                    <textarea id="version_now" style="display:none;" >${jobInfo.glueSource}</textarea>
+									<#if jobLogGlues?exists && jobLogGlues?size gt 0 >
+										<#list jobLogGlues as glue>
+                                            <li>
+                                                <a href="javascript:;" class="source_version" version="version_${glue.id}" glueType="${glue.glueType}" >
+                                                    <#list GlueTypeEnum as item><#if item == glue.glueType>${item.desc}</#if></#list>: ${glue.glueRemark}
+                                                </a>
+                                            </li>
+                                            <textarea id="version_${glue.id}" style="display:none;" >${glue.glueSource}</textarea>
+										</#list>
+									</#if>
+                                </ul>
+                            </li>
+                            <li id="save" >
+								<a href="javascript:;" >
+									<i class="fa fa-fw fa-save" ></i>
+                                    ${I18n.system_save}
+								</a>
+							</li>
+                            <li>
+                                <a href="javascript:window.close();" >
+                                    <i class="fa fa-fw fa-close" ></i>
+                                ${I18n.system_close}
+                                </a>
+                            </li>
+                        </ul>
+                    </div>
+
+                </div>
+            </nav>
+        </header>
+
+		<div class="content-wrapper" id="ideWindow" ></div>
+
+		<!-- footer -->
+		<#--<@netCommon.commonFooter />-->
+	</div>
+
+    <!-- 保存.模态框 -->
+    <div class="modal fade" id="saveModal" tabindex="-1" role="dialog"  aria-hidden="true">
+        <div class="modal-dialog ">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h4 class="modal-title" ><i class="fa fa-fw fa-save"></i>${I18n.system_save}</h4>
+                </div>
+                <div class="modal-body">
+                    <div class="form-horizontal form" role="form" >
+                        <div class="form-group">
+                            <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_glue_remark}<font color="red">*</font></label>
+                            <div class="col-sm-10"><input type="text" class="form-control" id="glueRemark" placeholder="${I18n.system_please_input}${I18n.jobinfo_glue_remark}" maxlength="64" ></div>
+                        </div>
+                        <hr>
+                        <div class="form-group">
+                            <div class="col-sm-offset-3 col-sm-6">
+                                <button type="button" class="btn btn-primary ok" >${I18n.system_save}</button>
+                                <button type="button" class="btn btn-default" data-dismiss="modal">${I18n.system_cancel}</button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+	
+<@netCommon.commonScript />
+
+
+    <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js" />
+    <#assign glueTypeIdeMode = "text/x-java" />
+
+    <#if jobInfo.glueType == "GLUE_GROOVY" >
+        <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js" />
+        <#assign glueTypeIdeMode = "text/x-java" />
+    <#elseif jobInfo.glueType == "GLUE_SHELL" >
+        <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/shell/shell.js" />
+        <#assign glueTypeIdeMode = "text/x-sh" />
+    <#elseif jobInfo.glueType == "GLUE_PYTHON" >
+        <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/python/python.js" />
+        <#assign glueTypeIdeMode = "text/x-python" />
+    <#elseif jobInfo.glueType == "GLUE_PHP" >
+        <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/php/php.js" />
+        <#assign glueTypeIdeMode = "text/x-php" />
+        <#assign glueTypeModeSrc02 = "${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js" />
+    <#elseif jobInfo.glueType == "GLUE_NODEJS" >
+        <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/javascript/javascript.js" />
+        <#assign glueTypeIdeMode = "text/javascript" />
+    <#elseif jobInfo.glueType == "GLUE_POWERSHELL" >
+        <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/powershell/powershell.js" />
+        <#assign glueTypeIdeMode = "powershell" />
+    </#if>
+
+
+<script src="${request.contextPath}/static/plugins/codemirror/lib/codemirror.js"></script>
+<script src="${glueTypeModeSrc}"></script>
+<#if glueTypeModeSrc02?exists>
+    <script src="${glueTypeModeSrc02}"></script>
+</#if>
+<script src="${request.contextPath}/static/plugins/codemirror/addon/hint/show-hint.js"></script>
+<script src="${request.contextPath}/static/plugins/codemirror/addon/hint/anyword-hint.js"></script>
+
+<script>
+var id = '${jobInfo.id}';
+var ideMode = '${glueTypeIdeMode}';
+</script>
+<script src="${request.contextPath}/static/js/jobcode.index.1.js"></script>
+
+</body>
+</html>

+ 172 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/jobgroup/jobgroup.index.ftl

@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<html>
+<head>
+  	<#import "../common/common.macro.ftl" as netCommon>
+	<@netCommon.commonStyle />
+	<!-- DataTables -->
+  	<link rel="stylesheet" href="${request.contextPath}/static/adminlte/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css">
+    <title>${I18n.admin_name}</title>
+</head>
+<body class="hold-transition skin-blue sidebar-mini <#if cookieMap?exists && cookieMap["xxljob_adminlte_settings"]?exists && "off" == cookieMap["xxljob_adminlte_settings"].value >sidebar-collapse</#if> ">
+<div class="wrapper">
+	<!-- header -->
+	<@netCommon.commonHeader />
+	<!-- left -->
+	<@netCommon.commonLeft "jobgroup" />
+	
+	<!-- Content Wrapper. Contains page content -->
+	<div class="content-wrapper">
+		<!-- Content Header (Page header) -->
+		<section class="content-header">
+			<h1>${I18n.jobgroup_name}</h1>
+		</section>
+
+		<!-- Main content -->
+	    <section class="content">
+
+            <div class="row">
+                <div class="col-xs-3">
+                    <div class="input-group">
+                        <span class="input-group-addon">AppName</span>
+                        <input type="text" class="form-control" id="appname" autocomplete="on" placeholder="${I18n.system_please_input}AppName" >
+                    </div>
+                </div>
+                <div class="col-xs-3">
+                    <div class="input-group">
+                        <span class="input-group-addon">${I18n.jobgroup_field_title}</span>
+                        <input type="text" class="form-control" id="title" autocomplete="on" placeholder="${I18n.jobgroup_field_title}" >
+                    </div>
+                </div>
+                <div class="col-xs-2">
+                    <button class="btn btn-block btn-info" id="searchBtn">${I18n.system_search}</button>
+                </div>
+                <div class="col-xs-2">
+                    <button class="btn btn-block btn-success add" type="button">${I18n.jobinfo_field_add}</button>
+                </div>
+            </div>
+			
+			<div class="row">
+				<div class="col-xs-12">
+					<div class="box">
+			            <div class="box-body">
+			              	<table id="jobgroup_list" class="table table-bordered table-striped display" width="100%" >
+				                <thead>
+					            	<tr>
+                                        <th name="id" >ID</th>
+                                        <th name="appname" >AppName</th>
+                                        <th name="title" >${I18n.jobgroup_field_title}</th>
+                                        <th name="addressType" >${I18n.jobgroup_field_addressType}</th>
+                                        <th name="registryList" >OnLine ${I18n.jobgroup_field_registryList}</th>
+                                        <th>${I18n.system_opt}</th>
+					                </tr>
+				                </thead>
+                                <tbody>
+								</tbody>
+							</table>
+						</div>
+					</div>
+				</div>
+			</div>
+	    </section>
+	</div>
+
+    <!-- 新增.模态框 -->
+    <div class="modal fade" id="addModal" tabindex="-1" role="dialog"  aria-hidden="true">
+        <div class="modal-dialog ">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h4 class="modal-title" >${I18n.jobgroup_add}</h4>
+                </div>
+                <div class="modal-body">
+                    <form class="form-horizontal form" role="form" >
+                        <div class="form-group">
+                            <label for="lastname" class="col-sm-2 control-label">AppName<font color="red">*</font></label>
+                            <div class="col-sm-10"><input type="text" class="form-control" name="appname" placeholder="${I18n.system_please_input}AppName" maxlength="64" ></div>
+                        </div>
+                        <div class="form-group">
+                            <label for="lastname" class="col-sm-2 control-label">${I18n.jobgroup_field_title}<font color="red">*</font></label>
+                            <div class="col-sm-10"><input type="text" class="form-control" name="title" placeholder="${I18n.system_please_input}${I18n.jobgroup_field_title}" maxlength="12" ></div>
+                        </div>
+                        <div class="form-group">
+                            <label for="lastname" class="col-sm-2 control-label">${I18n.jobgroup_field_addressType}<font color="red">*</font></label>
+                            <div class="col-sm-10">
+                                <input type="radio" name="addressType" value="0" checked />${I18n.jobgroup_field_addressType_0}
+                                &nbsp;&nbsp;&nbsp;&nbsp;
+                                <input type="radio" name="addressType" value="1" />${I18n.jobgroup_field_addressType_1}
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label for="lastname" class="col-sm-2 control-label">${I18n.jobgroup_field_registryList}<font color="red">*</font></label>
+                            <div class="col-sm-10">
+                                <textarea class="textarea" name="addressList" maxlength="512" placeholder="${I18n.jobgroup_field_registryList_placeholder}" readonly="readonly" style="background-color:#eee; width: 100%; height: 100px; font-size: 14px; line-height: 15px; border: 1px solid #dddddd; padding: 5px;"></textarea>
+                            </div>
+                        </div>
+                        <hr>
+                        <div class="form-group">
+                            <div class="col-sm-offset-3 col-sm-6">
+                                <button type="submit" class="btn btn-primary"  >${I18n.system_save}</button>
+                                <button type="button" class="btn btn-default" data-dismiss="modal">${I18n.system_cancel}</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <!-- 更新.模态框 -->
+    <div class="modal fade" id="updateModal" tabindex="-1" role="dialog"  aria-hidden="true">
+        <div class="modal-dialog ">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h4 class="modal-title" >${I18n.jobgroup_edit}</h4>
+                </div>
+                <div class="modal-body">
+                    <form class="form-horizontal form" role="form" >
+                        <div class="form-group">
+                            <label for="lastname" class="col-sm-2 control-label">AppName<font color="red">*</font></label>
+                            <div class="col-sm-10"><input type="text" class="form-control" name="appname" placeholder="${I18n.system_please_input}AppName" maxlength="64" ></div>
+                        </div>
+                        <div class="form-group">
+                            <label for="lastname" class="col-sm-2 control-label">${I18n.jobgroup_field_title}<font color="red">*</font></label>
+                            <div class="col-sm-10"><input type="text" class="form-control" name="title" placeholder="${I18n.system_please_input}${I18n.jobgroup_field_title}" maxlength="12" ></div>
+                        </div>
+                        <div class="form-group">
+                            <label for="lastname" class="col-sm-2 control-label">${I18n.jobgroup_field_addressType}<font color="red">*</font></label>
+                            <div class="col-sm-10">
+                                <input type="radio" name="addressType" value="0" />${I18n.jobgroup_field_addressType_0}
+                                &nbsp;&nbsp;&nbsp;&nbsp;
+                                <input type="radio" name="addressType" value="1" />${I18n.jobgroup_field_addressType_1}
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label for="lastname" class="col-sm-2 control-label">${I18n.jobgroup_field_registryList}<font color="red">*</font></label>
+                            <div class="col-sm-10">
+                                <textarea class="textarea" name="addressList" maxlength="512" placeholder="${I18n.jobgroup_field_registryList_placeholder}" readonly="readonly" style="background-color:#eee; width: 100%; height: 100px; font-size: 14px; line-height: 15px; border: 1px solid #dddddd; padding: 5px;"></textarea>
+                            </div>
+                        </div>
+                        <hr>
+                        <div class="form-group">
+                            <div class="col-sm-offset-3 col-sm-6">
+                                <button type="submit" class="btn btn-primary"  >${I18n.system_save}</button>
+                                <button type="button" class="btn btn-default" data-dismiss="modal">${I18n.system_cancel}</button>
+                                <input type="hidden" name="id" >
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+	
+	<!-- footer -->
+	<@netCommon.commonFooter />
+</div>
+
+<@netCommon.commonScript />
+<!-- DataTables -->
+<script src="${request.contextPath}/static/adminlte/bower_components/datatables.net/js/jquery.dataTables.min.js"></script>
+<script src="${request.contextPath}/static/adminlte/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
+<script src="${request.contextPath}/static/js/jobgroup.index.1.js"></script>
+</body>
+</html>

+ 440 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/jobinfo/jobinfo.index.ftl

@@ -0,0 +1,440 @@
+<!DOCTYPE html>
+<html>
+<head>
+  	<#import "../common/common.macro.ftl" as netCommon>
+	<@netCommon.commonStyle />
+	<!-- DataTables -->
+  	<link rel="stylesheet" href="${request.contextPath}/static/adminlte/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css">
+    <title>${I18n.admin_name}</title>
+</head>
+<body class="hold-transition skin-blue sidebar-mini <#if cookieMap?exists && cookieMap["xxljob_adminlte_settings"]?exists && "off" == cookieMap["xxljob_adminlte_settings"].value >sidebar-collapse</#if>">
+<div class="wrapper">
+	<!-- header -->
+	<@netCommon.commonHeader />
+	<!-- left -->
+	<@netCommon.commonLeft "jobinfo" />
+	
+	<!-- Content Wrapper. Contains page content -->
+	<div class="content-wrapper">
+		<!-- Content Header (Page header) -->
+		<section class="content-header">
+			<h1>${I18n.jobinfo_name}</h1>
+		</section>
+		
+		<!-- Main content -->
+	    <section class="content">
+	    
+	    	<div class="row">
+	    		<div class="col-xs-3">
+	              	<div class="input-group">
+	                	<span class="input-group-addon">${I18n.jobinfo_field_jobgroup}</span>
+                		<select class="form-control" id="jobGroup" >
+                			<#list JobGroupList as group>
+                				<option value="${group.id}" <#if jobGroup==group.id>selected</#if> >${group.title}</option>
+                			</#list>
+	                  	</select>
+	              	</div>
+	            </div>
+                <div class="col-xs-1">
+                    <div class="input-group">
+                        <select class="form-control" id="triggerStatus" >
+                            <option value="-1" >${I18n.system_all}</option>
+                            <option value="0" >${I18n.jobinfo_opt_stop}</option>
+                            <option value="1" >${I18n.jobinfo_opt_start}</option>
+                        </select>
+                    </div>
+                </div>
+                <div class="col-xs-2">
+                    <div class="input-group">
+                        <input type="text" class="form-control" id="jobDesc" autocomplete="on" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_jobdesc}" >
+                    </div>
+                </div>
+                <div class="col-xs-2">
+                    <div class="input-group">
+                        <input type="text" class="form-control" id="executorHandler" autocomplete="on" placeholder="${I18n.system_please_input}JobHandler" >
+                    </div>
+                </div>
+                <div class="col-xs-2">
+                    <div class="input-group">
+                        <input type="text" class="form-control" id="author" autocomplete="on" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_author}" >
+                    </div>
+                </div>
+	            <div class="col-xs-1">
+	            	<button class="btn btn-block btn-info" id="searchBtn">${I18n.system_search}</button>
+	            </div>
+	            <div class="col-xs-1">
+	            	<button class="btn btn-block btn-success add" type="button">${I18n.jobinfo_field_add}</button>
+	            </div>
+          	</div>
+	    	
+			<div class="row">
+				<div class="col-xs-12">
+					<div class="box">
+			            <#--<div class="box-header hide">
+			            	<h3 class="box-title">调度列表</h3>
+			            </div>-->
+			            <div class="box-body" >
+			              	<table id="job_list" class="table table-bordered table-striped" width="100%" >
+				                <thead>
+					            	<tr>
+					            		<th name="id" >${I18n.jobinfo_field_id}</th>
+					                	<th name="jobGroup" >${I18n.jobinfo_field_jobgroup}</th>
+					                  	<th name="jobDesc" >${I18n.jobinfo_field_jobdesc}</th>
+                                        <th name="glueType" >${I18n.jobinfo_field_gluetype}</th>
+					                  	<th name="executorParam" >${I18n.jobinfo_field_executorparam}</th>
+                                        <th name="jobCron" >Cron</th>
+					                  	<th name="addTime" >addTime</th>
+					                  	<th name="updateTime" >updateTime</th>
+					                  	<th name="author" >${I18n.jobinfo_field_author}</th>
+					                  	<th name="alarmEmail" >${I18n.jobinfo_field_alarmemail}</th>
+					                  	<th name="triggerStatus" >${I18n.system_status}</th>
+					                  	<th>${I18n.system_opt}</th>
+					                </tr>
+				                </thead>
+				                <tbody></tbody>
+				                <tfoot></tfoot>
+							</table>
+						</div>
+					</div>
+				</div>
+			</div>
+	    </section>
+	</div>
+	
+	<!-- footer -->
+	<@netCommon.commonFooter />
+</div>
+
+<!-- job新增.模态框 -->
+<div class="modal fade" id="addModal" tabindex="-1" role="dialog"  aria-hidden="true">
+	<div class="modal-dialog modal-lg">
+		<div class="modal-content">
+			<div class="modal-header">
+            	<h4 class="modal-title" >${I18n.jobinfo_field_add}</h4>
+         	</div>
+         	<div class="modal-body">
+				<form class="form-horizontal form" role="form" >
+					<div class="form-group">
+						<label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_jobgroup}<font color="red">*</font></label>
+						<div class="col-sm-4">
+							<select class="form-control" name="jobGroup" >
+		            			<#list JobGroupList as group>
+		            				<option value="${group.id}" <#if jobGroup==group.id>selected</#if> >${group.title}</option>
+		            			</#list>
+		                  	</select>
+						</div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_jobdesc}<font color="red">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="jobDesc" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_jobdesc}" maxlength="50" ></div>
+					</div>
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorRouteStrategy}<font color="red">*</font></label>
+                        <div class="col-sm-4">
+                            <select class="form-control" name="executorRouteStrategy" >
+							<#list ExecutorRouteStrategyEnum as item>
+                                <option value="${item}" >${item.title}</option>
+							</#list>
+                            </select>
+                        </div>
+                        <label for="lastname" class="col-sm-2 control-label">Cron<font color="red">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="jobCron" placeholder="${I18n.system_please_input}Cron" maxlength="128" ></div>
+                    </div>
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_gluetype}<font color="red">*</font></label>
+                        <div class="col-sm-4">
+                            <select class="form-control glueType" name="glueType" >
+								<#list GlueTypeEnum as item>
+									<option value="${item}" >${item.desc}</option>
+								</#list>
+                            </select>
+                        </div>
+                        <label for="firstname" class="col-sm-2 control-label">JobHandler<font color="red">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="executorHandler" placeholder="${I18n.system_please_input}JobHandler" maxlength="100" ></div>
+                    </div>
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorBlockStrategy}<font color="red">*</font></label>
+                        <div class="col-sm-4">
+                            <select class="form-control" name="executorBlockStrategy" >
+								<#list ExecutorBlockStrategyEnum as item>
+                                    <option value="${item}" >${item.title}</option>
+                                </#list>
+                            </select>
+                        </div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_childJobId}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="childJobId" placeholder="${I18n.jobinfo_field_childJobId_placeholder}" maxlength="100" ></div>
+                    </div>
+                    <div class="form-group">
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_timeout}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="executorTimeout" placeholder="${I18n.jobinfo_field_executorTimeout_placeholder}" maxlength="6" ></div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorFailRetryCount}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="executorFailRetryCount" placeholder="${I18n.jobinfo_field_executorFailRetryCount_placeholder}" maxlength="4" ></div>
+                    </div>
+					<div class="form-group">
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_author}<font color="red">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="author" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_author}" maxlength="50" ></div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_alarmemail}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="alarmEmail" placeholder="${I18n.jobinfo_field_alarmemail_placeholder}" maxlength="100" ></div>
+					</div>
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorparam}<font color="black">*</font></label>
+                        <div class="col-sm-10">
+                            <textarea class="textarea form-control" name="executorParam" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_executorparam}" maxlength="512" style="height: 63px; line-height: 1.2;"></textarea>
+						</div>
+                    </div>
+
+                    <hr>
+					<div class="form-group">
+						<div class="col-sm-offset-3 col-sm-6">
+							<button type="submit" class="btn btn-primary"  >${I18n.system_save}</button>
+							<button type="button" class="btn btn-default" data-dismiss="modal">${I18n.system_cancel}</button>
+						</div>
+					</div>
+
+<input type="hidden" name="glueRemark" value="GLUE代码初始化" >
+<textarea name="glueSource" style="display:none;" ></textarea>
+<textarea class="glueSource_java" style="display:none;" >
+package com.xxl.job.service.handler;
+
+import com.xxl.job.core.log.XxlJobLogger;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.IJobHandler;
+
+public class DemoGlueJobHandler extends IJobHandler {
+
+	@Override
+	public ReturnT<String> execute(String param) throws Exception {
+		XxlJobLogger.log("XXL-JOB, Hello World.");
+		return ReturnT.SUCCESS;
+	}
+
+}
+</textarea>
+<textarea class="glueSource_shell" style="display:none;" >
+#!/bin/bash
+echo "xxl-job: hello shell"
+
+echo "${I18n.jobinfo_script_location}:$0"
+echo "${I18n.jobinfo_field_executorparam}:$1"
+echo "${I18n.jobinfo_shard_index} = $2"
+echo "${I18n.jobinfo_shard_total} = $3"
+<#--echo "参数数量:$#"
+for param in $*
+do
+    echo "参数 : $param"
+    sleep 1s
+done-->
+
+echo "Good bye!"
+exit 0
+</textarea>
+<textarea class="glueSource_python" style="display:none;" >
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+import time
+import sys
+
+print "xxl-job: hello python"
+
+print "${I18n.jobinfo_script_location}:", sys.argv[0]
+print "${I18n.jobinfo_field_executorparam}:", sys.argv[1]
+print "${I18n.jobinfo_shard_index}:", sys.argv[2]
+print "${I18n.jobinfo_shard_total}:", sys.argv[3]
+<#--for i in range(1, len(sys.argv)):
+	time.sleep(1)
+	print "参数", i, sys.argv[i]-->
+
+print "Good bye!"
+exit(0)
+<#--
+import logging
+logging.basicConfig(level=logging.DEBUG)
+logging.info("脚本文件:" + sys.argv[0])
+-->
+</textarea>
+<#--这里有问题,新建一个运行模式为 php 的任务后,GLUE 中没有下边的 php 代码-->
+<textarea class="glueSource_php" style="display:none;" >
+<?php
+
+    echo "xxl-job: hello php  \n";
+
+    echo "${I18n.jobinfo_script_location}:$argv[0]  \n";
+    echo "${I18n.jobinfo_field_executorparam}:$argv[1]  \n";
+    echo "${I18n.jobinfo_shard_index} = $argv[2]  \n";
+    echo "${I18n.jobinfo_shard_total} = $argv[3]  \n";
+
+    echo "Good bye!  \n";
+    exit(0);
+
+?>
+</textarea>
+<textarea class="glueSource_nodejs" style="display:none;" >
+#!/usr/bin/env node
+console.log("xxl-job: hello nodejs")
+
+var arguments = process.argv
+
+console.log("${I18n.jobinfo_script_location}: " + arguments[1])
+console.log("${I18n.jobinfo_field_executorparam}: " + arguments[2])
+console.log("${I18n.jobinfo_shard_index}: " + arguments[3])
+console.log("${I18n.jobinfo_shard_total}: " + arguments[4])
+<#--for (var i = 2; i < arguments.length; i++){
+	console.log("参数 %s = %s", (i-1), arguments[i]);
+}-->
+
+console.log("Good bye!")
+process.exit(0)
+</textarea>
+<textarea class="glueSource_powershell" style="display:none;" >
+Write-Host "xxl-job: hello powershell"
+
+Write-Host "${I18n.jobinfo_script_location}: " $MyInvocation.MyCommand.Definition
+Write-Host "${I18n.jobinfo_field_executorparam}: "
+	if ($args.Count -gt 2) { $args[0..($args.Count-3)] }
+Write-Host "${I18n.jobinfo_shard_index}: " $args[$args.Count-2]
+Write-Host "${I18n.jobinfo_shard_total}: " $args[$args.Count-1]
+
+Write-Host "Good bye!"
+exit 0
+</textarea>
+				</form>
+         	</div>
+		</div>
+	</div>
+</div>
+
+<!-- 更新.模态框 -->
+<div class="modal fade" id="updateModal" tabindex="-1" role="dialog"  aria-hidden="true">
+	<div class="modal-dialog modal-lg">
+		<div class="modal-content">
+			<div class="modal-header">
+            	<h4 class="modal-title" >${I18n.jobinfo_field_update}</h4>
+         	</div>
+         	<div class="modal-body">
+				<form class="form-horizontal form" role="form" >
+					<div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_jobgroup}<font color="red">*</font></label>
+                        <div class="col-sm-4">
+                            <select class="form-control" name="jobGroup" >
+							<#list JobGroupList as group>
+                                <option value="${group.id}" >${group.title}</option>
+							</#list>
+                            </select>
+                        </div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_jobdesc}<font color="red">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="jobDesc" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_jobdesc}" maxlength="50" ></div>
+                    </div>
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorRouteStrategy}<font color="red">*</font></label>
+                        <div class="col-sm-4">
+                            <select class="form-control" name="executorRouteStrategy" >
+							<#list ExecutorRouteStrategyEnum as item>
+                                <option value="${item}" >${item.title}</option>
+							</#list>
+                            </select>
+                        </div>
+                        <label for="lastname" class="col-sm-2 control-label">Cron<font color="red">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="jobCron" placeholder="${I18n.system_please_input}Cron" maxlength="128" ></div>
+                    </div>
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_gluetype}<font color="red">*</font></label>
+                        <div class="col-sm-4">
+                            <select class="form-control glueType" name="glueType" disabled >
+							<#list GlueTypeEnum as item>
+                                <option value="${item}" >${item.desc}</option>
+							</#list>
+                            </select>
+                        </div>
+                        <label for="firstname" class="col-sm-2 control-label">JobHandler<font color="red">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="executorHandler" placeholder="${I18n.system_please_input}JobHandler" maxlength="100" ></div>
+                    </div>
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorBlockStrategy}<font color="red">*</font></label>
+                        <div class="col-sm-4">
+                            <select class="form-control" name="executorBlockStrategy" >
+							<#list ExecutorBlockStrategyEnum as item>
+                                <option value="${item}" >${item.title}</option>
+							</#list>
+                            </select>
+                        </div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_childJobId}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="childJobId" placeholder="${I18n.jobinfo_field_childJobId_placeholder}" maxlength="100" ></div>
+                    </div>
+                    <div class="form-group">
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_timeout}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="executorTimeout" placeholder="${I18n.jobinfo_field_executorTimeout_placeholder}" maxlength="6" ></div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorFailRetryCount}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="executorFailRetryCount" placeholder="${I18n.jobinfo_field_executorFailRetryCount_placeholder}" maxlength="4" ></div>
+                    </div>
+                    <div class="form-group">
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_author}<font color="red">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="author" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_author}" maxlength="50" ></div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_alarmemail}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="alarmEmail" placeholder="${I18n.jobinfo_field_alarmemail_placeholder}" maxlength="100" ></div>
+                    </div>
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorparam}<font color="black">*</font></label>
+                        <div class="col-sm-10">
+                            <textarea class="textarea form-control" name="executorParam" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_executorparam}" maxlength="512" style="height: 63px; line-height: 1.2;"></textarea>
+						</div>
+                    </div>
+
+					<hr>
+					<div class="form-group">
+                        <div class="col-sm-offset-3 col-sm-6">
+							<button type="submit" class="btn btn-primary"  >${I18n.system_save}</button>
+							<button type="button" class="btn btn-default" data-dismiss="modal">${I18n.system_cancel}</button>
+                            <input type="hidden" name="id" >
+						</div>
+					</div>
+
+				</form>
+         	</div>
+		</div>
+	</div>
+</div>
+
+<#-- trigger -->
+<div class="modal fade" id="jobTriggerModal" tabindex="-1" role="dialog"  aria-hidden="true">
+    <div class="modal-dialog ">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title" >${I18n.jobinfo_opt_run}</h4>
+            </div>
+            <div class="modal-body">
+                <form class="form-horizontal form" role="form" >
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorparam}<font color="black">*</font></label>
+                        <div class="col-sm-10">
+                            <textarea class="textarea form-control" name="executorParam" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_executorparam}" maxlength="512" style="height: 63px; line-height: 1.2;"></textarea>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobgroup_field_registryList}<font color="black">*</font></label>
+                        <div class="col-sm-10">
+                            <textarea class="textarea form-control" name="addressList" placeholder="${I18n.jobinfo_opt_run_tips}" maxlength="512" style="height: 63px; line-height: 1.2;"></textarea>
+                        </div>
+                    </div>
+                    <hr>
+                    <div class="form-group">
+                        <div class="col-sm-offset-3 col-sm-6">
+                            <button type="button" class="btn btn-primary ok" >${I18n.system_save}</button>
+                            <button type="button" class="btn btn-default" data-dismiss="modal">${I18n.system_cancel}</button>
+                            <input type="hidden" name="id" >
+                        </div>
+                    </div>
+                </form>
+            </div>
+        </div>
+    </div>
+</div>
+
+<@netCommon.commonScript />
+<!-- DataTables -->
+<script src="${request.contextPath}/static/adminlte/bower_components/datatables.net/js/jquery.dataTables.min.js"></script>
+<script src="${request.contextPath}/static/adminlte/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
+<!-- moment -->
+<script src="${request.contextPath}/static/adminlte/bower_components/moment/moment.min.js"></script>
+<#-- cronGen -->
+<script src="${request.contextPath}/static/plugins/cronGen/cronGen<#if I18n.admin_i18n?default('')?length gt 0 >_${I18n.admin_i18n}</#if>.js"></script>
+<script src="${request.contextPath}/static/js/jobinfo.index.1.js"></script>
+</body>
+</html>

+ 73 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/joblog/joblog.detail.ftl

@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <#import "../common/common.macro.ftl" as netCommon>
+    <@netCommon.commonStyle />
+    <title>${I18n.admin_name}</title>
+</head>
+<body class="hold-transition skin-blue layout-top-nav">
+
+<div class="wrapper">
+
+    <header class="main-header">
+        <nav class="navbar navbar-static-top">
+            <div class="container">
+                <#-- icon -->
+                <div class="navbar-header">
+                    <a class="navbar-brand"><b>${I18n.joblog_rolling_log}</b> Console</a>
+                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
+                        <i class="fa fa-bars"></i>
+                    </button>
+                </div>
+
+                <#-- left nav -->
+                <div class="collapse navbar-collapse pull-left" id="navbar-collapse">
+                    <ul class="nav navbar-nav">
+                        <#--<li class="active" ><a href="javascript:;">任务:<span class="sr-only">(current)</span></a></li>-->
+                    </ul>
+                </div>
+
+                <#-- right nav -->
+                <div class="navbar-custom-menu">
+                    <ul class="nav navbar-nav">
+                        <li>
+                            <a href="javascript:window.location.reload();" >
+                                <i class="fa fa-fw fa-refresh" ></i>
+                                ${I18n.joblog_rolling_log_refresh}
+                            </a>
+                        </li>
+                    </ul>
+                </div>
+
+            </div>
+        </nav>
+    </header>
+
+    <div class="content-wrapper" >
+        <section class="content">
+            <pre style="font-size:12px;position:relative;" >
+                <div id="logConsole"></div>
+                <li class="fa fa-refresh fa-spin" style="font-size: 20px;float: left;" id="logConsoleRunning" ></li>
+                <div><hr><hr></div>
+            </pre>
+        </section>
+    </div>
+
+    <!-- footer -->
+    <@netCommon.commonFooter />
+
+</div>
+
+<@netCommon.commonScript />
+<script>
+    // 参数
+    var triggerCode = '${triggerCode}';
+    var handleCode = '${handleCode}';
+    var executorAddress = '${executorAddress!}';
+    var triggerTime = '${triggerTime?c}';
+    var logId = '${logId}';
+</script>
+<script src="${request.contextPath}/static/js/joblog.detail.1.js"></script>
+
+</body>
+</html>

+ 180 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/joblog/joblog.index.ftl

@@ -0,0 +1,180 @@
+<!DOCTYPE html>
+<html>
+<head>
+  	<#import "../common/common.macro.ftl" as netCommon>
+	<@netCommon.commonStyle />
+	<!-- DataTables -->
+  	<link rel="stylesheet" href="${request.contextPath}/static/adminlte/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css">
+  	<!-- daterangepicker -->
+  	<link rel="stylesheet" href="${request.contextPath}/static/adminlte/bower_components/bootstrap-daterangepicker/daterangepicker.css">
+    <title>${I18n.admin_name}</title>
+</head>
+<body class="hold-transition skin-blue sidebar-mini <#if cookieMap?exists && cookieMap["xxljob_adminlte_settings"]?exists && "off" == cookieMap["xxljob_adminlte_settings"].value >sidebar-collapse</#if> ">
+<div class="wrapper">
+	<!-- header -->
+	<@netCommon.commonHeader />
+	<!-- left -->
+	<@netCommon.commonLeft "joblog" />
+	
+	<!-- Content Wrapper. Contains page content -->
+	<div class="content-wrapper">
+		<!-- Content Header (Page header) -->
+		<section class="content-header">
+			<h1>${I18n.joblog_name}</h1>
+		</section>
+		
+		<!-- Main content -->
+	    <section class="content">
+	    	<div class="row">
+	    		<div class="col-xs-2">
+ 					<div class="input-group">
+	                	<span class="input-group-addon">${I18n.jobinfo_field_jobgroup}</span>
+                		<select class="form-control" id="jobGroup"  paramVal="<#if jobInfo?exists>${jobInfo.jobGroup}</#if>" >
+                            <#if Request["XXL_JOB_LOGIN_IDENTITY"].role == 1>
+                                <option value="0" >${I18n.system_all}</option>  <#-- 仅管理员支持查询全部;普通用户仅支持查询有权限的 jobGroup -->
+                            </#if>
+                			<#list JobGroupList as group>
+                				<option value="${group.id}" >${group.title}</option>
+                			</#list>
+	                  	</select>
+	              	</div>
+	            </div>
+	            <div class="col-xs-2">
+	              	<div class="input-group">
+	                	<span class="input-group-addon">${I18n.jobinfo_job}</span>
+                        <select class="form-control" id="jobId" paramVal="<#if jobInfo?exists>${jobInfo.id}</#if>" >
+                            <option value="0" >${I18n.system_all}</option>
+						</select>
+	              	</div>
+	            </div>
+
+                <div class="col-xs-2">
+                    <div class="input-group">
+                        <span class="input-group-addon">${I18n.joblog_status}</span>
+                        <select class="form-control" id="logStatus" >
+                            <option value="-1" >${I18n.joblog_status_all}</option>
+                            <option value="1" >${I18n.joblog_status_suc}</option>
+                            <option value="2" >${I18n.joblog_status_fail}</option>
+                            <option value="3" >${I18n.joblog_status_running}</option>
+                        </select>
+                    </div>
+                </div>
+
+	            <div class="col-xs-4">
+              		<div class="input-group">
+                		<span class="input-group-addon">
+	                  		${I18n.joblog_field_triggerTime}
+	                	</span>
+	                	<input type="text" class="form-control" id="filterTime" readonly >
+	              	</div>
+	            </div>
+
+                <div class="col-xs-1">
+                    <button class="btn btn-block btn-info" id="searchBtn">${I18n.system_search}</button>
+                </div>
+
+	            <div class="col-xs-1">
+                    <button class="btn btn-block btn-nomal" id="clearLog">${I18n.joblog_clean}</button>
+	            </div>
+          	</div>
+			
+			<div class="row">
+				<div class="col-xs-12">
+					<div class="box">
+			            <#--<div class="box-header hide"><h3 class="box-title">调度日志</h3></div>-->
+			            <div class="box-body">
+			              	<table id="joblog_list" class="table table-bordered table-striped display" width="100%" >
+				                <thead>
+					            	<tr>
+                                        <th name="jobId" >${I18n.jobinfo_field_id}</th>
+                                        <th name="jobGroup" >jobGroup</th>
+										<#--<th name="executorAddress" >执行器地址</th>
+										<th name="glueType" >运行模式</th>
+                                      	<th name="executorParam" >任务参数</th>-->
+                                        <th name="triggerTime" >${I18n.joblog_field_triggerTime}</th>
+                                        <th name="triggerCode" >${I18n.joblog_field_triggerCode}</th>
+                                        <th name="triggerMsg" >${I18n.joblog_field_triggerMsg}</th>
+					                  	<th name="handleTime" >${I18n.joblog_field_handleTime}</th>
+					                  	<th name="handleCode" >${I18n.joblog_field_handleCode}</th>
+					                  	<th name="handleMsg" >${I18n.joblog_field_handleMsg}</th>
+					                  	<th name="handleMsg" >${I18n.system_opt}</th>
+					                </tr>
+				                </thead>
+				                <tbody></tbody>
+							</table>
+						</div>
+					</div>
+				</div>
+			</div>
+	    </section>
+	</div>
+	
+	<!-- footer -->
+	<@netCommon.commonFooter />
+</div>
+
+<!-- 日志清理.模态框 -->
+<div class="modal fade" id="clearLogModal" tabindex="-1" role="dialog"  aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title" >${I18n.joblog_clean_log}</h4>
+            </div>
+            <div class="modal-body">
+                <form class="form-horizontal form" role="form" >
+                    <div class="form-group">
+                        <label class="col-sm-3 control-label"">${I18n.jobinfo_field_jobgroup}:</label>
+                        <div class="col-sm-9">
+                            <input type="text" class="form-control jobGroupText" readonly >
+							<input type="hidden" name="jobGroup" >
+						</div>
+                    </div>
+
+                    <div class="form-group">
+                        <label class="col-sm-3 control-label"">${I18n.jobinfo_job}:</label>
+                        <div class="col-sm-9">
+                            <input type="text" class="form-control jobIdText" readonly >
+                            <input type="hidden" name="jobId" >
+						</div>
+                    </div>
+
+                    <div class="form-group">
+                        <label class="col-sm-3 control-label"">${I18n.joblog_clean_type}:</label>
+                        <div class="col-sm-9">
+                            <select class="form-control" name="type" >
+                                <option value="1" >${I18n.joblog_clean_type_1}</option>
+                                <option value="2" >${I18n.joblog_clean_type_2}</option>
+                                <option value="3" >${I18n.joblog_clean_type_3}</option>
+                                <option value="4" >${I18n.joblog_clean_type_4}</option>
+                                <option value="5" >${I18n.joblog_clean_type_5}</option>
+                                <option value="6" >${I18n.joblog_clean_type_6}</option>
+                                <option value="7" >${I18n.joblog_clean_type_7}</option>
+                                <option value="8" >${I18n.joblog_clean_type_8}</option>
+                                <option value="9" >${I18n.joblog_clean_type_9}</option>
+                            </select>
+                        </div>
+                    </div>
+
+                    <hr>
+                    <div class="form-group">
+                        <div class="col-sm-offset-3 col-sm-6">
+                            <button type="button" class="btn btn-primary ok" >${I18n.system_ok}</button>
+                            <button type="button" class="btn btn-default" data-dismiss="modal">${I18n.system_cancel}</button>
+                        </div>
+                    </div>
+                </form>
+            </div>
+        </div>
+    </div>
+</div>
+
+<@netCommon.commonScript />
+<!-- DataTables -->
+<script src="${request.contextPath}/static/adminlte/bower_components/datatables.net/js/jquery.dataTables.min.js"></script>
+<script src="${request.contextPath}/static/adminlte/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
+<!-- daterangepicker -->
+<script src="${request.contextPath}/static/adminlte/bower_components/moment/moment.min.js"></script>
+<script src="${request.contextPath}/static/adminlte/bower_components/bootstrap-daterangepicker/daterangepicker.js"></script>
+<script src="${request.contextPath}/static/js/joblog.index.1.js"></script>
+</body>
+</html>