From a9da562e40a1bca49b8fe3957fde4ea44bd8431b Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Wed, 10 May 2017 19:42:50 +0200
Subject: [PATCH] added option to delete database before import

---
 .../budgetmaster/logic/ServerConnection.java  |  87 +++++++-------
 .../deadlocker8/budgetmaster/logic/Utils.java |  28 ++---
 .../deadlocker8/budgetmaster/main/Main.java   |   6 +-
 .../budgetmaster/ui/SettingsController.java   | 106 +++++++++++++++++-
 .../budgetmaster/ui/SettingsTab.fxml          |  35 ++++--
 5 files changed, 182 insertions(+), 80 deletions(-)

diff --git a/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java b/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java
index ad3448231..a603722b4 100644
--- a/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java
+++ b/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java
@@ -16,6 +16,7 @@ import javax.net.ssl.X509TrustManager;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 
+import de.deadlocker8.budgetmasterserver.main.Database;
 import tools.ConvertTo;
 import tools.Read;
 
@@ -48,10 +49,10 @@ public class ServerConnection
 		// Install the all-trusting trust manager
 		SSLContext sc = SSLContext.getInstance("SSL");
 		sc.init(null, trustAllCerts, new java.security.SecureRandom());
-		HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());		
-		
-		//check whitelist
-		HttpsURLConnection.setDefaultHostnameVerifier((hostname, sslSession)->settings.getTrustedHosts().contains(hostname));		
+		HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+
+		// check whitelist
+		HttpsURLConnection.setDefaultHostnameVerifier((hostname, sslSession) -> settings.getTrustedHosts().contains(hostname));
 	}
 
 	/*
@@ -78,7 +79,7 @@ public class ServerConnection
 			return null;
 		}
 	}
-	
+
 	public Category getCategory(int ID) throws Exception
 	{
 		URL url = new URL(settings.getUrl() + "/category/single?secret=" + Helpers.getURLEncodedString(settings.getSecret()) + "&id=" + ID);
@@ -129,7 +130,7 @@ public class ServerConnection
 		BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
 		reader.close();
 	}
-	
+
 	/*
 	 * Payment
 	 */
@@ -176,7 +177,7 @@ public class ServerConnection
 			return null;
 		}
 	}
