diff --git a/quartz-manager/.gitignore b/quartz-manager-backend/.gitignore similarity index 100% rename from quartz-manager/.gitignore rename to quartz-manager-backend/.gitignore diff --git a/quartz-manager/pom.xml b/quartz-manager-backend/pom.xml similarity index 83% rename from quartz-manager/pom.xml rename to quartz-manager-backend/pom.xml index 12e3f20..e3dea41 100644 --- a/quartz-manager/pom.xml +++ b/quartz-manager-backend/pom.xml @@ -5,7 +5,7 @@ it.fabioformosa quartz-manager - 0.0.1-SNAPSHOT + 2.0.1-SNAPSHOT war quartz-manager @@ -20,13 +20,14 @@ UTF-8 + UTF-8 1.8 org.springframework.boot - spring-boot-devtools + spring-boot-starter-web org.springframework.boot @@ -38,32 +39,25 @@ org.springframework.boot - spring-boot-starter-thymeleaf - - - org.thymeleaf.extras - thymeleaf-extras-springsecurity4 + spring-boot-starter-data-jpa org.springframework.boot - spring-boot-starter-velocity - 1.4.7.RELEASE + spring-boot-devtools - org.springframework.boot - spring-boot-starter-web - - - org.codehaus.groovy - groovy - - - - net.sourceforge.nekohtml - nekohtml - - - + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework + spring-messaging + + + org.springframework + spring-tx + + org.springframework.boot spring-boot-starter-tomcat provided @@ -75,23 +69,45 @@ - org.springframework.boot - spring-boot-starter-websocket - - - org.springframework - spring-messaging - - + io.jsonwebtoken + jjwt + 0.9.0 + - org.webjars - bootstrap - 3.3.6 + joda-time + joda-time - nz.net.ultraq.thymeleaf - thymeleaf-layout-dialect - + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-annotations + + + com.h2database + h2 + runtime + + + + org.codehaus.groovy + groovy + + + + net.sourceforge.nekohtml + nekohtml + + + + io.rest-assured + spring-mock-mvc + 3.0.5 + test + + org.quartz-scheduler quartz @@ -102,11 +118,7 @@ commons-io 1.3.2 - - org.springframework - spring-tx - - + io.projectreactor diff --git a/quartz-manager/src/main/java/it/fabioformosa/QuartManagerApplication.java b/quartz-manager-backend/src/main/java/it/fabioformosa/QuartManagerApplication.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/QuartManagerApplication.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/QuartManagerApplication.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/ServletInitializer.java b/quartz-manager-backend/src/main/java/it/fabioformosa/ServletInitializer.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/ServletInitializer.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/ServletInitializer.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/aspects/ProgressUpdater.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/aspects/ProgressUpdater.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/aspects/ProgressUpdater.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/aspects/ProgressUpdater.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/aspects/ProgressUpdaterImpl.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/aspects/ProgressUpdaterImpl.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/aspects/ProgressUpdaterImpl.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/aspects/ProgressUpdaterImpl.java diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/MVCConfig.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/MVCConfig.java new file mode 100644 index 0000000..8d5c43d --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/MVCConfig.java @@ -0,0 +1,22 @@ +package it.fabioformosa.quartzmanager.configuration; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +public class MVCConfig extends WebMvcConfigurerAdapter { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/login").setViewName("login"); + registry.addViewController("/").setViewName("redirect:/manager"); + + registry.addViewController("/templates/manager/config-form.html").setViewName("manager/config-form"); + registry.addViewController("/templates/manager/progress-panel.html") + .setViewName("manager/progress-panel"); + registry.addViewController("/templates/manager/logs-panel.html").setViewName("manager/logs-panel"); + + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/SchedulerConfig.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/SchedulerConfig.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/SchedulerConfig.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/SchedulerConfig.java diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfig.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfig.java new file mode 100644 index 0000000..853af29 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfig.java @@ -0,0 +1,90 @@ +package it.fabioformosa.quartzmanager.configuration; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +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.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +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.RestAuthenticationEntryPoint; +import it.fabioformosa.quartzmanager.security.auth.TokenAuthenticationFilter; +import it.fabioformosa.quartzmanager.security.service.impl.CustomUserDetailsService; + +/** + * Created by fan.jin on 2016-10-19. + */ + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Value("${jwt.cookie}") + private String TOKEN_COOKIE; + + @Autowired + private CustomUserDetailsService jwtUserDetailsService; + + @Autowired + private RestAuthenticationEntryPoint restAuthenticationEntryPoint; + + @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() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint).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); + + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfig.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfigOld.java similarity index 61% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfig.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfigOld.java index bbf562f..f3e0dff 100644 --- a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfig.java +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/WebSecurityConfigOld.java @@ -2,13 +2,9 @@ package it.fabioformosa.quartzmanager.configuration; import javax.annotation.Resource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -16,48 +12,50 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import it.fabioformosa.quartzmanager.security.AjaxAuthenticationFilter; import it.fabioformosa.quartzmanager.security.ComboEntryPoint; -@Configuration -@EnableWebSecurity -public class WebSecurityConfig extends WebSecurityConfigurerAdapter { +//@Configuration +//@EnableWebSecurity +public class WebSecurityConfigOld extends WebSecurityConfigurerAdapter { - @Configuration - @Order(1) + // @Configuration + // @Order(1) public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() // - .antMatcher("/notifications").authorizeRequests().anyRequest().hasAnyRole("ADMIN").and() - .httpBasic(); + .antMatcher("/notifications").authorizeRequests().anyRequest().hasAnyRole("ADMIN").and() + .httpBasic(); // http.antMatcher("/logs/**").authorizeRequests().anyRequest() // .permitAll(); } } - @Configuration - @Order(2) + // @Configuration + // @Order(2) public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter { @Resource private ComboEntryPoint comboEntryPoint; - @Override - protected void configure(HttpSecurity http) throws Exception { - http.exceptionHandling().authenticationEntryPoint(comboEntryPoint).and().csrf().disable()// - .authorizeRequests().anyRequest().authenticated().and()// - .addFilterBefore(new AjaxAuthenticationFilter(authenticationManager()), - UsernamePasswordAuthenticationFilter.class)// - .formLogin().loginPage("/login").permitAll().and().logout() - .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/manager"); - } - @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**"); } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().ignoringAntMatchers("/api/login", "/api/signup").and() // + .exceptionHandling().authenticationEntryPoint(comboEntryPoint).and().csrf().disable()// + .authorizeRequests().anyRequest().authenticated().and()// + .addFilterBefore(new AjaxAuthenticationFilter(authenticationManager()), + UsernamePasswordAuthenticationFilter.class)// + .formLogin().loginPage("/api/login").permitAll().and().logout() + .logoutRequestMatcher(new AntPathRequestMatcher("/api/logout")) + .logoutSuccessUrl("/manager"); + } } - @Autowired + // @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN"); } diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/WebsocketConfig.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/WebsocketConfig.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/WebsocketConfig.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/configuration/WebsocketConfig.java diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/AuthenticationController.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/AuthenticationController.java new file mode 100644 index 0000000..9f8bdc5 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/AuthenticationController.java @@ -0,0 +1,81 @@ +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.MediaType; +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 org.springframework.web.bind.annotation.RestController; + +import it.fabioformosa.quartzmanager.security.TokenHelper; +import it.fabioformosa.quartzmanager.security.model.UserTokenState; +import it.fabioformosa.quartzmanager.security.service.impl.CustomUserDetailsService; + +/** + * Created by fan.jin on 2017-05-10. + */ + +@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 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); + } + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/ManagerController.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/ManagerController.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/ManagerController.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/ManagerController.java diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/PublicController.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/PublicController.java new file mode 100644 index 0000000..57cf88d --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/PublicController.java @@ -0,0 +1,27 @@ +package it.fabioformosa.quartzmanager.controllers; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +/** + * Created by fan.jin on 2017-05-08. + */ + +@RestController +@RequestMapping( value = "/api", produces = MediaType.APPLICATION_JSON_VALUE ) +public class PublicController { + + @RequestMapping( method = GET, value= "/foo") + public Map getFoo() { + Map fooObj = new HashMap<>(); + fooObj.put("foo", "bar"); + return fooObj; + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/SchedulerController.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/SchedulerController.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/SchedulerController.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/SchedulerController.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/SessionController.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/SessionController.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/SessionController.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/SessionController.java diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/UserController.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/UserController.java new file mode 100644 index 0000000..79754ad --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/UserController.java @@ -0,0 +1,82 @@ +package it.fabioformosa.quartzmanager.controllers; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; + +import it.fabioformosa.quartzmanager.exceptions.ResourceConflictException; +import it.fabioformosa.quartzmanager.security.model.User; +import it.fabioformosa.quartzmanager.security.model.UserRequest; +import it.fabioformosa.quartzmanager.security.service.UserService; + +/** + * Created by fan.jin on 2016-10-15. + */ + +@RestController +@RequestMapping(value = "/api", produces = MediaType.APPLICATION_JSON_VALUE) +public class UserController { + + @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 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 resetCredentials() { + this.userService.resetCredentials(); + Map 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(); + } + +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/WebsocketController.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/WebsocketController.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/controllers/WebsocketController.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/controllers/WebsocketController.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerConfigParam.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerConfigParam.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerConfigParam.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerConfigParam.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerProgress.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerProgress.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerProgress.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerProgress.java diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/exceptions/ExceptionHandlingController.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/exceptions/ExceptionHandlingController.java new file mode 100644 index 0000000..3e4e308 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/exceptions/ExceptionHandlingController.java @@ -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 resourceConflict(ResourceConflictException ex) { + ExceptionResponse response = new ExceptionResponse(); + response.setErrorCode("Conflict"); + response.setErrorMessage(ex.getMessage()); + return new ResponseEntity(response, HttpStatus.CONFLICT); + } +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/exceptions/ExceptionResponse.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/exceptions/ExceptionResponse.java new file mode 100644 index 0000000..e642282 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/exceptions/ExceptionResponse.java @@ -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; + } +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/exceptions/ResourceConflictException.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/exceptions/ResourceConflictException.java new file mode 100644 index 0000000..1fd5470 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/exceptions/ResourceConflictException.java @@ -0,0 +1,22 @@ +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); + this.setResourceId(resourceId); + } + + public Long getResourceId() { + return resourceId; + } + + public void setResourceId(Long resourceId) { + this.resourceId = resourceId; + } +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/jobs/AbstractLoggingJob.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/jobs/AbstractLoggingJob.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/jobs/AbstractLoggingJob.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/jobs/AbstractLoggingJob.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/jobs/MisfireTestJob.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/jobs/MisfireTestJob.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/jobs/MisfireTestJob.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/jobs/MisfireTestJob.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/jobs/SampleJob.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/jobs/SampleJob.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/jobs/SampleJob.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/jobs/SampleJob.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/jobs/entities/LogRecord.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/jobs/entities/LogRecord.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/jobs/entities/LogRecord.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/jobs/entities/LogRecord.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/scheduler/AutowiringSpringBeanJobFactory.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/scheduler/AutowiringSpringBeanJobFactory.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/scheduler/AutowiringSpringBeanJobFactory.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/scheduler/AutowiringSpringBeanJobFactory.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/scheduler/TriggerMonitor.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/scheduler/TriggerMonitor.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/scheduler/TriggerMonitor.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/scheduler/TriggerMonitor.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/scheduler/TriggerMonitorImpl.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/scheduler/TriggerMonitorImpl.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/scheduler/TriggerMonitorImpl.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/scheduler/TriggerMonitorImpl.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/security/AjaxAuthenticationFilter.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/AjaxAuthenticationFilter.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/security/AjaxAuthenticationFilter.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/AjaxAuthenticationFilter.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/security/ComboEntryPoint.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/ComboEntryPoint.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/security/ComboEntryPoint.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/ComboEntryPoint.java diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/security/RESTRequestMatcher.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/RESTRequestMatcher.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/security/RESTRequestMatcher.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/RESTRequestMatcher.java diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/TokenHelper.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/TokenHelper.java new file mode 100644 index 0000000..ab3a02b --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/TokenHelper.java @@ -0,0 +1,164 @@ +package it.fabioformosa.quartzmanager.security; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +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.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import java.util.Date; +import java.util.Map; + + +/** + * Created by fan.jin on 2016-10-19. + */ + +@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 String getUsernameFromToken(String token) { + String username; + try { + final Claims claims = this.getClaimsFromToken(token); + username = claims.getSubject(); + } catch (Exception e) { + username = null; + } + return username; + } + + public String generateToken(String username) { + return Jwts.builder() + .setIssuer( APP_NAME ) + .setSubject(username) + .setIssuedAt(generateCurrentDate()) + .setExpiration(generateExpirationDate()) + .signWith( SIGNATURE_ALGORITHM, SECRET ) + .compact(); + } + + private Claims getClaimsFromToken(String token) { + Claims claims; + try { + claims = Jwts.parser() + .setSigningKey(this.SECRET) + .parseClaimsJws(token) + .getBody(); + } catch (Exception e) { + claims = null; + } + return claims; + } + + String generateToken(Map claims) { + return Jwts.builder() + .setClaims(claims) + .setExpiration(generateExpirationDate()) + .signWith( SIGNATURE_ALGORITHM, SECRET ) + .compact(); + } + + 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 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 long getCurrentTimeMillis() { + return DateTime.now().getMillis(); + } + + private Date generateCurrentDate() { + return new Date(getCurrentTimeMillis()); + } + + private Date generateExpirationDate() { + + return new Date(getCurrentTimeMillis() + this.EXPIRES_IN * 1000); + } + + 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; + } + + /** + * 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 null 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; + } +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/security/WebsocketRequestMatcher.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/WebsocketRequestMatcher.java similarity index 100% rename from quartz-manager/src/main/java/it/fabioformosa/quartzmanager/security/WebsocketRequestMatcher.java rename to quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/WebsocketRequestMatcher.java diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/AnonAuthentication.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/AnonAuthentication.java new file mode 100644 index 0000000..3b368bc --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/AnonAuthentication.java @@ -0,0 +1,51 @@ +package it.fabioformosa.quartzmanager.security.auth; + +import org.springframework.security.authentication.AbstractAuthenticationToken; + +/** + * Created by fan.jin on 2017-04-04. + */ + +public class AnonAuthentication extends AbstractAuthenticationToken { + + public AnonAuthentication() { + super( null ); + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getPrincipal() { + return null; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public int hashCode() { + int hash = 7; + return hash; + } + + @Override + public boolean equals( Object obj ) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + return true; + } + + +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/AuthenticationFailureHandler.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/AuthenticationFailureHandler.java new file mode 100644 index 0000000..7585552 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/AuthenticationFailureHandler.java @@ -0,0 +1,25 @@ +package it.fabioformosa.quartzmanager.security.auth; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Created by fan.jin on 2016-11-07. + */ + +@Component +public class AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + + super.onAuthenticationFailure(request, response, exception); + } +} \ No newline at end of file diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/AuthenticationSuccessHandler.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/AuthenticationSuccessHandler.java new file mode 100644 index 0000000..c2b9588 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/AuthenticationSuccessHandler.java @@ -0,0 +1,66 @@ +package it.fabioformosa.quartzmanager.security.auth; + +import java.io.IOException; + +import javax.servlet.ServletException; +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.security.core.Authentication; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +/** + * Created by fan.jin on 2016-11-07. + */ +import com.fasterxml.jackson.databind.ObjectMapper; + +import it.fabioformosa.quartzmanager.security.TokenHelper; +import it.fabioformosa.quartzmanager.security.model.User; +import it.fabioformosa.quartzmanager.security.model.UserTokenState; + +@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 ); + + } +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/LogoutSuccess.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/LogoutSuccess.java new file mode 100644 index 0000000..5a757a6 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/LogoutSuccess.java @@ -0,0 +1,36 @@ +package it.fabioformosa.quartzmanager.security.auth; + +import com.fasterxml.jackson.databind.ObjectMapper; +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 javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by fan.jin on 2017-05-06. + */ +@Component +public class LogoutSuccess implements LogoutSuccessHandler { + + @Autowired + ObjectMapper objectMapper; + + @Override + public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { + Map result = new HashMap<>(); + result.put( "result", "success" ); + response.setContentType("application/json"); + response.getWriter().write( objectMapper.writeValueAsString( result ) ); + response.setStatus(HttpServletResponse.SC_OK); + + } + +} \ No newline at end of file diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/RestAuthenticationEntryPoint.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/RestAuthenticationEntryPoint.java new file mode 100644 index 0000000..837e4ee --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/RestAuthenticationEntryPoint.java @@ -0,0 +1,30 @@ +package it.fabioformosa.quartzmanager.security.auth; + +/** + * Created by fan.jin on 2016-11-12. + */ + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Created by fan.jin on 2016-11-07. + */ +@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()); + } +} + diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/TokenAuthenticationFilter.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/TokenAuthenticationFilter.java new file mode 100644 index 0000000..e1aaecc --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/TokenAuthenticationFilter.java @@ -0,0 +1,94 @@ +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; + +/** + * Created by fan.jin on 2016-10-19. + */ +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 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 pathsToSkip ) { + Assert.notNull(pathsToSkip, "path cannot be null."); + List m = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList()); + OrRequestMatcher matchers = new OrRequestMatcher(m); + return matchers.matches(request); + } + +} \ No newline at end of file diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/TokenBasedAuthentication.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/TokenBasedAuthentication.java new file mode 100644 index 0000000..4cd60cf --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/auth/TokenBasedAuthentication.java @@ -0,0 +1,43 @@ +package it.fabioformosa.quartzmanager.security.auth; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; + + +/** + * Created by fan.jin on 2016-11-11. + */ +public class TokenBasedAuthentication extends AbstractAuthenticationToken { + + private String token; + private final UserDetails principle; + + public TokenBasedAuthentication( UserDetails principle ) { + super( principle.getAuthorities() ); + this.principle = principle; + } + + public String getToken() { + return token; + } + + public void setToken( String token ) { + this.token = token; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public Object getCredentials() { + return token; + } + + @Override + public UserDetails getPrincipal() { + return principle; + } + +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/Authority.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/Authority.java new file mode 100644 index 0000000..a9e9b3f --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/Authority.java @@ -0,0 +1,47 @@ +package it.fabioformosa.quartzmanager.security.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.springframework.security.core.GrantedAuthority; + +import javax.persistence.*; + +/** + * Created by fan.jin on 2016-11-03. + */ + +@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; + } + + public void setName(String name) { + this.name = name; + } + + @JsonIgnore + public String getName() { + return name; + } + + @JsonIgnore + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/User.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/User.java new file mode 100644 index 0000000..8226be6 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/User.java @@ -0,0 +1,128 @@ +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; + +/** + * Created by fan.jin on 2016-10-15. + */ + +@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 authorities; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + 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 void setAuthorities(List authorities) { + this.authorities = authorities; + } + + @Override + public Collection getAuthorities() { + return this.authorities; + } + + // 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; + } +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/UserRequest.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/UserRequest.java new file mode 100644 index 0000000..f89e4fd --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/UserRequest.java @@ -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; + } +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/UserTokenState.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/UserTokenState.java new file mode 100644 index 0000000..2712742 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/model/UserTokenState.java @@ -0,0 +1,35 @@ +package it.fabioformosa.quartzmanager.security.model; + +/** + * Created by fan.jin on 2016-10-17. + */ +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 void setAccess_token(String access_token) { + this.access_token = access_token; + } + + public Long getExpires_in() { + return expires_in; + } + + public void setExpires_in(Long expires_in) { + this.expires_in = expires_in; + } +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/repository/AuthorityRepository.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/repository/AuthorityRepository.java new file mode 100644 index 0000000..3741ee2 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/repository/AuthorityRepository.java @@ -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 findByName(String name); +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/repository/UserRepository.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/repository/UserRepository.java new file mode 100644 index 0000000..8783be2 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/repository/UserRepository.java @@ -0,0 +1,13 @@ +package it.fabioformosa.quartzmanager.security.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import it.fabioformosa.quartzmanager.security.model.User; + +/** + * Created by fan.jin on 2016-10-15. + */ +public interface UserRepository extends JpaRepository { + User findByUsername( String username ); +} + diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/AuthorityService.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/AuthorityService.java new file mode 100644 index 0000000..03d0fb5 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/AuthorityService.java @@ -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 findById(Long id); + + List findByname(String name); + +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/UserService.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/UserService.java new file mode 100644 index 0000000..64a164e --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/UserService.java @@ -0,0 +1,21 @@ +package it.fabioformosa.quartzmanager.security.service; + +import java.util.List; + +import it.fabioformosa.quartzmanager.security.model.User; +import it.fabioformosa.quartzmanager.security.model.UserRequest; + +/** + * Created by fan.jin on 2016-10-15. + */ +public interface UserService { + List findAll(); + + User findById(Long id); + + User findByUsername(String username); + + void resetCredentials(); + + User save(UserRequest user); +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/impl/AuthorityServiceImpl.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/impl/AuthorityServiceImpl.java new file mode 100644 index 0000000..20c4d92 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/impl/AuthorityServiceImpl.java @@ -0,0 +1,38 @@ +package it.fabioformosa.quartzmanager.security.service.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +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 findById(Long id) { + // TODO Auto-generated method stub + + Authority auth = this.authorityRepository.findOne(id); + List auths = new ArrayList<>(); + auths.add(auth); + return auths; + } + + @Override + public List findByname(String name) { + // TODO Auto-generated method stub + Authority auth = this.authorityRepository.findByName(name); + List auths = new ArrayList<>(); + auths.add(auth); + return auths; + } + +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/impl/CustomUserDetailsService.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/impl/CustomUserDetailsService.java new file mode 100644 index 0000000..99afb76 --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/impl/CustomUserDetailsService.java @@ -0,0 +1,70 @@ +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 org.springframework.stereotype.Service; + +import it.fabioformosa.quartzmanager.security.model.User; +import it.fabioformosa.quartzmanager.security.repository.UserRepository; + +/** + * Created by fan.jin on 2016-10-31. + */ + +@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; + } + +} diff --git a/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/impl/UserServiceImpl.java b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..3b2e13f --- /dev/null +++ b/quartz-manager-backend/src/main/java/it/fabioformosa/quartzmanager/security/service/impl/UserServiceImpl.java @@ -0,0 +1,78 @@ +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 org.springframework.stereotype.Service; + +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; + +/** + * Created by fan.jin on 2016-10-15. + */ + +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private AuthorityService authService; + + @Override + @PreAuthorize("hasRole('ADMIN')") + public List findAll() throws AccessDeniedException { + List 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 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 auth = authService.findByname("ROLE_USER"); + user.setAuthorities(auth); + this.userRepository.save(user); + return user; + } + +} diff --git a/quartz-manager/src/main/resources/application.properties b/quartz-manager-backend/src/main/resources/application.properties similarity index 57% rename from quartz-manager/src/main/resources/application.properties rename to quartz-manager-backend/src/main/resources/application.properties index e8bb65f..53cebd9 100644 --- a/quartz-manager/src/main/resources/application.properties +++ b/quartz-manager-backend/src/main/resources/application.properties @@ -1,5 +1,5 @@ server.context-path=/quartz-manager -server.port=9000 +server.port=8080 server.session.timeout=28800 spring.thymeleaf.cache=false @@ -11,4 +11,10 @@ job.frequency=4000 job.repeatCount=19 logging.level.org.springframework.web=WARN -logging.level.it.fabioformosa=INFO \ No newline at end of file +logging.level.it.fabioformosa=INFO + +app.name: quartz-manager +jwt.header: Authorization +jwt.expires_in: 600 +jwt.secret: queenvictoria +jwt.cookie: AUTH-TOKEN \ No newline at end of file diff --git a/quartz-manager-backend/src/main/resources/banner.txt b/quartz-manager-backend/src/main/resources/banner.txt new file mode 100644 index 0000000..d8bd923 --- /dev/null +++ b/quartz-manager-backend/src/main/resources/banner.txt @@ -0,0 +1,8 @@ + ____ _ __ __ + / __ \ | | | \/ | + | | | |_ _ __ _ _ __| |_ ____ | \ / | __ _ _ __ __ _ __ _ ___ _ __ + | | | | | | |/ _` | '__| __|_ / | |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '__| + | |__| | |_| | (_| | | | |_ / / | | | | (_| | | | | (_| | (_| | __/ | + \___\_\\__,_|\__,_|_| \__/___| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_| + __/ | + |___/ diff --git a/quartz-manager-backend/src/main/resources/import.sql b/quartz-manager-backend/src/main/resources/import.sql new file mode 100644 index 0000000..7fd55c0 --- /dev/null +++ b/quartz-manager-backend/src/main/resources/import.sql @@ -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', 'Fan', 'Jin'); +INSERT INTO user (id, username, password, firstname, lastname) VALUES (2, 'admin', '$2a$04$Vbug2lwwJGrvUXTj6z7ff.97IzVBkrJ1XfApfGNl.Z695zqcnPYra', 'Jing', 'Xiao'); + +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); diff --git a/quartz-manager/src/main/resources/quartz.properties b/quartz-manager-backend/src/main/resources/quartz.properties similarity index 100% rename from quartz-manager/src/main/resources/quartz.properties rename to quartz-manager-backend/src/main/resources/quartz.properties diff --git a/quartz-manager/src/main/resources/static/css/animate.css b/quartz-manager-backend/src/main/resources/static/css/animate.css similarity index 100% rename from quartz-manager/src/main/resources/static/css/animate.css rename to quartz-manager-backend/src/main/resources/static/css/animate.css diff --git a/quartz-manager/src/main/resources/static/css/dashboard-bootstrap.css b/quartz-manager-backend/src/main/resources/static/css/dashboard-bootstrap.css similarity index 100% rename from quartz-manager/src/main/resources/static/css/dashboard-bootstrap.css rename to quartz-manager-backend/src/main/resources/static/css/dashboard-bootstrap.css diff --git a/quartz-manager/src/main/resources/static/css/scheduler.css b/quartz-manager-backend/src/main/resources/static/css/scheduler.css similarity index 100% rename from quartz-manager/src/main/resources/static/css/scheduler.css rename to quartz-manager-backend/src/main/resources/static/css/scheduler.css diff --git a/quartz-manager/src/main/resources/static/css/signin.css b/quartz-manager-backend/src/main/resources/static/css/signin.css similarity index 100% rename from quartz-manager/src/main/resources/static/css/signin.css rename to quartz-manager-backend/src/main/resources/static/css/signin.css diff --git a/quartz-manager/src/main/resources/static/js/app/app.js b/quartz-manager-backend/src/main/resources/static/js/app/app.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/app.js rename to quartz-manager-backend/src/main/resources/static/js/app/app.js diff --git a/quartz-manager/src/main/resources/static/js/app/components/authentication/authentication.js b/quartz-manager-backend/src/main/resources/static/js/app/components/authentication/authentication.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/components/authentication/authentication.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/authentication/authentication.js diff --git a/quartz-manager/src/main/resources/static/js/app/components/configurator/configurator-directives.js b/quartz-manager-backend/src/main/resources/static/js/app/components/configurator/configurator-directives.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/components/configurator/configurator-directives.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/configurator/configurator-directives.js diff --git a/quartz-manager/src/main/resources/static/js/app/components/configurator/configurator-module.js b/quartz-manager-backend/src/main/resources/static/js/app/components/configurator/configurator-module.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/components/configurator/configurator-module.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/configurator/configurator-module.js diff --git a/quartz-manager/src/main/resources/static/js/app/components/progress/logs-directive.js b/quartz-manager-backend/src/main/resources/static/js/app/components/progress/logs-directive.js similarity index 97% rename from quartz-manager/src/main/resources/static/js/app/components/progress/logs-directive.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/progress/logs-directive.js index 4f64983..603de69 100644 --- a/quartz-manager/src/main/resources/static/js/app/components/progress/logs-directive.js +++ b/quartz-manager-backend/src/main/resources/static/js/app/components/progress/logs-directive.js @@ -31,7 +31,7 @@ angular.module('progress') var _handleNewMsgFromLogWebsocket = function(receivedMsg){ if(receivedMsg.type == 'SUCCESS') - _showNewLog(receivedMsg.message); + _showNewLo g(receivedMsg.message); else if(receivedMsg.type == 'ERROR') _refreshSession(); //if websocket has been closed for session expiration, try to refresh it }; diff --git a/quartz-manager/src/main/resources/static/js/app/components/progress/logs-service.js b/quartz-manager-backend/src/main/resources/static/js/app/components/progress/logs-service.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/components/progress/logs-service.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/progress/logs-service.js diff --git a/quartz-manager/src/main/resources/static/js/app/components/progress/progress-directive.js b/quartz-manager-backend/src/main/resources/static/js/app/components/progress/progress-directive.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/components/progress/progress-directive.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/progress/progress-directive.js diff --git a/quartz-manager/src/main/resources/static/js/app/components/progress/progress-module.js b/quartz-manager-backend/src/main/resources/static/js/app/components/progress/progress-module.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/components/progress/progress-module.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/progress/progress-module.js diff --git a/quartz-manager/src/main/resources/static/js/app/components/progress/progress-service.js b/quartz-manager-backend/src/main/resources/static/js/app/components/progress/progress-service.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/components/progress/progress-service.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/progress/progress-service.js diff --git a/quartz-manager/src/main/resources/static/js/app/components/starter/starter-directives.js b/quartz-manager-backend/src/main/resources/static/js/app/components/starter/starter-directives.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/components/starter/starter-directives.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/starter/starter-directives.js diff --git a/quartz-manager/src/main/resources/static/js/app/components/starter/starter-module.js b/quartz-manager-backend/src/main/resources/static/js/app/components/starter/starter-module.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/components/starter/starter-module.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/starter/starter-module.js diff --git a/quartz-manager/src/main/resources/static/js/app/components/websocket/websocket-factory.js b/quartz-manager-backend/src/main/resources/static/js/app/components/websocket/websocket-factory.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/components/websocket/websocket-factory.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/websocket/websocket-factory.js diff --git a/quartz-manager/src/main/resources/static/js/app/components/websocket/websocket-module.js b/quartz-manager-backend/src/main/resources/static/js/app/components/websocket/websocket-module.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/app/components/websocket/websocket-module.js rename to quartz-manager-backend/src/main/resources/static/js/app/components/websocket/websocket-module.js diff --git a/quartz-manager/src/main/resources/static/js/lib/bootbox.min.js b/quartz-manager-backend/src/main/resources/static/js/lib/bootbox.min.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/lib/bootbox.min.js rename to quartz-manager-backend/src/main/resources/static/js/lib/bootbox.min.js diff --git a/quartz-manager/src/main/resources/static/js/lib/http-auth-interceptor.js b/quartz-manager-backend/src/main/resources/static/js/lib/http-auth-interceptor.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/lib/http-auth-interceptor.js rename to quartz-manager-backend/src/main/resources/static/js/lib/http-auth-interceptor.js diff --git a/quartz-manager/src/main/resources/static/js/lib/http-auth-interceptor.min.js b/quartz-manager-backend/src/main/resources/static/js/lib/http-auth-interceptor.min.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/lib/http-auth-interceptor.min.js rename to quartz-manager-backend/src/main/resources/static/js/lib/http-auth-interceptor.min.js diff --git a/quartz-manager/src/main/resources/static/js/lib/sockjs-0.3.4.js b/quartz-manager-backend/src/main/resources/static/js/lib/sockjs-0.3.4.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/lib/sockjs-0.3.4.js rename to quartz-manager-backend/src/main/resources/static/js/lib/sockjs-0.3.4.js diff --git a/quartz-manager/src/main/resources/static/js/lib/sockjs-0.3.4.min.js b/quartz-manager-backend/src/main/resources/static/js/lib/sockjs-0.3.4.min.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/lib/sockjs-0.3.4.min.js rename to quartz-manager-backend/src/main/resources/static/js/lib/sockjs-0.3.4.min.js diff --git a/quartz-manager/src/main/resources/static/js/lib/stomp.js b/quartz-manager-backend/src/main/resources/static/js/lib/stomp.js similarity index 100% rename from quartz-manager/src/main/resources/static/js/lib/stomp.js rename to quartz-manager-backend/src/main/resources/static/js/lib/stomp.js diff --git a/quartz-manager/src/main/resources/templates/fragments/headerinc.html b/quartz-manager-backend/src/main/resources/templates/fragments/headerinc.html similarity index 100% rename from quartz-manager/src/main/resources/templates/fragments/headerinc.html rename to quartz-manager-backend/src/main/resources/templates/fragments/headerinc.html diff --git a/quartz-manager/src/main/resources/templates/layouts/standard.html b/quartz-manager-backend/src/main/resources/templates/layouts/standard.html similarity index 100% rename from quartz-manager/src/main/resources/templates/layouts/standard.html rename to quartz-manager-backend/src/main/resources/templates/layouts/standard.html diff --git a/quartz-manager/src/main/resources/templates/login.html b/quartz-manager-backend/src/main/resources/templates/login.html similarity index 100% rename from quartz-manager/src/main/resources/templates/login.html rename to quartz-manager-backend/src/main/resources/templates/login.html diff --git a/quartz-manager/src/main/resources/templates/manager/config-form.html b/quartz-manager-backend/src/main/resources/templates/manager/config-form.html similarity index 100% rename from quartz-manager/src/main/resources/templates/manager/config-form.html rename to quartz-manager-backend/src/main/resources/templates/manager/config-form.html diff --git a/quartz-manager/src/main/resources/templates/manager/logs-panel.html b/quartz-manager-backend/src/main/resources/templates/manager/logs-panel.html similarity index 100% rename from quartz-manager/src/main/resources/templates/manager/logs-panel.html rename to quartz-manager-backend/src/main/resources/templates/manager/logs-panel.html diff --git a/quartz-manager/src/main/resources/templates/manager/progress-panel.html b/quartz-manager-backend/src/main/resources/templates/manager/progress-panel.html similarity index 100% rename from quartz-manager/src/main/resources/templates/manager/progress-panel.html rename to quartz-manager-backend/src/main/resources/templates/manager/progress-panel.html diff --git a/quartz-manager/src/main/resources/templates/panelView.html b/quartz-manager-backend/src/main/resources/templates/panelView.html similarity index 100% rename from quartz-manager/src/main/resources/templates/panelView.html rename to quartz-manager-backend/src/main/resources/templates/panelView.html diff --git a/quartz-manager/src/test/java/it/fabioformosa/QuartManagerApplicationTests.java b/quartz-manager-backend/src/test/java/it/fabioformosa/QuartManagerApplicationTests.java similarity index 100% rename from quartz-manager/src/test/java/it/fabioformosa/QuartManagerApplicationTests.java rename to quartz-manager-backend/src/test/java/it/fabioformosa/QuartManagerApplicationTests.java diff --git a/quartz-manager-frontend/.angular-cli.json b/quartz-manager-frontend/.angular-cli.json new file mode 100644 index 0000000..f266ffd --- /dev/null +++ b/quartz-manager-frontend/.angular-cli.json @@ -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": {} + } +} diff --git a/quartz-manager-frontend/.editorconfig b/quartz-manager-frontend/.editorconfig new file mode 100644 index 0000000..6e87a00 --- /dev/null +++ b/quartz-manager-frontend/.editorconfig @@ -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 diff --git a/quartz-manager-frontend/.gitignore b/quartz-manager-frontend/.gitignore new file mode 100644 index 0000000..d3e4582 --- /dev/null +++ b/quartz-manager-frontend/.gitignore @@ -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 \ No newline at end of file diff --git a/quartz-manager-frontend/e2e/app.e2e-spec.ts b/quartz-manager-frontend/e2e/app.e2e-spec.ts new file mode 100644 index 0000000..2976b66 --- /dev/null +++ b/quartz-manager-frontend/e2e/app.e2e-spec.ts @@ -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'); + }); +}); diff --git a/quartz-manager-frontend/e2e/app.po.ts b/quartz-manager-frontend/e2e/app.po.ts new file mode 100644 index 0000000..092797a --- /dev/null +++ b/quartz-manager-frontend/e2e/app.po.ts @@ -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(); + } +} diff --git a/quartz-manager-frontend/e2e/tsconfig.e2e.json b/quartz-manager-frontend/e2e/tsconfig.e2e.json new file mode 100644 index 0000000..ac7a373 --- /dev/null +++ b/quartz-manager-frontend/e2e/tsconfig.e2e.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "module": "commonjs", + "target": "es5", + "types":[ + "jasmine", + "node" + ] + } +} diff --git a/quartz-manager-frontend/karma.conf.js b/quartz-manager-frontend/karma.conf.js new file mode 100644 index 0000000..84b4cd5 --- /dev/null +++ b/quartz-manager-frontend/karma.conf.js @@ -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 + }); +}; diff --git a/quartz-manager-frontend/package.json b/quartz-manager-frontend/package.json new file mode 100644 index 0000000..7a5b277 --- /dev/null +++ b/quartz-manager-frontend/package.json @@ -0,0 +1,56 @@ +{ + "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.0.2", + "@angular/common": "5.0.2", + "@angular/compiler": "5.0.2", + "@angular/core": "5.0.2", + "@angular/forms": "5.0.2", + "@angular/http": "5.0.2", + "@angular/material": "5.0.0-rc.1", + "@angular/cdk": "5.0.0-rc.1", + "@angular/platform-browser": "5.0.2", + "@angular/platform-browser-dynamic": "5.0.2", + "@angular/platform-server": "5.0.2", + "@angular/router": "5.0.2", + "@angular/flex-layout": "2.0.0-beta.10-4905443", + "core-js": "2.5.1", + "hammerjs": "2.0.8", + "rxjs": "5.5.2", + "zone.js": "0.8.18" + }, + "devDependencies": { + "@angular-devkit/core": "^0.2.0", + "@angular/cli": "1.5.3", + "@angular/compiler-cli": "5.0.2", + "@angular/language-service": "5.0.2", + "@types/hammerjs": "2.0.34", + "@types/jasmine": "2.5.54", + "@types/jasminewd2": "2.0.3", + "@types/node": "6.0.90", + "codelyzer": "3.2.2", + "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" + } +} diff --git a/quartz-manager-frontend/protractor.conf.js b/quartz-manager-frontend/protractor.conf.js new file mode 100644 index 0000000..1c5e1e5 --- /dev/null +++ b/quartz-manager-frontend/protractor.conf.js @@ -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 } })); + } +}; diff --git a/quartz-manager-frontend/proxy.conf.json b/quartz-manager-frontend/proxy.conf.json new file mode 100644 index 0000000..ffa07ba --- /dev/null +++ b/quartz-manager-frontend/proxy.conf.json @@ -0,0 +1,6 @@ +{ + "/quartz-manager": { + "target": "http://localhost:8080", + "secure": false + } +} diff --git a/quartz-manager-frontend/src/app/admin/admin.component.css b/quartz-manager-frontend/src/app/admin/admin.component.css new file mode 100644 index 0000000..e69de29 diff --git a/quartz-manager-frontend/src/app/admin/admin.component.html b/quartz-manager-frontend/src/app/admin/admin.component.html new file mode 100644 index 0000000..c76de4b --- /dev/null +++ b/quartz-manager-frontend/src/app/admin/admin.component.html @@ -0,0 +1,3 @@ +

+ This is admin page! +

diff --git a/quartz-manager-frontend/src/app/admin/admin.component.spec.ts b/quartz-manager-frontend/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..4e4d044 --- /dev/null +++ b/quartz-manager-frontend/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/quartz-manager-frontend/src/app/admin/admin.component.ts b/quartz-manager-frontend/src/app/admin/admin.component.ts new file mode 100644 index 0000000..d06c5fd --- /dev/null +++ b/quartz-manager-frontend/src/app/admin/admin.component.ts @@ -0,0 +1,16 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.css'] +}) +export class AdminComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} + diff --git a/quartz-manager-frontend/src/app/admin/index.ts b/quartz-manager-frontend/src/app/admin/index.ts new file mode 100644 index 0000000..415c81b --- /dev/null +++ b/quartz-manager-frontend/src/app/admin/index.ts @@ -0,0 +1,2 @@ +export * from './admin.component'; + diff --git a/quartz-manager-frontend/src/app/app-routing.module.ts b/quartz-manager-frontend/src/app/app-routing.module.ts new file mode 100644 index 0000000..6c2d5a1 --- /dev/null +++ b/quartz-manager-frontend/src/app/app-routing.module.ts @@ -0,0 +1,60 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { AppComponent } from './app.component'; +import { HomeComponent } from './home'; +import { LoginComponent } from './login'; +import { AdminComponent } from './admin'; +import { LoginGuard } from './guard'; +import { GuestGuard, AdminGuard } from './guard'; +import { NotFoundComponent } from './not-found'; +import { ChangePasswordComponent } from './change-password'; +import { ForbiddenComponent } from './forbidden'; +import { SignupComponent } from './signup'; + +export const routes: Routes = [ + { + path: '', + component: HomeComponent, + pathMatch: 'full' + }, + { + path:'signup', + component: SignupComponent, + canActivate: [GuestGuard], + pathMatch:'full' + }, + { + path: 'login', + component: LoginComponent, + canActivate: [GuestGuard] + }, + { + path: 'change-password', + component: ChangePasswordComponent, + canActivate: [LoginGuard] + }, + { + path: 'admin', + component: AdminComponent, + canActivate: [AdminGuard] + }, + { + path: '404', + component: NotFoundComponent + }, + { + path: '403', + component: ForbiddenComponent + }, + { + path: '**', + redirectTo: '/404' + } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], + providers: [] +}) +export class AppRoutingModule { } diff --git a/quartz-manager-frontend/src/app/app.component.html b/quartz-manager-frontend/src/app/app.component.html new file mode 100644 index 0000000..d2bb06e --- /dev/null +++ b/quartz-manager-frontend/src/app/app.component.html @@ -0,0 +1,5 @@ + +
+ +
+ diff --git a/quartz-manager-frontend/src/app/app.component.scss b/quartz-manager-frontend/src/app/app.component.scss new file mode 100644 index 0000000..5293b8d --- /dev/null +++ b/quartz-manager-frontend/src/app/app.component.scss @@ -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; + } +} diff --git a/quartz-manager-frontend/src/app/app.component.spec.ts b/quartz-manager-frontend/src/app/app.component.spec.ts new file mode 100644 index 0000000..f5a514e --- /dev/null +++ b/quartz-manager-frontend/src/app/app.component.spec.ts @@ -0,0 +1,65 @@ +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 { 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(); + })); + +}); diff --git a/quartz-manager-frontend/src/app/app.component.ts b/quartz-manager-frontend/src/app/app.component.ts new file mode 100644 index 0000000..d4c1da7 --- /dev/null +++ b/quartz-manager-frontend/src/app/app.component.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) + +export class AppComponent { +} diff --git a/quartz-manager-frontend/src/app/app.module.ts b/quartz-manager-frontend/src/app/app.module.ts new file mode 100644 index 0000000..e492f88 --- /dev/null +++ b/quartz-manager-frontend/src/app/app.module.ts @@ -0,0 +1,103 @@ +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, + MatInputModule, + MatIconRegistry, + MatProgressSpinnerModule +} 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 { HomeComponent } from './home'; +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, + ApiCardComponent, + FooterComponent, + GithubComponent +} from './component'; + +import { + ApiService, + AuthService, + UserService, + FooService, + ConfigService +} from './service'; +import { ChangePasswordComponent } from './change-password/change-password.component'; +import { ForbiddenComponent } from './forbidden/forbidden.component'; +import { AdminComponent } from './admin/admin.component'; +import { SignupComponent } from './signup/signup.component'; + +export function initUserFactory(userService: UserService) { + return () => userService.initUser(); +} + +@NgModule({ + declarations: [ + AppComponent, + HeaderComponent, + FooterComponent, + ApiCardComponent, + HomeComponent, + GithubComponent, + LoginComponent, + NotFoundComponent, + AccountMenuComponent, + ChangePasswordComponent, + ForbiddenComponent, + AdminComponent, + SignupComponent + ], + imports: [ + BrowserAnimationsModule, + BrowserModule, + FormsModule, + ReactiveFormsModule, + HttpModule, + HttpClientModule, + AppRoutingModule, + MatMenuModule, + MatTooltipModule, + MatButtonModule, + MatIconModule, + MatInputModule, + MatToolbarModule, + MatCardModule, + MatProgressSpinnerModule, + FlexLayoutModule + ], + providers: [ + LoginGuard, + GuestGuard, + AdminGuard, + FooService, + AuthService, + ApiService, + UserService, + ConfigService, + MatIconRegistry, + { + 'provide': APP_INITIALIZER, + 'useFactory': initUserFactory, + 'deps': [UserService], + 'multi': true + } + ], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/quartz-manager-frontend/src/app/change-password/change-password.component.html b/quartz-manager-frontend/src/app/change-password/change-password.component.html new file mode 100644 index 0000000..b080f4a --- /dev/null +++ b/quartz-manager-frontend/src/app/change-password/change-password.component.html @@ -0,0 +1,18 @@ +
+ + Change Your Password +

{{notification.msgBody}}

+ +
+ + + + + + + +
+ +
+
+
diff --git a/quartz-manager-frontend/src/app/change-password/change-password.component.scss b/quartz-manager-frontend/src/app/change-password/change-password.component.scss new file mode 100644 index 0000000..d60c8de --- /dev/null +++ b/quartz-manager-frontend/src/app/change-password/change-password.component.scss @@ -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; + } + +} diff --git a/quartz-manager-frontend/src/app/change-password/change-password.component.spec.ts b/quartz-manager-frontend/src/app/change-password/change-password.component.spec.ts new file mode 100644 index 0000000..5fc506a --- /dev/null +++ b/quartz-manager-frontend/src/app/change-password/change-password.component.spec.ts @@ -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; + + 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(); + }); +}); diff --git a/quartz-manager-frontend/src/app/change-password/change-password.component.ts b/quartz-manager-frontend/src/app/change-password/change-password.component.ts new file mode 100644 index 0000000..b08085c --- /dev/null +++ b/quartz-manager-frontend/src/app/change-password/change-password.component.ts @@ -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.'}; + }); + + } + +} diff --git a/quartz-manager-frontend/src/app/change-password/index.ts b/quartz-manager-frontend/src/app/change-password/index.ts new file mode 100644 index 0000000..d71e3de --- /dev/null +++ b/quartz-manager-frontend/src/app/change-password/index.ts @@ -0,0 +1 @@ +export * from './change-password.component'; diff --git a/quartz-manager-frontend/src/app/component/api-card/api-card.component.html b/quartz-manager-frontend/src/app/component/api-card/api-card.component.html new file mode 100644 index 0000000..1d4d0c7 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/api-card/api-card.component.html @@ -0,0 +1,26 @@ + + + {{title}} + {{subTitle}} + + + +

