commit b6626fce5c568c03370c2ae031ee17d0b3dbe7e1 Author: fabio.formosa Date: Fri Mar 25 00:30:44 2016 +0100 First commit diff --git a/quartz-manager/.gitignore b/quartz-manager/.gitignore new file mode 100644 index 0000000..233a70f --- /dev/null +++ b/quartz-manager/.gitignore @@ -0,0 +1,9 @@ +/target +/Work +/.mvn +/.project +/.settings +/.springBeans +/mvnw +/mvnw.cmd +/.classpath diff --git a/quartz-manager/pom.xml b/quartz-manager/pom.xml new file mode 100644 index 0000000..94314d8 --- /dev/null +++ b/quartz-manager/pom.xml @@ -0,0 +1,136 @@ + + + 4.0.0 + + it.fabioformosa + quartz-manager + 0.0.1-SNAPSHOT + war + + quartz-manager + Manager Panel for Quartz Scheduler + + + org.springframework.boot + spring-boot-starter-parent + 1.3.2.RELEASE + + + + + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-devtools + + + org.springframework.boot + spring-boot-starter-mail + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-velocity + + + org.springframework.boot + spring-boot-starter-web + + + org.codehaus.groovy + groovy + + + + net.sourceforge.nekohtml + nekohtml + + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.webjars + bootstrap + 3.3.6 + + + nz.net.ultraq.thymeleaf + thymeleaf-layout-dialect + + + org.quartz-scheduler + quartz + 2.2.2 + + + org.apache.commons + commons-io + 1.3.2 + + + org.springframework + spring-tx + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.codehaus.gmavenplus + gmavenplus-plugin + 1.5 + + + + addSources + addTestSources + generateStubs + compile + testGenerateStubs + testCompile + removeStubs + removeTestStubs + + + + + + + + + + diff --git a/quartz-manager/src/main/java/it/fabioformosa/QuartManagerApplication.java b/quartz-manager/src/main/java/it/fabioformosa/QuartManagerApplication.java new file mode 100644 index 0000000..a754547 --- /dev/null +++ b/quartz-manager/src/main/java/it/fabioformosa/QuartManagerApplication.java @@ -0,0 +1,12 @@ +package it.fabioformosa; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class QuartManagerApplication { + + public static void main(String[] args) { + SpringApplication.run(QuartManagerApplication.class, args); + } +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/ServletInitializer.java b/quartz-manager/src/main/java/it/fabioformosa/ServletInitializer.java new file mode 100644 index 0000000..db8703c --- /dev/null +++ b/quartz-manager/src/main/java/it/fabioformosa/ServletInitializer.java @@ -0,0 +1,13 @@ +package it.fabioformosa; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.web.SpringBootServletInitializer; + +public class ServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(QuartManagerApplication.class); + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/MVCConfig.java b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/MVCConfig.java new file mode 100644 index 0000000..ca25ab1 --- /dev/null +++ b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/MVCConfig.java @@ -0,0 +1,55 @@ +package it.fabioformosa.quartzmanager.configuration; + +import nz.net.ultraq.thymeleaf.LayoutDialect; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.thymeleaf.spring4.SpringTemplateEngine; +import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver; +import org.thymeleaf.spring4.view.ThymeleafViewResolver; + +@Configuration +public class MVCConfig extends WebMvcConfigurerAdapter { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/login").setViewName("login"); + registry.addViewController("/").setViewName("redirect:/manager"); + + registry.addViewController("/templates/manager/config-form.html") + .setViewName("manager/config-form"); + registry.addViewController("/templates/manager/progress-panel.html") + .setViewName("manager/progress-panel"); + + } + + @Bean + public SpringTemplateEngine templateEngine() { + final SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine(); + springTemplateEngine.addTemplateResolver(templateResolver()); + springTemplateEngine.addDialect(new LayoutDialect()); + return springTemplateEngine; + } + + @Bean + public SpringResourceTemplateResolver templateResolver() { + final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); + templateResolver.setCacheable(false); + templateResolver.setPrefix("classpath:/templates/"); + templateResolver.setSuffix(".html"); + templateResolver.setTemplateMode("HTML5"); + return templateResolver; + } + + @Bean + public ViewResolver viewResolver() { + ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); + viewResolver.setTemplateEngine(templateEngine()); + viewResolver.setOrder(1); + return viewResolver; + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/SchedulerConfig.java b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/SchedulerConfig.java new file mode 100644 index 0000000..432ab03 --- /dev/null +++ b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/SchedulerConfig.java @@ -0,0 +1,85 @@ +package it.fabioformosa.quartzmanager.configuration; + +import it.fabioformosa.quartzmanager.jobs.SampleJob; +import it.fabioformosa.quartzmanager.scheduler.AutowiringSpringBeanJobFactory; + +import java.io.IOException; +import java.util.Properties; + +import org.quartz.JobDetail; +import org.quartz.Trigger; +import org.quartz.spi.JobFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.PropertiesFactoryBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.scheduling.quartz.JobDetailFactoryBean; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; +import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; + +@Configuration +@ConditionalOnProperty(name = "quartz.enabled") +public class SchedulerConfig { + + private static JobDetailFactoryBean createJobDetail(Class jobClass) { + JobDetailFactoryBean factoryBean = new JobDetailFactoryBean(); + factoryBean.setJobClass(jobClass); + factoryBean.setDurability(false); + return factoryBean; + } + + private static SimpleTriggerFactoryBean createTrigger(JobDetail jobDetail, + long pollFrequencyMs, int repeatCount) { + SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean(); + factoryBean.setJobDetail(jobDetail); + factoryBean.setStartDelay(0L); + factoryBean.setRepeatInterval(pollFrequencyMs); + factoryBean.setRepeatCount(repeatCount); + return factoryBean; + } + + @Bean + public JobDetailFactoryBean jobDetail() { + return createJobDetail(SampleJob.class); + } + + @Bean + public JobFactory jobFactory(ApplicationContext applicationContext) { + AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); + jobFactory.setApplicationContext(applicationContext); + return jobFactory; + } + + @Bean + public Properties quartzProperties() throws IOException { + PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); + propertiesFactoryBean.setLocation(new ClassPathResource( + "/quartz.properties")); + propertiesFactoryBean.afterPropertiesSet(); + return propertiesFactoryBean.getObject(); + } + + @Bean(name = "jobTrigger") + public SimpleTriggerFactoryBean sampleJobTrigger( + @Qualifier("jobDetail") JobDetail jobDetail, + @Value("${job.frequency}") long frequency, + @Value("${job.repeatCount}") int repeatCount) { + return createTrigger(jobDetail, frequency, repeatCount); + } + + @Bean(name = "scheduler") + public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, + @Qualifier("jobTrigger") Trigger sampleJobTrigger) + throws IOException { + SchedulerFactoryBean factory = new SchedulerFactoryBean(); + factory.setJobFactory(jobFactory); + factory.setQuartzProperties(quartzProperties()); + factory.setTriggers(sampleJobTrigger); + factory.setAutoStartup(false); + return factory; + } +} \ No newline at end of file diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfig.java b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfig.java new file mode 100644 index 0000000..cd2a02b --- /dev/null +++ b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfig.java @@ -0,0 +1,57 @@ +package it.fabioformosa.quartzmanager.configuration; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +@Configuration +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Configuration + @Order(1) + public static class ApiWebSecurityConfig extends + WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable().antMatcher("/notifications") + .authorizeRequests().anyRequest().hasAnyRole("ADMIN").and() + .httpBasic(); + } + } + + @Configuration + @Order(2) + public static class FormWebSecurityConfig extends + WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable().authorizeRequests().anyRequest() + .authenticated().and().formLogin().loginPage("/login") + .permitAll().and().logout() + .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + .logoutSuccessUrl("/manager"); + } + + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", + "/lib/**"); + } + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) + throws Exception { + auth.inMemoryAuthentication().withUser("admin").password("admin") + .roles("ADMIN"); + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/ManagerController.java b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/ManagerController.java new file mode 100644 index 0000000..8059145 --- /dev/null +++ b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/ManagerController.java @@ -0,0 +1,39 @@ +package it.fabioformosa.quartzmanager.controllers; + +import javax.annotation.Resource; + +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +@Controller +@RequestMapping("/manager") +public class ManagerController { + + public enum SchedulerStates { + RUNNING, STOPPED, PAUSED + } + + @Resource + private Scheduler scheduler; + + @RequestMapping + public ModelAndView getPanelView() throws SchedulerException { + ModelAndView mav = new ModelAndView("panelView"); + + String schedulerState; + if (scheduler.isShutdown() || !scheduler.isStarted()) + schedulerState = SchedulerStates.STOPPED.toString(); + else if (scheduler.isStarted() && scheduler.isInStandbyMode()) + schedulerState = SchedulerStates.PAUSED.toString(); + else + schedulerState = SchedulerStates.RUNNING.toString(); + + mav.addObject("schedulerState", schedulerState.toLowerCase()); + + return mav; + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/SchedulerController.java b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/SchedulerController.java new file mode 100644 index 0000000..90a1262 --- /dev/null +++ b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/SchedulerController.java @@ -0,0 +1,114 @@ +package it.fabioformosa.quartzmanager.controllers; + +import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam; +import it.fabioformosa.quartzmanager.dto.TriggerProgress; + +import javax.annotation.Resource; + +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.SimpleScheduleBuilder; +import org.quartz.SimpleTrigger; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; +import org.quartz.impl.triggers.SimpleTriggerImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/scheduler") +public class SchedulerController { + + private static final int MILLS_IN_A_DAY = 1000 * 60 * 60 * 24; + + private final Logger log = LoggerFactory + .getLogger(SchedulerController.class); + + @Resource + private Scheduler scheduler; + + @Resource + private SimpleTrigger jobTrigger = null; + + private long fromMillsIntervalToTriggerPerDay(long repeatIntervalInMills) { + return Math.round(MILLS_IN_A_DAY / repeatIntervalInMills); + } + + private int fromTriggerPerDayToSecInterval(long triggerPerDay) { + return Math.round((MILLS_IN_A_DAY / triggerPerDay) / 1000); + } + + @RequestMapping(value = "/config", method = RequestMethod.GET) + public SchedulerConfigParam getConfig() { + SchedulerConfigParam config = new SchedulerConfigParam(); + config.setMaxCount(jobTrigger.getRepeatCount()); + long repeatIntervalInMills = jobTrigger.getRepeatInterval(); + config.setTriggerPerDay(fromMillsIntervalToTriggerPerDay(repeatIntervalInMills)); + return config; + } + + @RequestMapping("/progress") + public TriggerProgress getProgressInfo() throws SchedulerException { + + SimpleTriggerImpl jobTrigger = ((SimpleTriggerImpl) scheduler + .getTrigger(this.jobTrigger.getKey())); + + TriggerProgress progress = new TriggerProgress(); + if (jobTrigger != null && jobTrigger.getJobKey() != null) { + progress.setJobKey(jobTrigger.getJobKey().getName()); + progress.setJobClass(jobTrigger.getClass().getSimpleName()); + progress.setTimesTriggered(jobTrigger.getTimesTriggered()); + progress.setRepeatCount(jobTrigger.getRepeatCount()); + progress.setFinalFireTime(jobTrigger.getFinalFireTime()); + progress.setNextFireTime(jobTrigger.getNextFireTime()); + progress.setPreviousFireTime(jobTrigger.getPreviousFireTime()); + } + return progress; + } + + @RequestMapping("/pause") + public void pause() throws SchedulerException { + scheduler.standby(); + } + + @RequestMapping(value = "/config", method = RequestMethod.POST) + public SchedulerConfigParam postConfig( + @RequestBody SchedulerConfigParam config) throws SchedulerException { + + TriggerBuilder triggerBuilder = jobTrigger + .getTriggerBuilder(); + + int intervalInSeconds = fromTriggerPerDayToSecInterval(config + .getTriggerPerDay()); + Trigger newTrigger = triggerBuilder.withSchedule( + SimpleScheduleBuilder.simpleSchedule() + .withIntervalInSeconds(intervalInSeconds) + .withRepeatCount(config.getMaxCount())).build(); + + scheduler.rescheduleJob(jobTrigger.getKey(), newTrigger); + this.jobTrigger = (SimpleTrigger) newTrigger; + return config; + } + + @RequestMapping("/resume") + public void resume() throws SchedulerException { + scheduler.start(); + } + + @RequestMapping("/run") + public void run() throws SchedulerException { + log.info("Starting scheduler..."); + scheduler.start(); + } + + @RequestMapping("/stop") + public void stop() throws SchedulerException { + log.info("Stopping scheduler..."); + scheduler.shutdown(true); + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerConfigParam.java b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerConfigParam.java new file mode 100644 index 0000000..f04a7d7 --- /dev/null +++ b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerConfigParam.java @@ -0,0 +1,30 @@ +package it.fabioformosa.quartzmanager.dto; + +public class SchedulerConfigParam { + + public long triggerPerDay; + public int maxCount; + + public int getMaxCount() { + return maxCount; + } + + public long getTriggerPerDay() { + return triggerPerDay; + } + + public void setMaxCount(int maxCount) { + this.maxCount = maxCount; + } + + public void setTriggerPerDay(long triggerPerDay) { + this.triggerPerDay = triggerPerDay; + } + + @Override + public String toString() { + return "SchedulerConfigParam [triggerPerDay=" + triggerPerDay + + ", maxCount=" + maxCount + "]"; + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerProgress.java b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerProgress.java new file mode 100644 index 0000000..3b663be --- /dev/null +++ b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerProgress.java @@ -0,0 +1,84 @@ +package it.fabioformosa.quartzmanager.dto; + +import java.util.Date; + +public class TriggerProgress { + + private int timesTriggered; + + private int repeatCount; + + private Date finalFireTime; + + private Date nextFireTime; + + private Date previousFireTime; + + private String jobKey; + + private String jobClass; + + public Date getFinalFireTime() { + return finalFireTime; + } + + public String getJobClass() { + return jobClass; + } + + public String getJobKey() { + return jobKey; + } + + public Date getNextFireTime() { + return nextFireTime; + } + + public int getPercentage() { + if (this.repeatCount <= 0) + return -1; + return Math.round((float) timesTriggered / (float) this.repeatCount + * 100); + } + + public Date getPreviousFireTime() { + return previousFireTime; + } + + public int getRepeatCount() { + return repeatCount; + } + + public int getTimesTriggered() { + return timesTriggered; + } + + public void setFinalFireTime(Date finalFireTime) { + this.finalFireTime = finalFireTime; + } + + public void setJobClass(String jobClass) { + this.jobClass = jobClass; + } + + public void setJobKey(String jobKey) { + this.jobKey = jobKey; + } + + public void setNextFireTime(Date nextFireTime) { + this.nextFireTime = nextFireTime; + } + + public void setPreviousFireTime(Date previousFireTime) { + this.previousFireTime = previousFireTime; + } + + public void setRepeatCount(int repeatCount) { + this.repeatCount = repeatCount; + } + + public void setTimesTriggered(int timesTriggered) { + this.timesTriggered = timesTriggered; + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/jobs/SampleJob.java b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/jobs/SampleJob.java new file mode 100644 index 0000000..439f12b --- /dev/null +++ b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/jobs/SampleJob.java @@ -0,0 +1,16 @@ +package it.fabioformosa.quartzmanager.jobs; + +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SampleJob implements Job { + + private final Logger log = LoggerFactory.getLogger(SampleJob.class); + + @Override + public void execute(JobExecutionContext jobExecutionContext) { + log.info("Hello!"); + } +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/scheduler/AutowiringSpringBeanJobFactory.java b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/scheduler/AutowiringSpringBeanJobFactory.java new file mode 100644 index 0000000..b2c4715 --- /dev/null +++ b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/scheduler/AutowiringSpringBeanJobFactory.java @@ -0,0 +1,26 @@ +package it.fabioformosa.quartzmanager.scheduler; + +import org.quartz.spi.TriggerFiredBundle; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.scheduling.quartz.SpringBeanJobFactory; + +public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory + implements ApplicationContextAware { + + private transient AutowireCapableBeanFactory beanFactory; + + @Override + protected Object createJobInstance(final TriggerFiredBundle bundle) + throws Exception { + final Object job = super.createJobInstance(bundle); + beanFactory.autowireBean(job); + return job; + } + + @Override + public void setApplicationContext(final ApplicationContext context) { + beanFactory = context.getAutowireCapableBeanFactory(); + } +} \ No newline at end of file diff --git a/quartz-manager/src/main/resources/application.properties b/quartz-manager/src/main/resources/application.properties new file mode 100644 index 0000000..2b1b810 --- /dev/null +++ b/quartz-manager/src/main/resources/application.properties @@ -0,0 +1,13 @@ +server.context-path=/quartz-manager +server.port=9000 + +spring.thymeleaf.cache=false +spring.thymeleaf.mode=LEGACYHTML5 + +quartz.enabled=true + +job.frequency=2000 +job.repeatCount=20 + +logging.level.org.springframework.web=WARN +logging.level.it.fabioformosa=INFO \ No newline at end of file diff --git a/quartz-manager/src/main/resources/quartz.properties b/quartz-manager/src/main/resources/quartz.properties new file mode 100644 index 0000000..995ffb6 --- /dev/null +++ b/quartz-manager/src/main/resources/quartz.properties @@ -0,0 +1,3 @@ +org.quartz.scheduler.instanceName=example +org.quartz.scheduler.instanceId=AUTO +org.quartz.threadPool.threadCount=5 \ No newline at end of file diff --git a/quartz-manager/src/main/resources/static/css/dashboard-bootstrap.css b/quartz-manager/src/main/resources/static/css/dashboard-bootstrap.css new file mode 100644 index 0000000..de31574 --- /dev/null +++ b/quartz-manager/src/main/resources/static/css/dashboard-bootstrap.css @@ -0,0 +1,105 @@ +/* + * Base structure + */ + +/* Move down content because we have a fixed navbar that is 50px tall */ +body { + padding-top: 50px; +} + + +/* + * Global add-ons + */ + +.sub-header { + padding-bottom: 10px; + border-bottom: 1px solid #eee; +} + +/* + * Top navigation + * Hide default border to remove 1px line. + */ +.navbar-fixed-top { + border: 0; +} + +/* + * Sidebar + */ + +/* Hide for mobile, show later */ +.sidebar { + display: none; +} +@media (min-width: 768px) { + .sidebar { + position: fixed; + top: 51px; + bottom: 0; + left: 0; + z-index: 1000; + display: block; + padding: 20px; + overflow-x: hidden; + overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ + background-color: #f5f5f5; + border-right: 1px solid #eee; + } +} + +/* Sidebar navigation */ +.nav-sidebar { + margin-right: -21px; /* 20px padding + 1px border */ + margin-bottom: 20px; + margin-left: -20px; +} +.nav-sidebar > li > a { + padding-right: 20px; + padding-left: 20px; +} +.nav-sidebar > .active > a, +.nav-sidebar > .active > a:hover, +.nav-sidebar > .active > a:focus { + color: #fff; + background-color: #428bca; +} + + +/* + * Main content + */ + +.main { + padding: 20px; +} +@media (min-width: 768px) { + .main { + padding-right: 40px; + padding-left: 40px; + } +} +.main .page-header { + margin-top: 0; +} + + +/* + * Placeholder dashboard ideas + */ + +.placeholders { + margin-bottom: 30px; + text-align: center; +} +.placeholders h4 { + margin-bottom: 0; +} +.placeholder { + margin-bottom: 20px; +} +.placeholder img { + display: inline-block; + border-radius: 50%; +} \ No newline at end of file diff --git a/quartz-manager/src/main/resources/static/css/scheduler.css b/quartz-manager/src/main/resources/static/css/scheduler.css new file mode 100644 index 0000000..4bff081 --- /dev/null +++ b/quartz-manager/src/main/resources/static/css/scheduler.css @@ -0,0 +1,31 @@ +.infoInNavbar{ + color: white; + line-height: 20px; + display: block; + padding: 13px 15px; + font-size: 14px; +} + +.widget-panel{ + padding: 0; +} + +.center{ + text-align: center; +} + +.green{ + color: green; +} + +.red{ + color: red; +} + +#schedulerControllerBtn{ + cursor: pointer; +} + +.large-btn{ + width: 200px; +} \ No newline at end of file diff --git a/quartz-manager/src/main/resources/static/css/signin.css b/quartz-manager/src/main/resources/static/css/signin.css new file mode 100644 index 0000000..7c3e522 --- /dev/null +++ b/quartz-manager/src/main/resources/static/css/signin.css @@ -0,0 +1,41 @@ +body { + padding-top: 40px; + padding-bottom: 40px; + background-color: #eee; +} + +.form-signin { + max-width: 330px; + padding: 15px; + margin: 0 auto; +} + +.form-signin .form-signin-heading, +.form-signin .checkbox { + margin-bottom: 10px; +} +.form-signin .checkbox { + font-weight: normal; +} +.form-signin .form-control { + position: relative; + height: auto; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 10px; + font-size: 16px; +} +.form-signin .form-control:focus { + z-index: 2; +} +.form-signin input[type="email"] { + margin-bottom: -1px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.form-signin input[type="password"] { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} diff --git a/quartz-manager/src/main/resources/static/js/app/app.js b/quartz-manager/src/main/resources/static/js/app/app.js new file mode 100644 index 0000000..dbeaf36 --- /dev/null +++ b/quartz-manager/src/main/resources/static/js/app/app.js @@ -0,0 +1 @@ +var schedulerApp = angular.module('schedulerApp', ['starter', 'configurator', 'progress']); diff --git a/quartz-manager/src/main/resources/static/js/app/components/configurator/configurator-directives.js b/quartz-manager/src/main/resources/static/js/app/components/configurator/configurator-directives.js new file mode 100644 index 0000000..06ec467 --- /dev/null +++ b/quartz-manager/src/main/resources/static/js/app/components/configurator/configurator-directives.js @@ -0,0 +1,28 @@ +angular.module('configurator') +.directive('configForm', function(){ + + var configBackup = {triggerPerDay : '', maxCount : ''}; + + return{ + restrict: 'E', + controller : ['$scope', '$http', function($scope, $http){ + $http.get('scheduler/config').then(function(res){ + $scope.config = res.data; + configBackup = res.data; + }); + + $scope.submitConfig = function (){ + $http.post('scheduler/config', $scope.config) + .then(function(res){ + configBackup = $scope.config; + }, function(error){ + $scope.congif = configBackup; + }); + }; + + }], + templateUrl : 'templates/manager/config-form.html', + link : function($scope){ + } + }; +}); \ No newline at end of file diff --git a/quartz-manager/src/main/resources/static/js/app/components/configurator/configurator-module.js b/quartz-manager/src/main/resources/static/js/app/components/configurator/configurator-module.js new file mode 100644 index 0000000..1dd35de --- /dev/null +++ b/quartz-manager/src/main/resources/static/js/app/components/configurator/configurator-module.js @@ -0,0 +1 @@ +var starterModule = angular.module('configurator', []); \ No newline at end of file diff --git a/quartz-manager/src/main/resources/static/js/app/components/progress/progress-directives.js b/quartz-manager/src/main/resources/static/js/app/components/progress/progress-directives.js new file mode 100644 index 0000000..e4ec2e1 --- /dev/null +++ b/quartz-manager/src/main/resources/static/js/app/components/progress/progress-directives.js @@ -0,0 +1,14 @@ +angular.module('progress') +.directive('progressPanel', function(){ + + return{ + restrict: 'E', + controller : ['$scope', '$http', function($scope, $http){ + $http.get('scheduler/progress').then(function(res){ + $scope.progress = res.data; + $scope.percentageStr = $scope.progress.percentage + '%'; + }); + }], + templateUrl : 'templates/manager/progress-panel.html' + }; +}); \ No newline at end of file diff --git a/quartz-manager/src/main/resources/static/js/app/components/progress/progress-module.js b/quartz-manager/src/main/resources/static/js/app/components/progress/progress-module.js new file mode 100644 index 0000000..2083de6 --- /dev/null +++ b/quartz-manager/src/main/resources/static/js/app/components/progress/progress-module.js @@ -0,0 +1 @@ +var starterModule = angular.module('progress', []); \ No newline at end of file diff --git a/quartz-manager/src/main/resources/static/js/app/components/starter/starter-directives.js b/quartz-manager/src/main/resources/static/js/app/components/starter/starter-directives.js new file mode 100644 index 0000000..09d8e19 --- /dev/null +++ b/quartz-manager/src/main/resources/static/js/app/components/starter/starter-directives.js @@ -0,0 +1,82 @@ +angular.module('starter') +.directive('starterBtn', function(){ + + return{ + restrict: 'EA', + scope : { + schedulerState : '@initState' + }, + controller : ['$scope', '$http', function($scope, $http){ + + var startScheduler = function(){ + var onStartingSuccess = function(res){ + $scope.schedulerState = 'running'; + }; + var onStartingError = function(res){ + console.log(JSON.stringify(res)); + }; + $http.get('scheduler/run').then(onStartingSuccess, onStartingError); + }; + + var stopScheduler = function(){ + var onStoppingSuccess = function(res){ + $scope.schedulerState = 'stopped'; + }; + var onStoppingError = function(res){ + console.log(JSON.stringify(res)); + }; + $http.get('scheduler/stop').then(onStoppingSuccess, onStoppingError); + }; + + var pauseScheduler = function(){ + var onSuccess = function(res){ + $scope.schedulerState = 'paused'; + }; + var onError = function(res){ + console.log(JSON.stringify(res)); + }; + $http.get('scheduler/pause').then(onSuccess, onError); + }; + + var resumeScheduler = function(){ + var onSuccess = function(res){ + $scope.schedulerState = 'running'; + }; + var onError = function(res){ + console.log(JSON.stringify(res)); + }; + $http.get('scheduler/resume').then(onSuccess, onError); + }; + + $scope.stop = function(){ + if($scope.schedulerState != 'stopped') + stopScheduler(); + }; + + $scope.startOrPause = function(){ + switch ($scope.schedulerState) { + case 'running': pauseScheduler(); + break; + case 'paused': resumeScheduler(); + break; + default: + startScheduler(); + break; + } + }; + + }], + template: + '' +// + '' + }; +}); \ No newline at end of file diff --git a/quartz-manager/src/main/resources/static/js/app/components/starter/starter-module.js b/quartz-manager/src/main/resources/static/js/app/components/starter/starter-module.js new file mode 100644 index 0000000..bfc3f68 --- /dev/null +++ b/quartz-manager/src/main/resources/static/js/app/components/starter/starter-module.js @@ -0,0 +1 @@ +var starterModule = angular.module('starter', []); \ No newline at end of file diff --git a/quartz-manager/src/main/resources/static/js/manager.js b/quartz-manager/src/main/resources/static/js/manager.js new file mode 100644 index 0000000..fb99aaf --- /dev/null +++ b/quartz-manager/src/main/resources/static/js/manager.js @@ -0,0 +1,5 @@ +function toggleScheduler(isRunning){ + alert(!isRunning); + + $.get(''); +} \ No newline at end of file diff --git a/quartz-manager/src/main/resources/templates/fragments/headerinc.html b/quartz-manager/src/main/resources/templates/fragments/headerinc.html new file mode 100644 index 0000000..26e78ae --- /dev/null +++ b/quartz-manager/src/main/resources/templates/fragments/headerinc.html @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quartz-manager/src/main/resources/templates/layouts/standard.html b/quartz-manager/src/main/resources/templates/layouts/standard.html new file mode 100644 index 0000000..706653f --- /dev/null +++ b/quartz-manager/src/main/resources/templates/layouts/standard.html @@ -0,0 +1,61 @@ + + + + + Quartz Manager - Panel View + + + + + + + +
+
+ + + + + +
+

JOB DASHBOARD

+
+

Page content goes here

+
+
+ + +
+
+ + \ No newline at end of file diff --git a/quartz-manager/src/main/resources/templates/login.html b/quartz-manager/src/main/resources/templates/login.html new file mode 100644 index 0000000..2ce55b1 --- /dev/null +++ b/quartz-manager/src/main/resources/templates/login.html @@ -0,0 +1,37 @@ + + + + Quartz Manager + + + + + + + +
+ + + +
+ + + \ No newline at end of file diff --git a/quartz-manager/src/main/resources/templates/manager/config-form.html b/quartz-manager/src/main/resources/templates/manager/config-form.html new file mode 100644 index 0000000..002d82d --- /dev/null +++ b/quartz-manager/src/main/resources/templates/manager/config-form.html @@ -0,0 +1,17 @@ +
+
+ + +
+ +
+ + +
+ + +
diff --git a/quartz-manager/src/main/resources/templates/manager/progress-panel.html b/quartz-manager/src/main/resources/templates/manager/progress-panel.html new file mode 100644 index 0000000..633dbba --- /dev/null +++ b/quartz-manager/src/main/resources/templates/manager/progress-panel.html @@ -0,0 +1,31 @@ +
+
+ {{percentageStr}} +
+
+ +counter +  {{progress.timesTriggered}} / {{progress.repeatCount}} + +

+ +job key  {{progress.jobKey}} +
+job class  {{progress.jobClass}} +
+ +

+ +prev fire time  {{progress.previousFireTime|date:'dd-MM-yyyy HH:mm:ss'}} +
+next fire time  {{progress.nextFireTime|date:'dd-MM-yyyy HH:mm:ss'}} +
+final fire time  {{progress.finalFireTime|date:'dd-MM-yyyy HH:mm:ss'}} +
+ + + diff --git a/quartz-manager/src/main/resources/templates/panelView.html b/quartz-manager/src/main/resources/templates/panelView.html new file mode 100644 index 0000000..2405f12 --- /dev/null +++ b/quartz-manager/src/main/resources/templates/panelView.html @@ -0,0 +1,53 @@ + + + + +
+ + +
+
+
+

Scheduler Control

+
+
+
+ + + +
+
+
+ +
+
+

Scheduler Config

+
+
+ +
+
+
+ +
+
+
+

Job Output

+
+
+ +
+
+
+ +
+ + + + + + \ No newline at end of file diff --git a/quartz-manager/src/test/java/it/fabioformosa/QuartManagerApplicationTests.java b/quartz-manager/src/test/java/it/fabioformosa/QuartManagerApplicationTests.java new file mode 100644 index 0000000..778d4d1 --- /dev/null +++ b/quartz-manager/src/test/java/it/fabioformosa/QuartManagerApplicationTests.java @@ -0,0 +1,20 @@ +package it.fabioformosa; + +import it.fabioformosa.QuartManagerApplication; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = QuartManagerApplication.class) +@WebAppConfiguration +public class QuartManagerApplicationTests { + + @Test + public void contextLoads() { + } + +}