aboutsummaryrefslogtreecommitdiff
path: root/connector
diff options
context:
space:
mode:
authorChristian Kollmann <christian.kollmann@a-sit.at>2021-12-02 15:03:09 +0100
committerChristian Kollmann <christian.kollmann@a-sit.at>2021-12-02 16:13:48 +0100
commit6fff1b53525348d531c96b45c920a8ce72288f60 (patch)
tree8db3e70ab71df189eb6cdb2bfbe28b1a67e2227c /connector
parente5934d538aabcfc1f3b92472753de729d6ce1cce (diff)
downloadNational_eIDAS_Gateway-6fff1b53525348d531c96b45c920a8ce72288f60.tar.gz
National_eIDAS_Gateway-6fff1b53525348d531c96b45c920a8ce72288f60.tar.bz2
National_eIDAS_Gateway-6fff1b53525348d531c96b45c920a8ce72288f60.zip
Display all results to user after residency search
Diffstat (limited to 'connector')
-rw-r--r--connector/src/main/java/at/asitplus/eidas/specific/connector/controller/AdresssucheController.java82
-rw-r--r--connector/src/main/resources/templates/residency.html122
-rw-r--r--connector/src/main/webapp/img/ajax-loader.gifbin0 -> 673 bytes
-rw-r--r--connector/src/test/resources/config/properties/messages.properties7
-rw-r--r--connector/src/test/resources/config/properties/messages_de.properties9
-rw-r--r--connector/src/test/resources/config/templates/residency.html122
-rw-r--r--connector/src/test/resources/config/webcontent/img/ajax-loader.gifbin0 -> 673 bytes
7 files changed, 265 insertions, 77 deletions
diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/AdresssucheController.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/AdresssucheController.java
index 8b25a7bd..c35aa8b9 100644
--- a/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/AdresssucheController.java
+++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/AdresssucheController.java
@@ -35,8 +35,11 @@ import at.gv.egiz.eaaf.core.api.gui.ISpringMvcGuiFormBuilder;
import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
import at.gv.egiz.eaaf.core.api.utils.IPendingRequestIdGenerationStrategy;
import at.gv.egiz.eaaf.core.exceptions.GuiBuildException;
+import lombok.AllArgsConstructor;
+import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.CompareToBuilder;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ResourceLoader;
@@ -48,6 +51,8 @@ import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.util.*;
+import java.util.stream.Collectors;
/**
* Default process-engine signaling controller.
@@ -88,7 +93,7 @@ public class AdresssucheController {
}
@RequestMapping(value = {"/residency/search"}, method = {RequestMethod.POST})
- public ResponseEntity<AdresssucheOutput> search(@RequestParam("municipality") String municipality,
+ public ResponseEntity<AdresssucheResult> search(@RequestParam("municipality") String municipality,
@RequestParam("village") String village,
@RequestParam("street") String street,
@RequestParam("number") String number,
@@ -104,7 +109,7 @@ public class AdresssucheController {
try {
Adressdaten searchInput = buildSearchInput(municipality, village, street, number);
ZmrAddressSoapClient.AddressInfo searchOutput = client.searchAddress(searchInput);
- AdresssucheOutput output = buildResponse(searchOutput);
+ AdresssucheResult output = buildResponse(searchOutput);
return ResponseEntity.ok(output);
} catch (EidasSAuthenticationException e) {
log.warn("Search failed", e);
@@ -112,18 +117,22 @@ public class AdresssucheController {
}
}
- private AdresssucheOutput buildResponse(ZmrAddressSoapClient.AddressInfo searchOutput) {
+ private AdresssucheResult buildResponse(ZmrAddressSoapClient.AddressInfo searchOutput) {
if (searchOutput.getPersonResult().isEmpty()) {
log.warn("No result from ZMR");
- return new AdresssucheOutput(null, null, null, null);
+ return new AdresssucheResult(Collections.emptyList(), 0, false, null);
}
- Adressdaten adressdaten = searchOutput.getPersonResult().iterator().next();
- String municipality = adressdaten.getPostAdresse().getGemeinde();
- String village = adressdaten.getPostAdresse().getOrtschaft();
- String street = adressdaten.getPostAdresse().getZustelladresse().getStrassenname();
- String number = adressdaten.getPostAdresse().getZustelladresse().getOrientierungsnummer();
- log.debug("Result from ZMR: '{}', '{}', '{}', '{}'", municipality, village, street, number);
- return new AdresssucheOutput(municipality, village, street, number);
+ boolean moreResults = false;
+ new HashSet<>();
+ log.info("Result level is {}", searchOutput.getLevel());
+ Set<AdresssucheOutput> result = searchOutput.getPersonResult().stream()
+ .map(Adressdaten::getPostAdresse)
+ .map(it -> new AdresssucheOutput(it.getGemeinde(), it.getOrtschaft(),
+ it.getZustelladresse().getStrassenname(), it.getZustelladresse().getOrientierungsnummer()))
+ .collect(Collectors.toSet());
+ // TODO Add configuration option for the limit of 30
+ List<AdresssucheOutput> sorted = result.stream().sorted().limit(30).collect(Collectors.toList());
+ return new AdresssucheResult(sorted, result.size(), moreResults, searchOutput.getLevel().name());
}
@NotNull
@@ -150,43 +159,32 @@ public class AdresssucheController {
return searchInput;
}
- public static class AdresssucheOutput {
+ @Data
+ @AllArgsConstructor
+ public static class AdresssucheResult {
+ private final Collection<AdresssucheOutput> results;
+ private final int resultCount;
+ private final boolean moreResults;
+ private final String detailLevel;
+
+ }
+
+ @Data
+ @AllArgsConstructor
+ public static class AdresssucheOutput implements Comparable<AdresssucheOutput> {
private final String municipality;
private final String village;
private final String street;
private final String number;
- public AdresssucheOutput(String municipality, String village, String street, String number) {
- this.municipality = municipality;
- this.village = village;
- this.street = street;
- this.number = number;
- }
-
- public String getMunicipality() {
- return municipality;
- }
-
- public String getVillage() {
- return village;
- }
-
- public String getStreet() {
- return street;
- }
-
- public String getNumber() {
- return number;
- }
-
@Override
- public String toString() {
- return "AdresssucheOutput{" +
- "municipality='" + municipality + '\'' +
- ", village='" + village + '\'' +
- ", street='" + street + '\'' +
- ", number='" + number + '\'' +
- '}';
+ public int compareTo(@NotNull AdresssucheOutput o) {
+ return new CompareToBuilder()
+ .append(this.municipality, o.municipality)
+ .append(this.village, o.village)
+ .append(this.street, o.street)
+ .append(this.number, o.number)
+ .toComparison();
}
}
diff --git a/connector/src/main/resources/templates/residency.html b/connector/src/main/resources/templates/residency.html
index 38f490ca..3f0532dd 100644
--- a/connector/src/main/resources/templates/residency.html
+++ b/connector/src/main/resources/templates/residency.html
@@ -12,24 +12,86 @@
th:attr="src=@{/static/js/jquery-3.6.0.min.js}"></script>
<title th:text="#{gui.residency.title}">Österreichischer Wohnsitz</title>
<script type="text/javascript" th:inline="javascript">
+ $(document).ready(function () {
+ $("#textResult").hide();
+ $("#tableResult").hide();
+ $("#loading").hide();
+ $.ajaxSetup({
+ beforeSend: function () {
+ $("#loading").show();
+ },
+ complete: function () {
+ $("#loading").hide();
+ }
+ });
+ });
+
function search() {
let updatedText = /*[[#{gui.residency.updated}]]*/ 'Updated text';
let errorText = /*[[#{gui.residency.error}]]*/ 'Error';
+ let applyText = /*[[#{gui.residency.apply}]]*/ 'Apply';
+ let foundText = /*[[#{gui.residency.found}]]*/ 'Found {0}';
+ let uniqueText = /*[[#{gui.residency.unique}]]*/ 'Unique';
+ let invalidInputText = /*[[#{gui.residency.header.inputinvalid}]]*/ 'Invalid';
+ if (!$("#inputForm #inputMunicipality").val().trim() && !$("#inputForm #inputVillage").val().trim()) {
+ $("#textResult").show().text(invalidInputText);
+ return;
+ }
$.ajax({
type: "POST",
url: "http://localhost:8080/ms_connector/residency/search",
data: $("#inputForm").serialize()
}).done(function (data, textStatus, jqXHR) {
- $("#inputMunicipality").val(data["municipality"]);
- $("#inputVillage").val(data["village"]);
- $("#inputStreet").val(data["street"]);
- $("#inputNumber").val(data["number"]);
- $("#textInfo").text(updatedText);
+ if (data["resultCount"] == 1) {
+ $("#textResult").show().text(uniqueText);
+ $("#tableResult tbody").empty();
+ $("#tableResult").hide();
+ $("#inputForm #inputMunicipality").val(data["results"][0]["municipality"]);
+ $("#inputForm #inputVillage").val(data["results"][0]["village"]);
+ $("#inputForm #inputStreet").val(data["results"][0]["street"]);
+ $("#inputForm #inputNumber").val(data["results"][0]["number"]);
+ return;
+ }
+ $("#textResult").show().text(foundText.replace("{0}", data["resultCount"]));
+ $("#tableResult").show();
+ $("#tableResult tbody").empty();
+ $.each(data.results, function (i, output) {
+ $("#tableResult tbody")
+ .append($("<tr>")
+ .append($("<td>").text(output["municipality"] !== null ? output["municipality"] : ""))
+ .append($("<td>").text(output["village"] !== null ? output["village"] : ""))
+ .append($("<td>").text(output["street"] !== null ? output["street"] : ""))
+ .append($("<td>").text(output["number"] !== null ? output["number"] : ""))
+ .append($("<td>").text(applyText).attr("href", "#").click(function () {
+ $("#inputForm #inputMunicipality").val($(this).parent().children("td:nth-child(1)").text());
+ $("#inputForm #inputVillage").val($(this).parent().children("td:nth-child(2)").text());
+ $("#inputForm #inputStreet").val($(this).parent().children("td:nth-child(3)").text());
+ $("#inputForm #inputNumber").val($(this).parent().children("td:nth-child(4)").text());
+ $("#textResult").show().text(updatedText);
+ search();
+ }))
+ );
+ })
}).fail(function (jqXHR, textStatus, errorThrown) {
- $("#textInfo").text(errorText);
+ $("#textResult").show().text(errorText);
})
}
+
+ function clearInput() {
+ $("#inputForm #inputMunicipality").val("");
+ $("#inputForm #inputVillage").val("");
+ $("#inputForm #inputStreet").val("");
+ $("#inputForm #inputNumber").val("");
+ $("#textResult").hide();
+ $("#tableResult").hide();
+ }
</script>
+ <style>
+ td {
+ padding: 0.5em;
+ margin: 0.5em;
+ }
+ </style>
</head>
<body>
@@ -41,38 +103,64 @@
<h2 th:text="#{gui.residency.header.selection}">Search your Austrian Residency</h2>
<div id="residency">
- <div>
- <p><span id="textInfo">Infotext</span></p>
- </div>
<form id="inputForm" method="post" action="$contextPath$submitEndpoint"
th:attr="action=@{${submitEndpoint}}">
+ <div th:text="#{gui.residency.header.help}">Please enter a Municipality or Village first</div>
<div>
- <label for="inputMunicipality">Municipality</label>
- <input type="text" id="inputMunicipality" name="municipality" value="Municipality"/>
+ <label for="inputMunicipality" th:text="#{gui.residency.input.municipality}">Municipality</label>
+ <input type="text" id="inputMunicipality" name="municipality" value=""/>
</div>
<div>
- <label for="inputVillage">Village</label>
- <input type="text" id="inputVillage" name="village" value="Village"/>
+ <label for="inputVillage" th:text="#{gui.residency.input.village}">Village</label>
+ <input type="text" id="inputVillage" name="village" value=""/>
</div>
<div>
- <label for="inputStreet">Street</label>
- <input type="text" id="inputStreet" name="street" value="Street"/>
+ <label for="inputStreet" th:text="#{gui.residency.input.street}">Street</label>
+ <input type="text" id="inputStreet" name="street" value=""/>
</div>
<div>
- <label for="inputNumber">Number</label>
- <input type="text" id="inputNumber" name="number" value="Number"/>
+ <label for="inputNumber" th:text="#{gui.residency.input.number}">Number</label>
+ <input type="text" id="inputNumber" name="number" value=""/>
</div>
<div>
<button type="button" class="block" onclick="search()" th:attr="value=#{gui.residency.search}">Search
</button>
</div>
<div>
+ <button type="button" class="block" onclick="clearInput()" th:attr="value=#{gui.residency.clear}">Clear
+ </button>
+ </div>
+ <div>
<button type="button" class="block" th:attr="value=#{gui.residency.proceed}">Proceed</button>
</div>
+ <div>
+ <img id="loading" src="$contextPath/static/img/ajax-loader.gif"
+ th:attr="src=@{/static/img/ajax-loader.gif}" />
+ </div>
<input type="hidden" name="pendingid" value="$pendingid" th:attr="value=${pendingid}"/>
</form>
</div>
+ <div id="result">
+ <div>
+ <p><span id="textResult"></span></p>
+ </div>
+ <table id="tableResult">
+ <thead>
+ <tr>
+ <th th:text="#{gui.residency.input.municipality}">Municipality</th>
+ <th th:text="#{gui.residency.input.village}">Village</th>
+ <th th:text="#{gui.residency.input.street}">Street</th>
+ <th th:text="#{gui.residency.input.number}">Number</th>
+ <th th:text="#{gui.residency.apply}">Apply</th>
+ </tr>
+ </thead>
+ <tbody>
+
+ </tbody>
+ </table>
+ </div>
+
<form class="block" method="post" action="$contextPath$submitEndpoint" th:attr="action=@{${submitEndpoint}}">
<input type="submit" class="btn btn-outline-primary btn-block" value="Abbrechen/Cancel"
th:attr="value=#{gui.residency.cancel}">
diff --git a/connector/src/main/webapp/img/ajax-loader.gif b/connector/src/main/webapp/img/ajax-loader.gif
new file mode 100644
index 00000000..f2a1bc0c
--- /dev/null
+++ b/connector/src/main/webapp/img/ajax-loader.gif
Binary files differ
diff --git a/connector/src/test/resources/config/properties/messages.properties b/connector/src/test/resources/config/properties/messages.properties
index 1e0f04d0..51befbfc 100644
--- a/connector/src/test/resources/config/properties/messages.properties
+++ b/connector/src/test/resources/config/properties/messages.properties
@@ -106,11 +106,18 @@ gui.residency.header1=Federal Ministry of Internal Affairs
gui.residency.header2=Austrian Central eIDAS Node
gui.residency.header3=Operated by Federal Ministry of Internal Affairs
gui.residency.header.selection=Search for your Austrian Residency
+gui.residency.header.help=You can search for the address that you have been registered at in the past. Please enter a \
+ Municipality or Village first to start the search.
+gui.residency.header.inputinvalid=Be sure to enter a value for Municipality or Village
gui.residency.cancel=Cancel
gui.residency.search=Search
+gui.residency.clear=Clear
gui.residency.proceed=Proceed
gui.residency.updated=Updated your input
+gui.residency.found=Found {0} results
+gui.residency.unique=Unique result found, please proceed
gui.residency.error=Error on Backend Call
+gui.residency.apply=Apply
gui.residency.input.municipality=Municipality
gui.residency.input.village=Village
gui.residency.input.street=Street
diff --git a/connector/src/test/resources/config/properties/messages_de.properties b/connector/src/test/resources/config/properties/messages_de.properties
index e0eea9d1..c67e445f 100644
--- a/connector/src/test/resources/config/properties/messages_de.properties
+++ b/connector/src/test/resources/config/properties/messages_de.properties
@@ -107,11 +107,18 @@ gui.residency.header1=Bundesministerium für Inneres
gui.residency.header2=Zentraler eIDAS Knoten der Republik Österreich
gui.residency.header3=Betrieben durch das Bundesministerium für Inneres
gui.residency.header.selection=Suche nach Österreichischem Wohnsitz
+gui.residency.header.help=Hier können Sie nach einem Wohnsitze in Österreich suchen. Bitte geben Sie zuerst eine \
+ Gemeinde oder Ortschaft ein um die Suche zu starten.
+gui.residency.header.inputinvalid=Bitte geben Sie einen Wert für Gemeinde oder Ortschaft ein
gui.residency.cancel=Abbrechen
gui.residency.search=Suche
-gui.residency.proceed=Weiter
+gui.residency.clear=Löschen
+gui.residency.proceed=Fortfahren
gui.residency.updated=Eingabe aktualisiert
+gui.residency.found={0} Ergebnisse gefunden
+gui.residency.unique=Eindeutiges Ergebnis gefunden, bitte fortfahren
gui.residency.error=Fehler bei Addresssuche
+gui.residency.apply=Übernehmen
gui.residency.input.municipality=Gemeinde
gui.residency.input.village=Ortschaft
gui.residency.input.street=Straße
diff --git a/connector/src/test/resources/config/templates/residency.html b/connector/src/test/resources/config/templates/residency.html
index 17e21044..77c13fb7 100644
--- a/connector/src/test/resources/config/templates/residency.html
+++ b/connector/src/test/resources/config/templates/residency.html
@@ -12,24 +12,86 @@
th:attr="src=@{/static/js/jquery-3.6.0.js}"></script>
<title th:text="#{gui.residency.title}">Österreichischer Wohnsitz</title>
<script type="text/javascript" th:inline="javascript">
+ $(document).ready(function () {
+ $("#textResult").hide();
+ $("#tableResult").hide();
+ $("#loading").hide();
+ $.ajaxSetup({
+ beforeSend: function () {
+ $("#loading").show();
+ },
+ complete: function () {
+ $("#loading").hide();
+ }
+ });
+ });
+
function search() {
let updatedText = /*[[#{gui.residency.updated}]]*/ 'Updated text';
let errorText = /*[[#{gui.residency.error}]]*/ 'Error';
+ let applyText = /*[[#{gui.residency.apply}]]*/ 'Apply';
+ let foundText = /*[[#{gui.residency.found}]]*/ 'Found {0}';
+ let uniqueText = /*[[#{gui.residency.unique}]]*/ 'Unique';
+ let invalidInputText = /*[[#{gui.residency.header.inputinvalid}]]*/ 'Invalid';
+ if (!$("#inputForm #inputMunicipality").val().trim() && !$("#inputForm #inputVillage").val().trim()) {
+ $("#textResult").show().text(invalidInputText);
+ return;
+ }
$.ajax({
type: "POST",
url: "http://localhost:8080/ms_connector/residency/search",
data: $("#inputForm").serialize()
}).done(function (data, textStatus, jqXHR) {
- $("#inputMunicipality").val(data["municipality"]);
- $("#inputVillage").val(data["village"]);
- $("#inputStreet").val(data["street"]);
- $("#inputNumber").val(data["number"]);
- $("#textInfo").text(updatedText);
+ if (data["resultCount"] == 1) {
+ $("#textResult").show().text(uniqueText);
+ $("#tableResult tbody").empty();
+ $("#tableResult").hide();
+ $("#inputForm #inputMunicipality").val(data["results"][0]["municipality"]);
+ $("#inputForm #inputVillage").val(data["results"][0]["village"]);
+ $("#inputForm #inputStreet").val(data["results"][0]["street"]);
+ $("#inputForm #inputNumber").val(data["results"][0]["number"]);
+ return;
+ }
+ $("#textResult").show().text(foundText.replace("{0}", data["resultCount"]));
+ $("#tableResult").show();
+ $("#tableResult tbody").empty();
+ $.each(data.results, function (i, output) {
+ $("#tableResult tbody")
+ .append($("<tr>")
+ .append($("<td>").text(output["municipality"] !== null ? output["municipality"] : ""))
+ .append($("<td>").text(output["village"] !== null ? output["village"] : ""))
+ .append($("<td>").text(output["street"] !== null ? output["street"] : ""))
+ .append($("<td>").text(output["number"] !== null ? output["number"] : ""))
+ .append($("<td>").text(applyText).attr("href", "#").click(function () {
+ $("#inputForm #inputMunicipality").val($(this).parent().children("td:nth-child(1)").text());
+ $("#inputForm #inputVillage").val($(this).parent().children("td:nth-child(2)").text());
+ $("#inputForm #inputStreet").val($(this).parent().children("td:nth-child(3)").text());
+ $("#inputForm #inputNumber").val($(this).parent().children("td:nth-child(4)").text());
+ $("#textResult").show().text(updatedText);
+ search();
+ }))
+ );
+ })
}).fail(function (jqXHR, textStatus, errorThrown) {
- $("#textInfo").text(errorText);
+ $("#textResult").show().text(errorText);
})
}
+
+ function clearInput() {
+ $("#inputForm #inputMunicipality").val("");
+ $("#inputForm #inputVillage").val("");
+ $("#inputForm #inputStreet").val("");
+ $("#inputForm #inputNumber").val("");
+ $("#textResult").hide();
+ $("#tableResult").hide();
+ }
</script>
+ <style>
+ td {
+ padding: 0.5em;
+ margin: 0.5em;
+ }
+ </style>
</head>
<body>
@@ -41,38 +103,64 @@
<h2 th:text="#{gui.residency.header.selection}">Search your Austrian Residency</h2>
<div id="residency">
- <div>
- <p><span id="textInfo">Infotext</span></p>
- </div>
<form id="inputForm" method="post" action="$contextPath$submitEndpoint"
th:attr="action=@{${submitEndpoint}}">
+ <div th:text="#{gui.residency.header.help}">Please enter a Municipality or Village first</div>
<div>
- <label for="inputMunicipality">Municipality</label>
- <input type="text" id="inputMunicipality" name="municipality" value="Municipality"/>
+ <label for="inputMunicipality" th:text="#{gui.residency.input.municipality}">Municipality</label>
+ <input type="text" id="inputMunicipality" name="municipality" value=""/>
</div>
<div>
- <label for="inputVillage">Village</label>
- <input type="text" id="inputVillage" name="village" value="Village"/>
+ <label for="inputVillage" th:text="#{gui.residency.input.village}">Village</label>
+ <input type="text" id="inputVillage" name="village" value=""/>
</div>
<div>
- <label for="inputStreet">Street</label>
- <input type="text" id="inputStreet" name="street" value="Street"/>
+ <label for="inputStreet" th:text="#{gui.residency.input.street}">Street</label>
+ <input type="text" id="inputStreet" name="street" value=""/>
</div>
<div>
- <label for="inputNumber">Number</label>
- <input type="text" id="inputNumber" name="number" value="Number"/>
+ <label for="inputNumber" th:text="#{gui.residency.input.number}">Number</label>
+ <input type="text" id="inputNumber" name="number" value=""/>
</div>
<div>
<button type="button" class="block" onclick="search()" th:attr="value=#{gui.residency.search}">Search
</button>
</div>
<div>
+ <button type="button" class="block" onclick="clearInput()" th:attr="value=#{gui.residency.clear}">Clear
+ </button>
+ </div>
+ <div>
<button type="button" class="block" th:attr="value=#{gui.residency.proceed}">Proceed</button>
</div>
+ <div>
+ <img id="loading" src="$contextPath/static/img/ajax-loader.gif"
+ th:attr="src=@{/static/img/ajax-loader.gif}" />
+ </div>
<input type="hidden" name="pendingid" value="$pendingid" th:attr="value=${pendingid}"/>
</form>
</div>
+ <div id="result">
+ <div>
+ <p><span id="textResult"></span></p>
+ </div>
+ <table id="tableResult">
+ <thead>
+ <tr>
+ <th th:text="#{gui.residency.input.municipality}">Municipality</th>
+ <th th:text="#{gui.residency.input.village}">Village</th>
+ <th th:text="#{gui.residency.input.street}">Street</th>
+ <th th:text="#{gui.residency.input.number}">Number</th>
+ <th th:text="#{gui.residency.apply}">Apply</th>
+ </tr>
+ </thead>
+ <tbody>
+
+ </tbody>
+ </table>
+ </div>
+
<form class="block" method="post" action="$contextPath$submitEndpoint" th:attr="action=@{${submitEndpoint}}">
<input type="submit" class="btn btn-outline-primary btn-block" value="Abbrechen/Cancel"
th:attr="value=#{gui.residency.cancel}">
diff --git a/connector/src/test/resources/config/webcontent/img/ajax-loader.gif b/connector/src/test/resources/config/webcontent/img/ajax-loader.gif
new file mode 100644
index 00000000..f2a1bc0c
--- /dev/null
+++ b/connector/src/test/resources/config/webcontent/img/ajax-loader.gif
Binary files differ