+ {{content}} +

+
+ + + +
+
Path: {{responseObj.path}}
+
Method: {{responseObj.method}}
+
Status: {{responseObj.status}}
+
Message: {{responseObj.body || responseObj.message}} 
+
+
diff --git a/quartz-manager-frontend/src/app/component/api-card/api-card.component.scss b/quartz-manager-frontend/src/app/component/api-card/api-card.component.scss new file mode 100644 index 0000000..903036c --- /dev/null +++ b/quartz-manager-frontend/src/app/component/api-card/api-card.component.scss @@ -0,0 +1,63 @@ +:host { + text-align: center; + max-width: 350px; +} + +mat-card { + text-align: left; + .response-success { + background-color: #dff0d8; + border-color: #d6e9c6; + color: #3c763d; + } + .response-error { + background-color: #f2dede; + border-color: #ebccd1; + color: #a94442; + } + + .response { + max-height: 0; + transition: max-height 1s; + margin-left: -16px; + margin-right: -16px; + border-radius: 4px; + overflow: hidden; + margin-bottom: -16px; + padding-bottom: 0; + } + + .expand { + padding: 15px; + border: 1px solid transparent; + max-height: 999px; + margin-top: 8px; + } + + mat-card-actions { + margin-bottom: 0; + padding-bottom: 0; + } + + pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + white-space: pre-wrap; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; + } +} + + +@media screen and (max-width: 599px) { + :host { + max-width: 999px; + } +} diff --git a/quartz-manager-frontend/src/app/component/api-card/api-card.component.ts b/quartz-manager-frontend/src/app/component/api-card/api-card.component.ts new file mode 100644 index 0000000..8082a42 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/api-card/api-card.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-api-card', + templateUrl: './api-card.component.html', + styleUrls: ['./api-card.component.scss'] +}) +export class ApiCardComponent implements OnInit { + + @Input() title: string; + @Input() subTitle: string; + @Input() imgUrl: string; + @Input() content: string; + @Input() apiText: string; + @Input() responseObj: any; + expand = false; + + + @Output() apiClick: EventEmitter = new EventEmitter(); + + constructor() { } + + ngOnInit() { + console.log(this.responseObj); + } + + onButtonClick() { + this.expand = true; + this.apiClick.next(this.apiText); + } + + responsePanelClass() { + const rClass = ['response']; + if (this.expand) { + rClass.push('expand'); + } + if (this.responseObj.status) { + this.responseObj.status === 200 ? + rClass.push('response-success') : + rClass.push('response-error'); + } + return rClass.join(' '); + } + +} diff --git a/quartz-manager-frontend/src/app/component/api-card/index.ts b/quartz-manager-frontend/src/app/component/api-card/index.ts new file mode 100644 index 0000000..abb8176 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/api-card/index.ts @@ -0,0 +1 @@ +export * from './api-card.component'; diff --git a/quartz-manager-frontend/src/app/component/footer/footer.component.html b/quartz-manager-frontend/src/app/component/footer/footer.component.html new file mode 100644 index 0000000..8086867 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/footer/footer.component.html @@ -0,0 +1,9 @@ +

+ Hand crafted with love by + Fan Jin + and our awesome + contributors. +

+ + + diff --git a/quartz-manager-frontend/src/app/component/footer/footer.component.scss b/quartz-manager-frontend/src/app/component/footer/footer.component.scss new file mode 100644 index 0000000..a5f4583 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/footer/footer.component.scss @@ -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; + } + +} + + diff --git a/quartz-manager-frontend/src/app/component/footer/footer.component.ts b/quartz-manager-frontend/src/app/component/footer/footer.component.ts new file mode 100644 index 0000000..da17d82 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/footer/footer.component.ts @@ -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() { + } + +} diff --git a/quartz-manager-frontend/src/app/component/footer/index.ts b/quartz-manager-frontend/src/app/component/footer/index.ts new file mode 100644 index 0000000..a50d573 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/footer/index.ts @@ -0,0 +1 @@ +export * from './footer.component'; diff --git a/quartz-manager-frontend/src/app/component/github/github.component.html b/quartz-manager-frontend/src/app/component/github/github.component.html new file mode 100644 index 0000000..b7734af --- /dev/null +++ b/quartz-manager-frontend/src/app/component/github/github.component.html @@ -0,0 +1,4 @@ +

