mirror of
https://github.com/fabioformosa/quartz-manager.git
synced 2026-05-15 14:20:30 +09:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
615761f0f3 | ||
|
|
dc081a1bed | ||
|
|
e04311914b | ||
|
|
bacd6401a3 | ||
|
|
f88bd50768 | ||
|
|
0b4dc4a1de |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/.project
|
||||||
55
README.md
55
README.md
@@ -1,22 +1,43 @@
|
|||||||
# quartz-manager
|
#QUARTZ MANAGER
|
||||||
GUI Manager for Quartz Scheduler.
|
GUI Manager for Quartz Scheduler.
|
||||||
Through this webapp you can launch and control your scheduled job.
|
|
||||||
GUI Console is composed by a managament panel to set trigger, start/stop scheduler and by a log panel with progress bar to get job output.
|
Through this webapp you can launch and control your scheduled job. The GUI Console is composed by a managament panel to set trigger, start/stop scheduler and a log panel with a progress bar to display the job output.
|
||||||
|
|
||||||
## SCREENSHOT
|
## SCREENSHOT
|
||||||

|

|
||||||
|
|
||||||
## HOW IT WORKS
|
## HOW IT WORKS
|
||||||
* Set up the trigger into the left sidebar in terms of: daily frequency and and max occurrences.
|
* Set up the trigger into the left sidebar in terms of: daily frequency and and max occurrences.
|
||||||
* Press the start button
|
* Press the start button
|
||||||
* The GUI manager updates the progress bar and reports all logs of your quartz job.
|
* The GUI manager updates the progress bar and reports all logs of your quartz job.
|
||||||
|
|
||||||
## HOW IT RUNS
|
## QUICK START
|
||||||
1. Quartz-Manager is a Spring Boot Application. To run by CLI `mvn spring-boot:run` or through your IDE. For more details [spring boot ref.](http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-running-your-application.html)
|
**[requirements]** Make sure you have installed
|
||||||
1. Open quartz-manager at the link: [http://localhost:9000/quartz-manager/manager](http://localhost:9000/quartz-manager/manager)
|
* [Java 8](https://java.com/download/) or greater
|
||||||
1. Log in with default credentials: `admin/admin`
|
* [Maven](https://maven.apache.org/)
|
||||||
|
* [npm](https://www.npmjs.com/get-npm), [node](https://nodejs.org) and [angular-cli](https://cli.angular.io/)
|
||||||
|
|
||||||
## HOW TO RUN YOUR JOB
|
```
|
||||||
|
#CLONE REPOSITORY
|
||||||
|
git clone https://github.com/fabioformosa/quartz-manager.git
|
||||||
|
|
||||||
|
# START QUARTZ-MANAGER-BACKEND
|
||||||
|
cd quartz-manager/quartz-manager-backend
|
||||||
|
mvn spring-boot:run
|
||||||
|
|
||||||
|
# START QUARTZ-MANAGER-FRONTEND
|
||||||
|
cd quartz-manager/quartz-manager-backend
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Open browser at [http://localhost:4200](http://localhost:4200)
|
||||||
|
1. Log in with **default credentials**: `admin/admin`
|
||||||
|
|
||||||
|
If you are not confident with maven CLI, you can start it by your IDE. For more details [spring boot ref.](http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-running-your-application.html)
|
||||||
|
|
||||||
|
## HOW TO RUN YOUR SCHEDULED JOB
|
||||||
By default, quartz-manager executes the dummy job that logs "hello world!".
|
By default, quartz-manager executes the dummy job that logs "hello world!".
|
||||||
Replace the dummy job (class: `it.fabioformosa.quartzmanager.jobs.SampleJob`) with yours. Follow these steps:
|
Replace the dummy job (class: `it.fabioformosa.quartzmanager.jobs.SampleJob`) with yours. Follow these steps:
|
||||||
|
|
||||||
@@ -26,7 +47,7 @@ Replace the dummy job (class: `it.fabioformosa.quartzmanager.jobs.SampleJob`) wi
|
|||||||
## HOW TO CHANGE SETTINGS
|
## HOW TO CHANGE SETTINGS
|
||||||
* Num of Threads: `/quartz-manager/src/main/resources/quartz.properties`
|
* Num of Threads: `/quartz-manager/src/main/resources/quartz.properties`
|
||||||
* Credentials: `it.fabioformosa.quartzmanager.configuration.WebSecurityConfig`
|
* Credentials: `it.fabioformosa.quartzmanager.configuration.WebSecurityConfig`
|
||||||
* Server context path (default `/quartz-manager`) and port (default `9000`): `/quartz-manager/src/main/resources/application.properties`
|
* quartz-manager backend context path (default `/quartz-manager`) and port (default `8080`): `/quartz-manager/src/main/resources/application.properties`
|
||||||
|
|
||||||
## Tech Overview
|
## Tech Overview
|
||||||
|
|
||||||
@@ -34,8 +55,18 @@ Replace the dummy job (class: `it.fabioformosa.quartzmanager.jobs.SampleJob`) wi
|
|||||||
|
|
||||||
**Application Server** Tomcat (embedded)
|
**Application Server** Tomcat (embedded)
|
||||||
|
|
||||||
**Frontend** Angularjs 1.6.7, Thymeleaf 2.1.4, Web-Socket (sockjs 0.3.4, stompjs)
|
**Frontend** Angular 5.2.0, Web-Socket (stompjs 2.3.3)
|
||||||
|
|
||||||
**Style** Bootstrap 3.3.4, animated.css, FontAwesome
|
**Style** angular material, FontAwesome 5
|
||||||
|
|
||||||
|
From quartz manager ver 2.x.x, the new structure of project is:
|
||||||
|
* REST backend (java based, using [http://www.quartz-scheduler.org/](http://www.quartz-scheduler.org/)
|
||||||
|
* Single Page Application frontend (angular 5)
|
||||||
|
|
||||||
|
(The previous version of quartz manager was a monolithic backend that provided also frontend developed with angularjs 1.6.x. You can find it at the branch 1.x.x)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
* this project has been created from [angular-spring-starter](https://github.com/bfwg/angular-spring-starter)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<groupId>it.fabioformosa</groupId>
|
<groupId>it.fabioformosa</groupId>
|
||||||
<artifactId>quartz-manager</artifactId>
|
<artifactId>quartz-manager</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>2.0.1-SNAPSHOT</version>
|
||||||
<packaging>war</packaging>
|
<packaging>war</packaging>
|
||||||
|
|
||||||
<name>quartz-manager</name>
|
<name>quartz-manager</name>
|
||||||
@@ -20,13 +20,14 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-devtools</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@@ -38,32 +39,25 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.thymeleaf.extras</groupId>
|
|
||||||
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-velocity</artifactId>
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
<version>1.4.7.RELEASE</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.codehaus.groovy</groupId>
|
<groupId>org.springframework</groupId>
|
||||||
<artifactId>groovy</artifactId>
|
<artifactId>spring-messaging</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
<dependency>
|
<groupId>org.springframework</groupId>
|
||||||
<groupId>net.sourceforge.nekohtml</groupId>
|
<artifactId>spring-tx</artifactId>
|
||||||
<artifactId>nekohtml</artifactId>
|
</dependency>
|
||||||
</dependency>
|
<dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
@@ -75,23 +69,45 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
<artifactId>jjwt</artifactId>
|
||||||
</dependency>
|
<version>0.9.0</version>
|
||||||
<dependency>
|
</dependency>
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-messaging</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.webjars</groupId>
|
<groupId>joda-time</groupId>
|
||||||
<artifactId>bootstrap</artifactId>
|
<artifactId>joda-time</artifactId>
|
||||||
<version>3.3.6</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>nz.net.ultraq.thymeleaf</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>thymeleaf-layout-dialect</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-annotations</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.groovy</groupId>
|
||||||
|
<artifactId>groovy</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.sourceforge.nekohtml</groupId>
|
||||||
|
<artifactId>nekohtml</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.rest-assured</groupId>
|
||||||
|
<artifactId>spring-mock-mvc</artifactId>
|
||||||
|
<version>3.0.5</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.quartz-scheduler</groupId>
|
<groupId>org.quartz-scheduler</groupId>
|
||||||
<artifactId>quartz</artifactId>
|
<artifactId>quartz</artifactId>
|
||||||
@@ -102,11 +118,7 @@
|
|||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-io</artifactId>
|
||||||
<version>1.3.2</version>
|
<version>1.3.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-tx</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Reactor -->
|
<!-- Reactor -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.projectreactor</groupId>
|
<groupId>io.projectreactor</groupId>
|
||||||
@@ -129,6 +141,11 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-aop</artifactId>
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package it.fabioformosa;
|
package it.fabioformosa;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class QuartManagerApplication {
|
public class QuartManagerApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(QuartManagerApplication.class, args);
|
SpringApplication.run(QuartManagerApplication.class, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.configuration;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.quartz.JobDetail;
|
||||||
|
import org.quartz.SimpleTrigger;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.jobs.myjobs.SampleJob;
|
||||||
|
import it.fabioformosa.quartzmanager.scheduler.AutowiringSpringBeanJobFactory;
|
||||||
|
import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor;
|
||||||
|
import it.fabioformosa.quartzmanager.scheduler.TriggerMonitorImpl;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
factoryBean
|
||||||
|
.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT);// in case of misfire, ignore all missed triggers and continue
|
||||||
|
return factoryBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "triggerMonitor")
|
||||||
|
public TriggerMonitor createTriggerMonitor(@Qualifier("jobTrigger") Trigger trigger) {
|
||||||
|
TriggerMonitor triggerMonitor = new TriggerMonitorImpl();
|
||||||
|
triggerMonitor.setTrigger(trigger);
|
||||||
|
return triggerMonitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,65 +1,75 @@
|
|||||||
package it.fabioformosa.quartzmanager.configuration;
|
package it.fabioformosa.quartzmanager.configuration;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.AjaxAuthenticationFilter;
|
import it.fabioformosa.quartzmanager.security.ComboEntryPoint;
|
||||||
import it.fabioformosa.quartzmanager.security.ComboEntryPoint;
|
import it.fabioformosa.quartzmanager.security.auth.AuthenticationFailureHandler;
|
||||||
|
import it.fabioformosa.quartzmanager.security.auth.AuthenticationSuccessHandler;
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
@Configuration
|
||||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
@EnableWebSecurity
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
@Configuration
|
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
@Order(1)
|
|
||||||
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter {
|
@Configuration
|
||||||
@Override
|
@Order(1)
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
http.csrf().disable() //
|
@Override
|
||||||
.antMatcher("/notifications").authorizeRequests().anyRequest().hasAnyRole("ADMIN").and()
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
.httpBasic();
|
http.csrf().disable() //
|
||||||
|
.antMatcher("/notifications").authorizeRequests().anyRequest().hasAnyRole("ADMIN").and()
|
||||||
// http.antMatcher("/logs/**").authorizeRequests().anyRequest()
|
.httpBasic();
|
||||||
// .permitAll();
|
|
||||||
}
|
// http.antMatcher("/logs/**").authorizeRequests().anyRequest()
|
||||||
}
|
// .permitAll();
|
||||||
|
}
|
||||||
@Configuration
|
}
|
||||||
@Order(2)
|
|
||||||
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter {
|
@Configuration
|
||||||
|
@Order(2)
|
||||||
@Resource
|
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
private ComboEntryPoint comboEntryPoint;
|
|
||||||
|
@Resource
|
||||||
@Override
|
private ComboEntryPoint comboEntryPoint;
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
|
||||||
http.exceptionHandling().authenticationEntryPoint(comboEntryPoint).and().csrf().disable()//
|
@Autowired
|
||||||
.authorizeRequests().anyRequest().authenticated().and()//
|
private AuthenticationSuccessHandler authenticationSuccessHandler;
|
||||||
.addFilterBefore(new AjaxAuthenticationFilter(authenticationManager()),
|
|
||||||
UsernamePasswordAuthenticationFilter.class)//
|
@Autowired
|
||||||
.formLogin().loginPage("/login").permitAll().and().logout()
|
private AuthenticationFailureHandler authenticationFailureHandler;
|
||||||
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/manager");
|
|
||||||
}
|
@Override
|
||||||
|
public void configure(WebSecurity web) throws Exception {
|
||||||
@Override
|
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
|
||||||
public void configure(WebSecurity web) throws Exception {
|
}
|
||||||
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
|
|
||||||
}
|
@Override
|
||||||
}
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
// http.csrf().ignoringAntMatchers("/api/login", "/api/signup").and() //
|
||||||
@Autowired
|
http.cors().and().csrf().disable()
|
||||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
.exceptionHandling().authenticationEntryPoint(comboEntryPoint).and()//
|
||||||
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
|
.authorizeRequests().anyRequest().authenticated().and()//
|
||||||
}
|
.formLogin().loginPage("/api/login").successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler).and().logout()
|
||||||
|
.logoutRequestMatcher(new AntPathRequestMatcher("/api/logout"))
|
||||||
}
|
.logoutSuccessUrl("/manager");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.configuration;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
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.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.security.ComboEntryPoint;
|
||||||
|
import it.fabioformosa.quartzmanager.security.auth.AuthenticationFailureHandler;
|
||||||
|
import it.fabioformosa.quartzmanager.security.auth.AuthenticationSuccessHandler;
|
||||||
|
import it.fabioformosa.quartzmanager.security.auth.LogoutSuccess;
|
||||||
|
import it.fabioformosa.quartzmanager.security.auth.TokenAuthenticationFilter;
|
||||||
|
import it.fabioformosa.quartzmanager.security.service.impl.CustomUserDetailsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT Temporary disabled
|
||||||
|
*
|
||||||
|
* @author Fabio.Formosa
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
//@Configuration
|
||||||
|
//@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
public class WebSecurityConfigJWT extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Value("${jwt.cookie}")
|
||||||
|
private String TOKEN_COOKIE;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CustomUserDetailsService jwtUserDetailsService;
|
||||||
|
|
||||||
|
// @Autowired
|
||||||
|
// private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
|
||||||
|
@Resource
|
||||||
|
private ComboEntryPoint comboEntryPoint;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LogoutSuccess logoutSuccess;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthenticationSuccessHandler authenticationSuccessHandler;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthenticationFailureHandler authenticationFailureHandler;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Override
|
||||||
|
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||||
|
return super.authenticationManagerBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder)
|
||||||
|
throws Exception {
|
||||||
|
authenticationManagerBuilder.userDetailsService(jwtUserDetailsService)
|
||||||
|
.passwordEncoder(passwordEncoder());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TokenAuthenticationFilter jwtAuthenticationTokenFilter() throws Exception {
|
||||||
|
return new TokenAuthenticationFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
// http.csrf().ignoringAntMatchers("/api/login", "/api/signup")
|
||||||
|
// .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
|
||||||
|
http.cors().and().csrf().disable()
|
||||||
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||||
|
.exceptionHandling().authenticationEntryPoint(comboEntryPoint).and()
|
||||||
|
.addFilterBefore(jwtAuthenticationTokenFilter(), BasicAuthenticationFilter.class)
|
||||||
|
.authorizeRequests().anyRequest().authenticated().and().formLogin().loginPage("/api/login")
|
||||||
|
.successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler)
|
||||||
|
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/api/logout"))
|
||||||
|
.logoutSuccessHandler(logoutSuccess).deleteCookies(TOKEN_COOKIE);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
package it.fabioformosa.quartzmanager.configuration;
|
package it.fabioformosa.quartzmanager.configuration;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||||
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
|
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
|
||||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSocketMessageBroker
|
@EnableWebSocketMessageBroker
|
||||||
public class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
|
public class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||||
config.enableSimpleBroker("/topic");
|
config.enableSimpleBroker("/topic");
|
||||||
config.setApplicationDestinationPrefixes("/job");
|
config.setApplicationDestinationPrefixes("/job");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||||
registry.addEndpoint("/logs").withSockJS();
|
registry.addEndpoint("/logs").setAllowedOrigins("/**").withSockJS();
|
||||||
registry.addEndpoint("/progress").withSockJS();
|
registry.addEndpoint("/progress").setAllowedOrigins("/**").withSockJS();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.controllers;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.security.TokenHelper;
|
||||||
|
import it.fabioformosa.quartzmanager.security.model.UserTokenState;
|
||||||
|
import it.fabioformosa.quartzmanager.security.service.impl.CustomUserDetailsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT Temporary disabled
|
||||||
|
*
|
||||||
|
* @author Fabio.Formosa
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
//@RestController
|
||||||
|
//@RequestMapping( value = "/api", produces = MediaType.APPLICATION_JSON_VALUE )
|
||||||
|
public class AuthenticationController {
|
||||||
|
|
||||||
|
static class PasswordChanger {
|
||||||
|
public String oldPassword;
|
||||||
|
public String newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CustomUserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
TokenHelper tokenHelper;
|
||||||
|
|
||||||
|
@Value("${jwt.expires_in}")
|
||||||
|
private int EXPIRES_IN;
|
||||||
|
|
||||||
|
@Value("${jwt.cookie}")
|
||||||
|
private String TOKEN_COOKIE;
|
||||||
|
|
||||||
|
@RequestMapping(value = "/changePassword", method = RequestMethod.POST)
|
||||||
|
@PreAuthorize("hasRole('USER')")
|
||||||
|
public ResponseEntity<?> changePassword(@RequestBody PasswordChanger passwordChanger) {
|
||||||
|
userDetailsService.changePassword(passwordChanger.oldPassword, passwordChanger.newPassword);
|
||||||
|
Map<String, String> result = new HashMap<>();
|
||||||
|
result.put( "result", "success" );
|
||||||
|
return ResponseEntity.accepted().body(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/refresh", method = RequestMethod.GET)
|
||||||
|
public ResponseEntity<?> refreshAuthenticationToken(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
|
||||||
|
String authToken = tokenHelper.getToken( request );
|
||||||
|
if (authToken != null && tokenHelper.canTokenBeRefreshed(authToken)) {
|
||||||
|
// TODO check user password last update
|
||||||
|
String refreshedToken = tokenHelper.refreshToken(authToken);
|
||||||
|
|
||||||
|
Cookie authCookie = new Cookie( TOKEN_COOKIE, refreshedToken );
|
||||||
|
authCookie.setPath( "/" );
|
||||||
|
authCookie.setHttpOnly( true );
|
||||||
|
authCookie.setMaxAge( EXPIRES_IN );
|
||||||
|
// Add cookie to response
|
||||||
|
response.addCookie( authCookie );
|
||||||
|
|
||||||
|
UserTokenState userTokenState = new UserTokenState(refreshedToken, EXPIRES_IN);
|
||||||
|
return ResponseEntity.ok(userTokenState);
|
||||||
|
} else {
|
||||||
|
UserTokenState userTokenState = new UserTokenState();
|
||||||
|
return ResponseEntity.accepted().body(userTokenState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.controllers;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
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.ResponseStatus;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam;
|
||||||
|
import it.fabioformosa.quartzmanager.dto.TriggerProgress;
|
||||||
|
import it.fabioformosa.quartzmanager.enums.SchedulerStates;
|
||||||
|
import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This controller provides scheduler info about config and status. It provides
|
||||||
|
* also methods to set new config and start/stop/resume the scheduler.
|
||||||
|
*
|
||||||
|
* @author Fabio.Formosa
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/scheduler")
|
||||||
|
public class SchedulerController {
|
||||||
|
|
||||||
|
private static final int MILLS_IN_A_DAY = 1000 * 60 * 60 * 24;
|
||||||
|
private static final int SEC_IN_A_DAY = 60 * 60 * 24;
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(SchedulerController.class);
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private Scheduler scheduler;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TriggerMonitor triggerMonitor;
|
||||||
|
|
||||||
|
@RequestMapping(value = "/config", method = RequestMethod.GET)
|
||||||
|
public SchedulerConfigParam getConfig() {
|
||||||
|
log.debug("SCHEDULER - GET CONFIG params");
|
||||||
|
SimpleTrigger simpleTrigger = (SimpleTrigger) triggerMonitor.getTrigger();
|
||||||
|
|
||||||
|
int maxCount = simpleTrigger.getRepeatCount() + 1;
|
||||||
|
long triggersPerDay = fromMillsIntervalToTriggerPerDay(simpleTrigger.getRepeatInterval());
|
||||||
|
|
||||||
|
return new SchedulerConfigParam(triggersPerDay, maxCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/progress")
|
||||||
|
public TriggerProgress getProgressInfo() throws SchedulerException {
|
||||||
|
log.trace("SCHEDULER - GET PROGRESS INFO");
|
||||||
|
TriggerProgress progress = new TriggerProgress();
|
||||||
|
|
||||||
|
SimpleTriggerImpl jobTrigger = (SimpleTriggerImpl) scheduler.getTrigger(triggerMonitor.getTrigger().getKey());
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(produces = "application/json")
|
||||||
|
public Map<String, String> getStatus() throws SchedulerException {
|
||||||
|
log.trace("SCHEDULER - GET STATUS");
|
||||||
|
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();
|
||||||
|
return Collections.singletonMap("data", schedulerState.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/pause")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
public void pause() throws SchedulerException {
|
||||||
|
log.info("SCHEDULER - PAUSE COMMAND");
|
||||||
|
scheduler.standby();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/config", method = RequestMethod.POST)
|
||||||
|
public SchedulerConfigParam postConfig(@RequestBody SchedulerConfigParam config) throws SchedulerException {
|
||||||
|
log.info("SCHEDULER - NEW CONFIG {}", config);
|
||||||
|
SimpleTrigger trigger = (SimpleTrigger) triggerMonitor.getTrigger();
|
||||||
|
|
||||||
|
TriggerBuilder<SimpleTrigger> triggerBuilder = trigger.getTriggerBuilder();
|
||||||
|
|
||||||
|
int intervalInMills = fromTriggerPerDayToMillsInterval(config.getTriggerPerDay());
|
||||||
|
Trigger newTrigger = triggerBuilder.withSchedule(SimpleScheduleBuilder.simpleSchedule()
|
||||||
|
.withIntervalInMilliseconds(intervalInMills).withRepeatCount(config.getMaxCount() - 1)).build();
|
||||||
|
|
||||||
|
scheduler.rescheduleJob(triggerMonitor.getTrigger().getKey(), newTrigger);
|
||||||
|
triggerMonitor.setTrigger(newTrigger);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/resume")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
public void resume() throws SchedulerException {
|
||||||
|
log.info("SCHEDULER - RESUME COMMAND");
|
||||||
|
scheduler.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/run")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
public void run() throws SchedulerException {
|
||||||
|
log.info("SCHEDULER - START COMMAND");
|
||||||
|
scheduler.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
@RequestMapping("/stop")
|
||||||
|
public void stop() throws SchedulerException {
|
||||||
|
log.info("SCHEDULER - STOP COMMAND");
|
||||||
|
scheduler.shutdown(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long fromMillsIntervalToTriggerPerDay(long repeatIntervalInMills) {
|
||||||
|
return (int) Math.ceil(MILLS_IN_A_DAY / repeatIntervalInMills);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int fromTriggerPerDayToMillsInterval(long triggerPerDay) {
|
||||||
|
return (int) Math.ceil(Long.valueOf(MILLS_IN_A_DAY) / triggerPerDay); // with ceil the triggerPerDay is a max value
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private int fromTriggerPerDayToSecInterval(long triggerPerDay) {
|
||||||
|
return (int) Math.ceil(Long.valueOf(SEC_IN_A_DAY) / triggerPerDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.controllers;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/session")
|
||||||
|
public class SessionController {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(SessionController.class);
|
||||||
|
|
||||||
|
@RequestMapping("/invalidate")
|
||||||
|
@PreAuthorize("hasAuthority('ADMIN')")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
public void invalidateSession(HttpSession session) {
|
||||||
|
session.invalidate();
|
||||||
|
log.info("Invalidated current session!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/refresh")
|
||||||
|
@PreAuthorize("hasAuthority('ADMIN')")
|
||||||
|
public HttpEntity<Void> refreshSession(HttpSession session) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.controllers;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(value = "/api", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT Temporary disabled
|
||||||
|
*
|
||||||
|
* @author Fabio.Formosa
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @Autowired
|
||||||
|
// private UserService userService;
|
||||||
|
|
||||||
|
|
||||||
|
// @RequestMapping(method = POST, value = "/signup")
|
||||||
|
// public ResponseEntity<?> addUser(@RequestBody UserRequest userRequest,
|
||||||
|
// UriComponentsBuilder ucBuilder) {
|
||||||
|
//
|
||||||
|
// User existUser = this.userService.findByUsername(userRequest.getUsername());
|
||||||
|
// if (existUser != null)
|
||||||
|
// throw new ResourceConflictException(userRequest.getId(), "Username already exists");
|
||||||
|
// User user = this.userService.save(userRequest);
|
||||||
|
// HttpHeaders headers = new HttpHeaders();
|
||||||
|
// headers.setLocation(ucBuilder.path("/api/user/{userId}").buildAndExpand(user.getId()).toUri());
|
||||||
|
// return new ResponseEntity<>(user, HttpStatus.CREATED);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @RequestMapping(method = GET, value = "/user/all")
|
||||||
|
// public List<User> loadAll() {
|
||||||
|
// return this.userService.findAll();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @RequestMapping(method = GET, value = "/user/{userId}")
|
||||||
|
// public User loadById(@PathVariable Long userId) {
|
||||||
|
// return this.userService.findById(userId);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// @RequestMapping(method = GET, value = "/user/reset-credentials")
|
||||||
|
// public ResponseEntity<Map> resetCredentials() {
|
||||||
|
// this.userService.resetCredentials();
|
||||||
|
// Map<String, String> result = new HashMap<>();
|
||||||
|
// result.put("result", "success");
|
||||||
|
// return ResponseEntity.accepted().body(result);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We are not using userService.findByUsername here(we could), so it is good that we are making
|
||||||
|
* sure that the user has role "ROLE_USER" to access this endpoint.
|
||||||
|
*/
|
||||||
|
// @RequestMapping("/whoami")
|
||||||
|
// // @PreAuthorize("hasRole('USER')")
|
||||||
|
// public User user() {
|
||||||
|
// return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
// }
|
||||||
|
|
||||||
|
@RequestMapping("/whoami")
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
public Object user() {
|
||||||
|
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.dto;
|
||||||
|
|
||||||
|
public class SchedulerConfigParam {
|
||||||
|
|
||||||
|
public long triggerPerDay;
|
||||||
|
public int maxCount;
|
||||||
|
|
||||||
|
public SchedulerConfigParam() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SchedulerConfigParam(long triggerPerDay, int maxCount) {
|
||||||
|
super();
|
||||||
|
this.triggerPerDay = triggerPerDay;
|
||||||
|
this.maxCount = 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,5 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.enums;
|
||||||
|
|
||||||
|
public enum SchedulerStates {
|
||||||
|
RUNNING, STOPPED, PAUSED
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.exceptions;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class ExceptionHandlingController {
|
||||||
|
|
||||||
|
@ExceptionHandler(ResourceConflictException.class)
|
||||||
|
public ResponseEntity<ExceptionResponse> resourceConflict(ResourceConflictException ex) {
|
||||||
|
ExceptionResponse response = new ExceptionResponse();
|
||||||
|
response.setErrorCode("Conflict");
|
||||||
|
response.setErrorMessage(ex.getMessage());
|
||||||
|
return new ResponseEntity<ExceptionResponse>(response, HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.exceptions;
|
||||||
|
|
||||||
|
public class ExceptionResponse {
|
||||||
|
|
||||||
|
private String errorCode;
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
public ExceptionResponse() {}
|
||||||
|
|
||||||
|
public String getErrorCode() {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorCode(String errorCode) {
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorMessage() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorMessage(String errorMessage) {
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.exceptions;
|
||||||
|
|
||||||
|
public class ResourceConflictException extends RuntimeException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1791564636123821405L;
|
||||||
|
|
||||||
|
private Long resourceId;
|
||||||
|
|
||||||
|
public ResourceConflictException(Long resourceId, String message) {
|
||||||
|
super(message);
|
||||||
|
setResourceId(resourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getResourceId() {
|
||||||
|
return resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceId(Long resourceId) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.jobs;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
import org.quartz.Job;
|
||||||
|
import org.quartz.JobExecutionContext;
|
||||||
|
import org.quartz.SchedulerException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.aspects.ProgressUpdater;
|
||||||
|
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends this class to create a job that produces LogRecord to be displayed
|
||||||
|
* into the GUI panel
|
||||||
|
*
|
||||||
|
* @author Fabio.Formosa
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract class AbstractLoggingJob implements Job {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AbstractLoggingJob.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SimpMessageSendingOperations messagingTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ProgressUpdater progressUpdater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param jobExecutionContext
|
||||||
|
* @return final log
|
||||||
|
*/
|
||||||
|
public abstract LogRecord doIt(JobExecutionContext jobExecutionContext);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void execute(JobExecutionContext jobExecutionContext) {
|
||||||
|
try {
|
||||||
|
LogRecord logMsg = doIt(jobExecutionContext);
|
||||||
|
logAndSend(logMsg);
|
||||||
|
progressUpdater.update();
|
||||||
|
} catch (SchedulerException e) {
|
||||||
|
log.error("Error updating progress " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void logAndSend(LogRecord logRecord) {
|
||||||
|
log.info(logRecord.getMessage());
|
||||||
|
logRecord.setThreadName(Thread.currentThread().getName());
|
||||||
|
messagingTemplate.convertAndSend("/topic/logs", logRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.jobs.entities;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log record produced by a job at the end of each run
|
||||||
|
*
|
||||||
|
* @author Fabio.Formosa
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class LogRecord {
|
||||||
|
|
||||||
|
public enum LogType {
|
||||||
|
INFO, WARN, ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date date;
|
||||||
|
private LogType type;
|
||||||
|
|
||||||
|
private String message;
|
||||||
|
private String threadName;
|
||||||
|
|
||||||
|
public LogRecord(LogType type, String msg) {
|
||||||
|
super();
|
||||||
|
this.type = type;
|
||||||
|
message = msg;
|
||||||
|
date = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDate() {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getThreadName() {
|
||||||
|
return threadName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDate(Date date) {
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String msg) {
|
||||||
|
message = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreadName(String threadName) {
|
||||||
|
this.threadName = threadName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(LogType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "LogRecord [date=" + date + ", type=" + type + ", message=" + message + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package it.fabioformosa.quartzmanager.jobs;
|
package it.fabioformosa.quartzmanager.jobs.myjobs;
|
||||||
|
|
||||||
import org.quartz.JobExecutionContext;
|
import org.quartz.JobExecutionContext;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.jobs.AbstractLoggingJob;
|
||||||
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord;
|
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord;
|
||||||
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord.LogType;
|
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord.LogType;
|
||||||
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.jobs.tests;
|
||||||
|
|
||||||
|
import org.quartz.JobExecutionContext;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.jobs.AbstractLoggingJob;
|
||||||
|
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord;
|
||||||
|
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord.LogType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This job can be used to test the misfire policy. It pretends to be a long
|
||||||
|
* processing job (sleeping for a while)
|
||||||
|
*
|
||||||
|
* @author Fabio.Formosa
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class MisfireTestJob extends AbstractLoggingJob {
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(MisfireTestJob.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LogRecord doIt(JobExecutionContext jobExecutionContext) {
|
||||||
|
try {
|
||||||
|
log.info("{} is going to sleep...", Thread.currentThread().getName());
|
||||||
|
|
||||||
|
Thread.sleep(10 * 1000);
|
||||||
|
|
||||||
|
log.info("{} woke up!", Thread.currentThread().getName());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LogRecord(LogType.INFO, "Hello!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT Temporary disabled
|
||||||
|
*
|
||||||
|
* @author Fabio.Formosa
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
//@Component
|
||||||
|
public class TokenHelper {
|
||||||
|
|
||||||
|
@Value("${app.name}")
|
||||||
|
private String APP_NAME;
|
||||||
|
|
||||||
|
@Value("${jwt.secret}")
|
||||||
|
private String SECRET;
|
||||||
|
|
||||||
|
@Value("${jwt.expires_in}")
|
||||||
|
private int EXPIRES_IN;
|
||||||
|
|
||||||
|
@Value("${jwt.header}")
|
||||||
|
private String AUTH_HEADER;
|
||||||
|
|
||||||
|
@Value("${jwt.cookie}")
|
||||||
|
private String AUTH_COOKIE;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
private SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512;
|
||||||
|
|
||||||
|
public Boolean canTokenBeRefreshed(String token) {
|
||||||
|
try {
|
||||||
|
final Date expirationDate = getClaimsFromToken(token).getExpiration();
|
||||||
|
// String username = getUsernameFromToken(token);
|
||||||
|
// UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||||
|
return expirationDate.compareTo(generateCurrentDate()) > 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateToken(String username) {
|
||||||
|
return Jwts.builder()
|
||||||
|
.setIssuer( APP_NAME )
|
||||||
|
.setSubject(username)
|
||||||
|
.setIssuedAt(generateCurrentDate())
|
||||||
|
.setExpiration(generateExpirationDate())
|
||||||
|
.signWith( SIGNATURE_ALGORITHM, SECRET )
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a specific HTTP cookie in a request.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* The HTTP request object.
|
||||||
|
* @param name
|
||||||
|
* The cookie name to look for.
|
||||||
|
* @return The cookie, or <code>null</code> if not found.
|
||||||
|
*/
|
||||||
|
public Cookie getCookieValueByName(HttpServletRequest request, String name) {
|
||||||
|
if (request.getCookies() == null)
|
||||||
|
return null;
|
||||||
|
for (int i = 0; i < request.getCookies().length; i++)
|
||||||
|
if (request.getCookies()[i].getName().equals(name))
|
||||||
|
return request.getCookies()[i];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken( HttpServletRequest request ) {
|
||||||
|
/**
|
||||||
|
* Getting the token from Cookie store
|
||||||
|
*/
|
||||||
|
Cookie authCookie = getCookieValueByName( request, AUTH_COOKIE );
|
||||||
|
if ( authCookie != null )
|
||||||
|
return authCookie.getValue();
|
||||||
|
/**
|
||||||
|
* Getting the token from Authentication header
|
||||||
|
* e.g Bearer your_token
|
||||||
|
*/
|
||||||
|
String authHeader = request.getHeader(AUTH_HEADER);
|
||||||
|
if ( authHeader != null && authHeader.startsWith("Bearer "))
|
||||||
|
return authHeader.substring(7);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsernameFromToken(String token) {
|
||||||
|
String username;
|
||||||
|
try {
|
||||||
|
final Claims claims = getClaimsFromToken(token);
|
||||||
|
username = claims.getSubject();
|
||||||
|
} catch (Exception e) {
|
||||||
|
username = null;
|
||||||
|
}
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String refreshToken(String token) {
|
||||||
|
String refreshedToken;
|
||||||
|
try {
|
||||||
|
final Claims claims = getClaimsFromToken(token);
|
||||||
|
claims.setIssuedAt(generateCurrentDate());
|
||||||
|
refreshedToken = generateToken(claims);
|
||||||
|
} catch (Exception e) {
|
||||||
|
refreshedToken = null;
|
||||||
|
}
|
||||||
|
return refreshedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date generateCurrentDate() {
|
||||||
|
return new Date(getCurrentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date generateExpirationDate() {
|
||||||
|
|
||||||
|
return new Date(getCurrentTimeMillis() + EXPIRES_IN * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Claims getClaimsFromToken(String token) {
|
||||||
|
Claims claims;
|
||||||
|
try {
|
||||||
|
claims = Jwts.parser()
|
||||||
|
.setSigningKey(SECRET)
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
} catch (Exception e) {
|
||||||
|
claims = null;
|
||||||
|
}
|
||||||
|
return claims;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getCurrentTimeMillis() {
|
||||||
|
return DateTime.now().getMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
String generateToken(Map<String, Object> claims) {
|
||||||
|
return Jwts.builder()
|
||||||
|
.setClaims(claims)
|
||||||
|
.setExpiration(generateExpirationDate())
|
||||||
|
.signWith( SIGNATURE_ALGORITHM, SECRET )
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.auth;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
|
|
||||||
|
public class AnonAuthentication extends AbstractAuthenticationToken {
|
||||||
|
|
||||||
|
public AnonAuthentication() {
|
||||||
|
super( null );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals( Object obj ) {
|
||||||
|
if ( this == obj )
|
||||||
|
return true;
|
||||||
|
if ( obj == null )
|
||||||
|
return false;
|
||||||
|
if ( getClass() != obj.getClass() )
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int hash = 7;
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAuthenticated() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.auth;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
AuthenticationException exception) throws IOException, ServletException {
|
||||||
|
|
||||||
|
super.onAuthenticationFailure(request, response, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.auth;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
|
||||||
|
|
||||||
|
// @Value("${jwt.expires_in}")
|
||||||
|
// private int EXPIRES_IN;
|
||||||
|
//
|
||||||
|
// @Value("${jwt.cookie}")
|
||||||
|
// private String TOKEN_COOKIE;
|
||||||
|
//
|
||||||
|
// @Autowired
|
||||||
|
// TokenHelper tokenHelper;
|
||||||
|
//
|
||||||
|
@Autowired
|
||||||
|
ObjectMapper objectMapper;
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
// Authentication authentication ) throws IOException, ServletException {
|
||||||
|
// clearAuthenticationAttributes(request);
|
||||||
|
// User user = (User)authentication.getPrincipal();
|
||||||
|
//
|
||||||
|
// String jws = tokenHelper.generateToken( user.getUsername() );
|
||||||
|
//
|
||||||
|
// // Create token auth Cookie
|
||||||
|
// Cookie authCookie = new Cookie( TOKEN_COOKIE, jws );
|
||||||
|
//
|
||||||
|
// authCookie.setHttpOnly( true );
|
||||||
|
//
|
||||||
|
// authCookie.setMaxAge( EXPIRES_IN );
|
||||||
|
//
|
||||||
|
// authCookie.setPath( "/" );
|
||||||
|
// // Add cookie to response
|
||||||
|
// response.addCookie( authCookie );
|
||||||
|
//
|
||||||
|
// // JWT is also in the response
|
||||||
|
// UserTokenState userTokenState = new UserTokenState(jws, EXPIRES_IN);
|
||||||
|
// String jwtResponse = objectMapper.writeValueAsString( userTokenState );
|
||||||
|
// response.setContentType("application/json");
|
||||||
|
// response.getWriter().write( jwtResponse );
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Authentication authentication ) throws IOException, ServletException {
|
||||||
|
// clearAuthenticationAttributes(request);
|
||||||
|
response.setContentType("application/json");
|
||||||
|
response.getWriter().write( objectMapper.writeValueAsString("OK"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.auth;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class LogoutSuccess implements LogoutSuccessHandler {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
Map<String, String> result = new HashMap<>();
|
||||||
|
result.put( "result", "success" );
|
||||||
|
response.setContentType("application/json");
|
||||||
|
response.getWriter().write( objectMapper.writeValueAsString( result ) );
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.auth;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commence(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
AuthenticationException authException) throws IOException {
|
||||||
|
// This is invoked when user tries to access a secured REST resource without supplying any credentials
|
||||||
|
// We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
|
||||||
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.auth;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.security.TokenHelper;
|
||||||
|
|
||||||
|
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The below paths will get ignored by the filter
|
||||||
|
*/
|
||||||
|
public static final String ROOT_MATCHER = "/";
|
||||||
|
|
||||||
|
public static final String FAVICON_MATCHER = "/favicon.ico";
|
||||||
|
|
||||||
|
public static final String HTML_MATCHER = "/**/*.html";
|
||||||
|
|
||||||
|
public static final String CSS_MATCHER = "/**/*.css";
|
||||||
|
public static final String JS_MATCHER = "/**/*.js";
|
||||||
|
public static final String IMG_MATCHER = "/images/*";
|
||||||
|
public static final String LOGIN_MATCHER = "/auth/login";
|
||||||
|
public static final String LOGOUT_MATCHER = "/auth/logout";
|
||||||
|
private final Log logger = LogFactory.getLog(this.getClass());
|
||||||
|
@Autowired
|
||||||
|
TokenHelper tokenHelper;
|
||||||
|
@Autowired
|
||||||
|
UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
private List<String> pathsToSkip = Arrays.asList(
|
||||||
|
ROOT_MATCHER,
|
||||||
|
HTML_MATCHER,
|
||||||
|
FAVICON_MATCHER,
|
||||||
|
CSS_MATCHER,
|
||||||
|
JS_MATCHER,
|
||||||
|
IMG_MATCHER,
|
||||||
|
LOGIN_MATCHER,
|
||||||
|
LOGOUT_MATCHER
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
|
|
||||||
|
|
||||||
|
String authToken = tokenHelper.getToken(request);
|
||||||
|
if (authToken != null && !skipPathRequest(request, pathsToSkip))
|
||||||
|
// get username from token
|
||||||
|
try {
|
||||||
|
String username = tokenHelper.getUsernameFromToken(authToken);
|
||||||
|
// get user
|
||||||
|
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||||
|
// create authentication
|
||||||
|
TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
|
||||||
|
authentication.setToken(authToken);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
} catch (Exception e) {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new AnonAuthentication());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new AnonAuthentication());
|
||||||
|
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean skipPathRequest(HttpServletRequest request, List<String> pathsToSkip ) {
|
||||||
|
Assert.notNull(pathsToSkip, "path cannot be null.");
|
||||||
|
List<RequestMatcher> m = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList());
|
||||||
|
OrRequestMatcher matchers = new OrRequestMatcher(m);
|
||||||
|
return matchers.matches(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.auth;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
|
||||||
|
public class TokenBasedAuthentication extends AbstractAuthenticationToken {
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
private final UserDetails principle;
|
||||||
|
|
||||||
|
public TokenBasedAuthentication( UserDetails principle ) {
|
||||||
|
super( principle.getAuthorities() );
|
||||||
|
this.principle = principle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails getPrincipal() {
|
||||||
|
return principle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAuthenticated() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken( String token ) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.model;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary enabled only inMemoryAuthentication
|
||||||
|
*
|
||||||
|
* @author Fabio.Formosa
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name="Authority")
|
||||||
|
public class Authority implements GrantedAuthority {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name="id")
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@Column(name="name")
|
||||||
|
String name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthority() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.model;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.JoinTable;
|
||||||
|
import javax.persistence.ManyToMany;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary enabled only inMemoryAuthentication
|
||||||
|
*
|
||||||
|
* @author Fabio.Formosa
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "USER")
|
||||||
|
public class User implements UserDetails, Serializable {
|
||||||
|
@Id
|
||||||
|
@Column(name = "id")
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "username")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Column(name = "password")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Column(name = "firstname")
|
||||||
|
private String firstname;
|
||||||
|
|
||||||
|
@Column(name = "lastname")
|
||||||
|
private String lastname;
|
||||||
|
|
||||||
|
|
||||||
|
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
|
@JoinTable(name = "user_authority",
|
||||||
|
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
|
||||||
|
inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id"))
|
||||||
|
private List<Authority> authorities;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstname() {
|
||||||
|
return firstname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastname() {
|
||||||
|
return lastname;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can add the below fields in the users table.
|
||||||
|
// For now, they are hardcoded.
|
||||||
|
@JsonIgnore
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorities(List<Authority> authorities) {
|
||||||
|
this.authorities = authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstname(String firstname) {
|
||||||
|
this.firstname = firstname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastname(String lastname) {
|
||||||
|
|
||||||
|
this.lastname = lastname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.model;
|
||||||
|
|
||||||
|
|
||||||
|
public class UserRequest {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private String firstname;
|
||||||
|
|
||||||
|
private String lastname;
|
||||||
|
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstname() {
|
||||||
|
return firstname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstname(String firstname) {
|
||||||
|
this.firstname = firstname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastname() {
|
||||||
|
return lastname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastname(String lastname) {
|
||||||
|
this.lastname = lastname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.model;
|
||||||
|
|
||||||
|
public class UserTokenState {
|
||||||
|
private String access_token;
|
||||||
|
private Long expires_in;
|
||||||
|
|
||||||
|
public UserTokenState() {
|
||||||
|
this.access_token = null;
|
||||||
|
this.expires_in = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserTokenState(String access_token, long expires_in) {
|
||||||
|
this.access_token = access_token;
|
||||||
|
this.expires_in = expires_in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccess_token() {
|
||||||
|
return access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getExpires_in() {
|
||||||
|
return expires_in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccess_token(String access_token) {
|
||||||
|
this.access_token = access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpires_in(Long expires_in) {
|
||||||
|
this.expires_in = expires_in;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.security.model.Authority;
|
||||||
|
|
||||||
|
public interface AuthorityRepository extends JpaRepository<Authority, Long> {
|
||||||
|
Authority findByName(String name);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.security.model.User;
|
||||||
|
|
||||||
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
|
User findByUsername( String username );
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.security.model.Authority;
|
||||||
|
|
||||||
|
public interface AuthorityService {
|
||||||
|
List<Authority> findById(Long id);
|
||||||
|
|
||||||
|
List<Authority> findByname(String name);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.security.model.User;
|
||||||
|
import it.fabioformosa.quartzmanager.security.model.UserRequest;
|
||||||
|
|
||||||
|
public interface UserService {
|
||||||
|
List<User> findAll();
|
||||||
|
|
||||||
|
User findById(Long id);
|
||||||
|
|
||||||
|
User findByUsername(String username);
|
||||||
|
|
||||||
|
void resetCredentials();
|
||||||
|
|
||||||
|
User save(UserRequest user);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.service.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.security.model.Authority;
|
||||||
|
import it.fabioformosa.quartzmanager.security.repository.AuthorityRepository;
|
||||||
|
import it.fabioformosa.quartzmanager.security.service.AuthorityService;
|
||||||
|
|
||||||
|
//@Service
|
||||||
|
public class AuthorityServiceImpl implements AuthorityService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthorityRepository authorityRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Authority> findById(Long id) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
Authority auth = this.authorityRepository.findOne(id);
|
||||||
|
List<Authority> auths = new ArrayList<>();
|
||||||
|
auths.add(auth);
|
||||||
|
return auths;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Authority> findByname(String name) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
Authority auth = this.authorityRepository.findByName(name);
|
||||||
|
List<Authority> auths = new ArrayList<>();
|
||||||
|
auths.add(auth);
|
||||||
|
return auths;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.service.impl;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.security.model.User;
|
||||||
|
import it.fabioformosa.quartzmanager.security.repository.UserRepository;
|
||||||
|
|
||||||
|
|
||||||
|
//@Service
|
||||||
|
public class CustomUserDetailsService implements UserDetailsService {
|
||||||
|
|
||||||
|
protected final Log LOGGER = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthenticationManager authenticationManager;
|
||||||
|
|
||||||
|
public void changePassword(String oldPassword, String newPassword) {
|
||||||
|
|
||||||
|
Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
String username = currentUser.getName();
|
||||||
|
|
||||||
|
if (authenticationManager != null) {
|
||||||
|
LOGGER.debug("Re-authenticating user '"+ username + "' for password change request.");
|
||||||
|
|
||||||
|
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, oldPassword));
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("No authentication manager set. can't change Password!");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug("Changing password for user '"+ username + "'");
|
||||||
|
|
||||||
|
User user = (User) loadUserByUsername(username);
|
||||||
|
|
||||||
|
user.setPassword(passwordEncoder.encode(newPassword));
|
||||||
|
userRepository.save(user);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
User user = userRepository.findByUsername(username);
|
||||||
|
if (user == null)
|
||||||
|
throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
|
||||||
|
else
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.security.service.impl;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.security.model.Authority;
|
||||||
|
import it.fabioformosa.quartzmanager.security.model.User;
|
||||||
|
import it.fabioformosa.quartzmanager.security.model.UserRequest;
|
||||||
|
import it.fabioformosa.quartzmanager.security.repository.UserRepository;
|
||||||
|
import it.fabioformosa.quartzmanager.security.service.AuthorityService;
|
||||||
|
import it.fabioformosa.quartzmanager.security.service.UserService;
|
||||||
|
|
||||||
|
//@Service
|
||||||
|
public class UserServiceImpl implements UserService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthorityService authService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public List<User> findAll() throws AccessDeniedException {
|
||||||
|
List<User> result = userRepository.findAll();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public User findById(Long id) throws AccessDeniedException {
|
||||||
|
User u = userRepository.findOne(id);
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
// @PreAuthorize("hasRole('USER')")
|
||||||
|
public User findByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
User u = userRepository.findByUsername(username);
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetCredentials() {
|
||||||
|
List<User> users = userRepository.findAll();
|
||||||
|
for (User user : users) {
|
||||||
|
user.setPassword(passwordEncoder.encode("123"));
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User save(UserRequest userRequest) {
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(userRequest.getUsername());
|
||||||
|
user.setPassword(passwordEncoder.encode(userRequest.getPassword()));
|
||||||
|
user.setFirstname(userRequest.getFirstname());
|
||||||
|
user.setLastname(userRequest.getLastname());
|
||||||
|
List<Authority> auth = authService.findByname("ROLE_USER");
|
||||||
|
user.setAuthorities(auth);
|
||||||
|
this.userRepository.save(user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
30
quartz-manager-backend/src/main/resources/application.yml
Normal file
30
quartz-manager-backend/src/main/resources/application.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
server:
|
||||||
|
context-path: /quartz-manager
|
||||||
|
port: 8080
|
||||||
|
session.timeout : 28800
|
||||||
|
|
||||||
|
spring:
|
||||||
|
thymeleaf:
|
||||||
|
cache: false
|
||||||
|
mode: LEGACYHTML5
|
||||||
|
|
||||||
|
quartz:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
job:
|
||||||
|
frequency: 4000
|
||||||
|
repeatCount: 19
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.springframework.web: WARN
|
||||||
|
it.fabioformosa: INFO
|
||||||
|
|
||||||
|
app:
|
||||||
|
name: quartz-manager
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
header: Authorization
|
||||||
|
expires_in: 600 # 10 minutes
|
||||||
|
secret: queenvictoria
|
||||||
|
cookie: AUTH-TOKEN
|
||||||
8
quartz-manager-backend/src/main/resources/banner.txt
Normal file
8
quartz-manager-backend/src/main/resources/banner.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
____ _ __ __
|
||||||
|
/ __ \ | | | \/ |
|
||||||
|
| | | |_ _ __ _ _ __| |_ ____ | \ / | __ _ _ __ __ _ __ _ ___ _ __
|
||||||
|
| | | | | | |/ _` | '__| __|_ / | |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '__|
|
||||||
|
| |__| | |_| | (_| | | | |_ / / | | | | (_| | | | | (_| | (_| | __/ |
|
||||||
|
\___\_\\__,_|\__,_|_| \__/___| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_|
|
||||||
|
__/ |
|
||||||
|
|___/
|
||||||
11
quartz-manager-backend/src/main/resources/import.sql
Normal file
11
quartz-manager-backend/src/main/resources/import.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
-- the password hash is generated by BCrypt Calculator Generator(https://www.dailycred.com/article/bcrypt-calculator)
|
||||||
|
INSERT INTO user (id, username, password, firstname, lastname) VALUES (1, 'user', '$2a$04$Vbug2lwwJGrvUXTj6z7ff.97IzVBkrJ1XfApfGNl.Z695zqcnPYra', 'John', 'Doe');
|
||||||
|
INSERT INTO user (id, username, password, firstname, lastname) VALUES (2, 'admin', '$2a$04$Vbug2lwwJGrvUXTj6z7ff.97IzVBkrJ1XfApfGNl.Z695zqcnPYra', 'Admin', 'Admin');
|
||||||
|
|
||||||
|
INSERT INTO authority (id, name) VALUES (1, 'ROLE_USER');
|
||||||
|
INSERT INTO authority (id, name) VALUES (2, 'ROLE_ADMIN');
|
||||||
|
|
||||||
|
INSERT INTO user_authority (user_id, authority_id) VALUES (1, 1);
|
||||||
|
INSERT INTO user_authority (user_id, authority_id) VALUES (2, 1);
|
||||||
|
INSERT INTO user_authority (user_id, authority_id) VALUES (2, 2);
|
||||||
23
quartz-manager-backend/src/main/resources/logback.xml
Normal file
23
quartz-manager-backend/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||||
|
<Pattern>
|
||||||
|
%d{yyyy-MM-dd HH:mm:ss.SSS} [%.7thread] %-5level [%-40.40logger{49}:%-3L] --- %m%n
|
||||||
|
</Pattern>
|
||||||
|
</layout>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="it.fabioformosa" level="DEBUG" additivity="false">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
<logger name="org.springframework" level="WARN" additivity="false">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<root level="WARN">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
57
quartz-manager-frontend/.angular-cli.json
Normal file
57
quartz-manager-frontend/.angular-cli.json
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"project": {
|
||||||
|
"name": "angular-spring-starter"
|
||||||
|
},
|
||||||
|
"apps": [
|
||||||
|
{
|
||||||
|
"root": "src",
|
||||||
|
"outDir": "../server/src/main/resources/static",
|
||||||
|
"assets": [
|
||||||
|
"assets",
|
||||||
|
"favicon.ico"
|
||||||
|
],
|
||||||
|
"index": "index.html",
|
||||||
|
"main": "main.ts",
|
||||||
|
"polyfills": "polyfills.ts",
|
||||||
|
"test": "test.ts",
|
||||||
|
"tsconfig": "tsconfig.app.json",
|
||||||
|
"testTsconfig": "tsconfig.spec.json",
|
||||||
|
"prefix": "app",
|
||||||
|
"styles": [
|
||||||
|
"styles.css"
|
||||||
|
],
|
||||||
|
"scripts": [],
|
||||||
|
"environmentSource": "environments/environment.ts",
|
||||||
|
"environments": {
|
||||||
|
"dev": "environments/environment.ts",
|
||||||
|
"prod": "environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"e2e": {
|
||||||
|
"protractor": {
|
||||||
|
"config": "./protractor.conf.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": [
|
||||||
|
{
|
||||||
|
"project": "src/tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"project": "src/tsconfig.spec.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"project": "e2e/tsconfig.e2e.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"test": {
|
||||||
|
"karma": {
|
||||||
|
"config": "./karma.conf.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaults": {
|
||||||
|
"styleExt": "css",
|
||||||
|
"component": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
quartz-manager-frontend/.editorconfig
Normal file
13
quartz-manager-frontend/.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Editor configuration, see http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
45
quartz-manager-frontend/.gitignore
vendored
Normal file
45
quartz-manager-frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# misc
|
||||||
|
/.sass-cache
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
npm-debug.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# e2e
|
||||||
|
/e2e/*.js
|
||||||
|
/e2e/*.map
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
#package-lock.json
|
||||||
|
package-lock.json
|
||||||
14
quartz-manager-frontend/e2e/app.e2e-spec.ts
Normal file
14
quartz-manager-frontend/e2e/app.e2e-spec.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { WebUiPage } from './app.po';
|
||||||
|
|
||||||
|
describe('web-ui App', () => {
|
||||||
|
let page: WebUiPage;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
page = new WebUiPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display message saying app works', () => {
|
||||||
|
page.navigateTo();
|
||||||
|
expect(page.getParagraphText()).toContain('ANGULAR-SPRING-JWT-STARTER');
|
||||||
|
});
|
||||||
|
});
|
||||||
11
quartz-manager-frontend/e2e/app.po.ts
Normal file
11
quartz-manager-frontend/e2e/app.po.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { browser, element, by } from 'protractor';
|
||||||
|
|
||||||
|
export class WebUiPage {
|
||||||
|
navigateTo() {
|
||||||
|
return browser.get('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
getParagraphText() {
|
||||||
|
return element(by.css('app-root app-header span')).getText();
|
||||||
|
}
|
||||||
|
}
|
||||||
12
quartz-manager-frontend/e2e/tsconfig.e2e.json
Normal file
12
quartz-manager-frontend/e2e/tsconfig.e2e.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../out-tsc/e2e",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"types":[
|
||||||
|
"jasmine",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
44
quartz-manager-frontend/karma.conf.js
Normal file
44
quartz-manager-frontend/karma.conf.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Karma configuration file, see link for more information
|
||||||
|
// https://karma-runner.github.io/0.13/config/configuration-file.html
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
basePath: '',
|
||||||
|
frameworks: ['jasmine', '@angular/cli'],
|
||||||
|
plugins: [
|
||||||
|
require('karma-jasmine'),
|
||||||
|
require('karma-chrome-launcher'),
|
||||||
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-coverage-istanbul-reporter'),
|
||||||
|
require('@angular/cli/plugins/karma')
|
||||||
|
],
|
||||||
|
client:{
|
||||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
|
},
|
||||||
|
files: [
|
||||||
|
{ pattern: './src/test.ts', watched: false }
|
||||||
|
],
|
||||||
|
preprocessors: {
|
||||||
|
'./src/test.ts': ['@angular/cli']
|
||||||
|
},
|
||||||
|
mime: {
|
||||||
|
'text/x-typescript': ['ts','tsx']
|
||||||
|
},
|
||||||
|
coverageIstanbulReporter: {
|
||||||
|
reports: [ 'html', 'lcovonly' ],
|
||||||
|
fixWebpackSourcePaths: true
|
||||||
|
},
|
||||||
|
angularCli: {
|
||||||
|
environment: 'dev'
|
||||||
|
},
|
||||||
|
reporters: config.angularCli && config.angularCli.codeCoverage
|
||||||
|
? ['progress', 'coverage-istanbul']
|
||||||
|
: ['progress', 'kjhtml'],
|
||||||
|
port: 9876,
|
||||||
|
colors: true,
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
autoWatch: true,
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
singleRun: false
|
||||||
|
});
|
||||||
|
};
|
||||||
61
quartz-manager-frontend/package.json
Normal file
61
quartz-manager-frontend/package.json
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"name": "quartz-manager-ui",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve --proxy-config proxy.conf.json",
|
||||||
|
"build": "ng build",
|
||||||
|
"test": "ng test",
|
||||||
|
"lint": "ng lint",
|
||||||
|
"e2e": "ng e2e"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "5.2.8",
|
||||||
|
"@angular/cdk": "5.2.4",
|
||||||
|
"@angular/common": "5.2.8",
|
||||||
|
"@angular/compiler": "5.2.8",
|
||||||
|
"@angular/core": "5.2.8",
|
||||||
|
"@angular/flex-layout": "5.0.0-beta.13",
|
||||||
|
"@angular/forms": "5.2.8",
|
||||||
|
"@angular/http": "5.2.8",
|
||||||
|
"@angular/material": "5.2.4",
|
||||||
|
"@angular/platform-browser": "5.2.8",
|
||||||
|
"@angular/platform-browser-dynamic": "5.2.8",
|
||||||
|
"@angular/platform-server": "5.2.8",
|
||||||
|
"@angular/router": "5.2.8",
|
||||||
|
"@fortawesome/fontawesome": "^1.1.4",
|
||||||
|
"@fortawesome/fontawesome-free-regular": "^5.0.8",
|
||||||
|
"@fortawesome/fontawesome-free-solid": "^5.0.8",
|
||||||
|
"@stomp/ng2-stompjs": "^0.6.3",
|
||||||
|
"core-js": "2.5.1",
|
||||||
|
"hammerjs": "2.0.8",
|
||||||
|
"rxjs": "5.5.2",
|
||||||
|
"stompjs": "^2.3.3",
|
||||||
|
"zone.js": "0.8.18"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/core": "^0.2.0",
|
||||||
|
"@angular/cli": "1.5.3",
|
||||||
|
"@angular/compiler-cli": "5.2.8",
|
||||||
|
"@angular/language-service": "5.2.8",
|
||||||
|
"@types/hammerjs": "2.0.34",
|
||||||
|
"@types/jasmine": "2.5.54",
|
||||||
|
"@types/jasminewd2": "2.0.3",
|
||||||
|
"@types/node": "6.0.90",
|
||||||
|
"codelyzer": "4.2.1",
|
||||||
|
"jasmine-core": "2.6.4",
|
||||||
|
"jasmine-spec-reporter": "4.1.1",
|
||||||
|
"karma": "1.7.1",
|
||||||
|
"karma-chrome-launcher": "2.1.1",
|
||||||
|
"karma-cli": "1.0.1",
|
||||||
|
"karma-coverage-istanbul-reporter": "1.3.0",
|
||||||
|
"karma-jasmine": "1.1.0",
|
||||||
|
"karma-jasmine-html-reporter": "0.2.2",
|
||||||
|
"protractor": "5.1.2",
|
||||||
|
"ts-node": "3.0.6",
|
||||||
|
"tslint": "5.7.0",
|
||||||
|
"typescript": "2.4.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
30
quartz-manager-frontend/protractor.conf.js
Normal file
30
quartz-manager-frontend/protractor.conf.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Protractor configuration file, see link for more information
|
||||||
|
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||||
|
|
||||||
|
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||||
|
|
||||||
|
exports.config = {
|
||||||
|
allScriptsTimeout: 11000,
|
||||||
|
specs: [
|
||||||
|
'./e2e/**/*.e2e-spec.ts'
|
||||||
|
],
|
||||||
|
capabilities: {
|
||||||
|
'browserName': 'chrome'
|
||||||
|
},
|
||||||
|
directConnect: true,
|
||||||
|
baseUrl: 'http://localhost:4200/',
|
||||||
|
framework: 'jasmine',
|
||||||
|
jasmineNodeOpts: {
|
||||||
|
showColors: true,
|
||||||
|
defaultTimeoutInterval: 30000,
|
||||||
|
print: function() {}
|
||||||
|
},
|
||||||
|
beforeLaunch: function() {
|
||||||
|
require('ts-node').register({
|
||||||
|
project: 'e2e/tsconfig.e2e.json'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onPrepare() {
|
||||||
|
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||||
|
}
|
||||||
|
};
|
||||||
7
quartz-manager-frontend/proxy.conf.json
Normal file
7
quartz-manager-frontend/proxy.conf.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"/quartz-manager": {
|
||||||
|
"target": "http://localhost:8080",
|
||||||
|
"secure": false,
|
||||||
|
"ws":true
|
||||||
|
}
|
||||||
|
}
|
||||||
55
quartz-manager-frontend/src/app/app-routing.module.ts
Normal file
55
quartz-manager-frontend/src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { LoginComponent } from './login';
|
||||||
|
import { LoginGuard } from './guard';
|
||||||
|
import { GuestGuard, AdminGuard } from './guard';
|
||||||
|
import { NotFoundComponent } from './not-found';
|
||||||
|
import { ChangePasswordComponent } from './change-password';
|
||||||
|
import { ForbiddenComponent } from './forbidden';
|
||||||
|
|
||||||
|
import { ManagerComponent } from './manager';
|
||||||
|
|
||||||
|
export const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: ManagerComponent,
|
||||||
|
canActivate: [AdminGuard],
|
||||||
|
pathMatch: 'full'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'manager',
|
||||||
|
component: ManagerComponent,
|
||||||
|
canActivate: [AdminGuard],
|
||||||
|
pathMatch: 'full'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'login',
|
||||||
|
component: LoginComponent,
|
||||||
|
canActivate: [GuestGuard]
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// path: 'change-password',
|
||||||
|
// component: ChangePasswordComponent,
|
||||||
|
// canActivate: [LoginGuard]
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
path: '404',
|
||||||
|
component: NotFoundComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '403',
|
||||||
|
component: ForbiddenComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '**',
|
||||||
|
redirectTo: '/404'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forRoot(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
providers: []
|
||||||
|
})
|
||||||
|
export class AppRoutingModule { }
|
||||||
5
quartz-manager-frontend/src/app/app.component.html
Normal file
5
quartz-manager-frontend/src/app/app.component.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<app-header></app-header>
|
||||||
|
<div class="content">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
<app-footer></app-footer>
|
||||||
21
quartz-manager-frontend/src/app/app.component.scss
Normal file
21
quartz-manager-frontend/src/app/app.component.scss
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
color: rgba(0,0,0,.54);
|
||||||
|
font-family: Roboto,"Helvetica Neue";
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin: 50px 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 600px) and (max-width: 1279px) {
|
||||||
|
.content {
|
||||||
|
margin: 20px 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 599px) {
|
||||||
|
.content {
|
||||||
|
margin: 8px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
quartz-manager-frontend/src/app/app.component.spec.ts
Normal file
66
quartz-manager-frontend/src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { HomeComponent } from './home';
|
||||||
|
import { ManagerComponent } from './manager';
|
||||||
|
import { LoginComponent } from './login';
|
||||||
|
import { MockApiService } from './service/mocks/api.service.mock';
|
||||||
|
|
||||||
|
import { LoginGuard } from './guard';
|
||||||
|
import { NotFoundComponent } from './not-found';
|
||||||
|
import {
|
||||||
|
ApiCardComponent,
|
||||||
|
FooterComponent,
|
||||||
|
GithubComponent,
|
||||||
|
} from './component';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MatToolbarModule,
|
||||||
|
MatIconRegistry
|
||||||
|
} from '@angular/material';
|
||||||
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
ApiService,
|
||||||
|
AuthService,
|
||||||
|
UserService,
|
||||||
|
FooService,
|
||||||
|
ConfigService
|
||||||
|
} from './service';
|
||||||
|
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
describe('AppComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
FooterComponent,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterTestingModule,
|
||||||
|
MatToolbarModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
MatIconRegistry,
|
||||||
|
{
|
||||||
|
provide: ApiService,
|
||||||
|
useClass: MockApiService
|
||||||
|
},
|
||||||
|
AuthService,
|
||||||
|
UserService,
|
||||||
|
FooService,
|
||||||
|
ConfigService
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create the app', async(() => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
const app = fixture.debugElement.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
15
quartz-manager-frontend/src/app/app.component.ts
Normal file
15
quartz-manager-frontend/src/app/app.component.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
|
// I remove temporary fontawesome5 and downgrade to fontawesome4
|
||||||
|
import fontawesome from '@fortawesome/fontawesome';
|
||||||
|
import solid from '@fortawesome/fontawesome-free-solid/';
|
||||||
|
fontawesome.library.add(solid);
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss']
|
||||||
|
})
|
||||||
|
|
||||||
|
export class AppComponent {
|
||||||
|
}
|
||||||
146
quartz-manager-frontend/src/app/app.module.ts
Normal file
146
quartz-manager-frontend/src/app/app.module.ts
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { NgModule, APP_INITIALIZER} from '@angular/core';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { HttpModule } from '@angular/http';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
// material
|
||||||
|
import {
|
||||||
|
MatButtonModule,
|
||||||
|
MatMenuModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatToolbarModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
MatCardModule,
|
||||||
|
MatChipsModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatIconRegistry,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
MatProgressBarModule,
|
||||||
|
} from '@angular/material';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
import { ManagerComponent } from './manager';
|
||||||
|
import { LoginComponent } from './login';
|
||||||
|
import { LoginGuard, GuestGuard, AdminGuard } from './guard';
|
||||||
|
import { NotFoundComponent } from './not-found';
|
||||||
|
import { AccountMenuComponent } from './component/header/account-menu/account-menu.component';
|
||||||
|
|
||||||
|
import {
|
||||||
|
HeaderComponent,
|
||||||
|
FooterComponent,
|
||||||
|
GithubComponent,
|
||||||
|
SchedulerConfigComponent,
|
||||||
|
SchedulerControlComponent,
|
||||||
|
LogsPanelComponent,
|
||||||
|
ProgressPanelComponent
|
||||||
|
} from './component';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ApiService,
|
||||||
|
AuthService,
|
||||||
|
UserService,
|
||||||
|
FooService,
|
||||||
|
SchedulerService,
|
||||||
|
ConfigService,
|
||||||
|
ProgressWebsocketService,
|
||||||
|
LogsWebsocketService
|
||||||
|
} from './service';
|
||||||
|
import { ChangePasswordComponent } from './change-password/change-password.component';
|
||||||
|
import { ForbiddenComponent } from './forbidden/forbidden.component';
|
||||||
|
|
||||||
|
export function initUserFactory(userService: UserService) {
|
||||||
|
return () => userService.jsessionInitUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
// const stompConfig: StompConfig = {
|
||||||
|
// // Which server?
|
||||||
|
// url: 'ws://localhost:8080/quartz-manager/progress',
|
||||||
|
|
||||||
|
// // Headers
|
||||||
|
// // Typical keys: login, passcode, host
|
||||||
|
// headers: {
|
||||||
|
// login: 'admin',
|
||||||
|
// passcode: 'admin'
|
||||||
|
// },
|
||||||
|
|
||||||
|
// // How often to heartbeat?
|
||||||
|
// // Interval in milliseconds, set to 0 to disable
|
||||||
|
// heartbeat_in: 0, // Typical value 0 - disabled
|
||||||
|
// heartbeat_out: 20000, // Typical value 20000 - every 20 seconds
|
||||||
|
// // Wait in milliseconds before attempting auto reconnect
|
||||||
|
// // Set to 0 to disable
|
||||||
|
// // Typical value 5000 (5 seconds)
|
||||||
|
// reconnect_delay: 5000,
|
||||||
|
|
||||||
|
// // Will log diagnostics on console
|
||||||
|
// debug: true
|
||||||
|
// };
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
HeaderComponent,
|
||||||
|
FooterComponent,
|
||||||
|
ManagerComponent,
|
||||||
|
GithubComponent,
|
||||||
|
LoginComponent,
|
||||||
|
NotFoundComponent,
|
||||||
|
AccountMenuComponent,
|
||||||
|
SchedulerConfigComponent,
|
||||||
|
SchedulerControlComponent,
|
||||||
|
LogsPanelComponent,
|
||||||
|
ProgressPanelComponent,
|
||||||
|
ChangePasswordComponent,
|
||||||
|
ForbiddenComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
BrowserModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
HttpModule,
|
||||||
|
HttpClientModule,
|
||||||
|
AppRoutingModule,
|
||||||
|
MatMenuModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatChipsModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatToolbarModule,
|
||||||
|
MatCardModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
MatProgressBarModule,
|
||||||
|
FlexLayoutModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
LoginGuard,
|
||||||
|
GuestGuard,
|
||||||
|
AdminGuard,
|
||||||
|
FooService,
|
||||||
|
SchedulerService,
|
||||||
|
ProgressWebsocketService,
|
||||||
|
LogsWebsocketService,
|
||||||
|
AuthService,
|
||||||
|
ApiService,
|
||||||
|
UserService,
|
||||||
|
ConfigService,
|
||||||
|
MatIconRegistry,
|
||||||
|
{
|
||||||
|
'provide': APP_INITIALIZER,
|
||||||
|
'useFactory': initUserFactory,
|
||||||
|
'deps': [UserService],
|
||||||
|
'multi': true
|
||||||
|
}
|
||||||
|
// StompService,
|
||||||
|
// ServerSocket
|
||||||
|
// {
|
||||||
|
// provide: StompConfig,
|
||||||
|
// useValue: stompConfig
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<div class="content" fxLayout="row" fxLayoutAlign="center">
|
||||||
|
<mat-card elevation="5" fxFlex>
|
||||||
|
<mat-card-subtitle>Change Your Password</mat-card-subtitle>
|
||||||
|
<p [class]="notification.msgType" *ngIf="notification">{{notification.msgBody}}</p>
|
||||||
|
<mat-card-content>
|
||||||
|
<form *ngIf="!submitted" [formGroup]="form" (ngSubmit)="onSubmit()" #changePasswordForm="ngForm">
|
||||||
|
<mat-input-container>
|
||||||
|
<input matInput formControlName="oldPassword" required type="password" placeholder="old password">
|
||||||
|
</mat-input-container>
|
||||||
|
<mat-input-container>
|
||||||
|
<input matInput formControlName="newPassword" required type="password" placeholder="new password">
|
||||||
|
</mat-input-container>
|
||||||
|
<button type="submit" [disabled]="!changePasswordForm.form.valid" mat-raised-button color="primary">Change Password</button>
|
||||||
|
</form>
|
||||||
|
<mat-spinner *ngIf="submitted" mode="indeterminate"></mat-spinner>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card {
|
||||||
|
max-width: 350px;
|
||||||
|
text-align: center;
|
||||||
|
animation: fadein 1s;
|
||||||
|
-o-animation: fadein 1s; /* Opera */
|
||||||
|
-moz-animation: fadein 1s; /* Firefox */
|
||||||
|
-webkit-animation: fadein 1s; /* Safari and Chrome */
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-input-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-spinner {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
margin: 20px auto 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #D50000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: #8BC34A;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 599px) {
|
||||||
|
|
||||||
|
.content {
|
||||||
|
/* https://github.com/angular/flex-layout/issues/295 */
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card {
|
||||||
|
/* https://github.com/angular/flex-layout/issues/295 */
|
||||||
|
display: block !important;
|
||||||
|
max-width: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import {
|
||||||
|
ApiService,
|
||||||
|
AuthService,
|
||||||
|
UserService,
|
||||||
|
ConfigService
|
||||||
|
} from '../service';
|
||||||
|
import { MockApiService } from '../service/mocks';
|
||||||
|
|
||||||
|
import { ChangePasswordComponent } from './change-password.component';
|
||||||
|
|
||||||
|
describe('ChangePasswordComponent', () => {
|
||||||
|
let component: ChangePasswordComponent;
|
||||||
|
let fixture: ComponentFixture<ChangePasswordComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
RouterTestingModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ChangePasswordComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: ApiService,
|
||||||
|
useClass: MockApiService
|
||||||
|
},
|
||||||
|
AuthService,
|
||||||
|
UserService,
|
||||||
|
ConfigService
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ChangePasswordComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { AuthService } from 'app/service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { DisplayMessage } from '../shared/models/display-message';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-change-password',
|
||||||
|
templateUrl: './change-password.component.html',
|
||||||
|
styleUrls: ['./change-password.component.scss']
|
||||||
|
})
|
||||||
|
export class ChangePasswordComponent implements OnInit {
|
||||||
|
|
||||||
|
form: FormGroup;
|
||||||
|
/**
|
||||||
|
* Boolean used in telling the UI
|
||||||
|
* that the form has been submitted
|
||||||
|
* and is awaiting a response
|
||||||
|
*/
|
||||||
|
submitted = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diagnostic message from received
|
||||||
|
* form request error
|
||||||
|
*/
|
||||||
|
notification: DisplayMessage;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
|
private formBuilder: FormBuilder
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
|
||||||
|
this.form = this.formBuilder.group({
|
||||||
|
oldPassword: ['', Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(64)])],
|
||||||
|
newPassword: ['', Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(32)])]
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
/**
|
||||||
|
* Innocent until proven guilty
|
||||||
|
*/
|
||||||
|
this.notification = undefined;
|
||||||
|
this.submitted = true;
|
||||||
|
|
||||||
|
this.authService.changePassowrd(this.form.value)
|
||||||
|
// show me the animation
|
||||||
|
.delay(1000)
|
||||||
|
.mergeMap(() => this.authService.logout())
|
||||||
|
.subscribe(() => {
|
||||||
|
this.router.navigate(['/login', { msgType: 'success', msgBody: 'Success! Please sign in with your new password.'}]);
|
||||||
|
}, error => {
|
||||||
|
this.submitted = false;
|
||||||
|
this.notification = { msgType: 'error', msgBody: 'Invalid old password.'};
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1
quartz-manager-frontend/src/app/change-password/index.ts
Normal file
1
quartz-manager-frontend/src/app/change-password/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './change-password.component';
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<p style="margin: 0px auto; padding: 0px; color: rgba(255, 255, 255, 0.541176); max-width: 356px;">
|
||||||
|
Hand crafted with love by
|
||||||
|
<a href="https://github.com/fabioformosa" style="color: rgba(255, 255, 255, 0.870588);">Fabio Formosa</a>
|
||||||
|
</p>
|
||||||
|
<a style="margin-top: 22px;" mat-icon-button href="https://github.com/fabioformosa/quartz-manager">
|
||||||
|
<img src="assets/image/github.png"/>
|
||||||
|
</a>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 15px;
|
||||||
|
display: block;
|
||||||
|
background-color: rgb(33, 33, 33);
|
||||||
|
height: 236px;
|
||||||
|
padding: 72px 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: auto;
|
||||||
|
color: #FFFFFF;
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-footer',
|
||||||
|
templateUrl: './footer.component.html',
|
||||||
|
styleUrls: ['./footer.component.scss']
|
||||||
|
})
|
||||||
|
export class FooterComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './footer.component';
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<h3>Want to help make this project? Check out repo.</h3>
|
||||||
|
<a href="https://github.com/fabioformosa/quartz-manager" color="accent" mat-raised-button mat-ripple>
|
||||||
|
<span>GITHUB</span>
|
||||||
|
</a>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
height: 236px;
|
||||||
|
padding: 72px 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: rgb(238, 238, 238);
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
:host h3 {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host a {
|
||||||
|
color: #000;
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-github',
|
||||||
|
templateUrl: './github.component.html',
|
||||||
|
styleUrls: ['./github.component.scss']
|
||||||
|
})
|
||||||
|
export class GithubComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './github.component';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<button mat-menu-item [routerLink]="['/change-password']">CHANGE PASSWORD</button>
|
||||||
|
<button mat-menu-item (click)="logout()">SIGN OUT</button>
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AuthService,
|
||||||
|
ConfigService,
|
||||||
|
ApiService,
|
||||||
|
UserService
|
||||||
|
} from '../../../service';
|
||||||
|
import {
|
||||||
|
MockUserService,
|
||||||
|
MockApiService
|
||||||
|
} from '../../../service/mocks';
|
||||||
|
import { AccountMenuComponent } from './account-menu.component';
|
||||||
|
|
||||||
|
describe('AccountMenuComponent', () => {
|
||||||
|
let component: AccountMenuComponent;
|
||||||
|
let fixture: ComponentFixture<AccountMenuComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
RouterTestingModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: UserService,
|
||||||
|
useClass: MockUserService
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ApiService,
|
||||||
|
useClass: MockApiService
|
||||||
|
},
|
||||||
|
AuthService,
|
||||||
|
ConfigService
|
||||||
|
],
|
||||||
|
declarations: [AccountMenuComponent],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AccountMenuComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import {
|
||||||
|
ConfigService,
|
||||||
|
AuthService,
|
||||||
|
UserService
|
||||||
|
} from '../../../service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-account-menu',
|
||||||
|
templateUrl: './account-menu.component.html',
|
||||||
|
styleUrls: ['./account-menu.component.scss']
|
||||||
|
})
|
||||||
|
export class AccountMenuComponent implements OnInit {
|
||||||
|
|
||||||
|
// TODO define user interface
|
||||||
|
user: any;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private config: ConfigService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
|
private userService: UserService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.user = this.userService.currentUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.authService.logout().subscribe(res => {
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<mat-toolbar color="primary" class="app-navbar">
|
||||||
|
<button mat-button mat-ripple routerLink="/">
|
||||||
|
<img alt="Quartz Manager" class="app-angular-logo" src="assets/image/angular-white-transparent.svg">
|
||||||
|
<span>Quartz Manager</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="right">
|
||||||
|
<div fxFlex="1 1 auto" fxLayout="row" fxLayoutAlign="flex-end center">
|
||||||
|
<button *ngIf="!hasSignedIn()" routerLink="/signup" mat-button mat-ripple>
|
||||||
|
<span>Sign up</span>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="!hasSignedIn()" routerLink="/login" mat-button mat-ripple>
|
||||||
|
<span>Login</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="greeting-button"
|
||||||
|
*ngIf="hasSignedIn()"
|
||||||
|
mat-button mat-ripple
|
||||||
|
[matMenuTriggerFor]="accountMenu">
|
||||||
|
<span>Hi, {{userName()}}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="greeting-hamburger"
|
||||||
|
*ngIf="hasSignedIn()"
|
||||||
|
mat-icon-button mat-ripple
|
||||||
|
[matMenuTriggerFor]="accountMenu">
|
||||||
|
<mat-icon>menu</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #accountMenu
|
||||||
|
class="app-header-accountMenu"
|
||||||
|
yposition="below"
|
||||||
|
[overlapTrigger]="false">
|
||||||
|
<app-account-menu ></app-account-menu>
|
||||||
|
</mat-menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-toolbar>
|
||||||
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
:host {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The menu popup is rendered outside the header component
|
||||||
|
// so we will restyle a couple things inside a global /deep/ selector
|
||||||
|
|
||||||
|
.app-navbar {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.right {
|
||||||
|
margin-left: auto;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-navbar span {
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-angular-logo {
|
||||||
|
margin: 0 4px 3px 0;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-hamburger {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/deep/ {
|
||||||
|
.app-header-accountMenu.mat-menu-panel {
|
||||||
|
border-radius: 3px;
|
||||||
|
max-width: initial;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
.mat-menu-content {
|
||||||
|
max-width: initial;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: -6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.greeting-hamburger {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.greeting-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import {
|
||||||
|
UserService,
|
||||||
|
AuthService
|
||||||
|
} from '../../service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-header',
|
||||||
|
templateUrl: './header.component.html',
|
||||||
|
styleUrls: ['./header.component.scss']
|
||||||
|
})
|
||||||
|
export class HeaderComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private userService: UserService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.authService.logout().subscribe(res => {
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSignedIn() {
|
||||||
|
return !!this.userService.currentUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
userName() {
|
||||||
|
const user = this.userService.currentUser;
|
||||||
|
return user.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './header.component';
|
||||||
7
quartz-manager-frontend/src/app/component/index.ts
Normal file
7
quartz-manager-frontend/src/app/component/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export * from './header';
|
||||||
|
export * from './github';
|
||||||
|
export * from './footer';
|
||||||
|
export * from './logs-panel';
|
||||||
|
export * from './scheduler-config';
|
||||||
|
export * from './scheduler-control';
|
||||||
|
export * from './progress-panel';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './logs-panel.component';
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title><b>JOB LOGS</b></mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div id="logs">
|
||||||
|
<div *ngFor = "let log of logs" fxLayout="row" fxLayout.xs="column">
|
||||||
|
<div fxFlex="1 1 20%">
|
||||||
|
<span [ngClass]="{'animated zoomIn firstLog': $first}"> [{{log.time|date:'medium'}}]</span>
|
||||||
|
</div>
|
||||||
|
<div fxFlex="1 1 10%">
|
||||||
|
<span [ngClass]="{'animated zoomIn firstLog': $first}">
|
||||||
|
<i class = "fas" [ngClass]="{'fa-check-circle green': log.type == 'INFO',
|
||||||
|
'fa-exclamation-triangle yellow': log.type == 'WARN',
|
||||||
|
'fa-times-circle red': log.type == 'ERROR'}"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div fxFlex="1 1 20%">
|
||||||
|
<span [ngClass]="{'animated zoomIn firstLog': $first}">
|
||||||
|
{{log.threadName}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div fxFlex="1 1 50%">
|
||||||
|
<span [ngClass]="{'animated zoomIn firstLog': $first}"> {{log.msg}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
.red{
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.green{
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow{
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user