-	
+
 	public RepeatingPayment getRepeatingPayment(int ID) throws Exception
 	{
 		URL url = new URL(settings.getUrl() + "/repeatingpayment/single?secret=" + Helpers.getURLEncodedString(settings.getSecret()) + "&id=" + ID);
@@ -186,7 +187,7 @@ public class ServerConnection
 
 		if(httpsCon.getResponseCode() == HttpsURLConnection.HTTP_OK)
 		{
-			String result = Read.getStringFromInputStream(httpsCon.getInputStream());		
+			String result = Read.getStringFromInputStream(httpsCon.getInputStream());
 			return gson.fromJson(result, RepeatingPayment.class);
 		}
 		else
@@ -197,12 +198,8 @@ public class ServerConnection
 
 	public void addNormalPayment(NormalPayment payment) throws Exception
 	{
-		URL url = new URL(settings.getUrl() + "/payment?secret=" + Helpers.getURLEncodedString(settings.getSecret()) + 
-				"&amount=" + payment.getAmount() + 
-				"&date=" + payment.getDate() + 
-				"&categoryID=" + payment.getCategoryID() + 
-				"&name=" + Helpers.getURLEncodedString(payment.getName()) + 
-				"&description=" + Helpers.getURLEncodedString(payment.getDescription()));
+		URL url = new URL(settings.getUrl() + "/payment?secret=" + Helpers.getURLEncodedString(settings.getSecret()) + "&amount=" + payment.getAmount() + "&date=" + payment.getDate() + "&categoryID=" + payment.getCategoryID() + "&name=" + Helpers.getURLEncodedString(payment.getName())
+				+ "&description=" + Helpers.getURLEncodedString(payment.getDescription()));
 		HttpsURLConnection httpsCon = (HttpsURLConnection)url.openConnection();
 		httpsCon.setRequestMethod("POST");
 		httpsCon.setDoInput(true);
@@ -213,19 +210,14 @@ public class ServerConnection
 
 	public void updateNormalPayment(NormalPayment payment) throws Exception
 	{
-		 URL url = new URL(settings.getUrl() + "/payment?secret=" + settings.getSecret() + 
-				 "&id=" + payment.getID() + 
-				 "&amount=" + payment.getAmount() + 
-				 "&date=" + payment.getDate() +
-				 "&categoryID=" + payment.getCategoryID() + 
-				 "&name=" + Helpers.getURLEncodedString(payment.getName()) +
-				 "&description=" + Helpers.getURLEncodedString(payment.getDescription()));
-		 HttpsURLConnection httpsCon = (HttpsURLConnection)url.openConnection();
-		 httpsCon.setRequestMethod("PUT");
-		 httpsCon.setDoInput(true);
-		 InputStream stream = httpsCon.getInputStream();
-		 BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
-		 reader.close();
+		URL url = new URL(settings.getUrl() + "/payment?secret=" + settings.getSecret() + "&id=" + payment.getID() + "&amount=" + payment.getAmount() + "&date=" + payment.getDate() + "&categoryID=" + payment.getCategoryID() + "&name=" + Helpers.getURLEncodedString(payment.getName())
+				+ "&description=" + Helpers.getURLEncodedString(payment.getDescription()));
+		HttpsURLConnection httpsCon = (HttpsURLConnection)url.openConnection();
+		httpsCon.setRequestMethod("PUT");
+		httpsCon.setDoInput(true);
+		InputStream stream = httpsCon.getInputStream();
+		BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+		reader.close();
 	}
 
 	public void addRepeatingPayment(RepeatingPayment payment) throws Exception
@@ -237,15 +229,8 @@ public class ServerConnection
 			repeatEndDate = "A";
 		}
 
-		URL url = new URL(settings.getUrl() + "/repeatingpayment?secret=" + Helpers.getURLEncodedString(settings.getSecret()) + 
-				"&amount=" + payment.getAmount() + 
-				"&date=" + payment.getDate() + 
-				"&categoryID=" + payment.getCategoryID() + 
-				"&name=" + Helpers.getURLEncodedString(payment.getName()) + 
-				"&repeatInterval=" + payment.getRepeatInterval() +
-				"&repeatEndDate=" + repeatEndDate + 
-				"&repeatMonthDay=" + payment.getRepeatMonthDay() +
-				"&description=" + Helpers.getURLEncodedString(payment.getDescription()));				
+		URL url = new URL(settings.getUrl() + "/repeatingpayment?secret=" + Helpers.getURLEncodedString(settings.getSecret()) + "&amount=" + payment.getAmount() + "&date=" + payment.getDate() + "&categoryID=" + payment.getCategoryID() + "&name=" + Helpers.getURLEncodedString(payment.getName())
+				+ "&repeatInterval=" + payment.getRepeatInterval() + "&repeatEndDate=" + repeatEndDate + "&repeatMonthDay=" + payment.getRepeatMonthDay() + "&description=" + Helpers.getURLEncodedString(payment.getDescription()));
 		HttpsURLConnection httpsCon = (HttpsURLConnection)url.openConnection();
 		httpsCon.setRequestMethod("POST");
 		httpsCon.setDoInput(true);
@@ -264,7 +249,7 @@ public class ServerConnection
 		BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
 		reader.close();
 	}
-	
+
 	public void deleteRepeatingPayment(RepeatingPaymentEntry payment) throws Exception
 	{
 		URL url = new URL(settings.getUrl() + "/repeatingpayment?secret=" + Helpers.getURLEncodedString(settings.getSecret()) + "&id=" + payment.getRepeatingPaymentID());
@@ -275,7 +260,7 @@ public class ServerConnection
 		BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
 		reader.close();
 	}
-	
+
 	/*
 	 * CATEGORYBUDGET
 	 */
@@ -300,7 +285,7 @@ public class ServerConnection
 			return null;
 		}
 	}
-	
+
 	/*
 	 * REST
 	 */