Want to help make this project awesome? Check out our repo.

+ + GITHUB + diff --git a/quartz-manager-frontend/src/app/component/github/github.component.scss b/quartz-manager-frontend/src/app/component/github/github.component.scss new file mode 100644 index 0000000..68d247d --- /dev/null +++ b/quartz-manager-frontend/src/app/component/github/github.component.scss @@ -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; +} diff --git a/quartz-manager-frontend/src/app/component/github/github.component.ts b/quartz-manager-frontend/src/app/component/github/github.component.ts new file mode 100644 index 0000000..e39ab16 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/github/github.component.ts @@ -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() { + } + +} diff --git a/quartz-manager-frontend/src/app/component/github/index.ts b/quartz-manager-frontend/src/app/component/github/index.ts new file mode 100644 index 0000000..d670976 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/github/index.ts @@ -0,0 +1 @@ +export * from './github.component'; diff --git a/quartz-manager-frontend/src/app/component/header/account-menu/account-menu.component.html b/quartz-manager-frontend/src/app/component/header/account-menu/account-menu.component.html new file mode 100644 index 0000000..8334975 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/header/account-menu/account-menu.component.html @@ -0,0 +1,2 @@ + + diff --git a/quartz-manager-frontend/src/app/component/header/account-menu/account-menu.component.scss b/quartz-manager-frontend/src/app/component/header/account-menu/account-menu.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/quartz-manager-frontend/src/app/component/header/account-menu/account-menu.component.spec.ts b/quartz-manager-frontend/src/app/component/header/account-menu/account-menu.component.spec.ts new file mode 100644 index 0000000..8353da3 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/header/account-menu/account-menu.component.spec.ts @@ -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; + + 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(); + }); +}); diff --git a/quartz-manager-frontend/src/app/component/header/account-menu/account-menu.component.ts b/quartz-manager-frontend/src/app/component/header/account-menu/account-menu.component.ts new file mode 100644 index 0000000..509f4ed --- /dev/null +++ b/quartz-manager-frontend/src/app/component/header/account-menu/account-menu.component.ts @@ -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']); + }); + } +} diff --git a/quartz-manager-frontend/src/app/component/header/header.component.html b/quartz-manager-frontend/src/app/component/header/header.component.html new file mode 100644 index 0000000..f3c8da5 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/header/header.component.html @@ -0,0 +1,40 @@ + + + + + +
+
+ + + + + + + +
+
+
+ diff --git a/quartz-manager-frontend/src/app/component/header/header.component.scss b/quartz-manager-frontend/src/app/component/header/header.component.scss new file mode 100644 index 0000000..122b0bd --- /dev/null +++ b/quartz-manager-frontend/src/app/component/header/header.component.scss @@ -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; + } +} + + diff --git a/quartz-manager-frontend/src/app/component/header/header.component.ts b/quartz-manager-frontend/src/app/component/header/header.component.ts new file mode 100644 index 0000000..c74f625 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/header/header.component.ts @@ -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.firstname + ' ' + user.lastname; + } + +} diff --git a/quartz-manager-frontend/src/app/component/header/index.ts b/quartz-manager-frontend/src/app/component/header/index.ts new file mode 100644 index 0000000..be62c26 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/header/index.ts @@ -0,0 +1 @@ +export * from './header.component'; diff --git a/quartz-manager-frontend/src/app/component/index.ts b/quartz-manager-frontend/src/app/component/index.ts new file mode 100644 index 0000000..3209f56 --- /dev/null +++ b/quartz-manager-frontend/src/app/component/index.ts @@ -0,0 +1,4 @@ +export * from './header'; +export * from './github'; +export * from './footer'; +export * from './api-card'; diff --git a/quartz-manager-frontend/src/app/forbidden/forbidden.component.css b/quartz-manager-frontend/src/app/forbidden/forbidden.component.css new file mode 100644 index 0000000..e69de29 diff --git a/quartz-manager-frontend/src/app/forbidden/forbidden.component.html b/quartz-manager-frontend/src/app/forbidden/forbidden.component.html new file mode 100644 index 0000000..7303f6e --- /dev/null +++ b/quartz-manager-frontend/src/app/forbidden/forbidden.component.html @@ -0,0 +1,3 @@ +

