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;
|
||||||
@ -33,7 +35,13 @@ 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
|
||||||
@ -12,6 +15,12 @@ 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;
|
||||||
@ -19,6 +28,7 @@ public class CalendarController {
|
|||||||
@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) {
|
||||||
@ -86,24 +96,24 @@ public class DoneController {
|
|||||||
}
|
}
|
||||||
return toItem(bean, model);
|
return toItem(bean, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,20 +28,28 @@ 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