@@ -313,7 +298,7 @@ public class ServerConnection
 
 		if(httpsCon.getResponseCode() == HttpsURLConnection.HTTP_OK)
 		{
-			String result = Read.getStringFromInputStream(httpsCon.getInputStream());		
+			String result = Read.getStringFromInputStream(httpsCon.getInputStream());
 			return gson.fromJson(result, Integer.class);
 		}
 		else
@@ -321,10 +306,10 @@ public class ServerConnection
 			return 0;
 		}
 	}
-	
+
 	/*
 	 * DATABASE
-	 */	
+	 */
 	public void deleteDatabase() throws Exception
 	{
 		URL url = new URL(settings.getUrl() + "/database?secret=" + Helpers.getURLEncodedString(settings.getSecret()));
@@ -335,7 +320,7 @@ public class ServerConnection
 		BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
 		reader.close();
 	}
-	
+
 	public String exportDatabase() throws Exception
 	{
 		URL url = new URL(settings.getUrl() + "/database?secret=" + Helpers.getURLEncodedString(settings.getSecret()));
@@ -345,11 +330,25 @@ public class ServerConnection
 
 		if(httpsCon.getResponseCode() == HttpsURLConnection.HTTP_OK)
 		{
-			return Read.getStringFromInputStream(httpsCon.getInputStream());				
+			return Read.getStringFromInputStream(httpsCon.getInputStream());
 		}
 		else
 		{
 			return null;
 		}
 	}
+
+	public void importDatabase(Database database) throws Exception
+	{
+		//TODO
+//		String databaseJSON = new Gson().toJson(database);
+//		
+//		URL url = new URL(settings.getUrl() + "/database?secret=" + Helpers.getURLEncodedString(settings.getSecret()));
+//		HttpsURLConnection httpsCon = (HttpsURLConnection)url.openConnection();
+//		httpsCon.setRequestMethod("POST");
+//		httpsCon.setDoInput(true);		
+//		InputStream stream = httpsCon.getInputStream();
+//		BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+//		reader.close();
+	}
 }
