First commit

This commit is contained in:
fabio.formosa
2016-03-25 00:30:44 +01:00
commit b6626fce5c
33 changed files with 1252 additions and 0 deletions

9
quartz-manager/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
/target
/Work
/.mvn
/.project
/.settings
/.springBeans
/mvnw
/mvnw.cmd
/.classpath

136
quartz-manager/pom.xml Normal file
View 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>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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 + "]";
}
}

View File

@@ -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;
}
}

View File

@@ -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!");
}
}

View File

@@ -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();
}
}

View 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

View File

@@ -0,0 +1,3 @@
org.quartz.scheduler.instanceName=example
org.quartz.scheduler.instanceId=AUTO
org.quartz.threadPool.threadCount=5

View File

@@ -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%;
}

View 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;
}

View 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;
}

View File

@@ -0,0 +1 @@
var schedulerApp = angular.module('schedulerApp', ['starter', 'configurator', 'progress']);

View File

@@ -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){
}
};
});

View File

@@ -0,0 +1 @@
var starterModule = angular.module('configurator', []);

View File

@@ -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'
};
});

View File

@@ -0,0 +1 @@
var starterModule = angular.module('progress', []);

View File

@@ -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>'
};
});

View File

@@ -0,0 +1 @@
var starterModule = angular.module('starter', []);

View File

@@ -0,0 +1,5 @@
function toggleScheduler(isRunning){
alert(!isRunning);
$.get('');
}

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>
&nbsp; {{progress.timesTriggered}} <span ng-show="progress.repeatCount > 0">/ {{progress.repeatCount}} </span>
<br/><br/>
<span class="label label-info">job key</span>&nbsp; {{progress.jobKey}}
<br/>
<span class="label label-info">job class</span>&nbsp; {{progress.jobClass}}
<br/>
<br/><br/>
<span class="label label-info">prev fire time</span>&nbsp; {{progress.previousFireTime|date:'dd-MM-yyyy HH:mm:ss'}}
<br/>
<span class="label label-info">next fire time</span>&nbsp; {{progress.nextFireTime|date:'dd-MM-yyyy HH:mm:ss'}}
<br/>
<span class="label label-info">final fire time</span>&nbsp; {{progress.finalFireTime|date:'dd-MM-yyyy HH:mm:ss'}}
<br/>

View 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>

View File

@@ -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() {
}
}