Преглед изворни кода

Refactor health check start:
* replace health tab in options by a button in gernal tab
* move info text of health tab into start controller
* replace lazy loading of config in controller by loading in dagger module
* add new scene+controller for failed config loading
* don't close options window on health check start

Armin Schrenk пре 4 година
родитељ
комит
cb5d628cfc

+ 9 - 0
src/main/java/org/cryptomator/common/vaults/Vault.java

@@ -19,6 +19,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
 import org.cryptomator.cryptofs.CryptoFileSystemProvider;
 import org.cryptomator.cryptofs.VaultConfig;
 import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
+import org.cryptomator.cryptofs.VaultConfigLoadException;
 import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
 import org.cryptomator.cryptolib.api.CryptoException;
 import org.cryptomator.cryptolib.api.MasterkeyLoader;
@@ -327,6 +328,14 @@ public class Vault {
 		return stats;
 	}
 
+	/**
+	 * Attempts to read the vault config file and parse it without verifying its integrity.
+	 *
+	 * @return an unverified vault config
+	 * @throws VaultConfigLoadException if the read file cannot be properly parsed
+	 * @throws IOException if reading the file fails
+	 *
+	 */
 	public UnverifiedVaultConfig getUnverifiedVaultConfig() throws IOException {
 		Path configPath = getPath().resolve(org.cryptomator.common.Constants.VAULTCONFIG_FILENAME);
 		String token = Files.readString(configPath, StandardCharsets.US_ASCII);

+ 1 - 0
src/main/java/org/cryptomator/ui/common/FxmlFile.java

@@ -12,6 +12,7 @@ public enum FxmlFile {
 	ERROR("/fxml/error.fxml"), //
 	FORGET_PASSWORD("/fxml/forget_password.fxml"), //
 	HEALTH_START("/fxml/health_start.fxml"), //
+	HEALTH_START_FAIL("/fxml/health_start_fail.fxml"), //
 	HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
 	LOCK_FORCED("/fxml/lock_forced.fxml"), //
 	LOCK_FAILED("/fxml/lock_failed.fxml"), //

+ 14 - 10
src/main/java/org/cryptomator/ui/health/HealthCheckComponent.java

@@ -4,10 +4,10 @@ import dagger.BindsInstance;
 import dagger.Lazy;
 import dagger.Subcomponent;
 import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.cryptofs.VaultConfig;
 import org.cryptomator.ui.common.FxmlFile;
 import org.cryptomator.ui.common.FxmlScene;
 
-import javax.inject.Named;
 import javafx.scene.Scene;
 import javafx.stage.Stage;
 
@@ -15,20 +15,26 @@ import javafx.stage.Stage;
 @Subcomponent(modules = {HealthCheckModule.class})
 public interface HealthCheckComponent {
 
+	LoadUnverifiedConfigResult loadConfig();
+
 	@HealthCheckWindow
 	Stage window();
 
-	@Named("windowToClose")
-	Stage windowToClose();
-
 	@FxmlScene(FxmlFile.HEALTH_START)
-	Lazy<Scene> scene();
+	Lazy<Scene> startScene();
+
+	@FxmlScene(FxmlFile.HEALTH_START_FAIL)
+	Lazy<Scene> failScene();
 
 	default Stage showHealthCheckWindow() {
 		Stage stage = window();
-		stage.setScene(scene().get());
+		var unverifiedConf = loadConfig();
+		if (unverifiedConf.config() != null) {
+			stage.setScene(startScene().get());
+		} else {
+			stage.setScene(failScene().get());
+		}
 		stage.show();
-		windowToClose().close();
 		return stage;
 	}
 
@@ -38,10 +44,8 @@ public interface HealthCheckComponent {
 		@BindsInstance
 		Builder vault(@HealthCheckWindow Vault vault);
 
-		@BindsInstance
-		Builder windowToClose(@Named("windowToClose") Stage window);
-
 		HealthCheckComponent build();
 	}
 
+	record LoadUnverifiedConfigResult(VaultConfig.UnverifiedVaultConfig config, Throwable error) {}
 }

+ 24 - 0
src/main/java/org/cryptomator/ui/health/HealthCheckModule.java

@@ -26,6 +26,7 @@ import javafx.beans.value.ChangeListener;
 import javafx.scene.Scene;
 import javafx.stage.Modality;
 import javafx.stage.Stage;
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -35,6 +36,17 @@ import java.util.concurrent.atomic.AtomicReference;
 @Module(subcomponents = {KeyLoadingComponent.class})
 abstract class HealthCheckModule {
 
+	@Provides
+	@HealthCheckScoped
+	static HealthCheckComponent.LoadUnverifiedConfigResult provideLoadConfigResult(@HealthCheckWindow Vault vault) {
+		try {
+			return new HealthCheckComponent.LoadUnverifiedConfigResult(vault.getUnverifiedVaultConfig(), null);
+		} catch (IOException e) {
+			return new HealthCheckComponent.LoadUnverifiedConfigResult(null, e);
+		}
+	}
+
+
 	@Provides
 	@HealthCheckScoped
 	static AtomicReference<Masterkey> provideMasterkeyRef() {
@@ -103,6 +115,13 @@ abstract class HealthCheckModule {
 		return fxmlLoaders.createScene(FxmlFile.HEALTH_START);
 	}
 
+	@Provides
+	@FxmlScene(FxmlFile.HEALTH_START_FAIL)
+	@HealthCheckScoped
+	static Scene provideHealthStartFailScene(@HealthCheckWindow FxmlLoaderFactory fxmlLoaders) {
+		return fxmlLoaders.createScene(FxmlFile.HEALTH_START_FAIL);
+	}
+
 	@Provides
 	@FxmlScene(FxmlFile.HEALTH_CHECK_LIST)
 	@HealthCheckScoped
@@ -115,6 +134,11 @@ abstract class HealthCheckModule {
 	@FxControllerKey(StartController.class)
 	abstract FxController bindStartController(StartController controller);
 
+	@Binds
+	@IntoMap
+	@FxControllerKey(StartFailController.class)
+	abstract FxController bindStartFailController(StartFailController controller);
+
 	@Binds
 	@IntoMap
 	@FxControllerKey(CheckListController.class)

+ 8 - 69
src/main/java/org/cryptomator/ui/health/StartController.java

@@ -1,7 +1,7 @@
 package org.cryptomator.ui.health;
 
+import com.google.common.base.Preconditions;
 import dagger.Lazy;
-import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.cryptofs.VaultConfig;
 import org.cryptomator.cryptofs.VaultConfigLoadException;
 import org.cryptomator.cryptofs.VaultKeyInvalidException;
@@ -18,14 +18,11 @@ import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
 import javafx.application.Platform;
-import javafx.beans.binding.BooleanBinding;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.fxml.FXML;
 import javafx.scene.Scene;
 import javafx.stage.Stage;
-import java.io.IOException;
-import java.io.UncheckedIOException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
 import java.util.concurrent.ExecutorService;
@@ -36,40 +33,26 @@ public class StartController implements FxController {
 
 	private static final Logger LOG = LoggerFactory.getLogger(StartController.class);
 
-	private final Vault vault;
 	private final Stage window;
-	private final CompletableFuture<VaultConfig.UnverifiedVaultConfig> unverifiedVaultConfig;
+	private final ObjectProperty<VaultConfig.UnverifiedVaultConfig> unverifiedVaultConfig;
 	private final KeyLoadingStrategy keyLoadingStrategy;
 	private final ExecutorService executor;
 	private final AtomicReference<Masterkey> masterkeyRef;
 	private final AtomicReference<VaultConfig> vaultConfigRef;
 	private final Lazy<Scene> checkScene;
 	private final Lazy<ErrorComponent.Builder> errorComponent;
-	private final ObjectProperty<State> state = new SimpleObjectProperty<>(State.LOADING);
-	private final BooleanBinding loading = state.isEqualTo(State.LOADING);
-	private final BooleanBinding failed = state.isEqualTo(State.FAILED);
-	private final BooleanBinding loaded = state.isEqualTo(State.LOADED);
-
-	public enum State {
-		LOADING,
-		FAILED,
-		LOADED
-	}
-
-	/* FXML */
 
 	@Inject
-	public StartController(@HealthCheckWindow Vault vault, @HealthCheckWindow Stage window, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy<Scene> checkScene, Lazy<ErrorComponent.Builder> errorComponent) {
-		this.vault = vault;
+	public StartController(@HealthCheckWindow Stage window, HealthCheckComponent.LoadUnverifiedConfigResult configLoadResult, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy<Scene> checkScene, Lazy<ErrorComponent.Builder> errorComponent) {
+		Preconditions.checkNotNull(configLoadResult.config());
 		this.window = window;
-		this.unverifiedVaultConfig = CompletableFuture.supplyAsync(this::loadConfig, executor);
+		this.unverifiedVaultConfig = new SimpleObjectProperty<>(configLoadResult.config());
 		this.keyLoadingStrategy = keyLoadingStrategy;
 		this.executor = executor;
 		this.masterkeyRef = masterkeyRef;
 		this.vaultConfigRef = vaultConfigRef;
 		this.checkScene = checkScene;
 		this.errorComponent = errorComponent;
-		this.unverifiedVaultConfig.whenCompleteAsync(this::loadedConfig, Platform::runLater);
 	}
 
 	@FXML
@@ -84,29 +67,10 @@ public class StartController implements FxController {
 		CompletableFuture.runAsync(this::loadKey, executor).whenCompleteAsync(this::loadedKey, Platform::runLater);
 	}
 
-	private VaultConfig.UnverifiedVaultConfig loadConfig() {
-		assert !Platform.isFxApplicationThread();
-		try {
-			return this.vault.getUnverifiedVaultConfig();
-		} catch (IOException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-
-	private void loadedConfig(VaultConfig.UnverifiedVaultConfig cfg, Throwable exception) {
-		assert Platform.isFxApplicationThread();
-		if (exception != null) {
-			state.set(State.FAILED);
-		} else {
-			assert cfg != null;
-			state.set(State.LOADED);
-		}
-	}
-
 	private void loadKey() {
 		assert !Platform.isFxApplicationThread();
-		assert unverifiedVaultConfig.isDone();
-		var unverifiedCfg = unverifiedVaultConfig.join();
+		assert unverifiedVaultConfig.get() != null;
+		var unverifiedCfg = unverifiedVaultConfig.get();
 		try (var masterkey = keyLoadingStrategy.loadKey(unverifiedCfg.getKeyId())) {
 			var verifiedCfg = unverifiedCfg.verify(masterkey.getEncoded(), unverifiedCfg.allegedVaultVersion());
 			vaultConfigRef.set(verifiedCfg);
@@ -150,35 +114,10 @@ public class StartController implements FxController {
 		}
 	}
 
-	/* Getter */
-
-	public BooleanBinding loadingProperty() {
-		return loading;
-	}
-
-	public boolean isLoading() {
-		return loading.get();
-	}
-
-	public BooleanBinding failedProperty() {
-		return failed;
-	}
-
-	public boolean isFailed() {
-		return failed.get();
-	}
-
-	public BooleanBinding loadedProperty() {
-		return loaded;
-	}
-
-	public boolean isLoaded() {
-		return loaded.get();
-	}
-
 	/* internal types */
 
 	private static class LoadingFailedException extends CompletionException {
+
 		LoadingFailedException(Throwable cause) {
 			super(cause);
 		}

+ 41 - 0
src/main/java/org/cryptomator/ui/health/StartFailController.java

@@ -0,0 +1,41 @@
+package org.cryptomator.ui.health;
+
+import com.google.common.base.Preconditions;
+import org.cryptomator.cryptofs.VaultConfigLoadException;
+import org.cryptomator.ui.common.FxController;
+
+import javax.inject.Inject;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+import javafx.fxml.FXML;
+import javafx.stage.Stage;
+
+public class StartFailController implements FxController {
+
+	@Inject
+	public StartFailController(@HealthCheckWindow Stage window, HealthCheckComponent.LoadUnverifiedConfigResult configLoadResult) {
+		Preconditions.checkNotNull(configLoadResult.error());
+		this.window = window;
+		this.loadError = new SimpleObjectProperty<>(configLoadResult.error());
+		this.localizedErrorMessage = new SimpleStringProperty(configLoadResult.error().getLocalizedMessage());
+		this.typeParseException = Bindings.createBooleanBinding(() -> loadError.get() instanceof VaultConfigLoadException);
+		this.typeIOException = typeParseException.not();
+
+	}
+
+	@FXML
+	public void close() {
+		window.close();
+	}
+
+	private final Stage window;
+	private final ObjectProperty<Throwable> loadError;
+	private final StringProperty localizedErrorMessage;
+	private final BooleanBinding typeIOException;
+	private final BooleanBinding typeParseException;
+
+}

+ 9 - 1
src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java

@@ -4,10 +4,12 @@ import org.cryptomator.common.settings.WhenUnlocked;
 import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.controls.NumericTextField;
+import org.cryptomator.ui.health.HealthCheckComponent;
 
 import javax.inject.Inject;
 import javafx.beans.Observable;
 import javafx.beans.binding.Bindings;
+import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.scene.control.CheckBox;
 import javafx.scene.control.ChoiceBox;
@@ -24,6 +26,7 @@ public class GeneralVaultOptionsController implements FxController {
 
 	private final Stage window;
 	private final Vault vault;
+	private final HealthCheckComponent.Builder healthCheckWindow;
 	private final ResourceBundle resourceBundle;
 
 	public TextField vaultName;
@@ -33,9 +36,10 @@ public class GeneralVaultOptionsController implements FxController {
 	public NumericTextField lockTimeInMinutesTextField;
 
 	@Inject
-	GeneralVaultOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ResourceBundle resourceBundle) {
+	GeneralVaultOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, HealthCheckComponent.Builder healthCheckWindow, ResourceBundle resourceBundle) {
 		this.window = window;
 		this.vault = vault;
+		this.healthCheckWindow = healthCheckWindow;
 		this.resourceBundle = resourceBundle;
 	}
 
@@ -104,4 +108,8 @@ public class GeneralVaultOptionsController implements FxController {
 			}
 		}
 	}
+
+	public void startHealthCheck() {
+		healthCheckWindow.vault(vault).build().showHealthCheckWindow();
+	}
 }

+ 0 - 30
src/main/java/org/cryptomator/ui/vaultoptions/HealthVaultOptionsController.java

@@ -1,30 +0,0 @@
-package org.cryptomator.ui.vaultoptions;
-
-import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.health.HealthCheckComponent;
-
-import javax.inject.Inject;
-import javafx.event.ActionEvent;
-import javafx.fxml.FXML;
-import javafx.stage.Stage;
-
-@VaultOptionsScoped
-public class HealthVaultOptionsController implements FxController {
-
-	private final Stage window;
-	private final Vault vault;
-	private final HealthCheckComponent.Builder healthCheckWindow;
-
-	@Inject
-	public HealthVaultOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, HealthCheckComponent.Builder healthCheckWindow) {
-		this.window = window;
-		this.vault = vault;
-		this.healthCheckWindow = healthCheckWindow;
-	}
-
-	@FXML
-	public void startHealthCheck(ActionEvent event) {
-		healthCheckWindow.vault(vault).windowToClose(window).build().showHealthCheckWindow();
-	}
-}

+ 0 - 5
src/main/java/org/cryptomator/ui/vaultoptions/SelectedVaultOptionsTab.java

@@ -25,9 +25,4 @@ public enum SelectedVaultOptionsTab {
 	 * Show Auto-Lock tab
 	 */
 	AUTOLOCK,
-
-	/**
-	 * Show health tab
-	 */
-	HEALTH;
 }

+ 0 - 2
src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java

@@ -24,7 +24,6 @@ public class VaultOptionsController implements FxController {
 	public Tab mountTab;
 	public Tab keyTab;
 	public Tab autoLockTab;
-	public Tab healthTab;
 
 	@Inject
 	VaultOptionsController(@VaultOptionsWindow Stage window, ObjectProperty<SelectedVaultOptionsTab> selectedTabProperty) {
@@ -50,7 +49,6 @@ public class VaultOptionsController implements FxController {
 			case MOUNT -> mountTab;
 			case KEY -> keyTab;
 			case AUTOLOCK ->  autoLockTab;
-			case HEALTH -> healthTab;
 		};
 	}
 

+ 0 - 5
src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java

@@ -83,9 +83,4 @@ abstract class VaultOptionsModule {
 	@IntoMap
 	@FxControllerKey(MasterkeyOptionsController.class)
 	abstract FxController bindMasterkeyOptionsController(MasterkeyOptionsController controller);
-
-	@Binds
-	@IntoMap
-	@FxControllerKey(HealthVaultOptionsController.class)
-	abstract FxController bindHealthOptionsController(HealthVaultOptionsController controller);
 }

+ 29 - 18
src/main/resources/fxml/health_start.fxml

@@ -1,11 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
 <?import javafx.geometry.Insets?>
 <?import javafx.scene.control.Button?>
 <?import javafx.scene.control.ButtonBar?>
-<?import javafx.scene.control.Label?>
 <?import javafx.scene.layout.VBox?>
+<?import javafx.scene.control.CheckBox?>
+<?import javafx.scene.layout.GridPane?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.layout.RowConstraints?>
+<?import javafx.scene.layout.ColumnConstraints?>
 <VBox xmlns:fx="http://javafx.com/fxml"
 	  xmlns="http://javafx.com/javafx"
 	  fx:controller="org.cryptomator.ui.health.StartController"
@@ -17,25 +20,33 @@
 		<Insets topRightBottomLeft="12"/>
 	</padding>
 	<children>
-		<Label text="TODO loading config..." visible="${controller.loading}" managed="${controller.loading}" wrapText="true" contentDisplay="LEFT">
-			<graphic>
-				<FontAwesome5IconView glyph="SPINNER"/>
-			</graphic>
-		</Label>
-		<Label text="%health.start.configInvalid" visible="${controller.failed}" managed="${controller.failed}" wrapText="true" contentDisplay="LEFT">
-			<graphic>
-				<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
-			</graphic>
-		</Label>
-		<Label text="%health.start.configValid" visible="${controller.loaded}" managed="${controller.loaded}" wrapText="true" contentDisplay="LEFT">
-			<graphic>
-				<FontAwesome5IconView glyph="CHECK" styleClass="glyph-icon-primary"/>
-			</graphic>
-		</Label>
+		<Label text="%vaultOptions.health.introduction" wrapText="true"/>
+		<Label text="%vaultOptions.health.remarks" wrapText="true"/>
+		<GridPane >
+			<padding>
+				<Insets left="6"/>
+			</padding>
+			<columnConstraints>
+				<ColumnConstraints minWidth="20" halignment="LEFT"/>
+				<ColumnConstraints fillWidth="true"/>
+			</columnConstraints>
+			<rowConstraints>
+				<RowConstraints valignment="TOP"/>
+				<RowConstraints valignment="TOP"/>
+				<RowConstraints valignment="TOP"/>
+			</rowConstraints>
+			<Label text="1." GridPane.rowIndex="0" GridPane.columnIndex="0" />
+			<Label text="%vaultOptions.health.remarkSync" wrapText="true" GridPane.rowIndex="0" GridPane.columnIndex="1" />
+			<Label text="2." GridPane.rowIndex="1" GridPane.columnIndex="0" />
+			<Label text="%vaultOptions.health.remarkFix" wrapText="true" GridPane.rowIndex="1" GridPane.columnIndex="1" />
+			<Label text="3." GridPane.rowIndex="2" GridPane.columnIndex="0" />
+			<Label text="%vaultOptions.health.remarkBackup" wrapText="true" GridPane.rowIndex="2" GridPane.columnIndex="1" />
+		</GridPane>
+		<CheckBox text="%vaultOptions.health.affirmation" fx:id="affirmationBox"/>
 		<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
 			<buttons>
 				<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
-				<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" disable="${!controller.loaded}" defaultButton="true" onAction="#next"/>
+				<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" disable="${!affirmationBox.selected}" defaultButton="true" onAction="#next"/>
 			</buttons>
 		</ButtonBar>
 	</children>

+ 19 - 0
src/main/resources/fxml/health_start_fail.fxml

@@ -0,0 +1,19 @@
+<?import javafx.scene.layout.VBox?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.control.ButtonBar?>
+<?import javafx.scene.control.Button?>
+<VBox xmlns="http://javafx.com/javafx"
+	  xmlns:fx="http://javafx.com/fxml"
+	  fx:controller="org.cryptomator.ui.health.StartFailController"
+	  minWidth="400"
+	  maxWidth="400"
+	  minHeight="145"
+	  spacing="12">
+
+	<Label text="TODO: Error on loading" />
+	<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
+		<buttons>
+			<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
+		</buttons>
+	</ButtonBar>
+</VBox>

+ 0 - 8
src/main/resources/fxml/vault_options.fxml

@@ -36,13 +36,5 @@
 				<fx:include source="/fxml/vault_options_masterkey.fxml"/>
 			</content>
 		</Tab>
-		<Tab fx:id="healthTab" id="HEALTH" text="%vaultOptions.health">
-			<graphic>
-				<FontAwesome5IconView glyph="STETHOSCOPE"/>
-			</graphic>
-			<content>
-				<fx:include source="/fxml/vault_options_health.fxml"/>
-			</content>
-		</Tab>
 	</tabs>
 </TabPane>

+ 7 - 0
src/main/resources/fxml/vault_options_general.fxml

@@ -12,6 +12,7 @@
 <?import javafx.scene.text.Text?>
 <?import org.cryptomator.ui.controls.NumericTextField?>
 <?import org.cryptomator.ui.controls.FormattedLabel?>
+<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
 <VBox xmlns:fx="http://javafx.com/fxml"
 	  xmlns="http://javafx.com/javafx"
 	  fx:controller="org.cryptomator.ui.vaultoptions.GeneralVaultOptionsController"
@@ -39,5 +40,11 @@
 			<Label text="%vaultOptions.general.actionAfterUnlock"/>
 			<ChoiceBox fx:id="actionAfterUnlockChoiceBox"/>
 		</HBox>
+
+		<Button fx:id="healthCheckButton" text="%vaultOptions.health.startBtn" onAction="#startHealthCheck">
+			<graphic>
+				<FontAwesome5IconView glyph="STETHOSCOPE"/>
+			</graphic>
+		</Button>
 	</children>
 </VBox>