added camp document editor, see #24

This commit is contained in:
Jottyfan
2025-10-22 23:04:52 +02:00
parent 13773f54de
commit a1fc517911
16 changed files with 353 additions and 59 deletions

View File

@@ -19,7 +19,7 @@
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17/"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21/"/>
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer">
<attributes>

View File

@@ -1,4 +1,4 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
org.eclipse.jdt.core.compiler.compliance=17
org.eclipse.jdt.core.compiler.source=17
org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
org.eclipse.jdt.core.compiler.compliance=21
org.eclipse.jdt.core.compiler.source=21

View File

@@ -1,14 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="CampOrganizer2">
<property name="context-root" value="CampOrganizer2"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/resources"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java"/>
</wb-module>
<?xml version="1.0" encoding="UTF-8"?>
<project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="CampOrganizer2">
<property name="context-root" value="CampOrganizer2"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/resources"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
</wb-module>
</project-modules>

View File

@@ -3,5 +3,5 @@
<fixed facet="jst.java"/>
<fixed facet="jst.web"/>
<installed facet="jst.web" version="2.4"/>
<installed facet="jst.java" version="17"/>
<installed facet="jst.java" version="21"/>
</faceted-project>

View File

@@ -1,6 +1,6 @@
plugins {
id 'org.springframework.boot' version '3.3.4'
id "io.spring.dependency-management" version "1.1.4"
id 'org.springframework.boot' version '3.5.0'
id "io.spring.dependency-management" version "1.1.7"
id 'java'
id 'war'
id 'eclipse'
@@ -8,15 +8,10 @@ plugins {
}
group = 'de.jottyfan.camporganizer'
version = '0.9.9'
version = '1.0.0'
description = """CampOrganizer2"""
sourceCompatibility = 17
targetCompatibility = 17
mainClassName = "de.jottyfan.camporganizer.Main"
repositories {
mavenCentral()
maven {
@@ -31,6 +26,16 @@ tasks.withType(JavaCompile).configureEach {
options.compilerArgs.add("-parameters")
}
application {
mainClass = 'de.jottyfan.camporganizer.Main'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
war {
doFirst {
manifest {
@@ -44,27 +49,27 @@ war {
}
dependencies {
implementation 'org.jooq:jooq:3.19.14'
implementation 'org.jooq:jooq:3.20.8'
implementation 'de.jottyfan:COJooq:2024.10.24'
implementation 'org.apache.logging.log4j:log4j-api:2.24.1'
implementation 'org.apache.logging.log4j:log4j-core:2.24.1'
implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.24.1'
implementation 'org.apache.logging.log4j:log4j-api:2.25.2'
implementation 'org.apache.logging.log4j:log4j-core:2.25.2'
implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.25.2'
implementation 'org.webjars:bootstrap:5.3.3'
implementation 'org.webjars:font-awesome:6.5.2'
implementation 'org.webjars:bootstrap:5.3.8'
implementation 'org.webjars:font-awesome:7.0.1'
implementation 'org.webjars:jquery:3.7.1'
implementation 'org.webjars:popper.js:2.11.7'
implementation 'org.webjars:datatables:1.13.5'
implementation 'org.webjars:select2:4.0.13'
implementation 'org.webjars.npm:fancyapps__fancybox:3.5.7'
implementation 'net.sf.biweekly:biweekly:0.6.7'
implementation 'net.sf.biweekly:biweekly:0.6.8'
// for using the keycloak rest interface
implementation 'org.keycloak:keycloak-server-spi:24.0.1'
implementation 'org.keycloak:keycloak-admin-client:24.0.1'
implementation 'org.jboss.resteasy:resteasy-client:6.2.6.Final'
implementation 'org.keycloak:keycloak-server-spi:26.4.1'
implementation 'org.keycloak:keycloak-admin-client:26.0.7'
implementation 'org.jboss.resteasy:resteasy-client:7.0.0.Final'
// backward compatibility until the complete registration is converted to keycloak
implementation 'org.jasypt:jasypt:1.9.3'
@@ -73,20 +78,20 @@ dependencies {
implementation 'com.rometools:rome:2.1.0'
// mail support
implementation 'commons-validator:commons-validator:1.8.0'
implementation 'commons-validator:commons-validator:1.10.0'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-jooq'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
implementation 'org.springframework.security:spring-security-oauth2-authorization-server:1.3.3'
implementation 'org.springframework.security:spring-security-oauth2-authorization-server:1.5.3'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.4.0'
implementation 'commons-io:commons-io:2.15.1'
implementation 'commons-io:commons-io:2.20.0'
runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -11,7 +11,6 @@ import org.springframework.security.oauth2.client.registration.InMemoryClientReg
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/**
*
@@ -35,16 +34,11 @@ public class SecurityConfiguration {
// @formatter:off
.oauth2Login(o -> o.defaultSuccessUrl("/"))
.logout(o -> o.logoutSuccessHandler(new OidcClientInitiatedLogoutSuccessHandler(crr)))
.authorizeHttpRequests(o -> o.requestMatchers(
AntPathRequestMatcher.antMatcher("/dashboard/**"),
AntPathRequestMatcher.antMatcher("/business/**"),
AntPathRequestMatcher.antMatcher("/confirmation/**"),
AntPathRequestMatcher.antMatcher("/userlogin/**")
).authenticated()
.authorizeHttpRequests(o -> o.requestMatchers("/dashboard/**", "/business/**", "/confirmation/**","/userlogin/**").authenticated()
.anyRequest().permitAll())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
.sessionManagement(o -> o.init(sec));
// @formatter:on
return sec.build();
}
}
}

View File

@@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import de.jottyfan.camporganizer.module.admin.model.CampBean;
import de.jottyfan.camporganizer.module.admin.model.CampDocumentBean;
import de.jottyfan.camporganizer.module.admin.model.DocumentBean;
import de.jottyfan.camporganizer.module.admin.model.LocationBean;
import de.jottyfan.camporganizer.module.camplist.CommonController;
@@ -192,4 +193,41 @@ public class AdminController extends CommonController {
redirect.addAttribute("error", error);
return error != null ? "redirect:/admin/camp/edit/" + id : "redirect:/admin/camp";
}
@GetMapping("/admin/campdocument")
public String getCampDocumentList(Model model, HttpServletRequest request) {
model.addAttribute("campmap", service.getCampMap());
model.addAttribute("documentmap", service.getDocumentsMap());
model.addAttribute("camps", service.getAllCamps());
model.addAttribute("documents", service.getDocumentsForCamps());
model.addAttribute("campdocuments", service.getAllCampDocuments());
model.addAttribute("campdocument", new CampDocumentBean());
return "/admin/campdocument";
}
@PostMapping("/admin/campdocument/add")
public String addCampDocument(@Valid @ModelAttribute("bean") CampDocumentBean bean, final BindingResult bindingResult,
Model model, HttpServletRequest request, RedirectAttributes redirect) {
if (bindingResult.hasErrors()) {
for (ObjectError error : bindingResult.getAllErrors()) {
LOGGER.error("error {}: {}", error.getCode(), error.getDefaultMessage());
}
model.addAttribute("campmap", service.getCampMap());
model.addAttribute("documentmap", service.getDocumentsMap());
model.addAttribute("camps", service.getAllCamps());
model.addAttribute("documents", service.getDocumentsForCamps());
model.addAttribute("campdocuments", service.getAllCampDocuments());
return "/admin/campdocument";
}
service.upsertCampDocument(bean);
return "redirect:/admin/campdocument";
}
@GetMapping("/admin/campdocument/delete/{id}")
public String deleteCampDocument(@PathVariable("id") Integer id, Model model, HttpServletRequest request,
RedirectAttributes redirect) {
service.deleteCampDocument(id);
return "redirect:/admin/campdocument";
}
}

View File

@@ -1,6 +1,7 @@
package de.jottyfan.camporganizer.module.admin;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMP;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMPDOCUMENT;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMPPROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_DOCUMENT;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_DOCUMENTROLE;
@@ -38,6 +39,7 @@ import org.jooq.UpdateSetMoreStep;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@@ -46,6 +48,7 @@ import de.jottyfan.camporganizer.db.jooq.enums.EnumDocument;
import de.jottyfan.camporganizer.db.jooq.enums.EnumFiletype;
import de.jottyfan.camporganizer.db.jooq.enums.EnumModule;
import de.jottyfan.camporganizer.db.jooq.tables.records.TCampRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TCampdocumentRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TCampprofileRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TDocumentRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TDocumentroleRecord;
@@ -53,6 +56,7 @@ import de.jottyfan.camporganizer.db.jooq.tables.records.TLocationRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TPersonRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TProfileRecord;
import de.jottyfan.camporganizer.module.admin.model.CampBean;
import de.jottyfan.camporganizer.module.admin.model.CampDocumentBean;
import de.jottyfan.camporganizer.module.admin.model.DocumentBean;
import de.jottyfan.camporganizer.module.admin.model.IntKeyValueBean;
import de.jottyfan.camporganizer.module.admin.model.LocationBean;
@@ -70,11 +74,17 @@ import jakarta.validation.Valid;
@Transactional(transactionManager = "transactionManager")
public class AdminRepository {
private final GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak;
private static final Logger LOGGER = LogManager.getLogger(AdminRepository.class);
@Autowired
private DSLContext jooq;
AdminRepository(GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak) {
this.userAuthoritiesMapperForKeycloak = userAuthoritiesMapperForKeycloak;
}
/**
* get the document with that ID
*
@@ -669,4 +679,64 @@ public class AdminRepository {
LOGGER.trace(sql);
sql.execute();
}
/**
* get all camp document beans
*
* @return a list of camp document beans; an empty list at least
*/
public List<CampDocumentBean> getAllCampDocuments() {
SelectWhereStep<TCampdocumentRecord> sql = jooq.selectFrom(T_CAMPDOCUMENT);
LOGGER.trace(sql);
return sql.fetchInto(CampDocumentBean.class);
}
/**
* add a camp document combination; if the combination already exists, ignore this
*
* @param fkCamp the camp id
* @param fkDocument the document id
*/
public void insertCampDocument(Integer fkCamp, Integer fkDocument) {
InsertReturningStep<TCampdocumentRecord> sql = jooq
// @formatter:off
.insertInto(T_CAMPDOCUMENT,
T_CAMPDOCUMENT.FK_CAMP,
T_CAMPDOCUMENT.FK_DOCUMENT)
.values(fkCamp, fkDocument)
.onConflict(T_CAMPDOCUMENT.FK_CAMP, T_CAMPDOCUMENT.FK_DOCUMENT)
.doNothing();
// @formatter:off
LOGGER.trace(sql);
sql.execute();
}
/**
* update the camp document combination
*
* @param pk the ID of the combination
* @param fkCamp the ID of the camp
* @param fkDocument the ID of the document
*/
public void updateCampDocument(Integer pk, Integer fkCamp, Integer fkDocument) {
UpdateConditionStep<TCampdocumentRecord> sql = jooq
// @formatter:off
.update(T_CAMPDOCUMENT)
.set(T_CAMPDOCUMENT.FK_CAMP, fkCamp)
.set(T_CAMPDOCUMENT.FK_DOCUMENT, fkDocument)
.where(T_CAMPDOCUMENT.PK.eq(pk));
// @formatter:on
LOGGER.trace(sql);
sql.execute();
}
/**
* delete from camp document
* @param id the ID of the camp document
*/
public void deleteCampDocument(Integer id) {
DeleteConditionStep<TCampdocumentRecord> sql = jooq.deleteFrom(T_CAMPDOCUMENT).where(T_CAMPDOCUMENT.PK.eq(id));
LOGGER.trace(sql);
sql.execute();
}
}

View File

@@ -4,8 +4,9 @@ import static de.jottyfan.camporganizer.db.jooq.Tables.T_DOCUMENT;
import java.io.IOException;
import java.util.List;
import jakarta.validation.Valid;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -14,11 +15,13 @@ import org.springframework.stereotype.Service;
import de.jottyfan.camporganizer.db.jooq.enums.EnumDocument;
import de.jottyfan.camporganizer.module.admin.model.CampBean;
import de.jottyfan.camporganizer.module.admin.model.CampDocumentBean;
import de.jottyfan.camporganizer.module.admin.model.DocumentBean;
import de.jottyfan.camporganizer.module.admin.model.LocationBean;
import de.jottyfan.camporganizer.module.admin.model.ProfileBean;
import de.jottyfan.camporganizer.module.mail.MailBean;
import de.jottyfan.camporganizer.module.mail.MailRepository;
import jakarta.validation.Valid;
/**
*
@@ -186,4 +189,62 @@ public class AdminService {
public String deleteCamp(Integer id) {
return adminRepository.deleteCamp(id);
}
/**
* get all camp documents
*
* @return a list of camp documents; an empty list at least
*/
public List<CampDocumentBean> getAllCampDocuments() {
return adminRepository.getAllCampDocuments();
}
/**
* get all documents for camps
*
* @return the list of documents for camps
*/
public List<DocumentBean> getDocumentsForCamps() {
return adminRepository.getAllDocumentsWith(T_DOCUMENT.DOCTYPE.eq(EnumDocument.camp));
}
/**
* get a map of camp beans
*
* @return the map
*/
public Map<Integer, CampBean> getCampMap() {
return getAllCamps().stream().collect(Collectors.toMap(CampBean::getPk, Function.identity()));
}
/**
* get a map of document beans
*
* @return the map
*/
public Map<Integer, DocumentBean> getDocumentsMap() {
return getAllDocuments().stream().collect(Collectors.toMap(DocumentBean::getPk, Function.identity()));
}
/**
* upsert a camp document bean
*
* @param bean the bean
*/
public void upsertCampDocument(@Valid CampDocumentBean bean) {
if (bean.getPk() == null) {
adminRepository.insertCampDocument(bean.getFkCamp(), bean.getFkDocument());
} else {
adminRepository.updateCampDocument(bean.getPk(), bean.getFkCamp(), bean.getFkDocument());
}
}
/**
* delete the camp document combination
*
* @param id the ID of the combination
*/
public void deleteCampDocument(Integer id) {
adminRepository.deleteCampDocument(id);
}
}

View File

@@ -94,6 +94,11 @@ public class CampBean implements Serializable {
return bean;
}
@Override
public String toString() {
return getFullname();
}
public String getFullname() {
return new StringBuilder().append(name).append(" ").append(arrive == null ? "?" : arrive.format(DateTimeFormatter.ofPattern("yyyy"))).toString();
}

View File

@@ -0,0 +1,58 @@
package de.jottyfan.camporganizer.module.admin.model;
import java.io.Serializable;
/**
*
* @author jotty
*
*/
public class CampDocumentBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer pk;
private Integer fkCamp;
private Integer fkDocument;
/**
* @return the pk
*/
public Integer getPk() {
return pk;
}
/**
* @param pk the pk to set
*/
public void setPk(Integer pk) {
this.pk = pk;
}
/**
* @return the fkCamp
*/
public Integer getFkCamp() {
return fkCamp;
}
/**
* @param fkCamp the fkCamp to set
*/
public void setFkCamp(Integer fkCamp) {
this.fkCamp = fkCamp;
}
/**
* @return the fkDocument
*/
public Integer getFkDocument() {
return fkDocument;
}
/**
* @param fkDocument the fkDocument to set
*/
public void setFkDocument(Integer fkDocument) {
this.fkDocument = fkDocument;
}
}

View File

@@ -51,6 +51,13 @@ public class DocumentBean implements Serializable{
}
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(name).append(".").append(filetype);
return buf.toString();
}
public Integer getPk() {
return pk;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div class="tablebox" sec:authorize="hasRole('admin')">
<div class="alert alert-info">
Hier werden zusätzliche Dokumente für Freizeiten bereitgestellt. Die Wegbeschreibung, die Bestätigung und die Jahrespläne sind davon nicht betroffen.
</div>
<form th:action="@{/admin/campdocument/add}" method="post" th:object="${campdocument}">
<table id="cammpdocs" class="table table-striped" style="width: 100% !important">
<thead>
<tr>
<td>ID</td>
<td>Freizeit</td>
<td>Dokument</td>
</tr>
</thead>
<tbody>
<tr th:each="cd : ${campdocuments}">
<td>
<div class="dropdown" style="display: inline">
<button class="btn btn-outline-danger dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-trash-alt"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" th:href="@{/admin/campdocument/delete/{id}(id=${cd.pk})}">Kombination endgültig löschen</a>
</ul>
</div>
</td>
<td th:text="${campmap.get(cd.fkCamp)}"></td>
<td th:text="${documentmap.get(cd.fkDocument)}"></td>
</tr>
</tbody>
<tfoot>
<tr>
<td><button type="submit" class="btn btn-outline-primary"><i class="fa fa-plus"></i></button></td>
<td>
<select id="campnew" th:field="*{fkCamp}" class="form-select select2-single">
<option value="" label="--- bitte wählen ---" />
<option th:each="c : ${camps}" th:value="${c.pk}" th:label="${c}" />
</select>
</td>
<td>
<select id="documentnew" th:field="*{fkDocument}" class="form-select select2-single">
<option value="" label="--- bitte wählen ---" />
<option th:each="d : ${documents}" th:value="${d.pk}" th:label="${d}" />
</select>
</td>
</tr>
</tfoot>
</table>
</form>
<script>
$(document).ready(function() {
$("#campdocs").DataTable({
language : locale_de
});
});
</script>
</div>
</th:block>
</body>
</html>

View File

@@ -4,8 +4,8 @@
<title>Camp Organizer 2</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/bootstrap/5.3.3/css/bootstrap.min.css} " />
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/font-awesome/6.5.2/css/all.min.css} " />
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/bootstrap/5.3.8/css/bootstrap.min.css} " />
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/font-awesome/7.0.1/css/all.min.css} " />
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/datatables/1.13.5/css/jquery.dataTables.min.css}" />
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/select2/4.0.13/css/select2.min.css}" />
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/css/select2-bootstrap-5-theme.min.css}" />
@@ -13,7 +13,7 @@
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/fancyapps__fancybox/3.5.7/dist/jquery.fancybox.min.css}">
<script th:src="@{/webjars/jquery/3.7.1/jquery.min.js}"></script>
<script th:src="@{/webjars/fancyapps__fancybox/3.5.7/dist/jquery.fancybox.min.js}"></script>
<script th:src="@{/webjars/bootstrap/5.3.3/js/bootstrap.bundle.min.js}"></script>
<script th:src="@{/webjars/bootstrap/5.3.8/js/bootstrap.bundle.min.js}"></script>
<script th:src="@{/webjars/datatables/1.13.5/js/jquery.dataTables.min.js}"></script>
<script th:src="@{/webjars/select2/4.0.13/js/select2.full.min.js}"></script>
<script th:src="@{/js/dataTables.de.js}"></script>
@@ -107,7 +107,8 @@
<li><a th:href="@{/admin/mail}" class="dropdown-item menufont">Testmail</a></li>
<li><a th:href="@{/admin/document}" class="dropdown-item menufont">Dokumente</a></li>
<li><a th:href="@{/admin/location}" class="dropdown-item menufont">Freizeitheime</a></li>
<li><a th:href="@{/admin/camp}" class="dropdown-item menufont">Freizeiten</a>
<li><a th:href="@{/admin/camp}" class="dropdown-item menufont">Freizeiten</a></li>
<li><a th:href="@{/admin/campdocument}" class="dropdown-item menufont">Freizeit-Dokumente</a></li>
<li class="dropdown dropend">
<a class="dropdown-item dropdown-toggle menufont" href="#" role="button" data-bs-auto-close='outside' data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Rechteverwaltung</a>
<ul class="dropdown-menu">
@@ -135,9 +136,7 @@
<span class="icon_profile"></span>
</button>
<ul class="dropdown-menu dropdown-menu-end" th:if="${#strings.isEmpty(currentUser)}">
<li><a class="dropdown-item" th:href="@{/dashboard}">einloggen</a></li>
<li><hr /></li>
<li><a class="dropdown-item" th:href="@{/migration/login}">Login umziehen</a></li>
<li><a class="dropdown-item" th:href="@{/dashboard}">einloggen / registrieren</a></li>
</ul>
<ul class="dropdown-menu dropdown-menu-end" th:unless="${#strings.isEmpty(currentUser)}">
<li><a class="dropdown-item" th:href="@{${keycloakProfileUrl}}" target="_blank">Benutzername ändern</a></li>