+ Your access doesn't allow!! +

diff --git a/quartz-manager-frontend/src/app/forbidden/forbidden.component.spec.ts b/quartz-manager-frontend/src/app/forbidden/forbidden.component.spec.ts new file mode 100644 index 0000000..792abc5 --- /dev/null +++ b/quartz-manager-frontend/src/app/forbidden/forbidden.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ForbiddenComponent } from './forbidden.component'; + +describe('ForbiddenComponent', () => { + let component: ForbiddenComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ForbiddenComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ForbiddenComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/quartz-manager-frontend/src/app/forbidden/forbidden.component.ts b/quartz-manager-frontend/src/app/forbidden/forbidden.component.ts new file mode 100644 index 0000000..3dd20c0 --- /dev/null +++ b/quartz-manager-frontend/src/app/forbidden/forbidden.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-forbidden', + templateUrl: './forbidden.component.html', + styleUrls: ['./forbidden.component.css'] +}) +export class ForbiddenComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/quartz-manager-frontend/src/app/forbidden/index.ts b/quartz-manager-frontend/src/app/forbidden/index.ts new file mode 100644 index 0000000..f4ef30b --- /dev/null +++ b/quartz-manager-frontend/src/app/forbidden/index.ts @@ -0,0 +1 @@ +export * from './forbidden.component'; diff --git a/quartz-manager-frontend/src/app/guard/admin.guard.spec.ts b/quartz-manager-frontend/src/app/guard/admin.guard.spec.ts new file mode 100644 index 0000000..e6ca05e --- /dev/null +++ b/quartz-manager-frontend/src/app/guard/admin.guard.spec.ts @@ -0,0 +1,31 @@ +import { TestBed, async, inject } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { UserService } from '../service'; +import { AdminGuard } from './admin.guard'; +import { MockUserService } from '../service/mocks'; + +export class RouterStub { + navigate(commands?: any[], extras?: any) {} +} + +describe('AdminGuard', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + AdminGuard, + { + provide: Router, + useClass: RouterStub + } + { + provide: UserService, + useClass: MockUserService + } + ] + }); + }); + + it('should ...', inject([AdminGuard], (guard: AdminGuard) => { + expect(guard).toBeTruthy(); + })); +}); diff --git a/quartz-manager-frontend/src/app/guard/admin.guard.ts b/quartz-manager-frontend/src/app/guard/admin.guard.ts new file mode 100644 index 0000000..a3ec91e --- /dev/null +++ b/quartz-manager-frontend/src/app/guard/admin.guard.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { UserService } from '../service'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class AdminGuard implements CanActivate { + constructor(private router: Router, private userService: UserService) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + if (this.userService.currentUser) { + if (JSON.stringify(this.userService.currentUser.authorities).search('ROLE_ADMIN') !== -1) { + return true; + } else { + this.router.navigate(['/403']); + return false; + } + + } else { + console.log('NOT AN ADMIN ROLE'); + this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }}); + return false; + } + } +} + diff --git a/quartz-manager-frontend/src/app/guard/guest.guard.ts b/quartz-manager-frontend/src/app/guard/guest.guard.ts new file mode 100644 index 0000000..f2c8037 --- /dev/null +++ b/quartz-manager-frontend/src/app/guard/guest.guard.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { Router, CanActivate } from '@angular/router'; +import { UserService } from '../service'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class GuestGuard implements CanActivate { + + constructor(private router: Router, private userService: UserService) {} + + canActivate(): boolean { + if (this.userService.currentUser) { + this.router.navigate(['/']); + return false; + } else { + return true; + } + } +} diff --git a/quartz-manager-frontend/src/app/guard/index.ts b/quartz-manager-frontend/src/app/guard/index.ts new file mode 100644 index 0000000..617f1ea --- /dev/null +++ b/quartz-manager-frontend/src/app/guard/index.ts @@ -0,0 +1,4 @@ +export * from './login.guard'; +export * from './guest.guard'; +export * from './admin.guard'; + diff --git a/quartz-manager-frontend/src/app/guard/login.guard.ts b/quartz-manager-frontend/src/app/guard/login.guard.ts new file mode 100644 index 0000000..b120c4f --- /dev/null +++ b/quartz-manager-frontend/src/app/guard/login.guard.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { Router, CanActivate } from '@angular/router'; +import { UserService } from '../service'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class LoginGuard implements CanActivate { + + constructor(private router: Router, private userService: UserService) {} + + canActivate(): boolean { + if (this.userService.currentUser) { + return true; + } else { + this.router.navigate(['/']); + return false; + } + } +} diff --git a/quartz-manager-frontend/src/app/home/home.component.html b/quartz-manager-frontend/src/app/home/home.component.html new file mode 100644 index 0000000..33eefcf --- /dev/null +++ b/quartz-manager-frontend/src/app/home/home.component.html @@ -0,0 +1,39 @@ +
+ + + + + + + + +
+ diff --git a/quartz-manager-frontend/src/app/home/home.component.scss b/quartz-manager-frontend/src/app/home/home.component.scss new file mode 100644 index 0000000..7192410 --- /dev/null +++ b/quartz-manager-frontend/src/app/home/home.component.scss @@ -0,0 +1,42 @@ +app-api-card { + margin: 0 50px 0 0; + &.last { + margin: 0 0 0 0; + } +} + +app-github { + margin: 50px -70px -50px; +} + +@media screen and (min-width: 600px) and (max-width: 1279px) { + app-api-card { + margin: 0 4px 0 0; + &.last { + margin: 0 0 0 0; + } + } + + app-github { + margin: 20px -30px -20px; + } +} + +@media screen and (max-width: 599px) { + + .content { + /* https://github.com/angular/flex-layout/issues/295 */ + display: block !important; + } + + app-api-card { + /* https://github.com/angular/flex-layout/issues/295 */ + display: block !important; + margin: 0 0 12px 0; + } + + app-github { + margin: 8px -12px -8px; + } + +} diff --git a/quartz-manager-frontend/src/app/home/home.component.spec.ts b/quartz-manager-frontend/src/app/home/home.component.spec.ts new file mode 100644 index 0000000..d4ce3a8 --- /dev/null +++ b/quartz-manager-frontend/src/app/home/home.component.spec.ts @@ -0,0 +1,58 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { HomeComponent } from './home.component'; +import { ApiCardComponent, GithubComponent } from '../component'; +import { MockApiService } from '../service/mocks/api.service.mock'; + +import { + MatButtonModule, + MatCardModule +} from '@angular/material'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +import { + ApiService, + AuthService, + UserService, + FooService, + ConfigService +} from '../service'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + HomeComponent, + ApiCardComponent, + GithubComponent + ], + imports: [ + MatButtonModule, + MatCardModule + ], + providers: [ + { + provide: ApiService, + useClass: MockApiService + }, + AuthService, + UserService, + FooService, + ConfigService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/quartz-manager-frontend/src/app/home/home.component.ts b/quartz-manager-frontend/src/app/home/home.component.ts new file mode 100644 index 0000000..89d27cd --- /dev/null +++ b/quartz-manager-frontend/src/app/home/home.component.ts @@ -0,0 +1,71 @@ +import { Component, OnInit } from '@angular/core'; +import { + FooService, + ConfigService, + UserService +} from '../service'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.scss'] +}) +export class HomeComponent implements OnInit { + + fooResponse = {}; + whoamIResponse = {}; + allUserResponse = {}; + constructor( + private config: ConfigService, + private fooService: FooService, + private userService: UserService + ) { } + + ngOnInit() { + } + + makeRequest(path) { + if (path === this.config.foo_url) { + this.fooService.getFoo() + .subscribe(res => { + this.forgeResonseObj(this.fooResponse, res, path); + }, err => { + this.forgeResonseObj(this.fooResponse, err, path); + }); + } else if (path === this.config.whoami_url) { + this.userService.getMyInfo() + .subscribe(res => { + this.forgeResonseObj(this.whoamIResponse, res, path); + }, err => { + this.forgeResonseObj(this.whoamIResponse, err, path); + }); + } else { + this.userService.getAll() + .subscribe(res => { + this.forgeResonseObj(this.allUserResponse, res, path); + }, err => { + this.forgeResonseObj(this.allUserResponse, err, path); + }); + } + } + + forgeResonseObj(obj, res, path) { + obj['path'] = path; + obj['method'] = 'GET'; + if (res.ok === false) { + // err + obj['status'] = res.status; + try { + obj['body'] = JSON.stringify(JSON.parse(res._body), null, 2); + } catch (err) { + console.log(res); + obj['body'] = res.error.message; + } + } else { + // 200 + obj['status'] = 200; + obj['body'] = JSON.stringify(res, null, 2); + } + } + +} diff --git a/quartz-manager-frontend/src/app/home/index.ts b/quartz-manager-frontend/src/app/home/index.ts new file mode 100644 index 0000000..ab5a522 --- /dev/null +++ b/quartz-manager-frontend/src/app/home/index.ts @@ -0,0 +1 @@ +export * from './home.component'; diff --git a/quartz-manager-frontend/src/app/home_rem/home.component.html b/quartz-manager-frontend/src/app/home_rem/home.component.html new file mode 100644 index 0000000..33eefcf --- /dev/null +++ b/quartz-manager-frontend/src/app/home_rem/home.component.html @@ -0,0 +1,39 @@ +
+ + + + + + + + +
+ diff --git a/quartz-manager-frontend/src/app/home_rem/home.component.scss b/quartz-manager-frontend/src/app/home_rem/home.component.scss new file mode 100644 index 0000000..7192410 --- /dev/null +++ b/quartz-manager-frontend/src/app/home_rem/home.component.scss @@ -0,0 +1,42 @@ +app-api-card { + margin: 0 50px 0 0; + &.last { + margin: 0 0 0 0; + } +} + +app-github { + margin: 50px -70px -50px; +} + +@media screen and (min-width: 600px) and (max-width: 1279px) { + app-api-card { + margin: 0 4px 0 0; + &.last { + margin: 0 0 0 0; + } + } + + app-github { + margin: 20px -30px -20px; + } +} + +@media screen and (max-width: 599px) { + + .content { + /* https://github.com/angular/flex-layout/issues/295 */ + display: block !important; + } + + app-api-card { + /* https://github.com/angular/flex-layout/issues/295 */ + display: block !important; + margin: 0 0 12px 0; + } + + app-github { + margin: 8px -12px -8px; + } + +} diff --git a/quartz-manager-frontend/src/app/home_rem/home.component.spec.ts b/quartz-manager-frontend/src/app/home_rem/home.component.spec.ts new file mode 100644 index 0000000..d4ce3a8 --- /dev/null +++ b/quartz-manager-frontend/src/app/home_rem/home.component.spec.ts @@ -0,0 +1,58 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { HomeComponent } from './home.component'; +import { ApiCardComponent, GithubComponent } from '../component'; +import { MockApiService } from '../service/mocks/api.service.mock'; + +import { + MatButtonModule, + MatCardModule +} from '@angular/material'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +import { + ApiService, + AuthService, + UserService, + FooService, + ConfigService +} from '../service'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + HomeComponent, + ApiCardComponent, + GithubComponent + ], + imports: [ + MatButtonModule, + MatCardModule + ], + providers: [ + { + provide: ApiService, + useClass: MockApiService + }, + AuthService, + UserService, + FooService, + ConfigService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/quartz-manager-frontend/src/app/home_rem/home.component.ts b/quartz-manager-frontend/src/app/home_rem/home.component.ts new file mode 100644 index 0000000..89d27cd --- /dev/null +++ b/quartz-manager-frontend/src/app/home_rem/home.component.ts @@ -0,0 +1,71 @@ +import { Component, OnInit } from '@angular/core'; +import { + FooService, + ConfigService, + UserService +} from '../service'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.scss'] +}) +export class HomeComponent implements OnInit { + + fooResponse = {}; + whoamIResponse = {}; + allUserResponse = {}; + constructor( + private config: ConfigService, + private fooService: FooService, + private userService: UserService + ) { } + + ngOnInit() { + } + + makeRequest(path) { + if (path === this.config.foo_url) { + this.fooService.getFoo() + .subscribe(res => { + this.forgeResonseObj(this.fooResponse, res, path); + }, err => { + this.forgeResonseObj(this.fooResponse, err, path); + }); + } else if (path === this.config.whoami_url) { + this.userService.getMyInfo() + .subscribe(res => { + this.forgeResonseObj(this.whoamIResponse, res, path); + }, err => { + this.forgeResonseObj(this.whoamIResponse, err, path); + }); + } else { + this.userService.getAll() + .subscribe(res => { + this.forgeResonseObj(this.allUserResponse, res, path); + }, err => { + this.forgeResonseObj(this.allUserResponse, err, path); + }); + } + } + + forgeResonseObj(obj, res, path) { + obj['path'] = path; + obj['method'] = 'GET'; + if (res.ok === false) { + // err + obj['status'] = res.status; + try { + obj['body'] = JSON.stringify(JSON.parse(res._body), null, 2); + } catch (err) { + console.log(res); + obj['body'] = res.error.message; + } + } else { + // 200 + obj['status'] = 200; + obj['body'] = JSON.stringify(res, null, 2); + } + } + +} diff --git a/quartz-manager-frontend/src/app/home_rem/index.ts b/quartz-manager-frontend/src/app/home_rem/index.ts new file mode 100644 index 0000000..ab5a522 --- /dev/null +++ b/quartz-manager-frontend/src/app/home_rem/index.ts @@ -0,0 +1 @@ +export * from './home.component'; diff --git a/quartz-manager-frontend/src/app/login/index.ts b/quartz-manager-frontend/src/app/login/index.ts new file mode 100644 index 0000000..69c1644 --- /dev/null +++ b/quartz-manager-frontend/src/app/login/index.ts @@ -0,0 +1 @@ +export * from './login.component'; diff --git a/quartz-manager-frontend/src/app/login/login.component.html b/quartz-manager-frontend/src/app/login/login.component.html new file mode 100644 index 0000000..0a8ed29 --- /dev/null +++ b/quartz-manager-frontend/src/app/login/login.component.html @@ -0,0 +1,43 @@ +
+ + + + +

Angular Spring Starter

+
+ + +

{{title}}

+
+ + + +

{{notification.msgBody}}

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

Created by Fan Jin

+

Click below to go to repository

+ + +
+ +
+ +
diff --git a/quartz-manager-frontend/src/app/login/login.component.scss b/quartz-manager-frontend/src/app/login/login.component.scss new file mode 100644 index 0000000..adf75cd --- /dev/null +++ b/quartz-manager-frontend/src/app/login/login.component.scss @@ -0,0 +1,58 @@ +.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 { + display: block; +} + +mat-spinner { + width: 25px; + height: 25px; + margin: 20px auto 0 auto; +} + +button { + display: block; + width: 100%; +} + +.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; + } + +} + +a { + text-decoration: none; + cursor: auto; + color: #FFFFFF; +} diff --git a/quartz-manager-frontend/src/app/login/login.component.spec.ts b/quartz-manager-frontend/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..c8e8502 --- /dev/null +++ b/quartz-manager-frontend/src/app/login/login.component.spec.ts @@ -0,0 +1,49 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { LoginComponent } from './login.component'; +import { RouterTestingModule } from '@angular/router/testing'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { MockApiService } from '../service/mocks/api.service.mock'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { + ApiService, + AuthService, + UserService, + FooService, + ConfigService +} from '../service'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [LoginComponent], + imports: [ + ReactiveFormsModule, + RouterTestingModule + ], + providers: [ + UserService, + { + provide: ApiService, + useClass: MockApiService + }, + ConfigService, + AuthService + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/quartz-manager-frontend/src/app/login/login.component.ts b/quartz-manager-frontend/src/app/login/login.component.ts new file mode 100644 index 0000000..bc1fe91 --- /dev/null +++ b/quartz-manager-frontend/src/app/login/login.component.ts @@ -0,0 +1,108 @@ +import { Inject } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { DisplayMessage } from '../shared/models/display-message'; +import { Subscription } from 'rxjs/Subscription'; +import { + UserService, + AuthService +} from '../service'; + +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/SUbject'; + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] +}) +export class LoginComponent implements OnInit, OnDestroy { + title = 'Login'; + githubLink = 'https://github.com/bfwg/angular-spring-starter'; + form: FormGroup; + + /** + * Boolean used in telling the UI + * that the form has been submitted + * and is awaiting a response + */ + submitted = false; + + /** + * Notification message from received + * form request or router + */ + notification: DisplayMessage; + + returnUrl: string; + private ngUnsubscribe: Subject = new Subject(); + + constructor( + private userService: UserService, + private authService: AuthService, + private router: Router, + private route: ActivatedRoute, + private formBuilder: FormBuilder + ) { + + } + + ngOnInit() { + this.route.params + .takeUntil(this.ngUnsubscribe) + .subscribe((params: DisplayMessage) => { + this.notification = params; + }); + // get return url from route parameters or default to '/' + this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; + this.form = this.formBuilder.group({ + username: ['', Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(64)])], + password: ['', Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(32)])] + }); + } + + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + + onResetCredentials() { + this.userService.resetCredentials() + .takeUntil(this.ngUnsubscribe) + .subscribe(res => { + if (res.result === 'success') { + alert('Password has been reset to 123 for all accounts'); + } else { + alert('Server error'); + } + }); + } + + repository() { + window.location.href = this.githubLink; + } + + onSubmit() { + /** + * Innocent until proven guilty + */ + this.notification = undefined; + this.submitted = true; + + this.authService.login(this.form.value) + // show me the animation + .delay(1000) + .subscribe(data => { + this.userService.getMyInfo().subscribe(); + this.router.navigate([this.returnUrl]); + }, + error => { + this.submitted = false; + this.notification = { msgType: 'error', msgBody: 'Incorrect username or password.' }; + }); + + } + + +} diff --git a/quartz-manager-frontend/src/app/not-found/index.ts b/quartz-manager-frontend/src/app/not-found/index.ts new file mode 100644 index 0000000..d91ce3f --- /dev/null +++ b/quartz-manager-frontend/src/app/not-found/index.ts @@ -0,0 +1 @@ +export * from './not-found.component'; diff --git a/quartz-manager-frontend/src/app/not-found/not-found.component.css b/quartz-manager-frontend/src/app/not-found/not-found.component.css new file mode 100644 index 0000000..e69de29 diff --git a/quartz-manager-frontend/src/app/not-found/not-found.component.html b/quartz-manager-frontend/src/app/not-found/not-found.component.html new file mode 100644 index 0000000..fa63f06 --- /dev/null +++ b/quartz-manager-frontend/src/app/not-found/not-found.component.html @@ -0,0 +1,5 @@ +
+ +
\ No newline at end of file diff --git a/quartz-manager-frontend/src/app/not-found/not-found.component.spec.ts b/quartz-manager-frontend/src/app/not-found/not-found.component.spec.ts new file mode 100644 index 0000000..1acb86e --- /dev/null +++ b/quartz-manager-frontend/src/app/not-found/not-found.component.spec.ts @@ -0,0 +1,32 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NotFoundComponent } from './not-found.component'; + +describe('NotFoundComponent', () => { + let component: NotFoundComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [NotFoundComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NotFoundComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); + + it('

tag should contains \'Page Not Found\'', () => { + fixture = TestBed.createComponent(NotFoundComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Page Not Found'); + }); +}); diff --git a/quartz-manager-frontend/src/app/not-found/not-found.component.ts b/quartz-manager-frontend/src/app/not-found/not-found.component.ts new file mode 100644 index 0000000..c18f379 --- /dev/null +++ b/quartz-manager-frontend/src/app/not-found/not-found.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + templateUrl: './not-found.component.html' +}) +export class NotFoundComponent { + + constructor() { } + +} diff --git a/quartz-manager-frontend/src/app/polyfills.ts b/quartz-manager-frontend/src/app/polyfills.ts new file mode 100644 index 0000000..622efa0 --- /dev/null +++ b/quartz-manager-frontend/src/app/polyfills.ts @@ -0,0 +1,74 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; +import 'core-js/es7/reflect'; + + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. + +/*************************************************************************************************** + * MATERIAL 2 + */ +import 'hammerjs/hammer'; diff --git a/quartz-manager-frontend/src/app/service/api.service.ts b/quartz-manager-frontend/src/app/service/api.service.ts new file mode 100644 index 0000000..88c0ba9 --- /dev/null +++ b/quartz-manager-frontend/src/app/service/api.service.ts @@ -0,0 +1,76 @@ +import { HttpClient, HttpHeaders, HttpResponse, HttpRequest, HttpEventType, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/Rx'; +import 'rxjs/add/observable/throw'; +import { serialize } from 'app/shared/utilities/serialize'; + +export enum RequestMethod { + Get = 'GET', + Head = 'HEAD', + Post = 'POST', + Put = 'PUT', + Delete = 'DELETE', + Options = 'OPTIONS', + Patch = 'PATCH' +} + +@Injectable() +export class ApiService { + + headers = new HttpHeaders({ + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }); + + constructor( private http: HttpClient) { } + + get(path: string, args?: any): Observable { + const options = { + headers: this.headers, + withCredentials: true + }; + + if (args) { + options['params'] = serialize(args); + } + + return this.http.get(path, options) + .catch(this.checkError.bind(this)); + } + + post(path: string, body: any, customHeaders?: HttpHeaders): Observable { + return this.request(path, body, RequestMethod.Post, customHeaders); + } + + put(path: string, body: any): Observable { + return this.request(path, body, RequestMethod.Put); + } + + delete(path: string, body?: any): Observable { + return this.request(path, body, RequestMethod.Delete); + } + + private request(path: string, body: any, method = RequestMethod.Post, custemHeaders?: HttpHeaders): Observable { + const req = new HttpRequest(method, path, body, { + headers: custemHeaders || this.headers, + withCredentials: true + }); + + return this.http.request(req) + .filter(response => response instanceof HttpResponse) + .map((response: HttpResponse) => response.body) + .catch(error => this.checkError(error)); + } + + // Display error if logged in, otherwise redirect to IDP + private checkError(error: any): any { + if (error && error.status === 401) { + // this.redirectIfUnauth(error); + } else { + // this.displayError(error); + } + throw error; + } + +} diff --git a/quartz-manager-frontend/src/app/service/auth.service.ts b/quartz-manager-frontend/src/app/service/auth.service.ts new file mode 100644 index 0000000..0f0d97a --- /dev/null +++ b/quartz-manager-frontend/src/app/service/auth.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import { HttpHeaders } from '@angular/common/http'; +import { ApiService } from './api.service'; +import { UserService } from './user.service'; +import { ConfigService } from './config.service'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class AuthService { + + constructor( + private apiService: ApiService, + private userService: UserService, + private config: ConfigService, + ) { } + + login(user) { + const loginHeaders = new HttpHeaders({ + 'Accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded' + }); + const body = `username=${user.username}&password=${user.password}`; + return this.apiService.post(this.config.login_url, body, loginHeaders).map(() => { + console.log("Login success"); + this.userService.getMyInfo().subscribe(); + }); + } + + signup(user){ + const signupHeaders = new HttpHeaders({ + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }); + return this.apiService.post(this.config.signup_url, JSON.stringify(user), signupHeaders).map(() =>{ + console.log("Sign up success"); + }); + } + + logout() { + return this.apiService.post(this.config.logout_url, {}) + .map(() => { + this.userService.currentUser = null; + }); + } + + changePassowrd(passwordChanger) { + return this.apiService.post(this.config.change_password_url, passwordChanger); + } + +} diff --git a/quartz-manager-frontend/src/app/service/config.service.ts b/quartz-manager-frontend/src/app/service/config.service.ts new file mode 100644 index 0000000..7de5c30 --- /dev/null +++ b/quartz-manager-frontend/src/app/service/config.service.ts @@ -0,0 +1,65 @@ +import { Injectable } from '@angular/core'; +import { environment } from '../../environments/environment'; + +@Injectable() +export class ConfigService { + + private _api_url = '/quartz-manager/api' + + private _refresh_token_url = this._api_url + '/refresh'; + + private _login_url = this._api_url + '/login'; + + private _logout_url = this._api_url + '/logout'; + + private _change_password_url = this._api_url + '/changePassword'; + + private _whoami_url = this._api_url + '/whoami'; + + private _user_url = this._api_url + '/user'; + + private _users_url = this._user_url + '/all'; + + private _reset_credentials_url = this._user_url + '/reset-credentials'; + + private _foo_url = this._api_url + '/foo'; + + private _signup_url = this._api_url + '/signup'; + + get reset_credentials_url(): string { + return this._reset_credentials_url; + } + + get refresh_token_url(): string { + return this._refresh_token_url; + } + + get whoami_url(): string { + return this._whoami_url; + } + + get users_url(): string { + return this._users_url; + } + + get login_url(): string { + return this._login_url; + } + + get logout_url(): string { + return this._logout_url; + } + + get change_password_url(): string { + return this._change_password_url; + } + + get foo_url(): string { + return this._foo_url; + } + + get signup_url():string { + return this._signup_url; + } + +} diff --git a/quartz-manager-frontend/src/app/service/foo.service.ts b/quartz-manager-frontend/src/app/service/foo.service.ts new file mode 100644 index 0000000..fc926f8 --- /dev/null +++ b/quartz-manager-frontend/src/app/service/foo.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { Headers } from '@angular/http'; +import { ApiService } from './api.service'; +import { ConfigService } from './config.service'; + +@Injectable() +export class FooService { + + constructor( + private apiService: ApiService, + private config: ConfigService + ) { } + + getFoo() { + return this.apiService.get(this.config.foo_url); + } + +} diff --git a/quartz-manager-frontend/src/app/service/index.ts b/quartz-manager-frontend/src/app/service/index.ts new file mode 100644 index 0000000..619a227 --- /dev/null +++ b/quartz-manager-frontend/src/app/service/index.ts @@ -0,0 +1,5 @@ +export * from './api.service'; +export * from './user.service'; +export * from './config.service'; +export * from './auth.service'; +export * from './foo.service'; diff --git a/quartz-manager-frontend/src/app/service/mocks/api.service.mock.ts b/quartz-manager-frontend/src/app/service/mocks/api.service.mock.ts new file mode 100644 index 0000000..b4da49f --- /dev/null +++ b/quartz-manager-frontend/src/app/service/mocks/api.service.mock.ts @@ -0,0 +1,21 @@ +const MockObservable = { + mergeMap: (cb) => { + return cb({ id: 123 }); + }, + toPromise: () => { + return new Promise((resolve, reject) => { + resolve('resolved'); + }); + } +}; + +export class MockApiService { + get(path: string) { + return MockObservable; + } + post(path: string, body) {} + put(path: string, body) {} + anonGet(path: string) { + return MockObservable; + } +} diff --git a/quartz-manager-frontend/src/app/service/mocks/index.ts b/quartz-manager-frontend/src/app/service/mocks/index.ts new file mode 100644 index 0000000..217a722 --- /dev/null +++ b/quartz-manager-frontend/src/app/service/mocks/index.ts @@ -0,0 +1,2 @@ +export * from './api.service.mock'; +export * from './user.service.mock'; diff --git a/quartz-manager-frontend/src/app/service/mocks/user.service.mock.ts b/quartz-manager-frontend/src/app/service/mocks/user.service.mock.ts new file mode 100644 index 0000000..cbd0ac7 --- /dev/null +++ b/quartz-manager-frontend/src/app/service/mocks/user.service.mock.ts @@ -0,0 +1,5 @@ +export class MockUserService { + + currentUser = {}; + +} diff --git a/quartz-manager-frontend/src/app/service/user.service.ts b/quartz-manager-frontend/src/app/service/user.service.ts new file mode 100644 index 0000000..a93c693 --- /dev/null +++ b/quartz-manager-frontend/src/app/service/user.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { Headers } from '@angular/http'; +import { ApiService } from './api.service'; +import { ConfigService } from './config.service'; + +@Injectable() +export class UserService { + + currentUser; + + constructor( + private apiService: ApiService, + private config: ConfigService + ) { } + + initUser() { + const promise = this.apiService.get(this.config.refresh_token_url).toPromise() + .then(res => { + if (res.access_token !== null) { + return this.getMyInfo().toPromise() + .then(user => { + this.currentUser = user; + }); + } + }) + .catch(() => null); + return promise; + } + + resetCredentials() { + return this.apiService.get(this.config.reset_credentials_url); + } + + getMyInfo() { + return this.apiService.get(this.config.whoami_url).map(user => this.currentUser = user); + } + + getAll() { + return this.apiService.get(this.config.users_url); + } + +} diff --git a/quartz-manager-frontend/src/app/shared/models/display-message.ts b/quartz-manager-frontend/src/app/shared/models/display-message.ts new file mode 100644 index 0000000..fec7fe8 --- /dev/null +++ b/quartz-manager-frontend/src/app/shared/models/display-message.ts @@ -0,0 +1,4 @@ +export interface DisplayMessage { + msgType: string; + msgBody: string; +} diff --git a/quartz-manager-frontend/src/app/shared/utilities/loose-invalid.ts b/quartz-manager-frontend/src/app/shared/utilities/loose-invalid.ts new file mode 100644 index 0000000..73d7f63 --- /dev/null +++ b/quartz-manager-frontend/src/app/shared/utilities/loose-invalid.ts @@ -0,0 +1,3 @@ +export function looseInvalid(a: string|number): boolean { + return a === '' || a === null || a === undefined; +} diff --git a/quartz-manager-frontend/src/app/shared/utilities/serialize.ts b/quartz-manager-frontend/src/app/shared/utilities/serialize.ts new file mode 100644 index 0000000..6ab4e2c --- /dev/null +++ b/quartz-manager-frontend/src/app/shared/utilities/serialize.ts @@ -0,0 +1,14 @@ +import { HttpParams, HttpUrlEncodingCodec } from '@angular/common/http'; +import { looseInvalid } from './loose-invalid'; + +export function serialize(obj: any): HttpParams { + let params = new HttpParams(); + + for (const key in obj) { + if (obj.hasOwnProperty(key) && !looseInvalid(obj[key])) { + params = params.set(key, obj[key]); + } + } + + return params; +} diff --git a/quartz-manager-frontend/src/app/signup/index.ts b/quartz-manager-frontend/src/app/signup/index.ts new file mode 100644 index 0000000..704cf8e --- /dev/null +++ b/quartz-manager-frontend/src/app/signup/index.ts @@ -0,0 +1 @@ +export * from './signup.component'; \ No newline at end of file diff --git a/quartz-manager-frontend/src/app/signup/signup.component.html b/quartz-manager-frontend/src/app/signup/signup.component.html new file mode 100644 index 0000000..bb7d3d3 --- /dev/null +++ b/quartz-manager-frontend/src/app/signup/signup.component.html @@ -0,0 +1,56 @@ +
+ + + + +

Angular Spring Starter

+
+ + +

{{ title }}

+
+ + + +

{{notification.msgBody}}

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

+ Created by + Fan Jin + +

+

+ Click below to go to repository +

+ + +
+ +
+ +
\ No newline at end of file diff --git a/quartz-manager-frontend/src/app/signup/signup.component.scss b/quartz-manager-frontend/src/app/signup/signup.component.scss new file mode 100644 index 0000000..0b01b66 --- /dev/null +++ b/quartz-manager-frontend/src/app/signup/signup.component.scss @@ -0,0 +1,59 @@ +.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 { + display: block; + } + + mat-spinner { + width: 25px; + height: 25px; + margin: 20px auto 0 auto; + } + + button { + display: block; + width: 100%; + } + + .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; + } + + } + + a { + text-decoration: none; + cursor: auto; + color: #FFFFFF; + } + \ No newline at end of file diff --git a/quartz-manager-frontend/src/app/signup/signup.component.spec.ts b/quartz-manager-frontend/src/app/signup/signup.component.spec.ts new file mode 100644 index 0000000..43e46a5 --- /dev/null +++ b/quartz-manager-frontend/src/app/signup/signup.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SignupComponent } from './signup.component'; + +describe('SignupComponent', () => { + let component: SignupComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SignupComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SignupComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/quartz-manager-frontend/src/app/signup/signup.component.ts b/quartz-manager-frontend/src/app/signup/signup.component.ts new file mode 100644 index 0000000..6b256d3 --- /dev/null +++ b/quartz-manager-frontend/src/app/signup/signup.component.ts @@ -0,0 +1,102 @@ +import { Inject } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { DisplayMessage } from '../shared/models/display-message'; +import { Subscription } from 'rxjs/Subscription'; +import { + UserService, + AuthService +} from '../service'; + +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/SUbject'; + +@Component({ + selector: 'app-signup', + templateUrl: './signup.component.html', + styleUrls: ['./signup.component.scss'] +}) +export class SignupComponent implements OnInit, OnDestroy { + title = 'Sign up'; + githubLink = 'https://github.com/bfwg/angular-spring-starter'; + form: FormGroup; + + /** + * Boolean used in telling the UI + * that the form has been submitted + * and is awaiting a response + */ + submitted = false; + + /** + * Notification message from received + * form request or router + */ + notification: DisplayMessage; + + returnUrl: string; + private ngUnsubscribe: Subject = new Subject(); + + constructor( + private userService: UserService, + private authService: AuthService, + private router: Router, + private route: ActivatedRoute, + private formBuilder: FormBuilder + ) { + + } + + ngOnInit() { + this.route.params + .takeUntil(this.ngUnsubscribe) + .subscribe((params: DisplayMessage) => { + this.notification = params; + }); + // get return url from route parameters or default to '/' + this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; + this.form = this.formBuilder.group({ + username: ['', Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(64)])], + password: ['', Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(32)])], + firstname:[''], + lastname: [''] + }); + } + + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + + repository() { + window.location.href = this.githubLink; + } + + onSubmit() { + /** + * Innocent until proven guilty + */ + this.notification = undefined; + this.submitted = true; + + this.authService.signup(this.form.value) + // show me the animation + .delay(1000) + .subscribe(data => { + console.log(data); + this.authService.login(this.form.value).subscribe(data =>{ + this.userService.getMyInfo().subscribe(); + }) + this.router.navigate([this.returnUrl]); + }, + error => { + this.submitted = false; + console.log("Sign up error" + JSON.stringify(error)); + this.notification = { msgType: 'error', msgBody: error['error'].errorMessage }; + }); + + } + + +} diff --git a/quartz-manager-frontend/src/assets/.gitkeep b/quartz-manager-frontend/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/quartz-manager-frontend/src/assets/image/admin.png b/quartz-manager-frontend/src/assets/image/admin.png new file mode 100644 index 0000000..d57ddb1 Binary files /dev/null and b/quartz-manager-frontend/src/assets/image/admin.png differ diff --git a/quartz-manager-frontend/src/assets/image/angular-white-transparent.svg b/quartz-manager-frontend/src/assets/image/angular-white-transparent.svg new file mode 100644 index 0000000..25e1477 --- /dev/null +++ b/quartz-manager-frontend/src/assets/image/angular-white-transparent.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/quartz-manager-frontend/src/assets/image/foo.png b/quartz-manager-frontend/src/assets/image/foo.png new file mode 100644 index 0000000..0bc9a89 Binary files /dev/null and b/quartz-manager-frontend/src/assets/image/foo.png differ diff --git a/quartz-manager-frontend/src/assets/image/github.png b/quartz-manager-frontend/src/assets/image/github.png new file mode 100644 index 0000000..628da97 Binary files /dev/null and b/quartz-manager-frontend/src/assets/image/github.png differ diff --git a/quartz-manager-frontend/src/assets/image/user.png b/quartz-manager-frontend/src/assets/image/user.png new file mode 100644 index 0000000..5593710 Binary files /dev/null and b/quartz-manager-frontend/src/assets/image/user.png differ diff --git a/quartz-manager-frontend/src/environments/environment.prod.ts b/quartz-manager-frontend/src/environments/environment.prod.ts new file mode 100644 index 0000000..3612073 --- /dev/null +++ b/quartz-manager-frontend/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/quartz-manager-frontend/src/environments/environment.ts b/quartz-manager-frontend/src/environments/environment.ts new file mode 100644 index 0000000..b7f639a --- /dev/null +++ b/quartz-manager-frontend/src/environments/environment.ts @@ -0,0 +1,8 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const environment = { + production: false +}; diff --git a/quartz-manager-frontend/src/favicon.ico b/quartz-manager-frontend/src/favicon.ico new file mode 100644 index 0000000..8081c7c Binary files /dev/null and b/quartz-manager-frontend/src/favicon.ico differ diff --git a/quartz-manager-frontend/src/index.html b/quartz-manager-frontend/src/index.html new file mode 100644 index 0000000..013a5dd --- /dev/null +++ b/quartz-manager-frontend/src/index.html @@ -0,0 +1,23 @@ + + + + + Quartz Manager + + + + + + + + + + + + + + + + Loading... + + diff --git a/quartz-manager-frontend/src/main.ts b/quartz-manager-frontend/src/main.ts new file mode 100644 index 0000000..a9ca1ca --- /dev/null +++ b/quartz-manager-frontend/src/main.ts @@ -0,0 +1,11 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/quartz-manager-frontend/src/polyfills.ts b/quartz-manager-frontend/src/polyfills.ts new file mode 100644 index 0000000..622efa0 --- /dev/null +++ b/quartz-manager-frontend/src/polyfills.ts @@ -0,0 +1,74 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; +import 'core-js/es7/reflect'; + + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. + +/*************************************************************************************************** + * MATERIAL 2 + */ +import 'hammerjs/hammer'; diff --git a/quartz-manager-frontend/src/styles.css b/quartz-manager-frontend/src/styles.css new file mode 100644 index 0000000..dcbd1da --- /dev/null +++ b/quartz-manager-frontend/src/styles.css @@ -0,0 +1,6 @@ +/* You can add global styles to this file, and also import other style files */ +@import '~@angular/material/prebuilt-themes/deeppurple-amber.css'; + +body { + margin: 0; +} diff --git a/quartz-manager-frontend/src/test.ts b/quartz-manager-frontend/src/test.ts new file mode 100644 index 0000000..9bf7226 --- /dev/null +++ b/quartz-manager-frontend/src/test.ts @@ -0,0 +1,32 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare var __karma__: any; +declare var require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/quartz-manager-frontend/src/tsconfig.app.json b/quartz-manager-frontend/src/tsconfig.app.json new file mode 100644 index 0000000..5e2507d --- /dev/null +++ b/quartz-manager-frontend/src/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "es2015", + "baseUrl": "", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/quartz-manager-frontend/src/tsconfig.spec.json b/quartz-manager-frontend/src/tsconfig.spec.json new file mode 100644 index 0000000..510e3f1 --- /dev/null +++ b/quartz-manager-frontend/src/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "module": "commonjs", + "target": "es5", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/quartz-manager-frontend/src/typings.d.ts b/quartz-manager-frontend/src/typings.d.ts new file mode 100644 index 0000000..ef5c7bd --- /dev/null +++ b/quartz-manager-frontend/src/typings.d.ts @@ -0,0 +1,5 @@ +/* SystemJS module definition */ +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/quartz-manager-frontend/tsconfig.json b/quartz-manager-frontend/tsconfig.json new file mode 100644 index 0000000..a35a8ee --- /dev/null +++ b/quartz-manager-frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "baseUrl": "src", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2016", + "dom" + ] + } +} diff --git a/quartz-manager-frontend/tslint.json b/quartz-manager-frontend/tslint.json new file mode 100644 index 0000000..9113f13 --- /dev/null +++ b/quartz-manager-frontend/tslint.json @@ -0,0 +1,116 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "import-blacklist": [true, "rxjs"], + "import-spacing": true, + "indent": [ + true, + "spaces" + ], + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + "static-before-instance", + "variables-before-functions" + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [true, "ignore-params"], + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + + "directive-selector": [true, "attribute", "app", "camelCase"], + "component-selector": [true, "element", "app", "kebab-case"], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true, + "no-access-missing-member": true, + "templates-use-public": true, + "invoke-injectable": true + } +} diff --git a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/MVCConfig.java b/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/MVCConfig.java deleted file mode 100644 index a1ec413..0000000 --- a/quartz-manager/src/main/java/it/fabioformosa/quartzmanager/configuration/MVCConfig.java +++ /dev/null @@ -1,57 +0,0 @@ -package it.fabioformosa.quartzmanager.configuration; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.ViewResolver; -import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; -import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect; -import org.thymeleaf.spring4.SpringTemplateEngine; -import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver; -import org.thymeleaf.spring4.view.ThymeleafViewResolver; - -import nz.net.ultraq.thymeleaf.LayoutDialect; - -@Configuration -public class MVCConfig extends WebMvcConfigurerAdapter { - - @Override - public void addViewControllers(ViewControllerRegistry registry) { - registry.addViewController("/login").setViewName("login"); - registry.addViewController("/").setViewName("redirect:/manager"); - - registry.addViewController("/templates/manager/config-form.html").setViewName("manager/config-form"); - registry.addViewController("/templates/manager/progress-panel.html") - .setViewName("manager/progress-panel"); - registry.addViewController("/templates/manager/logs-panel.html").setViewName("manager/logs-panel"); - - } - - @Bean - public SpringTemplateEngine templateEngine() { - final SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine(); - springTemplateEngine.addTemplateResolver(templateResolver()); - springTemplateEngine.addDialect(new LayoutDialect()); - springTemplateEngine.addDialect(new SpringSecurityDialect()); - return springTemplateEngine; - } - - @Bean - public SpringResourceTemplateResolver templateResolver() { - final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); - templateResolver.setCacheable(false); - templateResolver.setPrefix("classpath:/templates/"); - templateResolver.setSuffix(".html"); - templateResolver.setTemplateMode("HTML5"); - return templateResolver; - } - - @Bean - public ViewResolver viewResolver() { - ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); - viewResolver.setTemplateEngine(templateEngine()); - viewResolver.setOrder(1); - return viewResolver; - } - -}