\ No newline at end of file
diff --git a/src/de/deadlocker8/budgetmaster/logic/Utils.java b/src/de/deadlocker8/budgetmaster/logic/Utils.java
index 79ba8e190..e50e6b350 100644
--- a/src/de/deadlocker8/budgetmaster/logic/Utils.java
+++ b/src/de/deadlocker8/budgetmaster/logic/Utils.java
@@ -24,8 +24,7 @@ public class Utils
 		Settings settings;
 		try
 		{
-			Gson gson = new Gson();
-			PathUtils.checkFolder(new File(PathUtils.getOSindependentPath() + bundle.getString("folder")));
+			Gson gson = new Gson();			
 			Reader reader = Files.newBufferedReader(Paths.get(PathUtils.getOSindependentPath() + bundle.getString("folder") + "/settings.json"), Charset.forName("UTF-8"));
 			settings = gson.fromJson(reader, Settings.class);	
 			reader.close();
@@ -41,28 +40,19 @@ public class Utils
 	{		
 		Gson gson = new Gson();
 		String jsonString = gson.toJson(settings);
-		
+		PathUtils.checkFolder(new File(PathUtils.getOSindependentPath() + bundle.getString("folder")));
 		Writer writer = Files.newBufferedWriter(Paths.get(PathUtils.getOSindependentPath() + bundle.getString("folder")  + "/settings.json"), Charset.forName("UTF-8"));
 		writer.write(jsonString);
 		writer.close();
 	}
 	
-	public static Database loadDatabaseJSON(File file)
-	{
-		Database database;
-		try
-		{
-			Gson gson = new Gson();
-			PathUtils.checkFolder(file.getParentFile());
-			Reader reader = Files.newBufferedReader(Paths.get(file.getAbsolutePath()), Charset.forName("UTF-8"));
-			database = gson.fromJson(reader, Database.class);	
-			reader.close();
-			return database;
-		}
-		catch(IOException e)
-		{
-			return null;
-		}
+	public static Database loadDatabaseJSON(File file) throws IOException
+	{	
+		Gson gson = new Gson();
+		Reader reader = Files.newBufferedReader(Paths.get(file.getAbsolutePath()), Charset.forName("UTF-8"));		
+		Database database = gson.fromJson(reader, Database.class);	
+		reader.close();
+		return database;		
 	}
 	
 	public static void saveDatabaseJSON(File file, String databaseJSON) throws IOException
diff --git a/src/de/deadlocker8/budgetmaster/main/Main.java b/src/de/deadlocker8/budgetmaster/main/Main.java
index cc47c5474..ebbc0ab0f 100644
--- a/src/de/deadlocker8/budgetmaster/main/Main.java
+++ b/src/de/deadlocker8/budgetmaster/main/Main.java
@@ -27,13 +27,13 @@ public class Main extends Application
 			FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("de/deadlocker8/budgetmaster/ui/GUI.fxml"));
 			Parent root = (Parent)loader.load();
 
-			Scene scene = new Scene(root, 600, 650);
+			Scene scene = new Scene(root, 610, 650);
 
 			((Controller)loader.getController()).init(stage);
 
 			stage.setResizable(true);
-			stage.setMinHeight(600);
-			stage.setMinWidth(600);
+			stage.setMinHeight(650);
+			stage.setMinWidth(610);
 			stage.getIcons().add(new Image("/de/deadlocker8/budgetmaster/resources/icon.png"));
 			stage.setTitle(bundle.getString("app.name"));
 			stage.setScene(scene);
diff --git a/src/de/deadlocker8/budgetmaster/ui/SettingsController.java b/src/de/deadlocker8/budgetmaster/ui/SettingsController.java
index ac6f9aba6..c66d001ae 100644
--- a/src/de/deadlocker8/budgetmaster/ui/SettingsController.java
+++ b/src/de/deadlocker8/budgetmaster/ui/SettingsController.java
@@ -9,13 +9,17 @@ import de.deadlocker8.budgetmaster.logic.Helpers;
 import de.deadlocker8.budgetmaster.logic.ServerConnection;
 import de.deadlocker8.budgetmaster.logic.Settings;
 import de.deadlocker8.budgetmaster.logic.Utils;
+import de.deadlocker8.budgetmasterserver.main.Database;
 import javafx.application.Platform;
 import javafx.fxml.FXML;
 import javafx.fxml.FXMLLoader;
 import javafx.scene.Parent;
 import javafx.scene.Scene;
+import javafx.scene.control.Alert;
 import javafx.scene.control.Alert.AlertType;
 import javafx.scene.control.Button;
+import javafx.scene.control.ButtonBar.ButtonData;
+import javafx.scene.control.ButtonType;
 import javafx.scene.control.Label;
 import javafx.scene.control.RadioButton;
 import javafx.scene.control.TextArea;
@@ -44,7 +48,8 @@ public class SettingsController
 	@FXML private TextField textFieldCurrency;
 	@FXML private Label labelCurrency;
 	@FXML private Button buttonSave;
-	@FXML private Button buttonBackupDB;
+	@FXML private Button buttonExportDB;
+	@FXML private Button buttonImportDB;
 	@FXML private Button buttonDeleteDB;
 	@FXML private RadioButton radioButtonRestActivated;
 	@FXML private RadioButton radioButtonRestDeactivated;
@@ -76,8 +81,9 @@ public class SettingsController
 		labelURL.setStyle("-fx-text-fill: " + controller.getBundle().getString("color.text"));
 		labelCurrency.setStyle("-fx-text-fill: " + controller.getBundle().getString("color.text"));
 		buttonSave.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 16;");
-		buttonBackupDB.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 16;");
-		buttonDeleteDB.setStyle("-fx-background-color: #FF5047; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 16;");
+		buttonExportDB.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 14;");
+		buttonImportDB.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 14;");
+		buttonDeleteDB.setStyle("-fx-background-color: #FF5047; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 14;");
 		textFieldURL.setPromptText("z.B. https://yourdomain.de");
 		textFieldCurrency.setPromptText("z.B. €, CHF, $");
 		textAreaTrustedHosts.setPromptText("z.B. localhost");
@@ -179,7 +185,7 @@ public class SettingsController
 		controller.showNotification("Erfolgreich gespeichert");
 	}
 
