mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-04-19 17:16:53 -04:00
Merge branch 'develop' into feature/modular
This commit is contained in:
@@ -56,7 +56,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
|
||||
- File names get encrypted
|
||||
- Folder structure gets obfuscated
|
||||
- Use as many vaults in your Dropbox as you want, each having individual passwords
|
||||
- Two thousand commits for the security of your data!! :tada:
|
||||
- Three thousand commits for the security of your data!! :tada:
|
||||
|
||||
### Privacy
|
||||
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -27,7 +27,7 @@
|
||||
<nonModularGroupIds>com.github.serceman,com.github.jnr,org.ow2.asm,net.java.dev.jna,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
|
||||
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>2.1.0-beta7</cryptomator.cryptofs.version>
|
||||
<cryptomator.cryptofs.version>2.1.0-beta8</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.0.0-rc1</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>1.0.0-beta2</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.0.0-beta2</cryptomator.integrations.mac.version>
|
||||
|
||||
@@ -59,8 +59,10 @@ public class KeychainManager implements KeychainAccessProvider {
|
||||
|
||||
@Override
|
||||
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
getKeychainOrFail().changePassphrase(key, passphrase);
|
||||
setPassphraseStored(key, true);
|
||||
if (isPassphraseStored(key)) {
|
||||
getKeychainOrFail().changePassphrase(key, passphrase);
|
||||
setPassphraseStored(key, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,8 +5,8 @@ import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptofs.VaultCipherCombo;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
@@ -182,7 +182,7 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
// 2. initialize vault:
|
||||
try {
|
||||
MasterkeyLoader loader = ignored -> masterkey.clone();
|
||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(VaultCipherCombo.SIV_CTRMAC).withKeyLoader(loader).build();
|
||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(CryptorProvider.Scheme.SIV_CTRMAC).withKeyLoader(loader).build();
|
||||
CryptoFileSystemProvider.initialize(path, fsProps, DEFAULT_KEY_ID);
|
||||
|
||||
// 3. write vault-internal readme file:
|
||||
|
||||
@@ -39,7 +39,7 @@ public enum FontAwesome5Icon {
|
||||
REDO("\uF01E"), //
|
||||
SEARCH("\uF002"), //
|
||||
SPINNER("\uF110"), //
|
||||
STOPWATCH("\uF2F2"), //
|
||||
STETHOSCOPE("\uF0f1"), //
|
||||
SYNC("\uF021"), //
|
||||
TIMES("\uF00D"), //
|
||||
TRASH("\uF1F8"), //
|
||||
|
||||
@@ -21,8 +21,8 @@ public class FontAwesome5IconView extends Text {
|
||||
private static final String FONT_PATH = "/css/fontawesome5-free-solid.otf";
|
||||
private static final Font FONT;
|
||||
|
||||
private ObjectProperty<FontAwesome5Icon> glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH);
|
||||
private DoubleProperty glyphSize = new SimpleDoubleProperty(this, "glyphSize", DEFAULT_GLYPH_SIZE);
|
||||
private final ObjectProperty<FontAwesome5Icon> glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH);
|
||||
private final DoubleProperty glyphSize = new SimpleDoubleProperty(this, "glyphSize", DEFAULT_GLYPH_SIZE);
|
||||
|
||||
static {
|
||||
try {
|
||||
@@ -42,7 +42,7 @@ public class FontAwesome5IconView extends Text {
|
||||
}
|
||||
|
||||
private void glyphChanged(@SuppressWarnings("unused") ObservableValue<? extends FontAwesome5Icon> observable, @SuppressWarnings("unused") FontAwesome5Icon oldValue, FontAwesome5Icon newValue) {
|
||||
setText(newValue.unicode());
|
||||
setText(newValue == null ? null : newValue.unicode());
|
||||
}
|
||||
|
||||
private void glyphSizeChanged(@SuppressWarnings("unused") ObservableValue<? extends Number> observable, @SuppressWarnings("unused") Number oldValue, Number newValue) {
|
||||
|
||||
@@ -16,7 +16,6 @@ public class BatchService extends Service<Void> {
|
||||
|
||||
private final Iterator<HealthCheckTask> remainingTasks;
|
||||
|
||||
@Inject
|
||||
public BatchService(Iterable<HealthCheckTask> tasks) {
|
||||
this.remainingTasks = tasks.iterator();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.util.stream.Stream;
|
||||
@HealthCheckScoped
|
||||
public class CheckDetailController implements FxController {
|
||||
|
||||
private final EasyObservableList<DiagnosticResult> results;
|
||||
private final EasyObservableList<Result> results;
|
||||
private final OptionalBinding<Worker.State> taskState;
|
||||
private final Binding<String> taskName;
|
||||
private final Binding<String> taskDuration;
|
||||
@@ -39,7 +39,7 @@ public class CheckDetailController implements FxController {
|
||||
private final ResultListCellFactory resultListCellFactory;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
public ListView<DiagnosticResult> resultsListView;
|
||||
public ListView<Result> resultsListView;
|
||||
private Subscription resultSubscription;
|
||||
|
||||
@Inject
|
||||
@@ -71,8 +71,8 @@ public class CheckDetailController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private Function<Stream<? extends DiagnosticResult>, Long> countSeverity(DiagnosticResult.Severity severity) {
|
||||
return stream -> stream.filter(item -> severity.equals(item.getSeverity())).count();
|
||||
private Function<Stream<? extends Result>, Long> countSeverity(DiagnosticResult.Severity severity) {
|
||||
return stream -> stream.filter(item -> severity.equals(item.diagnosis().getSeverity())).count();
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -15,101 +12,60 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.util.Callback;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
class CheckListCell extends ListCell<HealthCheckTask> {
|
||||
|
||||
private final FontAwesome5IconView stateIcon = new FontAwesome5IconView();
|
||||
private final Callback<HealthCheckTask, BooleanProperty> selectedGetter;
|
||||
private final ObjectProperty<State> stateProperty;
|
||||
|
||||
private CheckBox checkBox = new CheckBox();
|
||||
private BooleanProperty selectedProperty;
|
||||
|
||||
CheckListCell(Callback<HealthCheckTask, BooleanProperty> selectedGetter, ObservableValue<Boolean> switchIndicator) {
|
||||
this.selectedGetter = selectedGetter;
|
||||
this.stateProperty = new SimpleObjectProperty<>(State.SELECTION);
|
||||
switchIndicator.addListener(this::changeState);
|
||||
CheckListCell() {
|
||||
setPadding(new Insets(6));
|
||||
setAlignment(Pos.CENTER_LEFT);
|
||||
setContentDisplay(ContentDisplay.LEFT);
|
||||
getStyleClass().add("label");
|
||||
}
|
||||
|
||||
private void changeState(ObservableValue<? extends Boolean> observableValue, boolean oldValue, boolean newValue) {
|
||||
if (newValue) {
|
||||
stateProperty.set(State.RUN);
|
||||
} else {
|
||||
stateProperty.set(State.SELECTION);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(HealthCheckTask item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null) {
|
||||
setText(item.getTitle());
|
||||
}
|
||||
switch (stateProperty.get()) {
|
||||
case SELECTION -> updateItemSelection(item, empty);
|
||||
case RUN -> updateItemRun(item, empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateItemSelection(HealthCheckTask item, boolean empty) {
|
||||
if (!empty) {
|
||||
setGraphic(checkBox);
|
||||
|
||||
if (selectedProperty != null) {
|
||||
checkBox.selectedProperty().unbindBidirectional(selectedProperty);
|
||||
}
|
||||
selectedProperty = selectedGetter.call(item);
|
||||
if (selectedProperty != null) {
|
||||
checkBox.selectedProperty().bindBidirectional(selectedProperty);
|
||||
}
|
||||
} else {
|
||||
setGraphic(null);
|
||||
setText(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateItemRun(HealthCheckTask item, boolean empty) {
|
||||
if (item != null) {
|
||||
item.stateProperty().addListener(this::stateChanged);
|
||||
graphicProperty().bind(Bindings.createObjectBinding(() -> graphicForState(item.getState()), item.stateProperty()));
|
||||
stateIcon.setGlyph(glyphForState(item.getState()));
|
||||
stateIcon.glyphProperty().bind(Bindings.createObjectBinding(() -> glyphForState(item), item.stateProperty()));
|
||||
checkBox.selectedProperty().bindBidirectional(item.chosenForExecutionProperty());
|
||||
} else {
|
||||
graphicProperty().unbind();
|
||||
setGraphic(null);
|
||||
setText(null);
|
||||
checkBox.selectedProperty().unbind();
|
||||
}
|
||||
}
|
||||
|
||||
private void stateChanged(ObservableValue<? extends Worker.State> observable, Worker.State oldState, Worker.State newState) {
|
||||
stateIcon.setGlyph(glyphForState(newState));
|
||||
stateIcon.setVisible(true);
|
||||
}
|
||||
|
||||
private Node graphicForState(Worker.State state) {
|
||||
return switch (state) {
|
||||
case READY -> null;
|
||||
case READY -> checkBox;
|
||||
case SCHEDULED, RUNNING, FAILED, CANCELLED, SUCCEEDED -> stateIcon;
|
||||
};
|
||||
}
|
||||
|
||||
private FontAwesome5Icon glyphForState(Worker.State state) {
|
||||
return switch (state) {
|
||||
private FontAwesome5Icon glyphForState(HealthCheckTask item) {
|
||||
return switch (item.getState()) {
|
||||
case READY -> FontAwesome5Icon.COG; //just a placeholder
|
||||
case SCHEDULED -> FontAwesome5Icon.CLOCK;
|
||||
case RUNNING -> FontAwesome5Icon.SPINNER;
|
||||
case FAILED -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
case CANCELLED -> FontAwesome5Icon.BAN;
|
||||
case SUCCEEDED -> FontAwesome5Icon.CHECK;
|
||||
case SUCCEEDED -> checkFoundProblems(item) ? FontAwesome5Icon.EXCLAMATION_TRIANGLE : FontAwesome5Icon.CHECK;
|
||||
};
|
||||
}
|
||||
|
||||
private enum State {
|
||||
SELECTION,
|
||||
RUN;
|
||||
private boolean checkFoundProblems(HealthCheckTask item) {
|
||||
Predicate<DiagnosticResult.Severity> isProblem = severity -> switch (severity) {
|
||||
case WARN, CRITICAL -> true;
|
||||
case INFO, GOOD -> false;
|
||||
};
|
||||
return item.results().stream().map(Result::diagnosis).map(DiagnosticResult::getSeverity).anyMatch(isProblem);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
@@ -10,28 +11,24 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.IntegerBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.cell.CheckBoxListCell;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.StringConverter;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@@ -43,6 +40,7 @@ public class CheckListController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final ObservableList<HealthCheckTask> tasks;
|
||||
private final FilteredList<HealthCheckTask> chosenTasks;
|
||||
private final ReportWriter reportWriter;
|
||||
private final ExecutorService executorService;
|
||||
private final ObjectProperty<HealthCheckTask> selectedTask;
|
||||
@@ -50,19 +48,18 @@ public class CheckListController implements FxController {
|
||||
private final SimpleObjectProperty<Worker<?>> runningTask;
|
||||
private final Binding<Boolean> running;
|
||||
private final Binding<Boolean> finished;
|
||||
private final Map<HealthCheckTask, BooleanProperty> listPickIndicators;
|
||||
private final IntegerProperty numberOfPickedChecks;
|
||||
private final IntegerBinding chosenTaskCount;
|
||||
private final BooleanBinding anyCheckSelected;
|
||||
private final BooleanProperty showResultScreen;
|
||||
|
||||
/* FXML */
|
||||
public ListView<HealthCheckTask> checksListView;
|
||||
|
||||
|
||||
@Inject
|
||||
public CheckListController(@HealthCheckWindow Stage window, Lazy<Collection<HealthCheckTask>> tasks, ReportWriter reportWriteTask, ObjectProperty<HealthCheckTask> selectedTask, ExecutorService executorService, Lazy<ErrorComponent.Builder> errorComponentBuilder) {
|
||||
public CheckListController(@HealthCheckWindow Stage window, Lazy<List<HealthCheckTask>> tasks, ReportWriter reportWriteTask, ObjectProperty<HealthCheckTask> selectedTask, ExecutorService executorService, Lazy<ErrorComponent.Builder> errorComponentBuilder) {
|
||||
this.window = window;
|
||||
this.tasks = FXCollections.observableArrayList(tasks.get());
|
||||
this.tasks = FXCollections.observableList(tasks.get(), HealthCheckTask::observables);
|
||||
this.chosenTasks = this.tasks.filtered(HealthCheckTask::isChosenForExecution);
|
||||
this.reportWriter = reportWriteTask;
|
||||
this.executorService = executorService;
|
||||
this.selectedTask = selectedTask;
|
||||
@@ -70,13 +67,7 @@ public class CheckListController implements FxController {
|
||||
this.runningTask = new SimpleObjectProperty<>();
|
||||
this.running = EasyBind.wrapNullable(runningTask).mapObservable(Worker::runningProperty).orElse(false);
|
||||
this.finished = EasyBind.wrapNullable(runningTask).mapObservable(Worker::stateProperty).map(END_STATES::contains).orElse(false);
|
||||
this.listPickIndicators = new HashMap<>();
|
||||
this.numberOfPickedChecks = new SimpleIntegerProperty(0);
|
||||
this.tasks.forEach(task -> {
|
||||
var entrySelectedProp = new SimpleBooleanProperty(false);
|
||||
entrySelectedProp.addListener((observable, oldValue, newValue) -> numberOfPickedChecks.set(numberOfPickedChecks.get() + (newValue ? 1 : -1)));
|
||||
listPickIndicators.put(task, entrySelectedProp);
|
||||
});
|
||||
this.chosenTaskCount = Bindings.size(this.chosenTasks);
|
||||
this.anyCheckSelected = selectedTask.isNotNull();
|
||||
this.showResultScreen = new SimpleBooleanProperty(false);
|
||||
}
|
||||
@@ -84,27 +75,31 @@ public class CheckListController implements FxController {
|
||||
@FXML
|
||||
public void initialize() {
|
||||
checksListView.setItems(tasks);
|
||||
checksListView.setCellFactory(view -> new CheckListCell(listPickIndicators::get, showResultScreen));
|
||||
checksListView.setCellFactory(view -> new CheckListCell());
|
||||
selectedTask.bind(checksListView.getSelectionModel().selectedItemProperty());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void toggleSelectAll(ActionEvent event) {
|
||||
if (event.getSource() instanceof CheckBox c) {
|
||||
listPickIndicators.forEach( (task, pickProperty) -> pickProperty.set(c.isSelected()));
|
||||
tasks.forEach(t -> t.chosenForExecutionProperty().set(c.isSelected()));
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void runSelectedChecks() {
|
||||
Preconditions.checkState(runningTask.get() == null);
|
||||
var batch = checksListView.getItems().filtered(item -> listPickIndicators.get(item).get());
|
||||
var batchService = new BatchService(batch);
|
||||
|
||||
// prevent further interaction by cancelling non-chosen tasks:
|
||||
tasks.filtered(Predicates.not(chosenTasks::contains)).forEach(HealthCheckTask::cancel);
|
||||
|
||||
// run chosen tasks:
|
||||
var batchService = new BatchService(chosenTasks);
|
||||
batchService.setExecutor(executorService);
|
||||
batchService.start();
|
||||
runningTask.set(batchService);
|
||||
showResultScreen.set(true);
|
||||
checksListView.getSelectionModel().select(batch.get(0));
|
||||
checksListView.getSelectionModel().select(chosenTasks.get(0));
|
||||
checksListView.refresh();
|
||||
window.sizeToScene();
|
||||
}
|
||||
@@ -158,13 +153,12 @@ public class CheckListController implements FxController {
|
||||
return showResultScreen;
|
||||
}
|
||||
|
||||
public int getNumberOfPickedChecks() {
|
||||
return numberOfPickedChecks.get();
|
||||
public int getChosenTaskCount() {
|
||||
return chosenTaskCount.getValue();
|
||||
}
|
||||
|
||||
public IntegerProperty numberOfPickedChecksProperty() {
|
||||
return numberOfPickedChecks;
|
||||
public IntegerBinding chosenTaskCountProperty() {
|
||||
return chosenTaskCount;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||
import org.cryptomator.cryptofs.health.api.HealthCheck;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* FIXME: Remove in production release
|
||||
*/
|
||||
public class DummyHealthChecks {
|
||||
|
||||
public static class DummyCheck1 implements HealthCheck {
|
||||
|
||||
@Override
|
||||
public void check(Path path, VaultConfig vaultConfig, Masterkey masterkey, Cryptor cryptor, Consumer<DiagnosticResult> consumer) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
public static class DummyCheck2 implements HealthCheck {
|
||||
|
||||
@Override
|
||||
public void check(Path path, VaultConfig vaultConfig, Masterkey masterkey, Cryptor cryptor, Consumer<DiagnosticResult> consumer) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
public static class DummyCheck3 implements HealthCheck {
|
||||
|
||||
@Override
|
||||
public void check(Path path, VaultConfig vaultConfig, Masterkey masterkey, Cryptor cryptor, Consumer<DiagnosticResult> consumer) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@@ -17,6 +18,9 @@ public interface HealthCheckComponent {
|
||||
@HealthCheckWindow
|
||||
Stage window();
|
||||
|
||||
@Named("windowToClose")
|
||||
Stage windowToClose();
|
||||
|
||||
@FxmlScene(FxmlFile.HEALTH_START)
|
||||
Lazy<Scene> scene();
|
||||
|
||||
@@ -24,6 +28,7 @@ public interface HealthCheckComponent {
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
stage.show();
|
||||
windowToClose().close();
|
||||
return stage;
|
||||
}
|
||||
|
||||
@@ -33,6 +38,9 @@ public interface HealthCheckComponent {
|
||||
@BindsInstance
|
||||
Builder vault(@HealthCheckWindow Vault vault);
|
||||
|
||||
@BindsInstance
|
||||
Builder windowToClose(@Named("windowToClose") Stage window);
|
||||
|
||||
HealthCheckComponent build();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
@@ -63,7 +64,7 @@ abstract class HealthCheckModule {
|
||||
/* Only inject with Lazy-Wrapper!*/
|
||||
@Provides
|
||||
@HealthCheckScoped
|
||||
static Collection<HealthCheckTask> provideAvailableHealthCheckTasks(Collection<HealthCheck> availableHealthChecks, @HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, SecureRandom csprng, ResourceBundle resourceBundle) {
|
||||
static List<HealthCheckTask> provideAvailableHealthCheckTasks(Collection<HealthCheck> availableHealthChecks, @HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, SecureRandom csprng, ResourceBundle resourceBundle) {
|
||||
return availableHealthChecks.stream().map(check -> new HealthCheckTask(vault.getPath(), vaultConfigRef.get(), masterkeyRef.get(), csprng, check, resourceBundle)).toList();
|
||||
}
|
||||
|
||||
@@ -86,10 +87,10 @@ abstract class HealthCheckModule {
|
||||
@HealthCheckScoped
|
||||
static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle, ChangeListener<Boolean> showingListener) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(resourceBundle.getString("health.title"));
|
||||
stage.setResizable(true);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.setTitle(resourceBundle.getString("health.title"));
|
||||
stage.setResizable(true);
|
||||
stage.showingProperty().addListener(showingListener); // bind masterkey lifecycle to window
|
||||
return stage;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,16 @@ package org.cryptomator.ui.health;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||
import org.cryptomator.cryptofs.health.api.HealthCheck;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -31,8 +35,9 @@ class HealthCheckTask extends Task<Void> {
|
||||
private final Masterkey masterkey;
|
||||
private final SecureRandom csprng;
|
||||
private final HealthCheck check;
|
||||
private final ObservableList<DiagnosticResult> results;
|
||||
private final ObservableList<Result> results;
|
||||
private final LongProperty durationInMillis;
|
||||
private final BooleanProperty chosenForExecution;
|
||||
|
||||
public HealthCheckTask(Path vaultPath, VaultConfig vaultConfig, Masterkey masterkey, SecureRandom csprng, HealthCheck check, ResourceBundle resourceBundle) {
|
||||
this.vaultPath = Objects.requireNonNull(vaultPath);
|
||||
@@ -40,7 +45,7 @@ class HealthCheckTask extends Task<Void> {
|
||||
this.masterkey = Objects.requireNonNull(masterkey);
|
||||
this.csprng = Objects.requireNonNull(csprng);
|
||||
this.check = Objects.requireNonNull(check);
|
||||
this.results = FXCollections.observableArrayList();
|
||||
this.results = FXCollections.observableArrayList(Result::observables);
|
||||
try {
|
||||
updateTitle(resourceBundle.getString("health." + check.identifier()));
|
||||
} catch (MissingResourceException e) {
|
||||
@@ -48,32 +53,22 @@ class HealthCheckTask extends Task<Void> {
|
||||
updateTitle(check.identifier());
|
||||
}
|
||||
this.durationInMillis = new SimpleLongProperty(-1);
|
||||
this.chosenForExecution = new SimpleBooleanProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void call() {
|
||||
Instant start = Instant.now();
|
||||
try (var masterkeyClone = masterkey.clone(); //
|
||||
var cryptor = vaultConfig.getCipherCombo().getCryptorProvider(csprng).withKey(masterkeyClone)) {
|
||||
check.check(vaultPath, vaultConfig, masterkeyClone, cryptor, result -> {
|
||||
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
||||
check.check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosis -> {
|
||||
if (isCancelled()) {
|
||||
throw new CancellationException();
|
||||
}
|
||||
// FIXME: slowdown for demonstration purposes only:
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
} else {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
Platform.runLater(() -> results.add(result));
|
||||
Platform.runLater(() -> results.add(Result.create(diagnosis)));
|
||||
});
|
||||
}
|
||||
Platform.runLater(() ->durationInMillis.set(Duration.between(start, Instant.now()).toMillis()));
|
||||
Platform.runLater(() -> durationInMillis.set(Duration.between(start, Instant.now()).toMillis()));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -89,7 +84,11 @@ class HealthCheckTask extends Task<Void> {
|
||||
|
||||
/* Getter */
|
||||
|
||||
public ObservableList<DiagnosticResult> results() {
|
||||
Observable[] observables() {
|
||||
return new Observable[]{results, chosenForExecution};
|
||||
}
|
||||
|
||||
public ObservableList<Result> results() {
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -105,4 +104,11 @@ class HealthCheckTask extends Task<Void> {
|
||||
return durationInMillis.get();
|
||||
}
|
||||
|
||||
public BooleanProperty chosenForExecutionProperty() {
|
||||
return chosenForExecution;
|
||||
}
|
||||
|
||||
public boolean isChosenForExecution() {
|
||||
return chosenForExecution.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public class ReportWriter {
|
||||
case SUCCEEDED -> {
|
||||
writer.write("STATUS: SUCCESS\nRESULTS:\n");
|
||||
for (var result : task.results()) {
|
||||
writer.write(REPORT_CHECK_RESULT.formatted(result.getSeverity(), result.toString()));
|
||||
writer.write(REPORT_CHECK_RESULT.formatted(result.diagnosis().getSeverity(), result.getDescription()));
|
||||
}
|
||||
}
|
||||
case CANCELLED -> writer.write("STATUS: CANCELED\n");
|
||||
|
||||
43
src/main/java/org/cryptomator/ui/health/Result.java
Normal file
43
src/main/java/org/cryptomator/ui/health/Result.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
|
||||
|
||||
enum FixState {
|
||||
NOT_FIXABLE,
|
||||
FIXABLE,
|
||||
FIXING,
|
||||
FIXED,
|
||||
FIX_FAILED
|
||||
}
|
||||
|
||||
public static Result create(DiagnosticResult diagnosis) {
|
||||
FixState initialState = switch (diagnosis.getSeverity()) {
|
||||
case WARN -> FixState.FIXABLE;
|
||||
default -> FixState.NOT_FIXABLE;
|
||||
};
|
||||
return new Result(diagnosis, new SimpleObjectProperty<>(initialState));
|
||||
}
|
||||
|
||||
public Observable[] observables() {
|
||||
return new Observable[]{fixState};
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return diagnosis.toString();
|
||||
}
|
||||
|
||||
public FixState getState() {
|
||||
return fixState.get();
|
||||
}
|
||||
|
||||
public void setState(FixState state) {
|
||||
this.fixState.set(state);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,25 +4,30 @@ import com.google.common.base.Preconditions;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.application.Platform;
|
||||
import java.nio.file.Path;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@HealthCheckScoped
|
||||
class ResultFixApplier {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResultFixApplier.class);
|
||||
|
||||
private final Path vaultPath;
|
||||
private final SecureRandom csprng;
|
||||
private final Masterkey masterkey;
|
||||
private final VaultConfig vaultConfig;
|
||||
private final ExecutorService sequentialExecutor;
|
||||
|
||||
@Inject
|
||||
public ResultFixApplier(@HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, SecureRandom csprng) {
|
||||
@@ -30,18 +35,32 @@ class ResultFixApplier {
|
||||
this.masterkey = masterkeyRef.get();
|
||||
this.vaultConfig = vaultConfigRef.get();
|
||||
this.csprng = csprng;
|
||||
this.sequentialExecutor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
public void fix(DiagnosticResult result) {
|
||||
Preconditions.checkArgument(result.getSeverity() == DiagnosticResult.Severity.WARN, "Unfixable result");
|
||||
public CompletionStage<Void> fix(Result result) {
|
||||
Preconditions.checkArgument(result.getState() == Result.FixState.FIXABLE);
|
||||
result.setState(Result.FixState.FIXING);
|
||||
return CompletableFuture.runAsync(() -> fix(result.diagnosis()), sequentialExecutor)
|
||||
.whenCompleteAsync((unused, throwable) -> {
|
||||
var fixed = throwable == null ? Result.FixState.FIXED : Result.FixState.FIX_FAILED;
|
||||
result.setState(fixed);
|
||||
}, Platform::runLater);
|
||||
}
|
||||
|
||||
public void fix(DiagnosticResult diagnosis) {
|
||||
Preconditions.checkArgument(diagnosis.getSeverity() == DiagnosticResult.Severity.WARN, "Unfixable result");
|
||||
try (var masterkeyClone = masterkey.clone(); //
|
||||
var cryptor = vaultConfig.getCipherCombo().getCryptorProvider(csprng).withKey(masterkeyClone)) {
|
||||
result.fix(vaultPath, vaultConfig, masterkeyClone, cryptor);
|
||||
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
||||
diagnosis.fix(vaultPath, vaultConfig, masterkeyClone, cryptor);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to apply fix", e);
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, e.getMessage());
|
||||
alert.showAndWait();
|
||||
//TODO: real error/not supported handling
|
||||
throw new FixFailedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class FixFailedException extends CompletionException {
|
||||
private FixFailedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +1,89 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||
import com.tobiasdiez.easybind.optional.OptionalBinding;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
|
||||
// unscoped because each cell needs its own controller
|
||||
public class ResultListCellController implements FxController {
|
||||
|
||||
private final ResultFixApplier fixApplier;
|
||||
private final ObjectProperty<DiagnosticResult> result;
|
||||
private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
|
||||
|
||||
private final ObjectProperty<Result> result;
|
||||
private final Binding<String> description;
|
||||
private final ResultFixApplier fixApplier;
|
||||
private final OptionalBinding<Result.FixState> fixState;
|
||||
private final ObjectBinding<FontAwesome5Icon> glyph;
|
||||
private final BooleanBinding fixable;
|
||||
private final BooleanBinding fixing;
|
||||
private final BooleanBinding fixed;
|
||||
|
||||
public FontAwesome5IconView iconView;
|
||||
public Button actionButton;
|
||||
public Button fixButton;
|
||||
|
||||
@Inject
|
||||
public ResultListCellController(ResultFixApplier fixApplier) {
|
||||
this.result = new SimpleObjectProperty<>(null);
|
||||
this.description = EasyBind.wrapNullable(result).map(DiagnosticResult::toString).orElse("");
|
||||
this.description = EasyBind.wrapNullable(result).map(Result::getDescription).orElse("");
|
||||
this.fixApplier = fixApplier;
|
||||
result.addListener(this::updateCellContent);
|
||||
}
|
||||
|
||||
private void updateCellContent(ObservableValue<? extends DiagnosticResult> observable, DiagnosticResult oldVal, DiagnosticResult newVal) {
|
||||
iconView.getStyleClass().clear();
|
||||
actionButton.setVisible(false);
|
||||
//TODO: see comment in case WARN
|
||||
actionButton.setManaged(false);
|
||||
switch (newVal.getSeverity()) {
|
||||
case INFO -> {
|
||||
iconView.setGlyph(FontAwesome5Icon.INFO_CIRCLE);
|
||||
iconView.getStyleClass().add("glyph-icon-muted");
|
||||
}
|
||||
case GOOD -> {
|
||||
iconView.setGlyph(FontAwesome5Icon.CHECK);
|
||||
iconView.getStyleClass().add("glyph-icon-primary");
|
||||
}
|
||||
case WARN -> {
|
||||
iconView.setGlyph(FontAwesome5Icon.EXCLAMATION_TRIANGLE);
|
||||
iconView.getStyleClass().add("glyph-icon-orange");
|
||||
//TODO: Neither is any fix implemented, nor it is ensured, that only fix is executed at a time with good ui indication
|
||||
// before both are not fix, do not show the button
|
||||
//actionButton.setVisible(true);
|
||||
}
|
||||
case CRITICAL -> {
|
||||
iconView.setGlyph(FontAwesome5Icon.TIMES);
|
||||
iconView.getStyleClass().add("glyph-icon-red");
|
||||
}
|
||||
}
|
||||
this.fixState = EasyBind.wrapNullable(result).mapObservable(Result::fixState);
|
||||
this.glyph = Bindings.createObjectBinding(this::getGlyph, result);
|
||||
this.fixable = Bindings.createBooleanBinding(this::isFixable, fixState);
|
||||
this.fixing = Bindings.createBooleanBinding(this::isFixing, fixState);
|
||||
this.fixed = Bindings.createBooleanBinding(this::isFixed, fixState);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void runResultAction() {
|
||||
final var realResult = result.get();
|
||||
if (realResult != null) {
|
||||
fixApplier.fix(realResult);
|
||||
public void initialize() {
|
||||
// see getGlyph() for relevant glyphs:
|
||||
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-muted", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.INFO_CIRCLE));
|
||||
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-primary", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.CHECK));
|
||||
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-orange", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.EXCLAMATION_TRIANGLE));
|
||||
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-red", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.TIMES));
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void fix() {
|
||||
Result r = result.get();
|
||||
if (r != null) {
|
||||
fixApplier.fix(r).whenCompleteAsync(this::fixFinished, Platform::runLater);
|
||||
}
|
||||
}
|
||||
|
||||
private void fixFinished(Void unused, Throwable exception) {
|
||||
if (exception != null) {
|
||||
LOG.error("Failed to apply fix", exception);
|
||||
// TODO ...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Getter & Setter */
|
||||
|
||||
|
||||
public DiagnosticResult getResult() {
|
||||
public Result getResult() {
|
||||
return result.get();
|
||||
}
|
||||
|
||||
public void setResult(DiagnosticResult result) {
|
||||
public void setResult(Result result) {
|
||||
this.result.set(result);
|
||||
}
|
||||
|
||||
public ObjectProperty<DiagnosticResult> resultProperty() {
|
||||
public ObjectProperty<Result> resultProperty() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -86,7 +91,49 @@ public class ResultListCellController implements FxController {
|
||||
return description.getValue();
|
||||
}
|
||||
|
||||
public ObjectBinding<FontAwesome5Icon> glyphProperty() {
|
||||
return glyph;
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getGlyph() {
|
||||
var r = result.get();
|
||||
if (r == null) {
|
||||
return null;
|
||||
}
|
||||
return switch (r.diagnosis().getSeverity()) {
|
||||
case INFO -> FontAwesome5Icon.INFO_CIRCLE;
|
||||
case GOOD -> FontAwesome5Icon.CHECK;
|
||||
case WARN -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
case CRITICAL -> FontAwesome5Icon.TIMES;
|
||||
};
|
||||
}
|
||||
|
||||
public Binding<String> descriptionProperty() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public BooleanBinding fixableProperty() {
|
||||
return fixable;
|
||||
}
|
||||
|
||||
public boolean isFixable() {
|
||||
return fixState.get().map(Result.FixState.FIXABLE::equals).orElse(false);
|
||||
}
|
||||
|
||||
public BooleanBinding fixingProperty() {
|
||||
return fixing;
|
||||
}
|
||||
|
||||
public boolean isFixing() {
|
||||
return fixState.get().map(Result.FixState.FIXING::equals).orElse(false);
|
||||
}
|
||||
|
||||
public BooleanBinding fixedProperty() {
|
||||
return fixed;
|
||||
}
|
||||
|
||||
public boolean isFixed() {
|
||||
return fixState.get().map(Result.FixState.FIXED::equals).orElse(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
|
||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -15,7 +14,7 @@ import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
@HealthCheckScoped
|
||||
public class ResultListCellFactory implements Callback<ListView<DiagnosticResult>, ListCell<DiagnosticResult>> {
|
||||
public class ResultListCellFactory implements Callback<ListView<Result>, ListCell<Result>> {
|
||||
|
||||
private final FxmlLoaderFactory fxmlLoaders;
|
||||
|
||||
@@ -25,7 +24,7 @@ public class ResultListCellFactory implements Callback<ListView<DiagnosticResult
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListCell<DiagnosticResult> call(ListView<DiagnosticResult> param) {
|
||||
public ListCell<Result> call(ListView<Result> param) {
|
||||
try {
|
||||
FXMLLoader fxmlLoader = fxmlLoaders.load("/fxml/health_result_listcell.fxml");
|
||||
return new ResultListCellFactory.Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
|
||||
@@ -34,7 +33,7 @@ public class ResultListCellFactory implements Callback<ListView<DiagnosticResult
|
||||
}
|
||||
}
|
||||
|
||||
private static class Cell extends ListCell<DiagnosticResult> {
|
||||
private static class Cell extends ListCell<Result> {
|
||||
|
||||
private final Parent node;
|
||||
private final ResultListCellController controller;
|
||||
@@ -45,7 +44,7 @@ public class ResultListCellFactory implements Callback<ListView<DiagnosticResult
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(DiagnosticResult item, boolean empty) {
|
||||
protected void updateItem(Result item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item == null || empty) {
|
||||
setText(null);
|
||||
|
||||
@@ -18,11 +18,16 @@ 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.util.Optional;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@@ -31,38 +36,40 @@ public class StartController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StartController.class);
|
||||
|
||||
private final Vault vault;
|
||||
private final Stage window;
|
||||
private final Optional<VaultConfig.UnverifiedVaultConfig> unverifiedVaultConfig;
|
||||
private final CompletableFuture<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;
|
||||
this.window = window;
|
||||
this.unverifiedVaultConfig = CompletableFuture.supplyAsync(this::loadConfig, executor);
|
||||
this.keyLoadingStrategy = keyLoadingStrategy;
|
||||
this.executor = executor;
|
||||
this.masterkeyRef = masterkeyRef;
|
||||
this.vaultConfigRef = vaultConfigRef;
|
||||
this.checkScene = checkScene;
|
||||
this.errorComponent = errorComponent;
|
||||
|
||||
//TODO: this is ugly
|
||||
//idea: delay the loading of the vault config and show a spinner (something like "check/load config") and react to the result of the loading
|
||||
//or: load vault config in a previous step to see if it is loadable.
|
||||
VaultConfig.UnverifiedVaultConfig tmp;
|
||||
try {
|
||||
tmp = vault.getUnverifiedVaultConfig();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
tmp = null;
|
||||
}
|
||||
this.unverifiedVaultConfig = Optional.ofNullable(tmp);
|
||||
this.unverifiedVaultConfig.whenCompleteAsync(this::loadedConfig, Platform::runLater);
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -74,41 +81,64 @@ public class StartController implements FxController {
|
||||
@FXML
|
||||
public void next() {
|
||||
LOG.trace("StartController.next()");
|
||||
executor.submit(this::loadKey);
|
||||
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.isPresent();
|
||||
try (var masterkey = keyLoadingStrategy.loadKey(unverifiedVaultConfig.orElseThrow().getKeyId())) {
|
||||
var unverifiedCfg = unverifiedVaultConfig.get();
|
||||
assert unverifiedVaultConfig.isDone();
|
||||
var unverifiedCfg = unverifiedVaultConfig.join();
|
||||
try (var masterkey = keyLoadingStrategy.loadKey(unverifiedCfg.getKeyId())) {
|
||||
var verifiedCfg = unverifiedCfg.verify(masterkey.getEncoded(), unverifiedCfg.allegedVaultVersion());
|
||||
vaultConfigRef.set(verifiedCfg);
|
||||
var old = masterkeyRef.getAndSet(masterkey.clone());
|
||||
if (old != null) {
|
||||
old.destroy();
|
||||
}
|
||||
Platform.runLater(this::loadedKey);
|
||||
} catch (MasterkeyLoadingFailedException e) {
|
||||
if (keyLoadingStrategy.recoverFromException(e)) {
|
||||
// retry
|
||||
loadKey();
|
||||
} else {
|
||||
Platform.runLater(() -> loadingKeyFailed(e));
|
||||
throw new LoadingFailedException(e);
|
||||
}
|
||||
} catch (VaultKeyInvalidException e) {
|
||||
Platform.runLater(() -> loadingKeyFailed(e));
|
||||
} catch (VaultConfigLoadException e) {
|
||||
Platform.runLater(() -> loadingKeyFailed(e));
|
||||
throw new LoadingFailedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadedKey() {
|
||||
LOG.debug("Loaded valid key");
|
||||
window.setScene(checkScene.get());
|
||||
private void loadedKey(Void unused, Throwable exception) {
|
||||
assert Platform.isFxApplicationThread();
|
||||
if (exception instanceof LoadingFailedException) {
|
||||
loadingKeyFailed(exception.getCause());
|
||||
} else if (exception != null) {
|
||||
loadingKeyFailed(exception);
|
||||
} else {
|
||||
LOG.debug("Loaded valid key");
|
||||
window.setScene(checkScene.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadingKeyFailed(Exception e) {
|
||||
private void loadingKeyFailed(Throwable e) {
|
||||
if (e instanceof UnlockCancelledException) {
|
||||
// ok
|
||||
} else if (e instanceof VaultKeyInvalidException) {
|
||||
@@ -120,8 +150,37 @@ public class StartController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInvalidConfig() {
|
||||
return unverifiedVaultConfig.isEmpty();
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package org.cryptomator.ui.vaultoptions;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.NumericTextField;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
@VaultOptionsScoped
|
||||
public class AutoLockVaultOptionsController implements FxController {
|
||||
|
||||
private final Vault vault;
|
||||
|
||||
public CheckBox lockAfterTimeCheckbox;
|
||||
public NumericTextField lockTimeInMinutesTextField;
|
||||
|
||||
@Inject
|
||||
AutoLockVaultOptionsController(@VaultOptionsWindow Vault vault) {
|
||||
this.vault = vault;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
lockAfterTimeCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().autoLockWhenIdle());
|
||||
Bindings.bindBidirectional(lockTimeInMinutesTextField.textProperty(), vault.getVaultSettings().autoLockIdleSeconds(), new IdleTimeSecondsConverter());
|
||||
}
|
||||
|
||||
private static class IdleTimeSecondsConverter extends StringConverter<Number> {
|
||||
|
||||
@Override
|
||||
public String toString(Number seconds) {
|
||||
int minutes = seconds.intValue() / 60; // int-truncate
|
||||
return Integer.toString(minutes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number fromString(String string) {
|
||||
try {
|
||||
int minutes = Integer.valueOf(string);
|
||||
return minutes * 60;
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,10 +3,11 @@ package org.cryptomator.ui.vaultoptions;
|
||||
import org.cryptomator.common.settings.WhenUnlocked;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.health.HealthCheckComponent;
|
||||
import org.cryptomator.ui.controls.NumericTextField;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
@@ -23,18 +24,18 @@ 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;
|
||||
public CheckBox unlockOnStartupCheckbox;
|
||||
public ChoiceBox<WhenUnlocked> actionAfterUnlockChoiceBox;
|
||||
public CheckBox lockAfterTimeCheckbox;
|
||||
public NumericTextField lockTimeInMinutesTextField;
|
||||
|
||||
@Inject
|
||||
GeneralVaultOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, HealthCheckComponent.Builder healthCheckWindow, ResourceBundle resourceBundle) {
|
||||
GeneralVaultOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.healthCheckWindow = healthCheckWindow;
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
@@ -47,6 +48,8 @@ public class GeneralVaultOptionsController implements FxController {
|
||||
actionAfterUnlockChoiceBox.getItems().addAll(WhenUnlocked.values());
|
||||
actionAfterUnlockChoiceBox.valueProperty().bindBidirectional(vault.getVaultSettings().actionAfterUnlock());
|
||||
actionAfterUnlockChoiceBox.setConverter(new WhenUnlockedConverter(resourceBundle));
|
||||
lockAfterTimeCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().autoLockWhenIdle());
|
||||
Bindings.bindBidirectional(lockTimeInMinutesTextField.textProperty(), vault.getVaultSettings().autoLockIdleSeconds(), new IdleTimeSecondsConverter());
|
||||
}
|
||||
|
||||
private void trimVaultNameOnFocusLoss(Observable observable, Boolean wasFocussed, Boolean isFocussed) {
|
||||
@@ -64,12 +67,6 @@ public class GeneralVaultOptionsController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showHealthCheck() {
|
||||
healthCheckWindow.vault(vault).build().showHealthCheckWindow();
|
||||
}
|
||||
|
||||
|
||||
private static class WhenUnlockedConverter extends StringConverter<WhenUnlocked> {
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
@@ -89,4 +86,22 @@ public class GeneralVaultOptionsController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private static class IdleTimeSecondsConverter extends StringConverter<Number> {
|
||||
|
||||
@Override
|
||||
public String toString(Number seconds) {
|
||||
int minutes = seconds.intValue() / 60; // int-truncate
|
||||
return Integer.toString(minutes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number fromString(String string) {
|
||||
try {
|
||||
int minutes = Integer.valueOf(string);
|
||||
return minutes * 60;
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,11 @@ public enum SelectedVaultOptionsTab {
|
||||
|
||||
/**
|
||||
* Show Auto-Lock tab
|
||||
*
|
||||
*/
|
||||
AUTOLOCK,
|
||||
|
||||
/**
|
||||
* Show health tab
|
||||
*/
|
||||
HEALTH;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ 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) {
|
||||
@@ -49,6 +50,7 @@ public class VaultOptionsController implements FxController {
|
||||
case MOUNT -> mountTab;
|
||||
case KEY -> keyTab;
|
||||
case AUTOLOCK -> autoLockTab;
|
||||
case HEALTH -> healthTab;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,6 @@ abstract class VaultOptionsModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(AutoLockVaultOptionsController.class)
|
||||
abstract FxController bindAutoLockVaultOptionsController(AutoLockVaultOptionsController controller);
|
||||
|
||||
@FxControllerKey(HealthVaultOptionsController.class)
|
||||
abstract FxController bindHealthOptionsController(HealthVaultOptionsController controller);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
org.cryptomator.ui.health.DummyHealthChecks$DummyCheck1
|
||||
org.cryptomator.ui.health.DummyHealthChecks$DummyCheck2
|
||||
org.cryptomator.ui.health.DummyHealthChecks$DummyCheck3
|
||||
@@ -3,17 +3,18 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import java.lang.Integer?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.health.CheckListController"
|
||||
minHeight="145"
|
||||
prefWidth="600"
|
||||
prefHeight="400"
|
||||
spacing="12">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
@@ -28,7 +29,7 @@
|
||||
<CheckBox onAction="#toggleSelectAll" text="%health.checkList.selectAllBox" visible="${!controller.showResultScreen}" managed="${!controller.showResultScreen}" />
|
||||
<ListView fx:id="checksListView" VBox.vgrow="ALWAYS"/>
|
||||
</VBox>
|
||||
<StackPane visible="${controller.showResultScreen}" managed="${controller.showResultScreen}" HBox.hgrow="ALWAYS" >
|
||||
<StackPane visible="${controller.showResultScreen}" HBox.hgrow="ALWAYS" >
|
||||
<VBox minWidth="300" alignment="CENTER" visible="${!controller.anyCheckSelected}" managed="${!controller.anyCheckSelected}" >
|
||||
<Label text="%health.check.detail.noSelectedCheck" wrapText="true" alignment="CENTER" />
|
||||
</VBox>
|
||||
@@ -39,7 +40,7 @@
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" onAction="#cancelCheck" disable="${!controller.running}" visible="${controller.showResultScreen}" managed="${controller.showResultScreen}" />
|
||||
<Button text="%health.check.exportBtn" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" disable="${!controller.finished}" visible="${controller.showResultScreen}" managed="${controller.showResultScreen}" onAction="#exportResults"/>
|
||||
<Button text="%health.check.runBatchBtn" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#runSelectedChecks" disable="${controller.numberOfPickedChecks == ZERO}" visible="${!controller.showResultScreen}" managed="${!controller.showResultScreen}"/>
|
||||
<Button text="%health.check.runBatchBtn" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#runSelectedChecks" disable="${controller.chosenTaskCount == ZERO}" visible="${!controller.showResultScreen}" managed="${!controller.showResultScreen}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</children>
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.health.ResultListCellController"
|
||||
@@ -18,12 +22,18 @@
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<children>
|
||||
<FontAwesome5IconView fx:id="iconView" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
<FontAwesome5IconView fx:id="iconView" HBox.hgrow="NEVER" glyphSize="16" glyph="${controller.glyph}"/>
|
||||
<Label text="${controller.description}"/>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<!-- TODO: setting the minWidth of the button is just a workaround.
|
||||
What we actually want to do is to prevent shrinking the button more than the text
|
||||
-> own subclass of HBox is needed -->
|
||||
<Button fx:id="actionButton" text="%health.check.fixBtn" onAction="#runResultAction" alignment="CENTER" visible="false" minWidth="-Infinity"/>
|
||||
<StackPane HBox.hgrow="NEVER">
|
||||
<children>
|
||||
<Button fx:id="fixButton" text="%health.check.fixBtn" visible="${controller.fixable}" managed="${controller.fixable}" onAction="#fix" alignment="CENTER" minWidth="-Infinity"/>
|
||||
<ProgressIndicator progress="-1" prefWidth="12" prefHeight="12" visible="${controller.fixing}" managed="${controller.fixing}"/>
|
||||
<FontAwesome5IconView glyph="CHECK" glyphSize="16" visible="${controller.fixed}" managed="${controller.fixed}"/>
|
||||
</children>
|
||||
</StackPane>
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
@@ -17,23 +17,25 @@
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Label text="%health.start.introduction" wrapText="true"/>
|
||||
|
||||
<!-- TODO: combine the two below labels to one and bind the properties accordingly or, preferably think about a new flow -->
|
||||
<Label text="%health.start.configInvalid" visible="${controller.invalidConfig}" managed="${controller.invalidConfig}" wrapText="true" contentDisplay="LEFT">
|
||||
<Label text="TODO loading config..." visible="${controller.loading}" managed="${controller.loading}" wrapText="true" contentDisplay="LEFT">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red" />
|
||||
<FontAwesome5IconView glyph="SPINNER"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%health.start.configValid" visible="${!controller.invalidConfig}" managed="${!controller.invalidConfig}" wrapText="true" contentDisplay="LEFT">
|
||||
<Label text="%health.start.configInvalid" visible="${controller.failed}" managed="${controller.failed}" wrapText="true" contentDisplay="LEFT">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CHECK" styleClass="glyph-icon-primary" />
|
||||
<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>
|
||||
<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.invalidConfig}" defaultButton="true" onAction="#next"/>
|
||||
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" disable="${!controller.loaded}" defaultButton="true" onAction="#next"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</children>
|
||||
|
||||
@@ -36,12 +36,12 @@
|
||||
<fx:include source="vault_options_masterkey.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab fx:id="autoLockTab" id="AUTOLOCK" text="%vaultOptions.autoLock">
|
||||
<Tab fx:id="healthTab" id="HEALTH" text="%vaultOptions.health">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="STOPWATCH"/>
|
||||
<FontAwesome5IconView glyph="STETHOSCOPE"/>
|
||||
</graphic>
|
||||
<content>
|
||||
<fx:include source="vault_options_autolock.fxml"/>
|
||||
<fx:include source="vault_options_health.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import org.cryptomator.ui.controls.NumericTextField?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.vaultoptions.AutoLockVaultOptionsController"
|
||||
spacing="6">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<TextFlow styleClass="text-flow" prefWidth="-Infinity">
|
||||
<CheckBox text="%vaultOptions.autoLock.lockAfterTimePart1" fx:id="lockAfterTimeCheckbox"/>
|
||||
<Text text=" "/>
|
||||
<NumericTextField fx:id="lockTimeInMinutesTextField" prefWidth="50"/>
|
||||
<Text text=" "/>
|
||||
<FormattedLabel format="%vaultOptions.autoLock.lockAfterTimePart2"/>
|
||||
</TextFlow>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -8,6 +8,10 @@
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import org.cryptomator.ui.controls.NumericTextField?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.vaultoptions.GeneralVaultOptionsController"
|
||||
@@ -21,12 +25,19 @@
|
||||
<TextField fx:id="vaultName"/>
|
||||
</HBox>
|
||||
|
||||
<TextFlow styleClass="text-flow" prefWidth="-Infinity">
|
||||
<CheckBox text="%vaultOptions.general.autoLock.lockAfterTimePart1" fx:id="lockAfterTimeCheckbox"/>
|
||||
<Text text=" "/>
|
||||
<NumericTextField fx:id="lockTimeInMinutesTextField" prefWidth="50"/>
|
||||
<Text text=" "/>
|
||||
<FormattedLabel format="%vaultOptions.general.autoLock.lockAfterTimePart2"/>
|
||||
</TextFlow>
|
||||
|
||||
<CheckBox text="%vaultOptions.general.unlockAfterStartup" fx:id="unlockOnStartupCheckbox"/>
|
||||
|
||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||
<Label text="%vaultOptions.general.actionAfterUnlock"/>
|
||||
<ChoiceBox fx:id="actionAfterUnlockChoiceBox"/>
|
||||
</HBox>
|
||||
<Button text="%vaultOptions.general.healthBtn" onAction="#showHealthCheck"/>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
49
src/main/resources/fxml/vault_options_health.fxml
Normal file
49
src/main/resources/fxml/vault_options_health.fxml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.shape.Box?>
|
||||
<?import javafx.scene.Group?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.collections.ObservableList?>
|
||||
<?import javafx.collections.FXCollections?>
|
||||
<?import javafx.scene.layout.RowConstraints?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.vaultoptions.HealthVaultOptionsController"
|
||||
spacing="6">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<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"/>
|
||||
<Button text="%vaultOptions.health.startBtn" disable="${!affirmationBox.selected}" onAction="#startHealthCheck"/>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -148,7 +148,6 @@ migration.impossible.moreInfo=The vault can still be opened with an older versio
|
||||
|
||||
# Health Check
|
||||
health.title=Vault Health Check
|
||||
health.start.introduction=The Vault Health Check is a collection of checks to detect and possibly fix problems in the internal structure of your vault. Please note, that not all problems are fixable. You need the vault password to perform the checks.
|
||||
health.start.configValid=Reading and parsing vault configuration file was successful. Proceed to select checks.
|
||||
health.start.configInvalid=Error while reading and parsing the vault configuration file.
|
||||
health.checkList.header=Available Health Checks
|
||||
@@ -304,12 +303,13 @@ wrongFileAlert.link=For further assistance, visit
|
||||
## General
|
||||
vaultOptions.general=General
|
||||
vaultOptions.general.vaultName=Vault Name
|
||||
vaultOptions.general.autoLock.lockAfterTimePart1=Lock when idle for
|
||||
vaultOptions.general.autoLock.lockAfterTimePart2=minutes
|
||||
vaultOptions.general.unlockAfterStartup=Unlock vault when starting Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=After successful unlock
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Do nothing
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Reveal Drive
|
||||
vaultOptions.general.actionAfterUnlock.ask=Ask
|
||||
vaultOptions.general.healthBtn=Start Health Check
|
||||
## Mount
|
||||
vaultOptions.mount=Mounting
|
||||
vaultOptions.mount.readonly=Read-Only
|
||||
@@ -328,10 +328,16 @@ vaultOptions.masterkey.forgetSavedPasswordBtn=Forget Saved Password
|
||||
vaultOptions.masterkey.recoveryKeyExplanation=A recovery key is your only means to restore access to a vault if you lose your password.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Display Recovery Key
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Recover Password
|
||||
## Auto Lock
|
||||
vaultOptions.autoLock=Auto-Lock
|
||||
vaultOptions.autoLock.lockAfterTimePart1=Lock when idle for
|
||||
vaultOptions.autoLock.lockAfterTimePart2=minutes
|
||||
## Health
|
||||
vaultOptions.health=Health Check
|
||||
vaultOptions.health.startBtn=Start Health Check
|
||||
vaultOptions.health.introduction=Health Check is a collection of checks to detect and possibly fix problems in the internal structure of your vault.
|
||||
vaultOptions.health.remarks=Please keep in mind:
|
||||
vaultOptions.health.remarkSync=Incomplete synchronisation causes most problems. Ensure that every device is completely synced.
|
||||
vaultOptions.health.remarkFix=Not all problems can be fixed.
|
||||
vaultOptions.health.remarkBackup=If data is corrupted, only a backup can help.
|
||||
vaultOptions.health.affirmation=I have read and understood the above information.
|
||||
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Recovery Key
|
||||
|
||||
Reference in New Issue
Block a user