mirror of
https://github.com/fabioformosa/quartz-manager.git
synced 2025-12-30 14:13:16 +09:00
First commit
This commit is contained in:
9
quartz-manager/.gitignore
vendored
Normal file
9
quartz-manager/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/target
|
||||
/Work
|
||||
/.mvn
|
||||
/.project
|
||||
/.settings
|
||||
/.springBeans
|
||||
/mvnw
|
||||
/mvnw.cmd
|
||||
/.classpath
|
||||
136
quartz-manager/pom.xml
Normal file
136
quartz-manager/pom.xml
Normal file
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>it.fabioformosa</groupId>
|
||||
<artifactId>quartz-manager</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<name>quartz-manager</name>
|
||||
<description>Manager Panel for Quartz Scheduler</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.3.2.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-velocity</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.nekohtml</groupId>
|
||||
<artifactId>nekohtml</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.webjars</groupId>
|
||||
<artifactId>bootstrap</artifactId>
|
||||
<version>3.3.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>nz.net.ultraq.thymeleaf</groupId>
|
||||
<artifactId>thymeleaf-layout-dialect</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.quartz-scheduler</groupId>
|
||||
<artifactId>quartz</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-tx</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.gmavenplus</groupId>
|
||||
<artifactId>gmavenplus-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>addSources</goal>
|
||||
<goal>addTestSources</goal>
|
||||
<goal>generateStubs</goal>
|
||||
<goal>compile</goal>
|
||||
<goal>testGenerateStubs</goal>
|
||||
<goal>testCompile</goal>
|
||||
<goal>removeStubs</goal>
|
||||
<goal>removeTestStubs</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<SimpleTrigger> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
13
quartz-manager/src/main/resources/application.properties
Normal file
13
quartz-manager/src/main/resources/application.properties
Normal file
@@ -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
|
||||
3
quartz-manager/src/main/resources/quartz.properties
Normal file
3
quartz-manager/src/main/resources/quartz.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
org.quartz.scheduler.instanceName=example
|
||||
org.quartz.scheduler.instanceId=AUTO
|
||||
org.quartz.threadPool.threadCount=5
|
||||
@@ -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%;
|
||||
}
|
||||
31
quartz-manager/src/main/resources/static/css/scheduler.css
Normal file
31
quartz-manager/src/main/resources/static/css/scheduler.css
Normal file
@@ -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;
|
||||
}
|
||||
41
quartz-manager/src/main/resources/static/css/signin.css
Normal file
41
quartz-manager/src/main/resources/static/css/signin.css
Normal file
@@ -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;
|
||||
}
|
||||
1
quartz-manager/src/main/resources/static/js/app/app.js
Normal file
1
quartz-manager/src/main/resources/static/js/app/app.js
Normal file
@@ -0,0 +1 @@
|
||||
var schedulerApp = angular.module('schedulerApp', ['starter', 'configurator', 'progress']);
|
||||
@@ -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){
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
var starterModule = angular.module('configurator', []);
|
||||
@@ -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'
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
var starterModule = angular.module('progress', []);
|
||||
@@ -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:
|
||||
'<button class="btn btn-default large-btn" ng-click="startOrPause()">'
|
||||
+ '<i id="schedulerControllerBtn1"'
|
||||
+ 'class="fa fa-2x"'
|
||||
+ 'ng-class="{\'fa-pause red\': schedulerState == \'running\', \'fa-play green\': schedulerState ==\'stopped\' || schedulerState == \'paused\'}">'
|
||||
+ '</i>'
|
||||
+ '</button>'
|
||||
// + '<button class="btn btn-default large-btn" ng-disabled="schedulerState == \'stopped\'" ng-click="stop()">'
|
||||
// + '<i id="schedulerControllerBtn2"'
|
||||
// + 'class="fa fa-2x fa-stop red">'
|
||||
// + '</i>'
|
||||
// + '</button>'
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
var starterModule = angular.module('starter', []);
|
||||
5
quartz-manager/src/main/resources/static/js/manager.js
Normal file
5
quartz-manager/src/main/resources/static/js/manager.js
Normal file
@@ -0,0 +1,5 @@
|
||||
function toggleScheduler(isRunning){
|
||||
alert(!isRunning);
|
||||
|
||||
$.get('');
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head lang="en" th:fragment="head">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
|
||||
<link href="http://cdn.jsdelivr.net/webjars/bootstrap/3.3.4/css/bootstrap.min.css"
|
||||
rel="stylesheet" media="screen" />
|
||||
|
||||
<link th:href="@{css/dashboard-bootstrap.css}" rel="stylesheet" media="screen" />
|
||||
<link th:href="@{css/scheduler.css}" rel="stylesheet" media="screen" />
|
||||
|
||||
<script src="http://cdn.jsdelivr.net/webjars/jquery/2.1.4/jquery.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"/>
|
||||
|
||||
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
|
||||
|
||||
<script th:src="@{/js/app/components/starter/starter-module.js}"></script>
|
||||
<script th:src="@{/js/app/components/starter/starter-directives.js}"></script>
|
||||
<script th:src="@{/js/app/components/configurator/configurator-module.js}"></script>
|
||||
<script th:src="@{/js/app/components/configurator/configurator-directives.js}"></script>
|
||||
<script th:src="@{/js/app/components/progress/progress-module.js}"></script>
|
||||
<script th:src="@{/js/app/components/progress/progress-directives.js}"></script>
|
||||
<script th:src="@{/js/app/app.js}"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
>
|
||||
|
||||
<head lang="en">
|
||||
<title>Quartz Manager - Panel View</title>
|
||||
<!--/*/ <th:block th:include="fragments/headerinc :: head"></th:block> /*/-->
|
||||
</head>
|
||||
|
||||
<body ng-app="schedulerApp">
|
||||
|
||||
<nav class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" th:href="@{/}">Quartz Manager</a>
|
||||
</div>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li th:inline="text">
|
||||
<span class="infoInNavbar">Hello [[${#httpServletRequest.remoteUser}]]! </span>
|
||||
</li>
|
||||
<li><a href="#">Settings</a></li>
|
||||
<li>
|
||||
<a th:href="@{/logout}">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
<!-- sidebar -->
|
||||
<div class="col-sm-3 col-md-2 sidebar">
|
||||
<ul class="nav nav-sidebar">
|
||||
<li class="active"><a href="#">Manager <span class="sr-only">(current)</span></a></li>
|
||||
</ul>
|
||||
</div> <!-- /sidebar -->
|
||||
|
||||
<!-- CONTENT-CONTAINER -->
|
||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
||||
<h1 class="page-header">JOB DASHBOARD</h1>
|
||||
<div class="row" layout:fragment="content">
|
||||
<p>Page content goes here</p>
|
||||
</div>
|
||||
</div><!-- /CONTENT-CONTAINER -->
|
||||
|
||||
<th:block layout:fragment="script"></th:block>
|
||||
</div><!-- row -->
|
||||
</div> <!-- container-fluid -->
|
||||
</body>
|
||||
</html>
|
||||
37
quartz-manager/src/main/resources/templates/login.html
Normal file
37
quartz-manager/src/main/resources/templates/login.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
|
||||
<head>
|
||||
<title>Quartz Manager</title>
|
||||
|
||||
|
||||
<link href="http://cdn.jsdelivr.net/webjars/bootstrap/3.3.4/css/bootstrap.min.css"
|
||||
rel="stylesheet" media="screen" />
|
||||
|
||||
<link th:href="@{css/signin.css}" rel="stylesheet" media="screen" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<form th:action="@{/login}" method="post" class="form-signin">
|
||||
<h2 class="form-signin-heading">Login</h2>
|
||||
|
||||
<div th:if="${param.error}">
|
||||
Invalid username and password.
|
||||
</div>
|
||||
<div th:if="${param.logout}">
|
||||
You have been logged out.
|
||||
</div>
|
||||
|
||||
<label for="inputUser" class="sr-only">Username</label>
|
||||
<input type="text" id="inputUser" name="username" class="form-control" placeholder="Username" required="" autofocus=""/>
|
||||
|
||||
<label for="inputPassword" class="sr-only" >Password</label>
|
||||
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required=""/>
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<form name="configForm">
|
||||
<div class="form-group">
|
||||
<label>Num per day</label>
|
||||
<input ng-model="config.triggerPerDay" type="number" class="form-control"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Repeat count</label>
|
||||
<input ng-model="config.maxCount" type="number" class="form-control"/>
|
||||
</div>
|
||||
|
||||
<button type="button"
|
||||
ng-click="submitConfig()"
|
||||
class="btn btn-default">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
@@ -0,0 +1,31 @@
|
||||
<div class="progress" ng-show="progress.percentage >= 0">
|
||||
<div class="progress-bar"
|
||||
role="progressbar"
|
||||
aria-valuenow="{{progress.percentage}}"
|
||||
aria-valuemin="0" aria-valuemax="100"
|
||||
ng-style="{width: percentageStr}">
|
||||
{{percentageStr}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="label label-info">counter</span>
|
||||
{{progress.timesTriggered}} <span ng-show="progress.repeatCount > 0">/ {{progress.repeatCount}} </span>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<span class="label label-info">job key</span> {{progress.jobKey}}
|
||||
<br/>
|
||||
<span class="label label-info">job class</span> {{progress.jobClass}}
|
||||
<br/>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<span class="label label-info">prev fire time</span> {{progress.previousFireTime|date:'dd-MM-yyyy HH:mm:ss'}}
|
||||
<br/>
|
||||
<span class="label label-info">next fire time</span> {{progress.nextFireTime|date:'dd-MM-yyyy HH:mm:ss'}}
|
||||
<br/>
|
||||
<span class="label label-info">final fire time</span> {{progress.finalFireTime|date:'dd-MM-yyyy HH:mm:ss'}}
|
||||
<br/>
|
||||
|
||||
|
||||
|
||||
53
quartz-manager/src/main/resources/templates/panelView.html
Normal file
53
quartz-manager/src/main/resources/templates/panelView.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorator="layouts/standard"
|
||||
>
|
||||
<head lang="en"></head>
|
||||
<body>
|
||||
<div class="row" layout:fragment="content">
|
||||
|
||||
|
||||
<div id="leftPanel" class="col-xs-12 col-md-3">
|
||||
<div class="panel panel-default widget-panel">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Scheduler Control</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="center">
|
||||
<th:block>
|
||||
<starter-btn th:attr="init-state=${schedulerState}"></starter-btn>
|
||||
</th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default widget-panel">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Scheduler Config</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<config-form></config-form>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /left-panel -->
|
||||
|
||||
<div id="rightPanel" class="col-xs-12 col-md-9">
|
||||
<div class="panel panel-default widget-panel">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Job Output</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<progress-panel></progress-panel>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /right-panel -->
|
||||
|
||||
</div><!-- row fragment-->
|
||||
|
||||
<th:block layout:fragment="script">
|
||||
<script th:src="@{/js/manager.js}"></script>
|
||||
</th:block>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user