-	public void backupDB()
+	public void exportDB()
 	{
 		FileChooser fileChooser = new FileChooser();
 		fileChooser.setTitle("Datenbank exportieren");
@@ -193,10 +199,10 @@ public class SettingsController
 			Worker.runLater(() -> {
 				try
 				{
-					ServerConnection connection = new ServerConnection(controller.getSettings());				
+					ServerConnection connection = new ServerConnection(controller.getSettings());
 					String databaseJSON = connection.exportDatabase();
 					Utils.saveDatabaseJSON(file, databaseJSON);
-					
+
 					Platform.runLater(() -> {
 						if(modalStage != null)
 						{
@@ -215,8 +221,92 @@ public class SettingsController
 			});
 		}
 	}
+	
+	private void importDatabase()
+	{
+		FileChooser fileChooser = new FileChooser();
+		fileChooser.setTitle("Datenbank importieren");
+		FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("JSON (*.json)", "*.json");
+		fileChooser.getExtensionFilters().add(extFilter);
+		File file = fileChooser.showOpenDialog(controller.getStage());
+		if(file != null)
+		{
+			Database database;
+			try
+			{
+				database = Utils.loadDatabaseJSON(file);
+				if(database.getCategories() == null || database.getNormalPayments() == null || database.getRepeatingPayments() == null)
+				{
+					AlertGenerator.showAlert(AlertType.ERROR, "Fehler", "", "Die angegebene Datei enthält kein gültiges BudgetMaster-Datenformat und kann daher nicht importiert werden.", controller.getIcon(), controller.getStage(), null, false);
+					return;
+				}
+			}
+			catch(IOException e1)
+			{
+				Logger.error(e1);
+				AlertGenerator.showAlert(AlertType.ERROR, "Fehler", "", "Beim Einlesen der Datei ist ein Fehler aufgetreten.", controller.getIcon(), controller.getStage(), null, false);
+				return;
+			}
+
+			Stage modalStage = showModal("Vorgang läuft", "Die Datenbank wird importiert, bitte warten...");
+
+			Worker.runLater(() -> {
+				try
+				{
+					ServerConnection connection = new ServerConnection(controller.getSettings());
+					connection.importDatabase(database);
 
+					Platform.runLater(() -> {
+						if(modalStage != null)
+						{
+							modalStage.close();
+						}
+						AlertGenerator.showAlert(AlertType.INFORMATION, "Erfolgreich exportiert", "", "Die Datenbank wurder erfolgreich importiert.", controller.getIcon(), controller.getStage(), null, false);
+					});
+				}
+				catch(Exception e)
+				{
+					Logger.error(e);
+					Platform.runLater(() -> {
+						controller.showConnectionErrorAlert(e.getMessage());
+					});
+				}
+			});
+		}
+	}
+
+	public void importDB()
+	{
+		Alert alert = new Alert(AlertType.CONFIRMATION);
+		alert.setTitle("Datenbank importieren");
+		alert.setHeaderText("");		
+		alert.setContentText("Soll die Datenbank vor dem Importieren gelöscht werden?");
+		Stage dialogStage = (Stage)alert.getDialogPane().getScene().getWindow();
+		dialogStage.getIcons().add(controller.getIcon());
+		dialogStage.initOwner(controller.getStage());
+
+		ButtonType buttonTypeDelete = new ButtonType("Ja, Datenbank löschen");
+		ButtonType buttonTypeAppend = new ButtonType("Nein, Daten hinzufügen");
+		ButtonType buttonTypeCancel = new ButtonType("Abbrechen", ButtonData.CANCEL_CLOSE);
+
+		alert.getButtonTypes().setAll(buttonTypeDelete, buttonTypeAppend, buttonTypeCancel);
+		Optional<ButtonType> result = alert.showAndWait();
+		if(result.get() == buttonTypeDelete)
+		{
+			deleteDatabase(true);
+		}	
+		else if(result.get() == buttonTypeAppend)
+		{	
+			importDatabase();
+		}
+	}
+	
 	public void deleteDB()
+	{
+		deleteDatabase(false);
+	}
+
+	public void deleteDatabase(boolean importPending)
 	{
 		String verificationCode = ConvertTo.toBase58(RandomCreations.generateRandomMixedCaseString(4, true), true, BASE58Type.UPPER);
 
@@ -244,6 +334,10 @@ public class SettingsController
 							if(modalStage != null)
 							{
 								modalStage.close();
+								if(importPending)
+								{
+									importDatabase();
+								}
 							}
 						});
 					}
diff --git a/src/de/deadlocker8/budgetmaster/ui/SettingsTab.fxml b/src/de/deadlocker8/budgetmaster/ui/SettingsTab.fxml
index 7b620aeca..8014a8cab 100644
--- a/src/de/deadlocker8/budgetmaster/ui/SettingsTab.fxml
+++ b/src/de/deadlocker8/budgetmaster/ui/SettingsTab.fxml
@@ -67,6 +67,14 @@
                               <Insets top="35.0" />
                            </VBox.margin>
                         </Label>
+                        <Label fx:id="labelSecret11121" alignment="CENTER_RIGHT" contentDisplay="RIGHT" maxHeight="-Infinity" prefWidth="158.0" textAlignment="RIGHT" wrapText="true">
+                           <font>
+                              <Font name="System Bold" size="16.0" />
+                           </font>
+                           <VBox.margin>
+                              <Insets top="15.0" />
+                           </VBox.margin>
+                        </Label>
                         <Region prefHeight="19.0" prefWidth="158.0" VBox.vgrow="ALWAYS" />
                      </children>
                      <HBox.margin>
@@ -95,13 +103,18 @@
                               </RadioButton>
                            </children>
                         </HBox>
-                        <TextArea fx:id="textAreaTrustedHosts" prefWidth="539.0" VBox.vgrow="ALWAYS">
+                        <TextArea fx:id="textAreaTrustedHosts" minHeight="110.0" prefHeight="148.0" prefWidth="539.0" VBox.vgrow="ALWAYS">
                            <VBox.margin>
                               <Insets />
                            </VBox.margin></TextArea>
                         <HBox alignment="CENTER_LEFT" prefHeight="11.0" prefWidth="539.0" spacing="30.0">
                            <children>
-                              <Button fx:id="buttonBackupDB" mnemonicParsing="false" onAction="#backupDB" text="Backup">
+                              <Button fx:id="buttonExportDB" mnemonicParsing="false" onAction="#exportDB" text="Exportieren">
+                                 <font>
+                                    <Font name="System Bold" size="14.0" />
+                                 </font>
+                              </Button>
+                              <Button fx:id="buttonImportDB" mnemonicParsing="false" onAction="#importDB" text="Importieren">
                                  <font>
                                     <Font name="System Bold" size="14.0" />
                                  </font>
@@ -112,17 +125,23 @@
                                  </font>
                               </Button>
                            </children>
+                           <VBox.margin>
+                              <Insets />
+                           </VBox.margin>
                         </HBox>
-                        <Region prefHeight="19.0" prefWidth="158.0" VBox.vgrow="ALWAYS" />
+                        <Button fx:id="buttonSave" mnemonicParsing="false" onAction="#save" text="Speichern">
+                           <font>
+                              <Font name="System Bold" size="14.0" />
+                           </font>
+                           <VBox.margin>
+                              <Insets top="15.0" />
+                           </VBox.margin>
+                        </Button>
+                        <Region prefWidth="158.0" VBox.vgrow="ALWAYS" />
                      </children>
                   </VBox>
                </children>
             </HBox>
-            <Button fx:id="buttonSave" mnemonicParsing="false" onAction="#save" text="Speichern">
-               <font>
-                  <Font name="System Bold" size="14.0" />
-               </font>
-            </Button>
          </children>
          <padding>
             <Insets right="50.0" />
-- 
GitLab