Compare commits
	
		
			10 Commits
		
	
	
		
			f07f8f3c06
			...
			3535dbf237
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 3535dbf237 | ||
|  | 4b8822e5ad | ||
|  | 094fa3f47a | ||
|  | 698b2e6dd5 | ||
|  | c84ef5800a | ||
|  | 3b8e0e4074 | ||
|  | 485bc5e8c3 | ||
|  | e7058a8b02 | ||
|  | 177a97d294 | ||
|  | 18d3efb87d | 
							
								
								
									
										21
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								build.gradle
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| plugins { | plugins { | ||||||
| 	id 'org.springframework.boot' version '3.1.1' | 	id 'org.springframework.boot' version '3.1.3' | ||||||
| 	id 'java' | 	id 'java' | ||||||
| 	id 'war' | 	id 'war' | ||||||
| } | } | ||||||
| @@ -7,7 +7,7 @@ plugins { | |||||||
| apply plugin: 'io.spring.dependency-management' | apply plugin: 'io.spring.dependency-management' | ||||||
|  |  | ||||||
| group = 'de.jottyfan' | group = 'de.jottyfan' | ||||||
| version = '1.2.9' | version = '1.3.3' | ||||||
|  |  | ||||||
| description = """timetrack""" | description = """timetrack""" | ||||||
|  |  | ||||||
| @@ -27,11 +27,11 @@ dependencies { | |||||||
| 	implementation 'org.apache.logging.log4j:log4j-core:2.20.0' | 	implementation 'org.apache.logging.log4j:log4j-core:2.20.0' | ||||||
| 	implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.20.0' | 	implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.20.0' | ||||||
|  |  | ||||||
| 	implementation 'org.webjars:bootstrap:5.2.3' | 	implementation 'org.webjars:bootstrap:5.3.1' | ||||||
| 	implementation 'org.webjars:font-awesome:5.15.4' | 	implementation 'org.webjars:font-awesome:6.4.2' | ||||||
| 	implementation 'org.webjars:jquery:3.6.4' | 	implementation 'org.webjars:jquery:3.7.1' | ||||||
| 	implementation 'org.webjars:popper.js:2.9.3' | 	implementation 'org.webjars:popper.js:2.11.7' | ||||||
| 	implementation 'org.webjars:datatables:1.13.2' | 	implementation 'org.webjars:datatables:1.13.5' | ||||||
|   implementation 'org.webjars:jquery-ui:1.13.2' |   implementation 'org.webjars:jquery-ui:1.13.2' | ||||||
|   implementation 'org.webjars:fullcalendar:5.11.3' |   implementation 'org.webjars:fullcalendar:5.11.3' | ||||||
|    |    | ||||||
| @@ -42,14 +42,14 @@ dependencies { | |||||||
| 	implementation 'org.springframework.boot:spring-boot-starter-jooq' | 	implementation 'org.springframework.boot:spring-boot-starter-jooq' | ||||||
| 	implementation 'org.springframework.boot:spring-boot-starter-security' | 	implementation 'org.springframework.boot:spring-boot-starter-security' | ||||||
|   implementation "org.springframework.boot:spring-boot-starter-oauth2-client" |   implementation "org.springframework.boot:spring-boot-starter-oauth2-client" | ||||||
|   implementation 'org.springframework.security:spring-security-oauth2-authorization-server:1.1.1' |   implementation 'org.springframework.security:spring-security-oauth2-authorization-server:1.1.2' | ||||||
| 	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' | 	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' | ||||||
| 	implementation 'org.springframework.boot:spring-boot-starter-web' | 	implementation 'org.springframework.boot:spring-boot-starter-web' | ||||||
| 	implementation 'org.springframework.boot:spring-boot-starter-test' | 	implementation 'org.springframework.boot:spring-boot-starter-test' | ||||||
| 	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' | 	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' | ||||||
| 	implementation 'de.jottyfan:timetrackjooq:0.1.1' | 	implementation 'de.jottyfan:timetrackjooq:0.1.2' | ||||||
|  |  | ||||||
| 	implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.0.0' | 	implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.2.1' | ||||||
|  |  | ||||||
| 	developmentOnly 'org.springframework.boot:spring-boot-devtools' | 	developmentOnly 'org.springframework.boot:spring-boot-devtools' | ||||||
| 	runtimeOnly 'org.postgresql:postgresql' | 	runtimeOnly 'org.postgresql:postgresql' | ||||||
| @@ -67,6 +67,7 @@ war { | |||||||
|     } |     } | ||||||
|     baseName = project.name |     baseName = project.name | ||||||
|     version = version |     version = version | ||||||
|  |     archiveName = 'timetrack.war' | ||||||
| } | } | ||||||
|  |  | ||||||
| test { | test { | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								debian/build.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								debian/build.sh
									
									
									
									
										vendored
									
									
								
							| @@ -1,20 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| mkdir -p timetrack/var/lib |  | ||||||
|  |  | ||||||
| cd .. |  | ||||||
| ./gradlew clean build |  | ||||||
|  |  | ||||||
| G=$(grep "^version =" build.gradle | sed -e "s/version = //g" | sed -e "s/'//g") |  | ||||||
|  |  | ||||||
| echo "found version $G" |  | ||||||
|  |  | ||||||
| cp -v build/libs/timetrack-$G.jar debian/timetrack/var/lib/timetrack.jar |  | ||||||
|  |  | ||||||
| cd debian |  | ||||||
|  |  | ||||||
| sed -i timetrack/DEBIAN/control -e "s/Version: */Version: $G/" |  | ||||||
|  |  | ||||||
| V=$(grep "Version" timetrack/DEBIAN/control | sed -e "s/Version: //g") |  | ||||||
|  |  | ||||||
| fakeroot dpkg -b timetrack timetrack_$V.deb |  | ||||||
							
								
								
									
										1
									
								
								debian/timetrack/DEBIAN/conffiles
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								debian/timetrack/DEBIAN/conffiles
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | |||||||
| /etc/timetrack.properties |  | ||||||
							
								
								
									
										9
									
								
								debian/timetrack/DEBIAN/control
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								debian/timetrack/DEBIAN/control
									
									
									
									
										vendored
									
									
								
							| @@ -1,9 +0,0 @@ | |||||||
| Package: timetrack |  | ||||||
| Version:  |  | ||||||
| Architecture: amd64 |  | ||||||
| Maintainer: Jörg Henke <jottyfan@gmx.de> |  | ||||||
| Depends: default-jdk |  | ||||||
| Section: ship |  | ||||||
| Priority: optional |  | ||||||
| Description: timetrack application to track work times |  | ||||||
|  Timetrack is a web application to track my work times for my daily reporting. |  | ||||||
							
								
								
									
										24
									
								
								debian/timetrack/DEBIAN/postinst
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								debian/timetrack/DEBIAN/postinst
									
									
									
									
										vendored
									
									
								
							| @@ -1,24 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| mkdir -p /var/run/timetrack |  | ||||||
| groupadd timetrack || true |  | ||||||
| useradd -r -g timetrack -d /etc/timetrack -s /sbin/nologin timetrack || true |  | ||||||
| chown timetrack:timetrack -R /etc/timetrack* || true |  | ||||||
| chown timetrack:timetrack -R /var/run/timetrack || true |  | ||||||
| chown timetrack:timetrack /usr/bin/timetrack || true |  | ||||||
| cd /var/lib |  | ||||||
|  |  | ||||||
| systemctl daemon-reload || true |  | ||||||
| R=$(systemctl show -p SubState --value timetrack) |  | ||||||
|  |  | ||||||
| if [ "running" == "$R" ] |  | ||||||
| then |  | ||||||
|   systemctl restart timetrack || true |  | ||||||
|   systemctl reload apache2 || true |  | ||||||
| else |  | ||||||
|   systemctl enable timetrack || true |  | ||||||
|   echo "+------------------------------------------------------------------------------+" |  | ||||||
|   echo "| configure timetrack in /etc/timetrack.properties; consider a port change...  |" |  | ||||||
|   echo "| start timetrack by calling sudo systemctl restart timetrack                  |" |  | ||||||
|   echo "+------------------------------------------------------------------------------+" |  | ||||||
| fi |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| [Unit] |  | ||||||
| Description=Timetrack |  | ||||||
| After=syslog.target network.target |  | ||||||
| Before=httpd.service |  | ||||||
|  |  | ||||||
| [Service] |  | ||||||
| User=timetrack |  | ||||||
| Group=timetrack |  | ||||||
| PIDFile=/var/run/timetrack/timetrack.pid |  | ||||||
| ExecStart=/usr/bin/timetrack |  | ||||||
| StandardOutput=null |  | ||||||
|  |  | ||||||
| [Install] |  | ||||||
| WantedBy=multi-user.target |  | ||||||
							
								
								
									
										21
									
								
								debian/timetrack/etc/timetrack.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								debian/timetrack/etc/timetrack.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,21 +0,0 @@ | |||||||
| # jooq |  | ||||||
| spring.datasource.driver-class-name=org.postgresql.Driver |  | ||||||
| spring.datasource.url=jdbc:postgresql://localhost:5432/timetrack |  | ||||||
| spring.datasource.username=timetrack |  | ||||||
| spring.datasource.password=timetrack |  | ||||||
|  |  | ||||||
| # application |  | ||||||
| server.port = 8083 |  | ||||||
|  |  | ||||||
| server.servlet.context-path=/timetrack |  | ||||||
|  |  | ||||||
| # keycloak |  | ||||||
| keycloak.auth-server-url = http://localhost:8080/ |  | ||||||
| keycloak.realm = jottyfan |  | ||||||
| keycloak.resource = timetrack |  | ||||||
| keycloak.public-client = true |  | ||||||
| keycloak.security-constraints[0].authRoles[0] = timetrack_user |  | ||||||
| keycloak.security-constraints[0].securityCollections[0].patterns[0] = /* |  | ||||||
| #keycloak.credentia |  | ||||||
| keycloak.use-resource-role-mappings=true |  | ||||||
| #keycloak.bearer-only=true |  | ||||||
							
								
								
									
										3
									
								
								debian/timetrack/usr/bin/timetrack
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								debian/timetrack/usr/bin/timetrack
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| java -Dspring.config.location=/etc/timetrack.properties -jar /var/lib/timetrack.jar |  | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | package de.jottyfan.timetrack.component; | ||||||
|  |  | ||||||
|  | import org.springframework.security.core.Authentication; | ||||||
|  | import org.springframework.security.core.context.SecurityContextHolder; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @author jotty | ||||||
|  |  *  | ||||||
|  |  */ | ||||||
|  | @Component | ||||||
|  | public class OAuth2Provider { | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * get the name of the authenticated user or null if not logged in | ||||||
|  | 	 *  | ||||||
|  | 	 * @return the name or null | ||||||
|  | 	 */ | ||||||
|  | 	public String getName() { | ||||||
|  | 		Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); | ||||||
|  | 		return authentication == null ? null : authentication.getName(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,78 @@ | |||||||
|  | package de.jottyfan.timetrack.config; | ||||||
|  |  | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  |  | ||||||
|  | import org.springframework.beans.factory.annotation.Value; | ||||||
|  | import org.springframework.context.annotation.Bean; | ||||||
|  | import org.springframework.context.annotation.Configuration; | ||||||
|  | import org.springframework.security.core.GrantedAuthority; | ||||||
|  | import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||||||
|  | import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; | ||||||
|  | import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; | ||||||
|  | import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @author jotty | ||||||
|  |  *  | ||||||
|  |  */ | ||||||
|  | @Configuration | ||||||
|  | public class AuthorizationConfiguration { | ||||||
|  |  | ||||||
|  | 	private static final String REALM_ACCESS_CLAIM = "realm_access"; | ||||||
|  | 	private static final String ROLES_CLAIM = "roles"; | ||||||
|  | 	private static final String RESOURCE_ACCESS_CLAIM = "resource_access"; | ||||||
|  |  | ||||||
|  | 	@Value("${spring.security.oauth2.client.registration.keycloak.client-id}") | ||||||
|  | 	private String clientId; | ||||||
|  |  | ||||||
|  | 	@Bean | ||||||
|  | 	GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() { | ||||||
|  | 		return authorities -> { | ||||||
|  | 			Set<GrantedAuthority> mappedAuthorities = new HashSet<>(); | ||||||
|  | 			var authority = authorities.iterator().next(); | ||||||
|  | 			boolean isOidc = authority instanceof OidcUserAuthority; | ||||||
|  |  | ||||||
|  | 			if (isOidc) { | ||||||
|  | 				var oidcUserAuthority = (OidcUserAuthority) authority; | ||||||
|  | 				var userInfo = oidcUserAuthority.getUserInfo(); | ||||||
|  |  | ||||||
|  | 				if (userInfo.hasClaim(REALM_ACCESS_CLAIM)) { | ||||||
|  | 					var realmAccess = userInfo.getClaimAsMap(REALM_ACCESS_CLAIM); | ||||||
|  | 					@SuppressWarnings("unchecked") | ||||||
|  | 					var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM); | ||||||
|  | 					mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles)); | ||||||
|  | 				} | ||||||
|  | 				if (userInfo.hasClaim(RESOURCE_ACCESS_CLAIM)) { | ||||||
|  | 					var resourceAccess = userInfo.getClaimAsMap(RESOURCE_ACCESS_CLAIM); | ||||||
|  | 					if (resourceAccess.containsKey(clientId)) { | ||||||
|  | 						@SuppressWarnings("unchecked") | ||||||
|  | 						var roles = (Collection<String>) ((Map<?, ?>) resourceAccess.get(clientId)).get(ROLES_CLAIM); | ||||||
|  | 						mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles)); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				var oauth2UserAuthority = (OAuth2UserAuthority) authority; | ||||||
|  | 				Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes(); | ||||||
|  |  | ||||||
|  | 				if (userAttributes.containsKey(REALM_ACCESS_CLAIM)) { | ||||||
|  | 					@SuppressWarnings("unchecked") | ||||||
|  | 					var realmAccess = (Map<String, Object>) userAttributes.get(REALM_ACCESS_CLAIM); | ||||||
|  | 					@SuppressWarnings("unchecked") | ||||||
|  | 					var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM); | ||||||
|  | 					mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles)); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return mappedAuthorities; | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private Collection<GrantedAuthority> generateAuthoritiesFromClaim(Collection<String> roles) { | ||||||
|  | 		return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(Collectors.toList()); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -47,7 +47,6 @@ public class InitialConfiguration { | |||||||
| 		DefaultConfiguration jooqConfiguration = new DefaultConfiguration(); | 		DefaultConfiguration jooqConfiguration = new DefaultConfiguration(); | ||||||
| 		jooqConfiguration.set(connectionProvider()); | 		jooqConfiguration.set(connectionProvider()); | ||||||
| 		jooqConfiguration.set(SQLDialect.POSTGRES); | 		jooqConfiguration.set(SQLDialect.POSTGRES); | ||||||
| //		jooqConfiguration.set(new DefaultExecuteListenerProvider(exceptionTransformer())); |  | ||||||
| 		return jooqConfiguration; | 		return jooqConfiguration; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,27 +1,21 @@ | |||||||
| package de.jottyfan.timetrack.config; | package de.jottyfan.timetrack.config; | ||||||
|  |  | ||||||
| import org.springframework.beans.factory.annotation.Value; |  | ||||||
|  |  | ||||||
| import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||||
| import org.springframework.context.annotation.Configuration; | import org.springframework.context.annotation.Configuration; | ||||||
| import org.springframework.core.convert.converter.Converter; | import org.springframework.security.config.Customizer; | ||||||
| import org.springframework.security.authentication.AbstractAuthenticationToken; |  | ||||||
| import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; | ||||||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||||||
| import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||||||
| import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler; | import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler; | ||||||
| import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; | import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; | ||||||
| import org.springframework.security.oauth2.jwt.Jwt; |  | ||||||
| import org.springframework.security.oauth2.jwt.JwtDecoder; |  | ||||||
| import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; |  | ||||||
| import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; |  | ||||||
| import org.springframework.security.web.SecurityFilterChain; | import org.springframework.security.web.SecurityFilterChain; | ||||||
|  | import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; | ||||||
| import de.jottyfan.timetrack.config.converter.KeycloakRealmRoleConverter; | import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; | ||||||
|  | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
|  * @author henkej |  * @author jotty | ||||||
|  *  |  *  | ||||||
|  */ |  */ | ||||||
| @Configuration | @Configuration | ||||||
| @@ -29,8 +23,10 @@ import de.jottyfan.timetrack.config.converter.KeycloakRealmRoleConverter; | |||||||
| @EnableMethodSecurity | @EnableMethodSecurity | ||||||
| public class SecurityConfiguration { | public class SecurityConfiguration { | ||||||
|  |  | ||||||
| 	@Value("${spring.security.oauth2.client.provider.keycloak.jwk-set-uri}") | 	@Bean | ||||||
| 	private String jwtSetUri; | 	protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { | ||||||
|  | 		return new NullAuthenticatedSessionStrategy(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	@Bean | 	@Bean | ||||||
| 	public SecurityFilterChain securityFilterChain(HttpSecurity sec, InMemoryClientRegistrationRepository crr) | 	public SecurityFilterChain securityFilterChain(HttpSecurity sec, InMemoryClientRegistrationRepository crr) | ||||||
| @@ -39,20 +35,10 @@ public class SecurityConfiguration { | |||||||
| 		// @formatter:off | 		// @formatter:off | ||||||
| 			.oauth2Login(o -> o.defaultSuccessUrl("/")) | 			.oauth2Login(o -> o.defaultSuccessUrl("/")) | ||||||
| 			.logout(o -> o.logoutSuccessHandler(new OidcClientInitiatedLogoutSuccessHandler(crr))) | 			.logout(o -> o.logoutSuccessHandler(new OidcClientInitiatedLogoutSuccessHandler(crr))) | ||||||
| 		  .authorizeHttpRequests(o -> o.requestMatchers("/public/**").permitAll().anyRequest().authenticated()) | 		  .authorizeHttpRequests(o -> o.requestMatchers(AntPathRequestMatcher.antMatcher("/public/**"), AntPathRequestMatcher.antMatcher("/theme/**")).permitAll().anyRequest().authenticated()) | ||||||
| 		  .oauth2ResourceServer(o -> o.jwt(j -> j.jwtAuthenticationConverter(getConverter()))); | 		  .oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults())) | ||||||
|  | 		  .sessionManagement(o -> o.init(sec)); | ||||||
| 		// @formatter:on | 		// @formatter:on | ||||||
| 		return sec.build(); | 		return sec.build(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	private Converter<Jwt, ? extends AbstractAuthenticationToken> getConverter() { |  | ||||||
| 		JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter(); |  | ||||||
| 		jwtConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter()); |  | ||||||
| 		return jwtConverter; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	@Bean |  | ||||||
| 	JwtDecoder jwtDecoder() { |  | ||||||
| 		return NimbusJwtDecoder.withJwkSetUri(this.jwtSetUri).build(); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| package de.jottyfan.timetrack.config.converter; |  | ||||||
|  |  | ||||||
| import java.util.Collection; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Map; |  | ||||||
| import java.util.stream.Collectors; |  | ||||||
|  |  | ||||||
| import org.springframework.core.convert.converter.Converter; |  | ||||||
| import org.springframework.security.core.GrantedAuthority; |  | ||||||
| import org.springframework.security.core.authority.SimpleGrantedAuthority; |  | ||||||
| import org.springframework.security.oauth2.jwt.Jwt; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  *  |  | ||||||
|  * @author henkej |  | ||||||
|  *  |  | ||||||
|  */ |  | ||||||
| public class KeycloakRealmRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>> { |  | ||||||
| 	@Override |  | ||||||
| 	public Collection<GrantedAuthority> convert(Jwt jwt) { |  | ||||||
| 		Object o = jwt.getClaims().get("realm_access"); |  | ||||||
| 		@SuppressWarnings("unchecked") |  | ||||||
| 		final Map<String, Object> realmAccess = (Map<String, Object>) o; |  | ||||||
| 		Object o2 = realmAccess.get("roles"); |  | ||||||
| 		@SuppressWarnings("unchecked") |  | ||||||
| 		List<String> l = (List<String>) o2; |  | ||||||
| 		return l.stream().map(roleName -> "ROLE_" + roleName).map(SimpleGrantedAuthority::new).collect(Collectors.toList()); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -14,10 +14,12 @@ import org.springframework.web.bind.annotation.GetMapping; | |||||||
| import org.springframework.web.bind.annotation.ModelAttribute; | import org.springframework.web.bind.annotation.ModelAttribute; | ||||||
| import org.springframework.web.bind.annotation.RequestMapping; | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
|  |  | ||||||
|  | import de.jottyfan.timetrack.component.OAuth2Provider; | ||||||
| import de.jottyfan.timetrack.modules.done.DoneBean; | import de.jottyfan.timetrack.modules.done.DoneBean; | ||||||
| import de.jottyfan.timetrack.modules.done.DoneModel; | import de.jottyfan.timetrack.modules.done.DoneModel; | ||||||
| import de.jottyfan.timetrack.modules.done.DoneService; | import de.jottyfan.timetrack.modules.done.DoneService; | ||||||
| import de.jottyfan.timetrack.modules.done.SummaryBean; | import de.jottyfan.timetrack.modules.done.SummaryBean; | ||||||
|  | import de.jottyfan.timetrack.modules.profile.ProfileService; | ||||||
| import jakarta.annotation.security.RolesAllowed; | import jakarta.annotation.security.RolesAllowed; | ||||||
| import jakarta.servlet.ServletException; | import jakarta.servlet.ServletException; | ||||||
| import jakarta.servlet.http.HttpServletRequest; | import jakarta.servlet.http.HttpServletRequest; | ||||||
| @@ -34,6 +36,12 @@ public class IndexController { | |||||||
| 	@Autowired | 	@Autowired | ||||||
| 	private DoneService doneService; | 	private DoneService doneService; | ||||||
| 	 | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private OAuth2Provider provider; | ||||||
|  |  | ||||||
|  | 	@Autowired | ||||||
|  | 	private ProfileService profileService; | ||||||
|  | 	 | ||||||
| 	@GetMapping("/logout") | 	@GetMapping("/logout") | ||||||
| 	public String getLogout(HttpServletRequest request) throws ServletException { | 	public String getLogout(HttpServletRequest request) throws ServletException { | ||||||
| 		request.logout(); | 		request.logout(); | ||||||
| @@ -43,11 +51,12 @@ public class IndexController { | |||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@RequestMapping("/") | 	@RequestMapping("/") | ||||||
| 	public String getIndex(@ModelAttribute DoneModel doneModel, Model model, OAuth2AuthenticationToken token) { | 	public String getIndex(@ModelAttribute DoneModel doneModel, Model model, OAuth2AuthenticationToken token) { | ||||||
| 		String username = doneService.getCurrentUser(token); | 		String username = provider.getName(); | ||||||
| 		Duration maxWorkTime = Duration.ofHours(8); // TODO: to the configuration file | 		Duration maxWorkTime = Duration.ofHours(8); // TODO: to the configuration file | ||||||
| 		LocalDate day = LocalDate.now(); | 		LocalDate day = LocalDate.now(); | ||||||
| 		List<DoneBean> list = doneService.getList(day, username); | 		List<DoneBean> list = doneService.getList(day, username); | ||||||
| 		model.addAttribute("sum", new SummaryBean(list, day, maxWorkTime)); | 		model.addAttribute("sum", new SummaryBean(list, day, maxWorkTime)); | ||||||
|  | 		model.addAttribute("theme", profileService.getTheme(username)); | ||||||
| 		LOGGER.debug("sum = {}", model.getAttribute("sum")); | 		LOGGER.debug("sum = {}", model.getAttribute("sum")); | ||||||
| 		return "public/index"; | 		return "public/index"; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -5,6 +5,9 @@ import org.springframework.stereotype.Controller; | |||||||
| import org.springframework.ui.Model; | import org.springframework.ui.Model; | ||||||
| import org.springframework.web.bind.annotation.GetMapping; | import org.springframework.web.bind.annotation.GetMapping; | ||||||
|  |  | ||||||
|  | import de.jottyfan.timetrack.component.OAuth2Provider; | ||||||
|  | import de.jottyfan.timetrack.modules.profile.ProfileService; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
|  * @author jotty |  * @author jotty | ||||||
| @@ -13,12 +16,19 @@ import org.springframework.web.bind.annotation.GetMapping; | |||||||
| @Controller | @Controller | ||||||
| public class CalendarController { | public class CalendarController { | ||||||
|  |  | ||||||
|  | 	@Autowired | ||||||
|  | 	private OAuth2Provider provider; | ||||||
|  | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private ProfileService profileService; | ||||||
|  | 	 | ||||||
| 	@Autowired | 	@Autowired | ||||||
| 	private CalendarService service; | 	private CalendarService service; | ||||||
| 	 | 	 | ||||||
| 	@GetMapping("/calendar") | 	@GetMapping("/calendar") | ||||||
| 	public String getCalendar(Model model) { | 	public String getCalendar(Model model) { | ||||||
| 		model.addAttribute("events", service.getJsonEvents()); | 		model.addAttribute("events", service.getJsonEvents()); | ||||||
|  | 		model.addAttribute("theme", profileService.getTheme(provider.getName())); | ||||||
| 		return "/calendar/calendar"; | 		return "/calendar/calendar"; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,7 +14,9 @@ import org.springframework.web.bind.annotation.RequestMapping; | |||||||
| import org.springframework.web.bind.annotation.RequestMethod; | import org.springframework.web.bind.annotation.RequestMethod; | ||||||
| import org.springframework.web.bind.annotation.ResponseBody; | import org.springframework.web.bind.annotation.ResponseBody; | ||||||
|  |  | ||||||
|  | import de.jottyfan.timetrack.component.OAuth2Provider; | ||||||
| import de.jottyfan.timetrack.db.contact.enums.EnumContacttype; | import de.jottyfan.timetrack.db.contact.enums.EnumContacttype; | ||||||
|  | import de.jottyfan.timetrack.modules.profile.ProfileService; | ||||||
| import jakarta.annotation.security.RolesAllowed; | import jakarta.annotation.security.RolesAllowed; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -25,6 +27,12 @@ import jakarta.annotation.security.RolesAllowed; | |||||||
| @Controller | @Controller | ||||||
| public class ContactController { | public class ContactController { | ||||||
|  |  | ||||||
|  | 	@Autowired | ||||||
|  | 	private OAuth2Provider provider; | ||||||
|  |  | ||||||
|  | 	@Autowired | ||||||
|  | 	private ProfileService profileService; | ||||||
|  | 	 | ||||||
| 	@Autowired | 	@Autowired | ||||||
| 	private ContactService contactService; | 	private ContactService contactService; | ||||||
|  |  | ||||||
| @@ -39,6 +47,7 @@ public class ContactController { | |||||||
| 	public String getList(Model model) { | 	public String getList(Model model) { | ||||||
| 		List<ContactBean> list = contactService.getList(); | 		List<ContactBean> list = contactService.getList(); | ||||||
| 		model.addAttribute("contactList", list); | 		model.addAttribute("contactList", list); | ||||||
|  | 		model.addAttribute("theme", profileService.getTheme(provider.getName())); | ||||||
| 		return "contact/list"; | 		return "contact/list"; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -57,6 +66,7 @@ public class ContactController { | |||||||
| 		} | 		} | ||||||
| 		model.addAttribute("contactBean", bean); | 		model.addAttribute("contactBean", bean); | ||||||
| 		model.addAttribute("types", Arrays.asList(EnumContacttype.values())); | 		model.addAttribute("types", Arrays.asList(EnumContacttype.values())); | ||||||
|  | 		model.addAttribute("theme", profileService.getTheme(provider.getName())); | ||||||
| 		return "contact/item"; | 		return "contact/item"; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ import java.util.List; | |||||||
|  |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.format.annotation.DateTimeFormat; | import org.springframework.format.annotation.DateTimeFormat; | ||||||
| import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |  | ||||||
| import org.springframework.stereotype.Controller; | import org.springframework.stereotype.Controller; | ||||||
| import org.springframework.ui.Model; | import org.springframework.ui.Model; | ||||||
| import org.springframework.web.bind.annotation.GetMapping; | import org.springframework.web.bind.annotation.GetMapping; | ||||||
| @@ -15,6 +14,8 @@ import org.springframework.web.bind.annotation.PathVariable; | |||||||
| import org.springframework.web.bind.annotation.RequestMapping; | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
| import org.springframework.web.bind.annotation.RequestMethod; | import org.springframework.web.bind.annotation.RequestMethod; | ||||||
|  |  | ||||||
|  | import de.jottyfan.timetrack.component.OAuth2Provider; | ||||||
|  | import de.jottyfan.timetrack.modules.profile.ProfileService; | ||||||
| import jakarta.annotation.security.RolesAllowed; | import jakarta.annotation.security.RolesAllowed; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -25,13 +26,19 @@ import jakarta.annotation.security.RolesAllowed; | |||||||
| @Controller | @Controller | ||||||
| public class DoneController { | public class DoneController { | ||||||
|  |  | ||||||
|  | 	@Autowired | ||||||
|  | 	private OAuth2Provider provider; | ||||||
|  | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private ProfileService profileService; | ||||||
|  |  | ||||||
| 	@Autowired | 	@Autowired | ||||||
| 	private DoneService doneService; | 	private DoneService doneService; | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@RequestMapping(value = "/done/list") | 	@RequestMapping(value = "/done/list") | ||||||
| 	public String getList(@ModelAttribute DoneModel doneModel, Model model, OAuth2AuthenticationToken token) { | 	public String getList(@ModelAttribute DoneModel doneModel, Model model) { | ||||||
| 		String username = doneService.getCurrentUser(token); | 		String username = provider.getName(); | ||||||
| 		Duration maxWorkTime = Duration.ofHours(8); // TODO: to the configuration file | 		Duration maxWorkTime = Duration.ofHours(8); // TODO: to the configuration file | ||||||
| 		LocalDate day = doneModel.getDay(); | 		LocalDate day = doneModel.getDay(); | ||||||
| 		List<DoneBean> list = doneService.getList(day, username); | 		List<DoneBean> list = doneService.getList(day, username); | ||||||
| @@ -46,15 +53,16 @@ public class DoneController { | |||||||
| 		model.addAttribute("moduleList", doneService.getModules(false)); | 		model.addAttribute("moduleList", doneService.getModules(false)); | ||||||
| 		model.addAttribute("jobList", doneService.getJobs(false)); | 		model.addAttribute("jobList", doneService.getJobs(false)); | ||||||
| 		model.addAttribute("billingList", doneService.getBillings(false)); | 		model.addAttribute("billingList", doneService.getBillings(false)); | ||||||
|  | 		model.addAttribute("theme", profileService.getTheme(username)); | ||||||
| 		return "done/list"; | 		return "done/list"; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@GetMapping("/done/abort/{day}") | 	@GetMapping("/done/abort/{day}") | ||||||
| 	public String abort(@PathVariable String day, Model model, OAuth2AuthenticationToken token) { | 	public String abort(@PathVariable String day, Model model) { | ||||||
| 		DoneModel doneModel = new DoneModel(); | 		DoneModel doneModel = new DoneModel(); | ||||||
| 		doneModel.setDayString(day); | 		doneModel.setDayString(day); | ||||||
| 		return getList(doneModel, model, token); | 		return getList(doneModel, model); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| @@ -66,6 +74,7 @@ public class DoneController { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	private String toItem(DoneBean bean, Model model) { | 	private String toItem(DoneBean bean, Model model) { | ||||||
|  | 		String username = provider.getName(); | ||||||
| 		DoneModel doneModel = new DoneModel(); | 		DoneModel doneModel = new DoneModel(); | ||||||
| 		doneModel.setDay(bean.getLocalDate()); | 		doneModel.setDay(bean.getLocalDate()); | ||||||
| 		model.addAttribute("doneBean", bean); | 		model.addAttribute("doneBean", bean); | ||||||
| @@ -74,11 +83,12 @@ public class DoneController { | |||||||
| 		model.addAttribute("moduleList", doneService.getModules(true)); | 		model.addAttribute("moduleList", doneService.getModules(true)); | ||||||
| 		model.addAttribute("jobList", doneService.getJobs(true)); | 		model.addAttribute("jobList", doneService.getJobs(true)); | ||||||
| 		model.addAttribute("billingList", doneService.getBillings(true)); | 		model.addAttribute("billingList", doneService.getBillings(true)); | ||||||
|  | 		model.addAttribute("theme", profileService.getTheme(username)); | ||||||
| 		return "done/item"; | 		return "done/item"; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
|   @GetMapping("/done/edit/{id}") | 	@GetMapping("/done/edit/{id}") | ||||||
| 	public String toItem(@PathVariable Integer id, Model model) { | 	public String toItem(@PathVariable Integer id, Model model) { | ||||||
| 		DoneBean bean = doneService.getBean(id); | 		DoneBean bean = doneService.getBean(id); | ||||||
| 		if (bean == null) { | 		if (bean == null) { | ||||||
| @@ -89,21 +99,21 @@ public class DoneController { | |||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@RequestMapping(value = "/done/upsert", method = RequestMethod.POST) | 	@RequestMapping(value = "/done/upsert", method = RequestMethod.POST) | ||||||
| 	public String doUpsert(Model model, @ModelAttribute DoneBean bean, OAuth2AuthenticationToken token) { | 	public String doUpsert(Model model, @ModelAttribute DoneBean bean) { | ||||||
| 		String username = doneService.getCurrentUser(token); | 		String username = provider.getName(); | ||||||
| 		Integer amount = doneService.doUpsert(bean, username); | 		Integer amount = doneService.doUpsert(bean, username); | ||||||
| 		DoneModel doneModel = new DoneModel(); | 		DoneModel doneModel = new DoneModel(); | ||||||
| 		doneModel.setDay(bean.getLocalDate()); | 		doneModel.setDay(bean.getLocalDate()); | ||||||
| 		return amount.equals(1) ? getList(doneModel, model, token) : toItem(bean.getPk(), model); | 		return amount.equals(1) ? getList(doneModel, model) : toItem(bean.getPk(), model); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@GetMapping(value = "/done/delete/{id}") | 	@GetMapping(value = "/done/delete/{id}") | ||||||
| 	public String doDelete(@PathVariable Integer id, Model model, OAuth2AuthenticationToken token) { | 	public String doDelete(@PathVariable Integer id, Model model) { | ||||||
| 		DoneBean bean = doneService.getBean(id); | 		DoneBean bean = doneService.getBean(id); | ||||||
| 		Integer amount = doneService.doDelete(id); | 		Integer amount = doneService.doDelete(id); | ||||||
| 		DoneModel doneModel = new DoneModel(); | 		DoneModel doneModel = new DoneModel(); | ||||||
| 		doneModel.setDay(bean.getLocalDate()); | 		doneModel.setDay(bean.getLocalDate()); | ||||||
| 		return amount.equals(1) ? getList(doneModel, model, token) : toItem(id, model); | 		return amount.equals(1) ? getList(doneModel, model) : toItem(id, model); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package de.jottyfan.timetrack.modules.done.job; | package de.jottyfan.timetrack.modules.done.job; | ||||||
|  |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |  | ||||||
| import org.springframework.stereotype.Controller; | import org.springframework.stereotype.Controller; | ||||||
| import org.springframework.ui.Model; | import org.springframework.ui.Model; | ||||||
| import org.springframework.web.bind.annotation.GetMapping; | import org.springframework.web.bind.annotation.GetMapping; | ||||||
| @@ -10,9 +9,11 @@ import org.springframework.web.bind.annotation.PathVariable; | |||||||
| import org.springframework.web.bind.annotation.RequestMapping; | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
| import org.springframework.web.bind.annotation.RequestMethod; | import org.springframework.web.bind.annotation.RequestMethod; | ||||||
|  |  | ||||||
|  | import de.jottyfan.timetrack.component.OAuth2Provider; | ||||||
| import de.jottyfan.timetrack.db.done.tables.records.TJobRecord; | import de.jottyfan.timetrack.db.done.tables.records.TJobRecord; | ||||||
| import de.jottyfan.timetrack.modules.done.DoneController; | import de.jottyfan.timetrack.modules.done.DoneController; | ||||||
| import de.jottyfan.timetrack.modules.done.DoneModel; | import de.jottyfan.timetrack.modules.done.DoneModel; | ||||||
|  | import de.jottyfan.timetrack.modules.profile.ProfileService; | ||||||
| import jakarta.annotation.security.RolesAllowed; | import jakarta.annotation.security.RolesAllowed; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -28,19 +29,27 @@ public class JobController { | |||||||
| 	@Autowired | 	@Autowired | ||||||
| 	private DoneController doneController; | 	private DoneController doneController; | ||||||
| 	 | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private OAuth2Provider provider; | ||||||
|  | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private ProfileService profileService; | ||||||
|  | 	 | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@GetMapping("/done/edit/job/{id}") | 	@GetMapping("/done/edit/job/{id}") | ||||||
| 	public String toJob(@PathVariable Integer id, Model model) { | 	public String toJob(@PathVariable Integer id, Model model) { | ||||||
|  | 		String username = provider.getName(); | ||||||
| 		TJobRecord job = jobService.get(id); | 		TJobRecord job = jobService.get(id); | ||||||
| 		model.addAttribute("jobBean", job); | 		model.addAttribute("jobBean", job); | ||||||
|  | 		model.addAttribute("theme", profileService.getTheme(username)); | ||||||
| 		return "done/job"; | 		return "done/job"; | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@RequestMapping(value = "/done/upsert/job", method = RequestMethod.POST) | 	@RequestMapping(value = "/done/upsert/job", method = RequestMethod.POST) | ||||||
| 	public String doUpsert(Model model, @ModelAttribute TJobRecord bean, OAuth2AuthenticationToken token) { | 	public String doUpsert(Model model, @ModelAttribute TJobRecord bean) { | ||||||
| 		Integer amount = jobService.doUpsert(bean); | 		Integer amount = jobService.doUpsert(bean); | ||||||
| 		return amount.equals(1) ? doneController.getList(new DoneModel(), model, token) : toJob(bean.getPk(), model); | 		return amount.equals(1) ? doneController.getList(new DoneModel(), model) : toJob(bean.getPk(), model); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| @@ -51,8 +60,8 @@ public class JobController { | |||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@GetMapping(value = "/done/delete/job/{id}") | 	@GetMapping(value = "/done/delete/job/{id}") | ||||||
| 	public String doDeleteJob(@PathVariable Integer id, Model model, OAuth2AuthenticationToken token) { | 	public String doDeleteJob(@PathVariable Integer id, Model model) { | ||||||
| 		Integer amount = jobService.doDelete(id); | 		Integer amount = jobService.doDelete(id); | ||||||
| 		return amount.equals(1) ? doneController.getList(new DoneModel(), model, token) : toJob(id, model); | 		return amount.equals(1) ? doneController.getList(new DoneModel(), model) : toJob(id, model); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,11 +1,6 @@ | |||||||
| package de.jottyfan.timetrack.modules.done.module; | package de.jottyfan.timetrack.modules.done.module; | ||||||
|  |  | ||||||
| import jakarta.annotation.security.RolesAllowed; |  | ||||||
|  |  | ||||||
| import org.apache.logging.log4j.LogManager; |  | ||||||
| import org.apache.logging.log4j.Logger; |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |  | ||||||
| import org.springframework.stereotype.Controller; | import org.springframework.stereotype.Controller; | ||||||
| import org.springframework.ui.Model; | import org.springframework.ui.Model; | ||||||
| import org.springframework.web.bind.annotation.GetMapping; | import org.springframework.web.bind.annotation.GetMapping; | ||||||
| @@ -14,9 +9,12 @@ import org.springframework.web.bind.annotation.PathVariable; | |||||||
| import org.springframework.web.bind.annotation.RequestMapping; | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
| import org.springframework.web.bind.annotation.RequestMethod; | import org.springframework.web.bind.annotation.RequestMethod; | ||||||
|  |  | ||||||
|  | import de.jottyfan.timetrack.component.OAuth2Provider; | ||||||
| import de.jottyfan.timetrack.db.done.tables.records.TModuleRecord; | import de.jottyfan.timetrack.db.done.tables.records.TModuleRecord; | ||||||
| import de.jottyfan.timetrack.modules.done.DoneController; | import de.jottyfan.timetrack.modules.done.DoneController; | ||||||
| import de.jottyfan.timetrack.modules.done.DoneModel; | import de.jottyfan.timetrack.modules.done.DoneModel; | ||||||
|  | import de.jottyfan.timetrack.modules.profile.ProfileService; | ||||||
|  | import jakarta.annotation.security.RolesAllowed; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
| @@ -32,19 +30,27 @@ public class ModuleController { | |||||||
| 	@Autowired | 	@Autowired | ||||||
| 	private DoneController doneController; | 	private DoneController doneController; | ||||||
| 	 | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private OAuth2Provider provider; | ||||||
|  | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private ProfileService profileService; | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@GetMapping("/done/edit/module/{id}") | 	@GetMapping("/done/edit/module/{id}") | ||||||
| 	public String toModule(@PathVariable Integer id, Model model) { | 	public String toModule(@PathVariable Integer id, Model model) { | ||||||
|  | 		String username = provider.getName(); | ||||||
| 		TModuleRecord module = moduleService.get(id); | 		TModuleRecord module = moduleService.get(id); | ||||||
| 		model.addAttribute("moduleBean", module); | 		model.addAttribute("moduleBean", module); | ||||||
|  | 		model.addAttribute("theme", profileService.getTheme(username)); | ||||||
| 		return "done/module"; | 		return "done/module"; | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@RequestMapping(value = "/done/upsert/module", method = RequestMethod.POST) | 	@RequestMapping(value = "/done/upsert/module", method = RequestMethod.POST) | ||||||
| 	public String doUpsert(Model model, @ModelAttribute TModuleRecord bean, OAuth2AuthenticationToken token) { | 	public String doUpsert(Model model, @ModelAttribute TModuleRecord bean) { | ||||||
| 		Integer amount = moduleService.doUpsert(bean); | 		Integer amount = moduleService.doUpsert(bean); | ||||||
| 		return amount.equals(1) ? doneController.getList(new DoneModel(), model, token) : toModule(bean.getPk(), model); | 		return amount.equals(1) ? doneController.getList(new DoneModel(), model) : toModule(bean.getPk(), model); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| @@ -55,8 +61,8 @@ public class ModuleController { | |||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@GetMapping(value = "/done/delete/module/{id}") | 	@GetMapping(value = "/done/delete/module/{id}") | ||||||
| 	public String doDeleteModule(@PathVariable Integer id, Model model, OAuth2AuthenticationToken token) { | 	public String doDeleteModule(@PathVariable Integer id, Model model) { | ||||||
| 		Integer amount = moduleService.doDelete(id); | 		Integer amount = moduleService.doDelete(id); | ||||||
| 		return amount.equals(1) ? doneController.getList(new DoneModel(), model, token) : toModule(id, model); | 		return amount.equals(1) ? doneController.getList(new DoneModel(), model) : toModule(id, model); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package de.jottyfan.timetrack.modules.done.project; | package de.jottyfan.timetrack.modules.done.project; | ||||||
|  |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |  | ||||||
| import org.springframework.stereotype.Controller; | import org.springframework.stereotype.Controller; | ||||||
| import org.springframework.ui.Model; | import org.springframework.ui.Model; | ||||||
| import org.springframework.web.bind.annotation.GetMapping; | import org.springframework.web.bind.annotation.GetMapping; | ||||||
| @@ -10,9 +9,11 @@ import org.springframework.web.bind.annotation.PathVariable; | |||||||
| import org.springframework.web.bind.annotation.RequestMapping; | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
| import org.springframework.web.bind.annotation.RequestMethod; | import org.springframework.web.bind.annotation.RequestMethod; | ||||||
|  |  | ||||||
|  | import de.jottyfan.timetrack.component.OAuth2Provider; | ||||||
| import de.jottyfan.timetrack.db.done.tables.records.TProjectRecord; | import de.jottyfan.timetrack.db.done.tables.records.TProjectRecord; | ||||||
| import de.jottyfan.timetrack.modules.done.DoneController; | import de.jottyfan.timetrack.modules.done.DoneController; | ||||||
| import de.jottyfan.timetrack.modules.done.DoneModel; | import de.jottyfan.timetrack.modules.done.DoneModel; | ||||||
|  | import de.jottyfan.timetrack.modules.profile.ProfileService; | ||||||
| import jakarta.annotation.security.RolesAllowed; | import jakarta.annotation.security.RolesAllowed; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -28,19 +29,27 @@ public class ProjectController { | |||||||
| 	@Autowired | 	@Autowired | ||||||
| 	private DoneController doneController; | 	private DoneController doneController; | ||||||
|  |  | ||||||
|  | 	@Autowired | ||||||
|  | 	private OAuth2Provider provider; | ||||||
|  | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private ProfileService profileService; | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@GetMapping("/done/edit/project/{id}") | 	@GetMapping("/done/edit/project/{id}") | ||||||
| 	public String toProject(@PathVariable Integer id, Model model) { | 	public String toProject(@PathVariable Integer id, Model model) { | ||||||
|  | 		String username = provider.getName(); | ||||||
| 		TProjectRecord project = projectService.get(id); | 		TProjectRecord project = projectService.get(id); | ||||||
| 		model.addAttribute("projectBean", project); | 		model.addAttribute("projectBean", project); | ||||||
|  | 		model.addAttribute("theme", profileService.getTheme(username)); | ||||||
| 		return "done/project"; | 		return "done/project"; | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@RequestMapping(value = "/done/upsert/project", method = RequestMethod.POST) | 	@RequestMapping(value = "/done/upsert/project", method = RequestMethod.POST) | ||||||
| 	public String doUpsert(Model model, @ModelAttribute TProjectRecord bean, OAuth2AuthenticationToken token) { | 	public String doUpsert(Model model, @ModelAttribute TProjectRecord bean) { | ||||||
| 		Integer amount = projectService.doUpsert(bean); | 		Integer amount = projectService.doUpsert(bean); | ||||||
| 		return amount.equals(1) ? doneController.getList(new DoneModel(), model, token) : toProject(bean.getPk(), model); | 		return amount.equals(1) ? doneController.getList(new DoneModel(), model) : toProject(bean.getPk(), model); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| @@ -51,8 +60,8 @@ public class ProjectController { | |||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@GetMapping(value = "/done/delete/project/{id}") | 	@GetMapping(value = "/done/delete/project/{id}") | ||||||
| 	public String doDeleteProject(@PathVariable Integer id, Model model, OAuth2AuthenticationToken token) { | 	public String doDeleteProject(@PathVariable Integer id, Model model) { | ||||||
| 		Integer amount = projectService.doDelete(id); | 		Integer amount = projectService.doDelete(id); | ||||||
| 		return amount.equals(1) ? doneController.getList(new DoneModel(), model, token) : toProject(id, model); | 		return amount.equals(1) ? doneController.getList(new DoneModel(), model) : toProject(id, model); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import java.util.Arrays; | |||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; | ||||||
| import org.springframework.stereotype.Controller; | import org.springframework.stereotype.Controller; | ||||||
| import org.springframework.ui.Model; | import org.springframework.ui.Model; | ||||||
| import org.springframework.web.bind.annotation.GetMapping; | import org.springframework.web.bind.annotation.GetMapping; | ||||||
| @@ -12,8 +13,10 @@ import org.springframework.web.bind.annotation.PathVariable; | |||||||
| import org.springframework.web.bind.annotation.RequestMapping; | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
| import org.springframework.web.bind.annotation.RequestMethod; | import org.springframework.web.bind.annotation.RequestMethod; | ||||||
|  |  | ||||||
|  | import de.jottyfan.timetrack.component.OAuth2Provider; | ||||||
| import de.jottyfan.timetrack.db.note.enums.EnumCategory; | import de.jottyfan.timetrack.db.note.enums.EnumCategory; | ||||||
| import de.jottyfan.timetrack.db.note.enums.EnumNotetype; | import de.jottyfan.timetrack.db.note.enums.EnumNotetype; | ||||||
|  | import de.jottyfan.timetrack.modules.profile.ProfileService; | ||||||
| import jakarta.annotation.security.RolesAllowed; | import jakarta.annotation.security.RolesAllowed; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -24,26 +27,35 @@ import jakarta.annotation.security.RolesAllowed; | |||||||
| @Controller | @Controller | ||||||
| public class NoteController { | public class NoteController { | ||||||
|  |  | ||||||
|  | 	@Autowired | ||||||
|  | 	private OAuth2Provider provider; | ||||||
|  |  | ||||||
|  | 	@Autowired | ||||||
|  | 	private ProfileService profileService; | ||||||
|  |  | ||||||
| 	@Autowired | 	@Autowired | ||||||
| 	private NoteService noteService; | 	private NoteService noteService; | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@RequestMapping(value = "/note/list") | 	@RequestMapping(value = "/note/list") | ||||||
| 	public String getList(Model model) { | 	public String getList(Model model) { | ||||||
|  | 		String username = provider.getName(); | ||||||
| 		List<NoteBean> list = noteService.getList(); | 		List<NoteBean> list = noteService.getList(); | ||||||
| 		model.addAttribute("noteList", list); | 		model.addAttribute("noteList", list); | ||||||
|  | 		model.addAttribute("theme", profileService.getTheme(username)); | ||||||
| 		return "note/list"; | 		return "note/list"; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@RequestMapping(value = "/note/add", method = RequestMethod.GET) | 	@RequestMapping(value = "/note/add", method = RequestMethod.GET) | ||||||
| 	public String toAdd(Model model) { | 	public String toAdd(Model model, OAuth2AuthenticationToken token) { | ||||||
| 		return toItem(null, model); | 		return toItem(null, model); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
|   @GetMapping("/note/edit/{id}") |   @GetMapping("/note/edit/{id}") | ||||||
| 	public String toItem(@PathVariable Integer id, Model model) { | 	public String toItem(@PathVariable Integer id, Model model) { | ||||||
|  | 		String username = provider.getName(); | ||||||
| 		NoteBean bean = noteService.getBean(id); | 		NoteBean bean = noteService.getBean(id); | ||||||
| 		if (bean == null) { | 		if (bean == null) { | ||||||
| 			bean = new NoteBean(); // the add case | 			bean = new NoteBean(); // the add case | ||||||
| @@ -51,19 +63,20 @@ public class NoteController { | |||||||
| 		model.addAttribute("noteBean", bean); | 		model.addAttribute("noteBean", bean); | ||||||
| 		model.addAttribute("types", Arrays.asList(EnumNotetype.values())); | 		model.addAttribute("types", Arrays.asList(EnumNotetype.values())); | ||||||
| 		model.addAttribute("categories", Arrays.asList(EnumCategory.values())); | 		model.addAttribute("categories", Arrays.asList(EnumCategory.values())); | ||||||
|  | 		model.addAttribute("theme", profileService.getTheme(username)); | ||||||
| 		return "note/item"; | 		return "note/item"; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@RequestMapping(value = "/note/upsert", method = RequestMethod.POST) | 	@RequestMapping(value = "/note/upsert", method = RequestMethod.POST) | ||||||
| 	public String doUpsert(Model model, @ModelAttribute NoteBean bean) { | 	public String doUpsert(Model model, @ModelAttribute NoteBean bean, OAuth2AuthenticationToken token) { | ||||||
| 		Integer amount = noteService.doUpsert(bean); | 		Integer amount = noteService.doUpsert(bean); | ||||||
| 		return amount.equals(1) ? getList(model) : toItem(bean.getPk(), model); | 		return amount.equals(1) ? getList(model) : toItem(bean.getPk(), model); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@RolesAllowed("timetrack_user") | 	@RolesAllowed("timetrack_user") | ||||||
| 	@GetMapping(value = "/note/delete/{id}") | 	@GetMapping(value = "/note/delete/{id}") | ||||||
| 	public String doDelete(@PathVariable Integer id, Model model) { | 	public String doDelete(@PathVariable Integer id, Model model, OAuth2AuthenticationToken token) { | ||||||
| 		Integer amount = noteService.doDelete(id); | 		Integer amount = noteService.doDelete(id); | ||||||
| 		return amount.equals(1) ? getList(model) : toItem(id, model); | 		return amount.equals(1) ? getList(model) : toItem(id, model); | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -0,0 +1,31 @@ | |||||||
|  | package de.jottyfan.timetrack.modules.profile; | ||||||
|  |  | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Controller; | ||||||
|  | import org.springframework.web.bind.annotation.PathVariable; | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
|  | import org.springframework.web.bind.annotation.RequestMethod; | ||||||
|  |  | ||||||
|  | import de.jottyfan.timetrack.component.OAuth2Provider; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @author jotty | ||||||
|  |  *  | ||||||
|  |  */ | ||||||
|  | @Controller | ||||||
|  | public class ProfileController { | ||||||
|  |  | ||||||
|  | 	@Autowired | ||||||
|  | 	private OAuth2Provider provider; | ||||||
|  |  | ||||||
|  | 	@Autowired | ||||||
|  | 	private ProfileService service; | ||||||
|  | 	 | ||||||
|  | 	@RequestMapping(value = "/profile/{theme}", method = RequestMethod.POST) | ||||||
|  | 	public String setTheme(@PathVariable String theme) { | ||||||
|  | 		String username = provider.getName(); | ||||||
|  | 		service.setTheme(username, theme); | ||||||
|  | 		return "redirect:/"; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,58 @@ | |||||||
|  | package de.jottyfan.timetrack.modules.profile; | ||||||
|  |  | ||||||
|  | import static de.jottyfan.timetrack.db.profile.Tables.T_PROFILE; | ||||||
|  |  | ||||||
|  | import org.apache.logging.log4j.LogManager; | ||||||
|  | import org.apache.logging.log4j.Logger; | ||||||
|  | import org.jooq.DSLContext; | ||||||
|  | import org.jooq.InsertOnDuplicateSetMoreStep; | ||||||
|  | import org.jooq.Record1; | ||||||
|  | import org.jooq.SelectConditionStep; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Repository; | ||||||
|  |  | ||||||
|  | import de.jottyfan.timetrack.db.profile.tables.records.TProfileRecord; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @author jotty | ||||||
|  |  *  | ||||||
|  |  */ | ||||||
|  | @Repository | ||||||
|  | public class ProfileRepository { | ||||||
|  | 	private static final Logger LOGGER = LogManager.getLogger(ProfileRepository.class); | ||||||
|  | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private DSLContext jooq; | ||||||
|  | 	 | ||||||
|  | 	public void setTheme(String username, String theme) { | ||||||
|  | 		InsertOnDuplicateSetMoreStep<TProfileRecord> sql = jooq | ||||||
|  | 		// @formatter:off | ||||||
|  | 			.insertInto(T_PROFILE, | ||||||
|  | 					        T_PROFILE.USERNAME, | ||||||
|  | 					        T_PROFILE.THEME) | ||||||
|  | 			.values(username, theme) | ||||||
|  | 			.onConflict(T_PROFILE.USERNAME) | ||||||
|  | 			.doUpdate() | ||||||
|  | 			.set(T_PROFILE.THEME, theme); | ||||||
|  | 		// @formatter:on | ||||||
|  | 		LOGGER.trace(sql.toString()); | ||||||
|  | 		sql.execute(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public String getTheme(String username) { | ||||||
|  | 		SelectConditionStep<Record1<String>> sql = jooq | ||||||
|  | 		// @formatter:off | ||||||
|  | 			.select(T_PROFILE.THEME) | ||||||
|  | 			.from(T_PROFILE) | ||||||
|  | 			.where(T_PROFILE.USERNAME.eq(username)); | ||||||
|  | 		// @formatter:on | ||||||
|  | 		LOGGER.trace(sql.toString()); | ||||||
|  | 		Record1<String> res = sql.fetchOne(); | ||||||
|  | 		if (res == null) { | ||||||
|  | 			return "light"; // default | ||||||
|  | 		} else { | ||||||
|  | 			return res.get(T_PROFILE.THEME); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | package de.jottyfan.timetrack.modules.profile; | ||||||
|  |  | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @author jotty | ||||||
|  |  *  | ||||||
|  |  */ | ||||||
|  | @Service | ||||||
|  | public class ProfileService { | ||||||
|  | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private ProfileRepository repository; | ||||||
|  | 	 | ||||||
|  | 	public void setTheme(String username, String theme) { | ||||||
|  | 		repository.setTheme(username, theme); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public String getTheme(String username) { | ||||||
|  | 		return username == null ? "light" : repository.getTheme(username); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,25 +1,24 @@ | |||||||
|  | # include properties file from /etc | ||||||
|  | spring.config.import = /etc/timetrack.properties | ||||||
|  |  | ||||||
| # jooq | # jooq | ||||||
| spring.datasource.driver-class-name = org.postgresql.Driver | spring.datasource.driver-class-name = org.postgresql.Driver | ||||||
| # todo: export to /etc/timetrack | spring.datasource.url = ${db.url} | ||||||
| spring.datasource.url = jdbc:postgresql://localhost:5432/timetrack | spring.datasource.username = ${db.username} | ||||||
| spring.datasource.username = timetrack | spring.datasource.password = ${db.password} | ||||||
| spring.datasource.password = timetrack |  | ||||||
|  |  | ||||||
| # security | # security | ||||||
| keycloak.url = http://localhost:8080/realms/jottyfan | spring.security.oauth2.client.registration.keycloak.client-id = ${keycloak.client-id} | ||||||
| keycloak.openid.url = ${keycloak.url}/protocol/openid-connect |  | ||||||
| spring.security.oauth2.client.registration.keycloak.client-id = timetrack |  | ||||||
| spring.security.oauth2.client.registration.keycloak.scope = openid | spring.security.oauth2.client.registration.keycloak.scope = openid | ||||||
| spring.security.oauth2.client.registration.keycloak.authorization-grant-type = authorization_code | spring.security.oauth2.client.registration.keycloak.authorization-grant-type = authorization_code | ||||||
| # todo: export to /etc/timetrack | spring.security.oauth2.client.registration.keycloak.redirect-uri = ${keycloak.redirect-uri} | ||||||
| spring.security.oauth2.client.registration.keycloak.redirect-uri = http://localhost:8888/timetrack/login/oauth2/code/timetrack | spring.security.oauth2.client.provider.keycloak.issuer-uri = ${keycloak.issuer-uri} | ||||||
| spring.security.oauth2.client.provider.keycloak.issuer-uri = ${keycloak.url} | spring.security.oauth2.client.provider.keycloak.authorization-uri = ${keycloak.openid-url}/auth | ||||||
| spring.security.oauth2.client.provider.keycloak.authorization-uri = ${keycloak.openid.url}/auth | spring.security.oauth2.client.provider.keycloak.token-uri = ${keycloak.openid-url}/token | ||||||
| spring.security.oauth2.client.provider.keycloak.token-uri = ${keycloak.openid.url}/token | spring.security.oauth2.client.provider.keycloak.user-info-uri = ${keycloak.openid-url}/userinfo | ||||||
| spring.security.oauth2.client.provider.keycloak.user-info-uri = ${keycloak.openid.url}/userinfo | spring.security.oauth2.client.provider.keycloak.jwk-set-uri = ${keycloak.openid-url}/certs | ||||||
| spring.security.oauth2.client.provider.keycloak.jwk-set-uri = ${keycloak.openid.url}/certs |  | ||||||
| spring.security.oauth2.client.provider.keycloak.user-name-attribute = preferred_username | spring.security.oauth2.client.provider.keycloak.user-name-attribute = preferred_username | ||||||
|  |  | ||||||
| # application | # application | ||||||
| server:.port = 8083 | server.port = 9001 | ||||||
| server.servlet.context-path = /timetrack | server.servlet.context-path = /timetrack | ||||||
|   | |||||||
| @@ -7,6 +7,10 @@ body { | |||||||
| 	height: calc(100% - 56px); | 	height: calc(100% - 56px); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme=dark] body { | ||||||
|  | 	background-color: rgb(36, 31, 49); | ||||||
|  | } | ||||||
|  |  | ||||||
| .openedSelect { | .openedSelect { | ||||||
| 	overflow: auto; | 	overflow: auto; | ||||||
| } | } | ||||||
| @@ -21,6 +25,10 @@ body { | |||||||
| 	color: black; | 	color: black; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme=dark] .navlinkstyle { | ||||||
|  | 	color: #aaa; | ||||||
|  | } | ||||||
|  |  | ||||||
| .navlinkstyle:hover { | .navlinkstyle:hover { | ||||||
| 	color: #1a5fb4; | 	color: #1a5fb4; | ||||||
| } | } | ||||||
| @@ -29,6 +37,11 @@ body { | |||||||
| 	background-color: ghostwhite; | 	background-color: ghostwhite; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme=dark] .navback { | ||||||
|  | 	color: ghostwhite; | ||||||
|  | 	background-color: #222; | ||||||
|  | } | ||||||
|  |  | ||||||
| .tabdivblurred { | .tabdivblurred { | ||||||
| 	padding: 8px; | 	padding: 8px; | ||||||
| 	padding-bottom: 0px; | 	padding-bottom: 0px; | ||||||
| @@ -43,6 +56,10 @@ body { | |||||||
| 	background-image: linear-gradient(to bottom, #ffc, #ee0) !important; | 	background-image: linear-gradient(to bottom, #ffc, #ee0) !important; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme=dark] .noty { | ||||||
|  | 	background-image: linear-gradient(to bottom, rgb(0, 0, 0), rgb(229, 165, 10)) !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| .glassy { | .glassy { | ||||||
| 	background-color: rgba(0, 0, 0s, 0.1); | 	background-color: rgba(0, 0, 0s, 0.1); | ||||||
| } | } | ||||||
| @@ -56,6 +73,10 @@ body { | |||||||
| 	min-width: 95vw !important; | 	min-width: 95vw !important; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme=dark] .formpane { | ||||||
|  | 	background: #111; | ||||||
|  | } | ||||||
|  |  | ||||||
| .menudangerbutton { | .menudangerbutton { | ||||||
| 	color: #e00 !important; | 	color: #e00 !important; | ||||||
| 	border: 1px solid rgba(0, 0, 0, 0); | 	border: 1px solid rgba(0, 0, 0, 0); | ||||||
| @@ -86,6 +107,11 @@ body { | |||||||
| 		!important; | 		!important; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme=dark] .page { | ||||||
|  | 		background-image: linear-gradient(to bottom, #123, #111) | ||||||
|  | 		!important; | ||||||
|  | } | ||||||
|  |  | ||||||
| .emph { | .emph { | ||||||
| 	border-radius: 3px !important; | 	border-radius: 3px !important; | ||||||
| 	border: 1px solid #3070b0 !important; | 	border: 1px solid #3070b0 !important; | ||||||
| @@ -94,6 +120,13 @@ body { | |||||||
| 		!important; | 		!important; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme=dark] .emph { | ||||||
|  | 	color: #ffffff !important; | ||||||
|  | 	background-image: linear-gradient(to bottom, #113531 0%, #135 100%) | ||||||
|  | 		!important; | ||||||
|  | 	 | ||||||
|  | } | ||||||
|  |  | ||||||
| .doneoverviewtext { | .doneoverviewtext { | ||||||
| 	font-size: 120%; | 	font-size: 120%; | ||||||
| } | } | ||||||
| @@ -131,22 +164,45 @@ body { | |||||||
| 	font-size: smaller | 	font-size: smaller | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme=dark] .billing { | ||||||
|  | 	color: white !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| .WP2 { | .WP2 { | ||||||
| 	background: radial-gradient(#ffff00, #ffe169) !important; | 	background: radial-gradient(#ffff00, #ffe169) !important; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme="dark"] .WP2 { | ||||||
|  | 	color: black !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| .WP4 { | .WP4 { | ||||||
|  | 	color: black; | ||||||
| 	background: radial-gradient(#00ffff, #69c3ff) !important; | 	background: radial-gradient(#00ffff, #69c3ff) !important; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme="dark"] .WP4 { | ||||||
|  | 	color: black !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| .WP5 { | .WP5 { | ||||||
|  | 	color: black; | ||||||
| 	background: radial-gradient(#ff0000, #e396ff) !important; | 	background: radial-gradient(#ff0000, #e396ff) !important; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme="dark"] .WP5 { | ||||||
|  | 	color: black !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| .TA3 { | .TA3 { | ||||||
|  | 	color: black; | ||||||
| 	background: radial-gradient(#99ff99, #ccffcc) !important; | 	background: radial-gradient(#99ff99, #ccffcc) !important; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme="dark"] .TA3 { | ||||||
|  | 	color: black !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| .left { | .left { | ||||||
| 	text-align: left; | 	text-align: left; | ||||||
| } | } | ||||||
| @@ -194,6 +250,10 @@ body { | |||||||
| 	border-radius: 4px; | 	border-radius: 4px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme=dark] .hoverlink { | ||||||
|  | 	color: white; | ||||||
|  | } | ||||||
|  |  | ||||||
| .hoverlink:hover { | .hoverlink:hover { | ||||||
| 	color: white; | 	color: white; | ||||||
| 	background-image: linear-gradient(to right bottom, #99c1f1, #1a5f64); | 	background-image: linear-gradient(to right bottom, #99c1f1, #1a5f64); | ||||||
| @@ -246,6 +306,10 @@ body { | |||||||
| 	border: 1px solid silver; | 	border: 1px solid silver; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [data-bs-theme=dark] .tab-pane-table { | ||||||
|  | 	background-color: rgb(36, 31, 49); | ||||||
|  | } | ||||||
|  |  | ||||||
| .tab-pane-glassy { | .tab-pane-glassy { | ||||||
| 	background: rgba(255, 255, 255, 0.5); | 	background: rgba(255, 255, 255, 0.5); | ||||||
| 	padding: 8px; | 	padding: 8px; | ||||||
|   | |||||||
| @@ -4,29 +4,30 @@ | |||||||
|  * Coolock Definition |  * Coolock Definition | ||||||
|  */ |  */ | ||||||
| class Clock { | class Clock { | ||||||
| 	constructor(size, selector, color, background) { | 	constructor(size, selector, arrowcolor, scalecolor, backgroundcolor) { | ||||||
|  |  | ||||||
| 		var canvas = $("<canvas>"); | 		var canvas = $("<canvas>"); | ||||||
| 		canvas.attr("id", "clockPanel"); | 		canvas.attr("id", "clockPanel"); | ||||||
| 		canvas.attr("width", size); | 		canvas.attr("width", size); | ||||||
| 		canvas.attr("height", size); | 		canvas.attr("height", size); | ||||||
| 		canvas.css("border-radius", "4px"); | 		canvas.css("border-radius", "4px"); | ||||||
| 		canvas.css("background", background); | 		canvas.css("background", backgroundcolor); | ||||||
| 		canvas[0].getContext("2d").canvas.width = size; | 		canvas[0].getContext("2d").canvas.width = size; | ||||||
| 		canvas[0].getContext("2d").canvas.height = size - 3; // -3 removes unneeded overlap | 		canvas[0].getContext("2d").canvas.height = size; | ||||||
|  |  | ||||||
| 		$(selector).append(canvas); | 		$(selector).append(canvas); | ||||||
| 		var ctx = canvas[0].getContext("2d"); | 		var ctx = canvas[0].getContext("2d"); | ||||||
| 		this.ctx = ctx; | 		this.ctx = ctx; | ||||||
| 		this.size = size; | 		this.size = size; | ||||||
| 		this.background = background; | 		this.background = backgroundcolor; | ||||||
| 		this.color = color; | 		this.color = arrowcolor; | ||||||
|  | 		this.scalecolor = scalecolor; | ||||||
|  |  | ||||||
| 		this.redraw(); | 		this.redraw(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	drawCircle = function(ctx, x, y, r, w) { | 	drawCircle = function(ctx, x, y, r, w) { | ||||||
| 		ctx.strokeStyle = this.color; | 		ctx.strokeStyle = this.background; | ||||||
| 		ctx.lineWidth = w; | 		ctx.lineWidth = w; | ||||||
| 		ctx.beginPath(); | 		ctx.beginPath(); | ||||||
| 		ctx.arc(x, y, r, 0, 2 * Math.PI); | 		ctx.arc(x, y, r, 0, 2 * Math.PI); | ||||||
| @@ -35,7 +36,7 @@ class Clock { | |||||||
|  |  | ||||||
| 	drawArrow = function(ctx, x1, y1, x2, y2, col) { | 	drawArrow = function(ctx, x1, y1, x2, y2, col) { | ||||||
| 		ctx.strokeStyle = col; | 		ctx.strokeStyle = col; | ||||||
| 		ctx.fillStyle = this.color; | 		ctx.fillStyle = this.background; | ||||||
| 		ctx.lineCap = "round"; | 		ctx.lineCap = "round"; | ||||||
| 		ctx.lineWidth = 2; | 		ctx.lineWidth = 2; | ||||||
| 		ctx.beginPath(); | 		ctx.beginPath(); | ||||||
|   | |||||||
| @@ -1,3 +1,16 @@ | |||||||
|  | toggleTheme = function() { | ||||||
|  | 	var oldValue = $("html").attr("data-bs-theme"); | ||||||
|  | 	var newValue = oldValue == "dark" ? "light" : "dark"; | ||||||
|  | 	$("html").attr("data-bs-theme", newValue); | ||||||
|  | 	var url = "profile/" + newValue; | ||||||
|  | 	$.ajax({ | ||||||
|  | 		url: url, | ||||||
|  | 		dataType: 'json', | ||||||
|  | 		type: "POST", | ||||||
|  | 		contentType: 'application/json' | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
| resetValue = function(selector, value) { | resetValue = function(selector, value) { | ||||||
| 	$(selector).val(value); | 	$(selector).val(value); | ||||||
| 	$(selector).change(); | 	$(selector).change(); | ||||||
| @@ -21,10 +34,10 @@ validateTime = function(inputField, okButtonId) { | |||||||
| 	var regexPattern = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/; | 	var regexPattern = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/; | ||||||
| 	var valid = value == "" ? true : regexPattern.test(value); | 	var valid = value == "" ? true : regexPattern.test(value); | ||||||
| 	if (valid) { | 	if (valid) { | ||||||
| 		$(inputField).css("background-color", "#cfc"); | 		$(inputField).css("background-color", "#cfc").css("color", "black"); | ||||||
| 		$("[id='" + okButtonId + "']").removeAttr('disabled'); | 		$("[id='" + okButtonId + "']").removeAttr('disabled'); | ||||||
| 	} else { | 	} else { | ||||||
| 		$(inputField).css("background-color", "#fcc"); | 		$(inputField).css("background-color", "#fcc").css("color", "black"); | ||||||
| 		$("[id='" + okButtonId + "']").attr('disabled', 'disabled'); | 		$("[id='" + okButtonId + "']").attr('disabled', 'disabled'); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -10,7 +10,7 @@ | |||||||
| 	<main layout:fragment="content"> | 	<main layout:fragment="content"> | ||||||
| 		<div class="container formpane"> | 		<div class="container formpane"> | ||||||
| 			<form th:action="@{/contact/upsert}" th:object="${contactBean}" method="post"> | 			<form th:action="@{/contact/upsert}" th:object="${contactBean}" method="post"> | ||||||
| 				<div class="row mb-3"> | 				<div class="row mb-3" th:style="${contactBean.pk == null ? 'display: none' : ''}"> | ||||||
| 					<label for="inputPk" class="col-sm-2 col-form-label">Inhalt von Eintrag</label> | 					<label for="inputPk" class="col-sm-2 col-form-label">Inhalt von Eintrag</label> | ||||||
| 					<div class="col-sm-10"> | 					<div class="col-sm-10"> | ||||||
| 						<input id="inputPk" type="text" th:field="*{pk}" class="form-control" readonly="readonly" /> | 						<input id="inputPk" type="text" th:field="*{pk}" class="form-control" readonly="readonly" /> | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ | |||||||
| 				<div class="accordion-body"> | 				<div class="accordion-body"> | ||||||
| 					<div class="row row-cols-12 ro-cols-lg-4 ro-cols-md-3 ro-cols-sd-2 g-4" style="margin: 8px"> | 					<div class="row row-cols-12 ro-cols-lg-4 ro-cols-md-3 ro-cols-sd-2 g-4" style="margin: 8px"> | ||||||
| 						<div class="col" th:each="contact : ${contactList}"> | 						<div class="col" th:each="contact : ${contactList}"> | ||||||
| 							<div class="card text-dark bg-light shadow" style="width: 18rem"> | 							<div class="card shadow" style="width: 18rem"> | ||||||
| 								<div class="card-header text-center"> | 								<div class="card-header text-center"> | ||||||
| 									<font th:text="${contact.forename} + ' ' + ${contact.surname}" style="font-size: larger"></font> | 									<font th:text="${contact.forename} + ' ' + ${contact.surname}" style="font-size: larger"></font> | ||||||
| 								</div> | 								</div> | ||||||
| @@ -38,7 +38,7 @@ | |||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div id="div_list" class="tab-pane fade tab-pane-table"> | 			<div id="div_list" class="tab-pane fade tab-pane-table"> | ||||||
| 				<div class="accordion-body" style="background-color: white"> | 				<div class="accordion-body"> | ||||||
| 					<table id="table" class="table table-striped table-condensed"> | 					<table id="table" class="table table-striped table-condensed"> | ||||||
| 						<thead> | 						<thead> | ||||||
| 							<tr> | 							<tr> | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ | |||||||
| 			<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_billing">Abrechnung</a></li> | 			<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_billing">Abrechnung</a></li> | ||||||
| 		</ul> | 		</ul> | ||||||
| 		<div class="tabdivblurred tab-content"> | 		<div class="tabdivblurred tab-content"> | ||||||
| 			<div id="div_list" class="tab-pane active tab-pane-table" style="background-color: white"> | 			<div id="div_list" class="tab-pane active tab-pane-table"> | ||||||
| 				<table class="table table-striped table-condensed"> | 				<table class="table table-striped table-condensed"> | ||||||
| 					<thead> | 					<thead> | ||||||
| 						<tr> | 						<tr> | ||||||
|   | |||||||
| @@ -1,22 +1,22 @@ | |||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> | <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" data-bs-theme="dark"> | ||||||
| <head> | <head> | ||||||
| <meta charset="UTF-8" /> | <meta charset="UTF-8" /> | ||||||
|  |  | ||||||
| <title>Timetrack</title> | <title>Timetrack</title> | ||||||
|  |  | ||||||
| <link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/5.2.3/css/bootstrap.min.css}" /> | <link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/5.3.1/css/bootstrap.min.css}" /> | ||||||
| <link rel="stylesheet" type="text/css" th:href="@{/webjars/datatables/1.13.2/css/dataTables.bootstrap5.min.css}" /> | <link rel="stylesheet" type="text/css" th:href="@{/webjars/datatables/1.13.5/css/dataTables.bootstrap5.min.css}" /> | ||||||
| <link rel="stylesheet" type="text/css" th:href="@{/webjars/font-awesome/5.15.4/css/all.min.css}" /> | <link rel="stylesheet" type="text/css" th:href="@{/webjars/font-awesome/6.4.2/css/all.min.css}" /> | ||||||
| <link rel="stylesheet" type="text/css" th:href="@{/webjars/fullcalendar/5.11.3/main.css}" /> | <link rel="stylesheet" type="text/css" th:href="@{/webjars/fullcalendar/5.11.3/main.css}" /> | ||||||
| <link rel="stylesheet" type="text/css" th:href="@{/css/style.css}"> | <link rel="stylesheet" type="text/css" th:href="@{/css/style.css}"> | ||||||
| <link rel="icon" type="image/png" sizes="32x32" th:href="@{/png/favicon/favicon-32x32.png}" /> | <link rel="icon" type="image/png" sizes="32x32" th:href="@{/png/favicon/favicon-32x32.png}" /> | ||||||
| <link rel="icon" type="image/png" sizes="16x16" th:href="@{/png/favicon/favicon-16x16.png}" /> | <link rel="icon" type="image/png" sizes="16x16" th:href="@{/png/favicon/favicon-16x16.png}" /> | ||||||
|  |  | ||||||
| <script th:src="@{/webjars/jquery/3.6.4/jquery.min.js}"></script> | <script th:src="@{/webjars/jquery/3.7.1/jquery.min.js}"></script> | ||||||
| <script th:src="@{/webjars/bootstrap/5.2.3/js/bootstrap.bundle.min.js}"></script> | <script th:src="@{/webjars/bootstrap/5.3.1/js/bootstrap.bundle.min.js}"></script> | ||||||
| <script th:src="@{/webjars/datatables/1.13.2/js/jquery.dataTables.min.js}"></script> | <script th:src="@{/webjars/datatables/1.13.5/js/jquery.dataTables.min.js}"></script> | ||||||
| <script th:src="@{/webjars/datatables/1.13.2/js/dataTables.bootstrap5.min.js}"></script> | <script th:src="@{/webjars/datatables/1.13.5/js/dataTables.bootstrap5.min.js}"></script> | ||||||
| <script th:src="@{/webjars/fullcalendar/5.11.3/main.js}"></script> | <script th:src="@{/webjars/fullcalendar/5.11.3/main.js}"></script> | ||||||
| <script th:src="@{/js/helper.js}"></script> | <script th:src="@{/js/helper.js}"></script> | ||||||
| <script th:src="@{/js/clock.js}"></script> | <script th:src="@{/js/clock.js}"></script> | ||||||
| @@ -24,7 +24,7 @@ | |||||||
|  |  | ||||||
| </head> | </head> | ||||||
| <body> | <body> | ||||||
| 	<nav class="navbar navbar-expand-lg navbar-light bg-light static-top"> | 	<nav class="navbar navbar-expand-lg static-top"> | ||||||
| 		<div class="container-fluid" style="width: 98%"> | 		<div class="container-fluid" style="width: 98%"> | ||||||
| 			<i class="fa fa-calendar-alt"></i> <a class="navbar-brand" style="margin-left: 8px; z-index: 1" th:href="@{/}">Timetrack</a><br /> | 			<i class="fa fa-calendar-alt"></i> <a class="navbar-brand" style="margin-left: 8px; z-index: 1" th:href="@{/}">Timetrack</a><br /> | ||||||
| 			<div class="version" th:text="${@manifestBean.getVersion()}"></div> | 			<div class="version" th:text="${@manifestBean.getVersion()}"></div> | ||||||
| @@ -47,12 +47,36 @@ | |||||||
| 					<li class="nav-item"><a class="nav-link titlemod"><font layout:fragment="title"></font></a></li> | 					<li class="nav-item"><a class="nav-link titlemod"><font layout:fragment="title"></font></a></li> | ||||||
| 					<li layout:fragment="menuitem" style="list-style-type: none"></li> | 					<li layout:fragment="menuitem" style="list-style-type: none"></li> | ||||||
| 					<li layout:fragment="menu" style="list-style-type: none"></li> | 					<li layout:fragment="menu" style="list-style-type: none"></li> | ||||||
| 					<li class="nav-item ms-auto"><div id="clock" class="clock"></div></li> | 					<li class="nav-item ms-auto"><div id="clock" class="clock" onclick="toggleTheme();resetClock()"></div></li> | ||||||
| 				</ul> | 				</ul> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<script type="text/javascript"> | 		<script type="text/javascript"> | ||||||
| 		  new Clock(38, "#clock", "#000", "rgba(255, 255, 255, 0.0)"); | 		//<![CDATA[ | ||||||
|  | 			$(document).ready(function(){ | ||||||
|  | 				var theme = "[[${theme}]]"; | ||||||
|  | 				$("html").attr("data-bs-theme", theme); | ||||||
|  | 				 | ||||||
|  | 				resetClock = function() { | ||||||
|  | 					$("#clock").empty(); | ||||||
|  | 					var theme = $("html").attr("data-bs-theme"); | ||||||
|  | 					var arrowcolor; | ||||||
|  | 					var scalecolor; | ||||||
|  | 					var backgroundcolor; | ||||||
|  | 					if (theme == "dark") { | ||||||
|  | 						arrowcolor = "#fff"; | ||||||
|  | 						scalecolor = "#aaa"; | ||||||
|  | 						backgroundcolor = "rgba(0, 0, 0, 0.3)" | ||||||
|  | 					} else { | ||||||
|  | 						arrowcolor = "#099"; | ||||||
|  | 						scalecolor = "#000"; | ||||||
|  | 						backgroundcolor = "rgba(0, 0, 0, 0)"; | ||||||
|  | 					} | ||||||
|  | 			  	new Clock(38, "#clock", arrowcolor, scalecolor, backgroundcolor); | ||||||
|  | 			  } | ||||||
|  | 			  resetClock(); | ||||||
|  | 			}); | ||||||
|  | 			//]]> | ||||||
| 		</script> | 		</script> | ||||||
| 	</nav> | 	</nav> | ||||||
| 	<main layout:fragment="content" class="page body"></main> | 	<main layout:fragment="content" class="page body"></main> | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ | |||||||
| 	<main layout:fragment="content"> | 	<main layout:fragment="content"> | ||||||
| 		<div class="container formpane"> | 		<div class="container formpane"> | ||||||
| 			<form th:action="@{/note/upsert}" th:object="${noteBean}" method="post"> | 			<form th:action="@{/note/upsert}" th:object="${noteBean}" method="post"> | ||||||
| 				<div class="row mb-3"> | 				<div class="row mb-3" th:style="${noteBean.pk == null ? 'display: none' : ''}"> | ||||||
| 					<label for="inputPk" class="col-sm-2 col-form-label">Inhalt von Eintrag</label> | 					<label for="inputPk" class="col-sm-2 col-form-label">Inhalt von Eintrag</label> | ||||||
| 					<div class="col-sm-10"> | 					<div class="col-sm-10"> | ||||||
| 						<input id="inputPk" type="text" th:field="*{pk}" class="form-control" readonly="readonly" /> | 						<input id="inputPk" type="text" th:field="*{pk}" class="form-control" readonly="readonly" /> | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ | |||||||
| 			<div id="div_dashboard" class="tab-pane active"> | 			<div id="div_dashboard" class="tab-pane active"> | ||||||
| 				<div class="row row-cols-12 ro-cols-lg-3 ro-cols-md-2 ro-cols-sd-1 g-4" style="margin: 8px"> | 				<div class="row row-cols-12 ro-cols-lg-3 ro-cols-md-2 ro-cols-sd-1 g-4" style="margin: 8px"> | ||||||
| 					<div class="col" th:each="note : ${noteList}"> | 					<div class="col" th:each="note : ${noteList}"> | ||||||
| 						<div class="card text-dark bg-light shadow" style="width: 100%"> | 						<div class="card shadow" style="width: 100%"> | ||||||
| 							<div class="card-header text-center"> | 							<div class="card-header text-center"> | ||||||
| 								<font th:text="${note.category}" style="font-size: larger">:</font> <font th:text="${note.title}" | 								<font th:text="${note.category}" style="font-size: larger">:</font> <font th:text="${note.title}" | ||||||
| 									style="font-size: larger; font-weight: bolder"></font> | 									style="font-size: larger; font-weight: bolder"></font> | ||||||
| @@ -40,7 +40,7 @@ | |||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div id="div_list" class="tab-pane fade tab-pane-table"> | 			<div id="div_list" class="tab-pane fade tab-pane-table"> | ||||||
| 				<div class="accordion-body" style="background-color: white"> | 				<div class="accordion-body"> | ||||||
| 					<table id="table" class="table table-striped table-condensed"> | 					<table id="table" class="table table-striped table-condensed"> | ||||||
| 						<thead> | 						<thead> | ||||||
| 							<tr> | 							<tr> | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ | |||||||
| 	<ul layout:fragment="menu"> | 	<ul layout:fragment="menu"> | ||||||
| 	</ul> | 	</ul> | ||||||
| 	<main layout:fragment="content"> | 	<main layout:fragment="content"> | ||||||
| 		<div class="card text-dark bg-light" style="width: 312px; margin: 24px"> | 		<div class="card" style="width: 312px; margin: 24px"> | ||||||
| 			<div class="card-header"><a class="btn btn-seondary btn-bordered btn-secondaryhover" style="width: 100%" | 			<div class="card-header"><a class="btn btn-seondary btn-bordered btn-secondaryhover" style="width: 100%" | ||||||
| 					th:href="@{/done/list}">heutige Arbeitszeiten</a></div> | 					th:href="@{/done/list}">heutige Arbeitszeiten</a></div> | ||||||
| 			<div class="card-body"> | 			<div class="card-body"> | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								src/main/resources/timetrack.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/main/resources/timetrack.properties
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | # example configuration for the /etc/timetrack.properties file | ||||||
|  |  | ||||||
|  | db.url = jdbc:postgresql://localhost:5432/timetrack | ||||||
|  | db.username = timetrack | ||||||
|  | db.password = timetrack | ||||||
|  |  | ||||||
|  | keycloak.client-id = timetrack | ||||||
|  | keycloak.issuer-uri = http://localhost:8080/realms/jottyfan | ||||||
|  | keycloak.openid-url = http://localhost:8080/realms/jottyfan/protocol/openid-connect | ||||||
|  | keycloak.redirect-uri = http://localhost:8083/timetrack/login/oauth2/code/timetrack | ||||||
		Reference in New Issue
	
	Block a user