mirror of
https://github.com/cryptomator/cryptomator.git
synced 2025-02-10 12:23:33 +00:00
- Improved shutdown hooks
- Redesigned UI, now a single-window application (todo: minimize to tray)
This commit is contained in:
parent
884b894e04
commit
d0f0c09585
@ -32,6 +32,7 @@ public final class WebDAVServer {
|
||||
private static final int MIN_THREADS = 4;
|
||||
private static final int THREAD_IDLE_SECONDS = 20;
|
||||
private final Server server;
|
||||
private int port;
|
||||
|
||||
public WebDAVServer() {
|
||||
final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(MAX_PENDING_REQUESTS);
|
||||
@ -42,9 +43,9 @@ public final class WebDAVServer {
|
||||
/**
|
||||
* @param workDir Path of encrypted folder.
|
||||
* @param cryptor A fully initialized cryptor instance ready to en- or decrypt streams.
|
||||
* @return port, on which the server did start
|
||||
* @return <code>true</code> upon success
|
||||
*/
|
||||
public int start(final String workDir, final Cryptor cryptor) {
|
||||
public synchronized boolean start(final String workDir, final Cryptor cryptor) {
|
||||
final ServerConnector connector = new ServerConnector(server);
|
||||
connector.setHost(LOCALHOST);
|
||||
|
||||
@ -58,20 +59,22 @@ public final class WebDAVServer {
|
||||
try {
|
||||
server.setConnectors(new Connector[] {connector});
|
||||
server.start();
|
||||
port = connector.getLocalPort();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Server couldn't be started", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return connector.getLocalPort();
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return server.isRunning();
|
||||
}
|
||||
|
||||
public boolean stop() {
|
||||
public synchronized boolean stop() {
|
||||
try {
|
||||
server.stop();
|
||||
port = 0;
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Server couldn't be stopped", ex);
|
||||
}
|
||||
@ -85,4 +88,8 @@ public final class WebDAVServer {
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,113 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.cryptomator.crypto.aes256.Aes256Cryptor;
|
||||
import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
||||
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.util.WebDavMounter;
|
||||
import org.cryptomator.ui.util.WebDavMounter.CommandFailedException;
|
||||
import org.cryptomator.webdav.WebDAVServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AccessController implements Initializable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AccessController.class);
|
||||
|
||||
private final Aes256Cryptor cryptor = new Aes256Cryptor();
|
||||
private final WebDAVServer server = new WebDAVServer();
|
||||
private ResourceBundle rb;
|
||||
private String unmountCmd;
|
||||
|
||||
@FXML
|
||||
private GridPane rootPane;
|
||||
|
||||
@FXML
|
||||
private Label messageLabel;
|
||||
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle rb) {
|
||||
this.rb = rb;
|
||||
}
|
||||
|
||||
@FXML
|
||||
protected void closeVault(ActionEvent event) {
|
||||
this.tryStop();
|
||||
this.rootPane.getScene().getWindow().hide();
|
||||
}
|
||||
|
||||
public boolean unlockStorage(Path masterKeyPath, SecPasswordField passwordField, Label errorMessageLabel) {
|
||||
final CharSequence password = passwordField.getCharacters();
|
||||
InputStream masterKeyInputStream = null;
|
||||
try {
|
||||
masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ);
|
||||
cryptor.decryptMasterKey(masterKeyInputStream, password);
|
||||
tryStart();
|
||||
return true;
|
||||
} catch (DecryptFailedException | IOException ex) {
|
||||
errorMessageLabel.setText(rb.getString("access.errorMessage.decryptionFailed"));
|
||||
LOG.error("Decryption failed for technical reasons.", ex);
|
||||
} catch (WrongPasswordException e) {
|
||||
errorMessageLabel.setText(rb.getString("access.errorMessage.wrongPassword"));
|
||||
} catch (UnsupportedKeyLengthException ex) {
|
||||
errorMessageLabel.setText(rb.getString("access.errorMessage.unsupportedKeyLengthInstallJCE"));
|
||||
LOG.error("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
|
||||
} finally {
|
||||
passwordField.swipe();
|
||||
IOUtils.closeQuietly(masterKeyInputStream);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void tryStart() {
|
||||
final Settings settings = Settings.load();
|
||||
final int webdavPort = server.start(settings.getWebdavWorkDir(), cryptor);
|
||||
if (webdavPort > 0) {
|
||||
try {
|
||||
unmountCmd = WebDavMounter.mount(webdavPort);
|
||||
MainApplication.addShutdownTask(this::tryStop);
|
||||
} catch (CommandFailedException e) {
|
||||
messageLabel.setText(String.format(rb.getString("access.messageLabel.mountFailed"), webdavPort));
|
||||
LOG.error("Mounting WebDAV share failed.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void tryStop() {
|
||||
if (server != null && server.isRunning()) {
|
||||
try {
|
||||
WebDavMounter.unmount(unmountCmd);
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.warn("Unmounting WebDAV share failed.", e);
|
||||
}
|
||||
server.stop();
|
||||
cryptor.swipeSensitiveData();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -24,21 +24,33 @@ import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.aes256.Aes256Cryptor;
|
||||
import org.cryptomator.ui.controls.ClearOnDisableListener;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class InitializeController implements Initializable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
|
||||
private static final int MAX_USERNAME_LENGTH = 250;
|
||||
|
||||
private ResourceBundle localization;
|
||||
private SecPasswordField referencePasswordField;
|
||||
private Path masterKeyPath;
|
||||
private InitializationFinishedCallback callback;
|
||||
private Directory directory;
|
||||
private InitializationListener listener;
|
||||
|
||||
@FXML
|
||||
private TextField usernameField;
|
||||
|
||||
@FXML
|
||||
private SecPasswordField passwordField;
|
||||
|
||||
@FXML
|
||||
private SecPasswordField retypePasswordField;
|
||||
@ -52,26 +64,69 @@ public class InitializeController implements Initializable {
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle rb) {
|
||||
this.localization = rb;
|
||||
usernameField.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
|
||||
usernameField.textProperty().addListener(this::usernameFieldDidChange);
|
||||
passwordField.textProperty().addListener(this::passwordFieldDidChange);
|
||||
retypePasswordField.textProperty().addListener(this::retypePasswordFieldDidChange);
|
||||
retypePasswordField.disableProperty().addListener(new ClearOnDisableListener(retypePasswordField));
|
||||
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Username field
|
||||
// ****************************************
|
||||
|
||||
public void filterAlphanumericKeyEvents(KeyEvent t) {
|
||||
if (t.getCharacter() == null || t.getCharacter().length() == 0) {
|
||||
return;
|
||||
}
|
||||
char c = t.getCharacter().charAt(0);
|
||||
if (!CharUtils.isAsciiAlphanumeric(c)) {
|
||||
t.consume();
|
||||
}
|
||||
}
|
||||
|
||||
public void usernameFieldDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
||||
if (StringUtils.length(newValue) > MAX_USERNAME_LENGTH) {
|
||||
usernameField.setText(newValue.substring(0, MAX_USERNAME_LENGTH));
|
||||
}
|
||||
passwordField.setDisable(StringUtils.isEmpty(newValue));
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Password field
|
||||
// ****************************************
|
||||
|
||||
private void passwordFieldDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
||||
retypePasswordField.setDisable(StringUtils.isEmpty(newValue));
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Retype password field
|
||||
// ****************************************
|
||||
|
||||
private void retypePasswordFieldDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
||||
boolean passwordsAreEqual = referencePasswordField.getText().equals(retypePasswordField.getText());
|
||||
boolean passwordsAreEqual = passwordField.getText().equals(retypePasswordField.getText());
|
||||
okButton.setDisable(!passwordsAreEqual);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// OK button
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
protected void initWorkDir(ActionEvent event) {
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor();
|
||||
final CharSequence password = referencePasswordField.getCharacters();
|
||||
protected void initializeVault(ActionEvent event) {
|
||||
final String masterKeyFileName = usernameField.getText() + Aes256Cryptor.MASTERKEY_FILE_EXT;
|
||||
final Path masterKeyPath = directory.getPath().resolve(masterKeyFileName);
|
||||
final CharSequence password = passwordField.getCharacters();
|
||||
OutputStream masterKeyOutputStream = null;
|
||||
try {
|
||||
masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
|
||||
cryptor.randomizeMasterKey();
|
||||
cryptor.encryptMasterKey(masterKeyOutputStream, password);
|
||||
cryptor.swipeSensitiveData();
|
||||
if (callback != null) {
|
||||
callback.initializationFinished(InitializationResult.SUCCESS);
|
||||
directory.getCryptor().randomizeMasterKey();
|
||||
directory.getCryptor().encryptMasterKey(masterKeyOutputStream, password);
|
||||
directory.getCryptor().swipeSensitiveData();
|
||||
if (listener != null) {
|
||||
listener.didInitialize(this);
|
||||
}
|
||||
} catch (FileAlreadyExistsException ex) {
|
||||
messageLabel.setText(localization.getString("initialize.messageLabel.alreadyInitialized"));
|
||||
@ -80,52 +135,35 @@ public class InitializeController implements Initializable {
|
||||
} catch (IOException ex) {
|
||||
LOG.error("I/O Exception", ex);
|
||||
} finally {
|
||||
usernameField.setText(null);
|
||||
passwordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
IOUtils.closeQuietly(masterKeyOutputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
protected void cancel(ActionEvent event) {
|
||||
if (callback != null) {
|
||||
callback.initializationFinished(InitializationResult.CANCELED);
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public SecPasswordField getReferencePasswordField() {
|
||||
return referencePasswordField;
|
||||
public Directory getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setReferencePasswordField(SecPasswordField referencePasswordField) {
|
||||
this.referencePasswordField = referencePasswordField;
|
||||
public void setDirectory(Directory directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
public Path getMasterKeyPath() {
|
||||
return masterKeyPath;
|
||||
public InitializationListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
public void setMasterKeyPath(Path masterKeyPath) {
|
||||
this.masterKeyPath = masterKeyPath;
|
||||
public void setListener(InitializationListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public InitializationFinishedCallback getCallback() {
|
||||
return callback;
|
||||
}
|
||||
/* callback */
|
||||
|
||||
public void setCallback(InitializationFinishedCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/* Modal callback stuff */
|
||||
|
||||
enum InitializationResult {
|
||||
CANCELED, SUCCESS
|
||||
};
|
||||
|
||||
interface InitializationFinishedCallback {
|
||||
void initializationFinished(InitializationResult result);
|
||||
interface InitializationListener {
|
||||
void didInitialize(InitializeController ctrl);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,7 +34,10 @@ public class MainApplication extends Application {
|
||||
@Override
|
||||
public void start(final Stage primaryStage) throws IOException {
|
||||
final ResourceBundle localizations = ResourceBundle.getBundle("localization");
|
||||
final Parent root = FXMLLoader.load(getClass().getResource("/main.fxml"), localizations);
|
||||
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/main.fxml"), localizations);
|
||||
final Parent root = loader.load();
|
||||
final MainController ctrl = loader.getController();
|
||||
ctrl.setStage(primaryStage);
|
||||
final Scene scene = new Scene(root);
|
||||
primaryStage.setTitle("Cryptomator");
|
||||
primaryStage.setScene(scene);
|
||||
@ -50,11 +53,11 @@ public class MainApplication extends Application {
|
||||
super.stop();
|
||||
}
|
||||
|
||||
static void addShutdownTask(Runnable r) {
|
||||
public static void addShutdownTask(Runnable r) {
|
||||
SHUTDOWN_TASKS.add(r);
|
||||
}
|
||||
|
||||
static void removeShutdownTask(Runnable r) {
|
||||
public static void removeShutdownTask(Runnable r) {
|
||||
SHUTDOWN_TASKS.remove(r);
|
||||
}
|
||||
|
||||
|
@ -11,315 +11,143 @@ package org.cryptomator.ui;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Iterator;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SplitMenuButton;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.aes256.Aes256Cryptor;
|
||||
import org.cryptomator.ui.InitializeController.InitializationResult;
|
||||
import org.cryptomator.ui.controls.ClearOnDisableListener;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.InitializeController.InitializationListener;
|
||||
import org.cryptomator.ui.UnlockController.UnlockListener;
|
||||
import org.cryptomator.ui.UnlockedController.LockListener;
|
||||
import org.cryptomator.ui.controls.DirectoryListCell;
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.util.MasterKeyFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MainController implements Initializable {
|
||||
public class MainController implements Initializable, InitializationListener, UnlockListener, LockListener {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
|
||||
private static final int MAX_USERNAME_LENGTH = 200;
|
||||
|
||||
private Stage stage;
|
||||
|
||||
@FXML
|
||||
private HBox rootPane;
|
||||
|
||||
@FXML
|
||||
private ListView<Directory> directoryList;
|
||||
|
||||
@FXML
|
||||
private Pane contentPane;
|
||||
|
||||
private ResourceBundle rb;
|
||||
|
||||
private Workflow workflow = Workflow.UNKNOWN;
|
||||
|
||||
private enum Workflow {
|
||||
UNKNOWN, INIT, OPEN
|
||||
};
|
||||
|
||||
@FXML
|
||||
private GridPane rootPane;
|
||||
|
||||
@FXML
|
||||
private TextField workDirTextField;
|
||||
|
||||
@FXML
|
||||
private TextField usernameField;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> usernameBox;
|
||||
|
||||
@FXML
|
||||
private SecPasswordField passwordField;
|
||||
|
||||
@FXML
|
||||
private SplitMenuButton openButton;
|
||||
|
||||
@FXML
|
||||
private Button initializeButton;
|
||||
|
||||
@FXML
|
||||
private Label messageLabel;
|
||||
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle rb) {
|
||||
this.rb = rb;
|
||||
// attach event handler
|
||||
workDirTextField.textProperty().addListener(this::workDirDidChange);
|
||||
usernameField.addEventFilter(KeyEvent.KEY_TYPED, this::filterUsernameKeyEvents);
|
||||
usernameField.disableProperty().addListener(new ClearOnDisableListener(usernameField));
|
||||
usernameField.textProperty().addListener(this::usernameFieldDidChange);
|
||||
usernameBox.valueProperty().addListener(this::usernameBoxDidChange);
|
||||
passwordField.disableProperty().addListener(new ClearOnDisableListener(passwordField));
|
||||
passwordField.textProperty().addListener(this::passwordFieldDidChange);
|
||||
passwordField.addEventHandler(KeyEvent.KEY_PRESSED, this::onPasswordFieldKeyPressed);
|
||||
|
||||
// load settings
|
||||
workDirTextField.setText(Settings.load().getWebdavWorkDir());
|
||||
usernameBox.setValue(Settings.load().getUsername());
|
||||
directoryList.setCellFactory(this::createDirecoryListCell);
|
||||
directoryList.getSelectionModel().getSelectedItems().addListener(this::selectedDirectoryDidChange);
|
||||
directoryList.getItems().addAll(Settings.load().getDirectories());
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Workdir field
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
protected void chooseWorkDir(ActionEvent event) {
|
||||
final File currentFolder = new File(workDirTextField.getText());
|
||||
private void didClickAddDirectory(ActionEvent event) {
|
||||
final DirectoryChooser dirChooser = new DirectoryChooser();
|
||||
if (currentFolder.exists()) {
|
||||
dirChooser.setInitialDirectory(currentFolder);
|
||||
}
|
||||
final File file = dirChooser.showDialog(rootPane.getScene().getWindow());
|
||||
final File file = dirChooser.showDialog(stage);
|
||||
if (file != null && file.canWrite()) {
|
||||
workDirTextField.setText(file.toString());
|
||||
final Directory dir = new Directory(file.toPath());
|
||||
directoryList.getItems().add(dir);
|
||||
Settings.load().getDirectories().clear();
|
||||
Settings.load().getDirectories().addAll(directoryList.getItems());
|
||||
directoryList.getSelectionModel().selectLast();
|
||||
}
|
||||
}
|
||||
|
||||
private void workDirDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
||||
if (StringUtils.isEmpty(newValue)) {
|
||||
usernameField.setDisable(true);
|
||||
usernameBox.setDisable(true);
|
||||
return;
|
||||
}
|
||||
private ListCell<Directory> createDirecoryListCell(ListView<Directory> param) {
|
||||
return new DirectoryListCell();
|
||||
}
|
||||
|
||||
private void selectedDirectoryDidChange(ListChangeListener.Change<? extends Directory> change) {
|
||||
final Directory selectedDir = directoryList.getSelectionModel().getSelectedItem();
|
||||
stage.setTitle(selectedDir.getName());
|
||||
try {
|
||||
final Path dir = FileSystems.getDefault().getPath(newValue);
|
||||
final Iterator<Path> masterKeys = MasterKeyFilter.filteredDirectory(dir).iterator();
|
||||
if (masterKeys.hasNext()) {
|
||||
workflow = Workflow.OPEN;
|
||||
showUsernameBox(masterKeys);
|
||||
showOpenButton();
|
||||
if (selectedDir.containsMasterKey()) {
|
||||
this.showUnlockView(selectedDir);
|
||||
} else {
|
||||
workflow = Workflow.INIT;
|
||||
showUsernameField();
|
||||
showInitializeButton();
|
||||
}
|
||||
usernameField.setDisable(false);
|
||||
usernameBox.setDisable(false);
|
||||
Settings.load().setWebdavWorkDir(newValue);
|
||||
} catch (InvalidPathException | IOException e) {
|
||||
usernameField.setDisable(true);
|
||||
usernameBox.setDisable(true);
|
||||
messageLabel.setText(rb.getString("main.messageLabel.invalidPath"));
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Username field
|
||||
// ****************************************
|
||||
|
||||
private void showUsernameField() {
|
||||
messageLabel.setText(rb.getString("main.messageLabel.initVaultMessage"));
|
||||
usernameBox.setVisible(false);
|
||||
usernameField.setVisible(true);
|
||||
Platform.runLater(usernameField::requestFocus);
|
||||
}
|
||||
|
||||
private void usernameFieldDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
||||
if (StringUtils.length(newValue) > MAX_USERNAME_LENGTH) {
|
||||
usernameField.setText(newValue.substring(0, MAX_USERNAME_LENGTH));
|
||||
}
|
||||
passwordField.setDisable(StringUtils.isEmpty(usernameField.getText()));
|
||||
}
|
||||
|
||||
private void filterUsernameKeyEvents(KeyEvent t) {
|
||||
if (t.getCharacter() == null || t.getCharacter().length() == 0) {
|
||||
return;
|
||||
}
|
||||
char c = t.getCharacter().charAt(0);
|
||||
if (!CharUtils.isAsciiAlphanumeric(c)) {
|
||||
t.consume();
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Username box
|
||||
// ****************************************
|
||||
|
||||
private void showUsernameBox(Iterator<Path> foundMasterKeys) {
|
||||
messageLabel.setText(rb.getString("main.messageLabel.openVaultMessage"));
|
||||
usernameField.setVisible(false);
|
||||
usernameBox.setVisible(true);
|
||||
|
||||
// update usernameBox options:
|
||||
usernameBox.getItems().clear();
|
||||
final String masterKeyExt = Aes256Cryptor.MASTERKEY_FILE_EXT.toLowerCase();
|
||||
foundMasterKeys.forEachRemaining(path -> {
|
||||
final String fileName = path.getFileName().toString();
|
||||
final int beginOfExt = fileName.toLowerCase().lastIndexOf(masterKeyExt);
|
||||
final String baseName = fileName.substring(0, beginOfExt);
|
||||
usernameBox.getItems().add(baseName);
|
||||
});
|
||||
|
||||
// autochoose user, if possible:
|
||||
if (usernameBox.getItems().size() == 1) {
|
||||
usernameBox.setValue(usernameBox.getItems().get(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void usernameBoxDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
||||
if (!Workflow.OPEN.equals(workflow)) {
|
||||
return;
|
||||
}
|
||||
if (newValue != null) {
|
||||
Settings.load().setUsername(newValue);
|
||||
}
|
||||
passwordField.setDisable(StringUtils.isEmpty(newValue));
|
||||
Platform.runLater(passwordField::requestFocus);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Password field
|
||||
// ****************************************
|
||||
|
||||
private void passwordFieldDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
||||
initializeButton.setDisable(StringUtils.isEmpty(newValue));
|
||||
openButton.setDisable(StringUtils.isEmpty(newValue));
|
||||
}
|
||||
|
||||
public void onPasswordFieldKeyPressed(KeyEvent event) {
|
||||
if (KeyCode.ENTER.equals(event.getCode())) {
|
||||
switch (workflow) {
|
||||
case OPEN:
|
||||
openButton.fire();
|
||||
break;
|
||||
case INIT:
|
||||
initializeButton.fire();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Initialize vault button
|
||||
// ****************************************
|
||||
|
||||
private void showInitializeButton() {
|
||||
openButton.setVisible(false);
|
||||
initializeButton.setVisible(true);
|
||||
}
|
||||
|
||||
@FXML
|
||||
protected void showInitializationDialog(ActionEvent event) {
|
||||
final Path storagePath = FileSystems.getDefault().getPath(workDirTextField.getText());
|
||||
final String masterKeyFileName = usernameField.getText() + Aes256Cryptor.MASTERKEY_FILE_EXT;
|
||||
final Path masterKeyPath = storagePath.resolve(masterKeyFileName);
|
||||
|
||||
try {
|
||||
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/initialize.fxml"), rb);
|
||||
final Parent initDialog = loader.load();
|
||||
final Scene dialogScene = new Scene(initDialog);
|
||||
final Stage dialog = new Stage();
|
||||
final InitializeController ctrl = loader.getController();
|
||||
ctrl.setReferencePasswordField(passwordField);
|
||||
ctrl.setMasterKeyPath(masterKeyPath);
|
||||
ctrl.setCallback(result -> {
|
||||
if (InitializationResult.SUCCESS.equals(result)) {
|
||||
this.initializationSucceeded();
|
||||
}
|
||||
dialog.close();
|
||||
});
|
||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||
dialog.initOwner(rootPane.getScene().getWindow());
|
||||
dialog.setTitle(rb.getString("initialize.title"));
|
||||
dialog.setScene(dialogScene);
|
||||
dialog.sizeToScene();
|
||||
dialog.setResizable(false);
|
||||
dialog.show();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load fxml file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializationSucceeded() {
|
||||
// trigger re-evaluation of work dir. there should be a masterkey file now.
|
||||
this.workDirDidChange(workDirTextField.textProperty(), workDirTextField.getText(), workDirTextField.getText());
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Open vault button
|
||||
// ****************************************
|
||||
|
||||
private void showOpenButton() {
|
||||
initializeButton.setVisible(false);
|
||||
openButton.setVisible(true);
|
||||
}
|
||||
|
||||
@FXML
|
||||
protected void openVault(ActionEvent event) {
|
||||
final Path storagePath = FileSystems.getDefault().getPath(workDirTextField.getText());
|
||||
final String masterKeyFileName = usernameBox.getValue() + Aes256Cryptor.MASTERKEY_FILE_EXT;
|
||||
final Path masterKeyPath = storagePath.resolve(masterKeyFileName);
|
||||
|
||||
try {
|
||||
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/access.fxml"), rb);
|
||||
final Parent accessDialog = loader.load();
|
||||
final Scene dialogScene = new Scene(accessDialog);
|
||||
final AccessController ctrl = loader.getController();
|
||||
if (ctrl.unlockStorage(masterKeyPath, passwordField, messageLabel)) {
|
||||
passwordField.swipe();
|
||||
final Stage dialog = new Stage();
|
||||
dialog.initModality(Modality.NONE);
|
||||
dialog.initOwner(rootPane.getScene().getWindow());
|
||||
dialog.setTitle(storagePath.getFileName().toString());
|
||||
dialog.setScene(dialogScene);
|
||||
dialog.sizeToScene();
|
||||
dialog.setResizable(false);
|
||||
dialog.show();
|
||||
dialog.setOnCloseRequest(windowEvent -> {
|
||||
ctrl.tryStop();
|
||||
});
|
||||
} else {
|
||||
Platform.runLater(passwordField::requestFocus);
|
||||
this.showInitializeView(selectedDir);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load fxml file.", e);
|
||||
LOG.error("Failed to analyze directory.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Subcontroller for right panel
|
||||
// ****************************************
|
||||
|
||||
private <T> T showView(String fxml) {
|
||||
try {
|
||||
final FXMLLoader loader = new FXMLLoader(getClass().getResource(fxml), rb);
|
||||
final Parent root = loader.load();
|
||||
contentPane.getChildren().clear();
|
||||
contentPane.getChildren().add(root);
|
||||
return loader.getController();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to load fxml file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void showInitializeView(Directory directory) {
|
||||
final InitializeController ctrl = showView("/initialize.fxml");
|
||||
ctrl.setDirectory(directory);
|
||||
ctrl.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didInitialize(InitializeController ctrl) {
|
||||
showUnlockView(ctrl.getDirectory());
|
||||
}
|
||||
|
||||
private void showUnlockView(Directory directory) {
|
||||
final UnlockController ctrl = showView("/unlock.fxml");
|
||||
ctrl.setDirectory(directory);
|
||||
ctrl.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didUnlock(UnlockController ctrl) {
|
||||
showUnlockedView(ctrl.getDirectory());
|
||||
}
|
||||
|
||||
private void showUnlockedView(Directory directory) {
|
||||
final UnlockedController ctrl = showView("/unlocked.fxml");
|
||||
ctrl.setDirectory(directory);
|
||||
ctrl.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didLock(UnlockedController ctrl) {
|
||||
showUnlockView(ctrl.getDirectory());
|
||||
}
|
||||
|
||||
/* public Getter/Setter */
|
||||
|
||||
public Stage getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
public void setStage(Stage stage) {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
}
|
||||
|
154
main/ui/src/main/java/org/cryptomator/ui/UnlockController.java
Normal file
154
main/ui/src/main/java/org/cryptomator/ui/UnlockController.java
Normal file
@ -0,0 +1,154 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.aes256.Aes256Cryptor;
|
||||
import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
||||
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.cryptomator.ui.util.MasterKeyFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UnlockController implements Initializable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
|
||||
|
||||
private ResourceBundle rb;
|
||||
private UnlockListener listener;
|
||||
private Directory directory;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> usernameBox;
|
||||
|
||||
@FXML
|
||||
private SecPasswordField passwordField;
|
||||
|
||||
@FXML
|
||||
private Label messageLabel;
|
||||
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle rb) {
|
||||
this.rb = rb;
|
||||
|
||||
usernameBox.valueProperty().addListener(this::didChooseUsername);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Username box
|
||||
// ****************************************
|
||||
|
||||
public void didChooseUsername(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
||||
if (newValue != null) {
|
||||
Platform.runLater(passwordField::requestFocus);
|
||||
}
|
||||
passwordField.setDisable(StringUtils.isEmpty(newValue));
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Unlock button
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
protected void didClickUnlockButton(ActionEvent event) {
|
||||
final String masterKeyFileName = usernameBox.getValue() + Aes256Cryptor.MASTERKEY_FILE_EXT;
|
||||
final Path masterKeyPath = directory.getPath().resolve(masterKeyFileName);
|
||||
final CharSequence password = passwordField.getCharacters();
|
||||
InputStream masterKeyInputStream = null;
|
||||
try {
|
||||
masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ);
|
||||
directory.getCryptor().decryptMasterKey(masterKeyInputStream, password);
|
||||
if (!directory.startServer()) {
|
||||
messageLabel.setText(rb.getString("unlock.messageLabel.startServerFailed"));
|
||||
return;
|
||||
}
|
||||
directory.mount();
|
||||
if (listener != null) {
|
||||
listener.didUnlock(this);
|
||||
}
|
||||
} catch (DecryptFailedException | IOException ex) {
|
||||
messageLabel.setText(rb.getString("unlock.errorMessage.decryptionFailed"));
|
||||
LOG.error("Decryption failed for technical reasons.", ex);
|
||||
} catch (WrongPasswordException e) {
|
||||
messageLabel.setText(rb.getString("unlock.errorMessage.wrongPassword"));
|
||||
} catch (UnsupportedKeyLengthException ex) {
|
||||
messageLabel.setText(rb.getString("unlock.errorMessage.unsupportedKeyLengthInstallJCE"));
|
||||
LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
|
||||
} finally {
|
||||
passwordField.swipe();
|
||||
IOUtils.closeQuietly(masterKeyInputStream);
|
||||
}
|
||||
}
|
||||
|
||||
private void findExistingUsernames() {
|
||||
try {
|
||||
DirectoryStream<Path> ds = MasterKeyFilter.filteredDirectory(directory.getPath());
|
||||
final String masterKeyExt = Aes256Cryptor.MASTERKEY_FILE_EXT.toLowerCase();
|
||||
usernameBox.getItems().clear();
|
||||
for (final Path path : ds) {
|
||||
final String fileName = path.getFileName().toString();
|
||||
final int beginOfExt = fileName.toLowerCase().lastIndexOf(masterKeyExt);
|
||||
final String baseName = fileName.substring(0, beginOfExt);
|
||||
usernameBox.getItems().add(baseName);
|
||||
}
|
||||
if (usernameBox.getItems().size() == 1) {
|
||||
usernameBox.getSelectionModel().selectFirst();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.trace("Invalid path: " + directory.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Directory getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setDirectory(Directory directory) {
|
||||
this.directory = directory;
|
||||
this.findExistingUsernames();
|
||||
}
|
||||
|
||||
public UnlockListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
public void setListener(UnlockListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/* callback */
|
||||
|
||||
interface UnlockListener {
|
||||
void didUnlock(UnlockController ctrl);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
|
||||
public class UnlockedController implements Initializable {
|
||||
|
||||
private ResourceBundle rb;
|
||||
private LockListener listener;
|
||||
private Directory directory;
|
||||
|
||||
@FXML
|
||||
private Label messageLabel;
|
||||
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle rb) {
|
||||
this.rb = rb;
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
protected void closeVault(ActionEvent event) {
|
||||
directory.unmount();
|
||||
directory.stopServer();
|
||||
if (listener != null) {
|
||||
listener.didLock(this);
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Directory getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setDirectory(Directory directory) {
|
||||
this.directory = directory;
|
||||
final String msg = String.format(rb.getString("unlocked.messageLabel.runningOnPort"), directory.getServer().getPort());
|
||||
messageLabel.setText(msg);
|
||||
}
|
||||
|
||||
public LockListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
public void setListener(LockListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/* callback */
|
||||
|
||||
interface LockListener {
|
||||
void didLock(UnlockedController ctrl);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.cryptomator.ui.controls;
|
||||
|
||||
import javafx.scene.control.ListCell;
|
||||
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
|
||||
public class DirectoryListCell extends ListCell<Directory> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Directory item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(item.getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
127
main/ui/src/main/java/org/cryptomator/ui/model/Directory.java
Normal file
127
main/ui/src/main/java/org/cryptomator/ui/model/Directory.java
Normal file
@ -0,0 +1,127 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.aes256.Aes256Cryptor;
|
||||
import org.cryptomator.ui.MainApplication;
|
||||
import org.cryptomator.ui.util.MasterKeyFilter;
|
||||
import org.cryptomator.ui.util.WebDavMounter;
|
||||
import org.cryptomator.ui.util.WebDavMounter.CommandFailedException;
|
||||
import org.cryptomator.webdav.WebDAVServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
@JsonSerialize(using = DirectorySerializer.class)
|
||||
@JsonDeserialize(using = DirectoryDeserializer.class)
|
||||
public class Directory implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3754487289683599469L;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Directory.class);
|
||||
|
||||
private final WebDAVServer server = new WebDAVServer();
|
||||
private final Aes256Cryptor cryptor = new Aes256Cryptor();
|
||||
private final Path path;
|
||||
private boolean unlocked;
|
||||
private String unmountCommand;
|
||||
private final Runnable shutdownTask = new ShutdownTask();
|
||||
|
||||
public Directory(final Path path) {
|
||||
if (!Files.isDirectory(path)) {
|
||||
throw new IllegalArgumentException("Not a directory: " + path);
|
||||
}
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public boolean containsMasterKey() throws IOException {
|
||||
return MasterKeyFilter.filteredDirectory(path).iterator().hasNext();
|
||||
}
|
||||
|
||||
public synchronized boolean startServer() {
|
||||
if (server.start(path.toString(), cryptor)) {
|
||||
MainApplication.addShutdownTask(shutdownTask);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stopServer() {
|
||||
if (server.isRunning()) {
|
||||
MainApplication.removeShutdownTask(shutdownTask);
|
||||
this.unmount();
|
||||
server.stop();
|
||||
cryptor.swipeSensitiveData();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean mount() {
|
||||
try {
|
||||
unmountCommand = WebDavMounter.mount(server.getPort());
|
||||
return true;
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.warn("mount failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean unmount() {
|
||||
try {
|
||||
if (StringUtils.isNotEmpty(unmountCommand)) {
|
||||
WebDavMounter.unmount(unmountCommand);
|
||||
unmountCommand = null;
|
||||
}
|
||||
return true;
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.warn("unmount failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Directory name without preceeding path components
|
||||
*/
|
||||
public String getName() {
|
||||
return path.getFileName().toString();
|
||||
}
|
||||
|
||||
public Aes256Cryptor getCryptor() {
|
||||
return cryptor;
|
||||
}
|
||||
|
||||
public boolean isUnlocked() {
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
public void setUnlocked(boolean unlocked) {
|
||||
this.unlocked = unlocked;
|
||||
}
|
||||
|
||||
public WebDAVServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
/* graceful shutdown */
|
||||
|
||||
private class ShutdownTask implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
stopServer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
public class DirectoryDeserializer extends JsonDeserializer<Directory> {
|
||||
|
||||
@Override
|
||||
public Directory deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
final JsonNode node = jp.readValueAsTree();
|
||||
final String pathStr = node.get("path").asText();
|
||||
final Path path = FileSystems.getDefault().getPath(pathStr);
|
||||
return new Directory(path);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
public class DirectorySerializer extends JsonSerializer<Directory> {
|
||||
|
||||
@Override
|
||||
public void serialize(Directory value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
|
||||
jgen.writeStartObject();
|
||||
jgen.writeStringField("path", value.getPath().toString());
|
||||
jgen.writeEndObject();
|
||||
}
|
||||
|
||||
}
|
@ -17,8 +17,11 @@ import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -40,18 +43,18 @@ public class Settings implements Serializable {
|
||||
final FileSystem fs = FileSystems.getDefault();
|
||||
|
||||
if (SystemUtils.IS_OS_WINDOWS && appdata != null) {
|
||||
SETTINGS_DIR = fs.getPath(appdata, "opencloudencryptor");
|
||||
SETTINGS_DIR = fs.getPath(appdata, "Cryptomator");
|
||||
} else if (SystemUtils.IS_OS_WINDOWS && appdata == null) {
|
||||
SETTINGS_DIR = fs.getPath(SystemUtils.USER_HOME, ".opencloudencryptor");
|
||||
SETTINGS_DIR = fs.getPath(SystemUtils.USER_HOME, ".Cryptomator");
|
||||
} else if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
SETTINGS_DIR = fs.getPath(SystemUtils.USER_HOME, "Library/Application Support/opencloudencryptor");
|
||||
SETTINGS_DIR = fs.getPath(SystemUtils.USER_HOME, "Library/Application Support/Cryptomator");
|
||||
} else {
|
||||
// (os.contains("solaris") || os.contains("sunos") || os.contains("linux") || os.contains("unix"))
|
||||
SETTINGS_DIR = fs.getPath(SystemUtils.USER_HOME, ".opencloudencryptor");
|
||||
SETTINGS_DIR = fs.getPath(SystemUtils.USER_HOME, ".Cryptomator");
|
||||
}
|
||||
}
|
||||
|
||||
private String webdavWorkDir;
|
||||
private Collection<Directory> directories;
|
||||
private String username;
|
||||
|
||||
private Settings() {
|
||||
@ -88,19 +91,20 @@ public class Settings implements Serializable {
|
||||
}
|
||||
|
||||
private static Settings defaultSettings() {
|
||||
final Settings result = new Settings();
|
||||
result.setWebdavWorkDir(System.getProperty("user.home", "."));
|
||||
return result;
|
||||
return new Settings();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public String getWebdavWorkDir() {
|
||||
return webdavWorkDir;
|
||||
public Collection<Directory> getDirectories() {
|
||||
if (directories == null) {
|
||||
directories = new ArrayList<>();
|
||||
}
|
||||
return directories;
|
||||
}
|
||||
|
||||
public void setWebdavWorkDir(String webdavWorkDir) {
|
||||
this.webdavWorkDir = webdavWorkDir;
|
||||
public void setDirectories(Collection<Directory> directories) {
|
||||
this.directories = directories;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
|
@ -14,38 +14,38 @@
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import org.cryptomator.ui.controls.*?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
|
||||
|
||||
<GridPane vgap="6.0" hgap="6.0" fx:controller="org.cryptomator.ui.InitializeController" xmlns:fx="http://javafx.com/fxml">
|
||||
<stylesheets>
|
||||
<URL value="@main.css" />
|
||||
</stylesheets>
|
||||
|
||||
<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.InitializeController" xmlns:fx="http://javafx.com/fxml">
|
||||
<padding>
|
||||
<Insets top="12.0" right="12.0" bottom="12.0" left="12.0" />
|
||||
<Insets top="24.0" right="24.0" bottom="24.0" left="24.0" />
|
||||
</padding>
|
||||
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints minWidth="100" />
|
||||
<ColumnConstraints minWidth="200" />
|
||||
<ColumnConstraints percentWidth="38.2"/>
|
||||
<ColumnConstraints percentWidth="61.8"/>
|
||||
</columnConstraints>
|
||||
|
||||
<children>
|
||||
<!-- Row 1 -->
|
||||
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.retypePassword" GridPane.halignment="RIGHT" />
|
||||
<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="0" GridPane.columnIndex="1" />
|
||||
<!-- Row 0 -->
|
||||
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.username" />
|
||||
<TextField fx:id="usernameField" GridPane.rowIndex="0" GridPane.columnIndex="1" />
|
||||
|
||||
<!-- Row 1 -->
|
||||
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.password" />
|
||||
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="1" GridPane.columnIndex="1" disable="true" />
|
||||
|
||||
<!-- Row 2 -->
|
||||
<Label fx:id="messageLabel" GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="5" textOverrun="ELLIPSIS" />
|
||||
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="%initialize.label.retypePassword" />
|
||||
<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="2" GridPane.columnIndex="1" disable="true" />
|
||||
|
||||
<!-- Row 3 -->
|
||||
<HBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.hgrow="ALWAYS" spacing="6.0" alignment="BOTTOM_RIGHT">
|
||||
<children>
|
||||
<Button text="%initialize.button.cancel" prefWidth="80.0" cancelButton="true" onAction="#cancel" focusTraversable="false"/>
|
||||
<Button fx:id="okButton" text="%initialize.button.ok" prefWidth="80.0" defaultButton="true" onAction="#initWorkDir" disable="true" focusTraversable="false"/>
|
||||
</children>
|
||||
</HBox>
|
||||
<Button fx:id="okButton" defaultButton="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" text="%initialize.button.ok" prefWidth="150.0" onAction="#initializeVault" focusTraversable="false" disable="true" />
|
||||
|
||||
<!-- Row 5 -->
|
||||
<Label fx:id="messageLabel" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" />
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
|
@ -6,28 +6,29 @@
|
||||
# Contributors:
|
||||
# Sebastian Stenzel - initial API and implementation
|
||||
#-------------------------------------------------------------------------------
|
||||
# main.fxml
|
||||
|
||||
main.label.workDir=Choose a folder
|
||||
main.label.username=Username
|
||||
main.label.password=Password
|
||||
|
||||
main.messageLabel.invalidPath=Invalid vault directory.
|
||||
main.messageLabel.initVaultMessage=Choose username and password to create a new vault.
|
||||
main.messageLabel.openVaultMessage=Please enter your password to unlock this vault.
|
||||
# welcome.fxml
|
||||
welcome.welcomeLabel=Welcome to Cryptomator
|
||||
|
||||
main.primaryButton.initVault=Create
|
||||
main.primaryButton.openVault=Open
|
||||
|
||||
# initialize.fxml
|
||||
initialize.title=Initialize Vault
|
||||
initialize.button.cancel=Cancel
|
||||
initialize.button.ok=Confirm
|
||||
initialize.label.username=Username
|
||||
initialize.label.password=Password
|
||||
initialize.label.retypePassword=Retype password
|
||||
initialize.button.ok=Create vault
|
||||
|
||||
# access.fxml
|
||||
access.button.close=Close
|
||||
access.errorMessage.wrongPassword=Wrong password.
|
||||
access.errorMessage.decryptionFailed=Decryption failed.
|
||||
access.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE.
|
||||
access.messageLabel.mountFailed=Mounting WebDAV share (Port %d) failed.
|
||||
|
||||
# unlock.fxml
|
||||
unlock.label.username=Username
|
||||
unlock.label.password=Password
|
||||
unlock.button.unlock=Unlock vault
|
||||
unlock.errorMessage.wrongPassword=Wrong password.
|
||||
unlock.errorMessage.decryptionFailed=Decryption failed.
|
||||
unlock.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE.
|
||||
unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
|
||||
|
||||
|
||||
# unlocked.fxml
|
||||
unlocked.messageLabel.runningOnPort=Vault is accessible via WebDAV on local port %d.
|
||||
unlocked.button.lock=Lock vault
|
@ -1,69 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (c) 2014 Sebastian Stenzel This file is licensed under the terms of the MIT license. See the LICENSE.txt file for more info. Contributors: Sebastian Stenzel - initial API and implementation -->
|
||||
<?import java.net.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import javafx.scene.control.SplitMenuButton?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import java.lang.String?>
|
||||
<?import org.cryptomator.ui.controls.*?>
|
||||
<!--
|
||||
Copyright (c) 2014 Sebastian Stenzel
|
||||
This file is licensed under the terms of the MIT license.
|
||||
See the LICENSE.txt file for more info.
|
||||
|
||||
Contributors:
|
||||
Sebastian Stenzel - initial API and implementation
|
||||
-->
|
||||
<?import java.net.URL?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.control.ToolBar?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
|
||||
<GridPane vgap="6.0" hgap="6.0" fx:id="rootPane" fx:controller="org.cryptomator.ui.MainController" xmlns:fx="http://javafx.com/fxml">
|
||||
<stylesheets>
|
||||
<URL value="@main.css" />
|
||||
</stylesheets>
|
||||
|
||||
<padding>
|
||||
<Insets top="12.0" right="12.0" bottom="12.0" left="12.0" />
|
||||
</padding>
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints minWidth="100" />
|
||||
<ColumnConstraints minWidth="200" />
|
||||
<ColumnConstraints minWidth="25" />
|
||||
</columnConstraints>
|
||||
|
||||
<HBox fx:id="rootPane" prefHeight="400.0" prefWidth="600.0" fx:controller="org.cryptomator.ui.MainController" xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<fx:define>
|
||||
<fx:include fx:id="welcomeView" source="welcome.fxml" />
|
||||
</fx:define>
|
||||
|
||||
<children>
|
||||
<!-- Row 0 -->
|
||||
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%main.label.workDir" GridPane.halignment="RIGHT" />
|
||||
<TextField fx:id="workDirTextField" GridPane.rowIndex="0" GridPane.columnIndex="1" editable="true" />
|
||||
<Button GridPane.rowIndex="0" GridPane.columnIndex="2" text="..." GridPane.hgrow="ALWAYS" maxWidth="Infinity" focusTraversable="false" onAction="#chooseWorkDir" />
|
||||
|
||||
<!-- Row 1 -->
|
||||
<Label fx:id="messageLabel" GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.hgrow="ALWAYS" maxWidth="Infinity" alignment="CENTER" />
|
||||
|
||||
<!-- Row 2 -->
|
||||
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="%main.label.username" GridPane.halignment="RIGHT" />
|
||||
<TextField fx:id="usernameField" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.columnSpan="2" disable="true" />
|
||||
<ComboBox fx:id="usernameBox" visible="false" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.hgrow="ALWAYS" maxWidth="Infinity" promptText="$access.label.username" disable="true" />
|
||||
|
||||
|
||||
<!-- Row 3 -->
|
||||
<Label GridPane.rowIndex="3" GridPane.columnIndex="0" text="%main.label.password" GridPane.halignment="RIGHT" />
|
||||
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="3" GridPane.columnIndex="1" GridPane.columnSpan="2" disable="true" />
|
||||
|
||||
<!-- Row 4 -->
|
||||
<Button fx:id="initializeButton" text="%main.primaryButton.initVault" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="3" prefWidth="120.0" GridPane.halignment="CENTER" onAction="#showInitializationDialog"
|
||||
disable="true" focusTraversable="false">
|
||||
<styleClass>
|
||||
<String fx:value="green" />
|
||||
</styleClass>
|
||||
</Button>
|
||||
<SplitMenuButton fx:id="openButton" visible="false" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="3" prefWidth="120.0" GridPane.halignment="CENTER" text="%main.primaryButton.openVault"
|
||||
onAction="#openVault" focusTraversable="false">
|
||||
<styleClass>
|
||||
<String fx:value="green" />
|
||||
</styleClass>
|
||||
<items>
|
||||
<MenuItem text="Add User" />
|
||||
<MenuItem text="Change Password" />
|
||||
</items>
|
||||
</SplitMenuButton>
|
||||
|
||||
<VBox prefWidth="200.0">
|
||||
<children>
|
||||
<ListView fx:id="directoryList" VBox.vgrow="ALWAYS" />
|
||||
<ToolBar VBox.vgrow="NEVER">
|
||||
<items>
|
||||
<Button text="+" onAction="#didClickAddDirectory" />
|
||||
</items>
|
||||
</ToolBar>
|
||||
</children>
|
||||
</VBox>
|
||||
<Pane fx:id="contentPane">
|
||||
<children>
|
||||
<fx:reference source="welcomeView"/>
|
||||
</children>
|
||||
</Pane>
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
</HBox>
|
||||
|
||||
|
@ -1,63 +0,0 @@
|
||||
@CHARSET "US-ASCII";
|
||||
|
||||
.root {
|
||||
-fx-background-color: linear-gradient(to bottom, #FFFFFF, #DDDDDD);
|
||||
}
|
||||
|
||||
.text {
|
||||
-fx-font-smoothing-type: lcd;
|
||||
}
|
||||
|
||||
.label {
|
||||
-fx-alignment: CENTER;
|
||||
-fx-font-family: "lucida-grande";
|
||||
}
|
||||
|
||||
.button,
|
||||
.combo-box {
|
||||
-fx-text-fill: #000000;
|
||||
-fx-background-color: linear-gradient(to bottom, #FFFFFF, #DDDDDD);
|
||||
-fx-border-color: #888888;
|
||||
-fx-background-insets: 0.0, 1.0;
|
||||
-fx-background-radius: 4.0, 4.0;
|
||||
-fx-border-radius: 3.0;
|
||||
-fx-border-width: 0.5;
|
||||
-fx-font-family: "lucida-grande";
|
||||
-fx-font-weight: normal;
|
||||
}
|
||||
|
||||
.text-field {
|
||||
-fx-border-radius: 3.0;
|
||||
-fx-border-width: 0.5;
|
||||
-fx-border-color: #888888;
|
||||
-fx-focus-color: #FF0000;
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 5 2 5 2;
|
||||
}
|
||||
|
||||
.text-field:focused {
|
||||
-fx-background-color: #DDDDDD;
|
||||
}
|
||||
|
||||
.button:armed,
|
||||
.button:selected,
|
||||
.combo-box:armed,
|
||||
.combo-box:selected {
|
||||
-fx-background-color: linear-gradient(to bottom, #DDDDDD, #CCCCCC 30%, #EEEEEE);
|
||||
}
|
||||
|
||||
.combo-box .list-cell {
|
||||
-fx-background-color: transparent;
|
||||
-fx-text-fill: -fx-text-base-color;
|
||||
}
|
||||
|
||||
.combo-box .list-cell:hover {
|
||||
-fx-background-color: #DDDDDD;
|
||||
}
|
||||
|
||||
.combo-box-popup .list-view {
|
||||
-fx-padding: 0 0 0 0;
|
||||
-fx-background-insets: 0, 0;
|
||||
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.6), 8, 0.0, 0, 0);
|
||||
}
|
||||
|
47
main/ui/src/main/resources/unlock.fxml
Normal file
47
main/ui/src/main/resources/unlock.fxml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2014 Sebastian Stenzel
|
||||
This file is licensed under the terms of the MIT license.
|
||||
See the LICENSE.txt file for more info.
|
||||
|
||||
Contributors:
|
||||
Sebastian Stenzel - initial API and implementation
|
||||
-->
|
||||
<?import java.net.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import java.lang.String?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
|
||||
|
||||
<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.UnlockController" xmlns:fx="http://javafx.com/fxml">
|
||||
<padding>
|
||||
<Insets top="24.0" right="24.0" bottom="24.0" left="24.0" />
|
||||
</padding>
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints percentWidth="38.2"/>
|
||||
<ColumnConstraints percentWidth="61.8"/>
|
||||
</columnConstraints>
|
||||
|
||||
<children>
|
||||
<!-- Row 0 -->
|
||||
<Label text="%unlock.label.username" GridPane.rowIndex="0" GridPane.columnIndex="0" />
|
||||
<ComboBox fx:id="usernameBox" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" promptText="$access.label.username" />
|
||||
|
||||
<!-- Row 1 -->
|
||||
<Label text="%unlock.label.password" GridPane.rowIndex="1" GridPane.columnIndex="0" />
|
||||
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
|
||||
|
||||
<!-- Row 2 -->
|
||||
<Button text="%unlock.button.unlock" defaultButton="true" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickUnlockButton" focusTraversable="false"/>
|
||||
|
||||
<!-- Row 5 -->
|
||||
<Label fx:id="messageLabel" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" />
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
|
@ -13,31 +13,25 @@
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import java.lang.String?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
|
||||
|
||||
<GridPane vgap="6.0" hgap="6.0" fx:id="rootPane" fx:controller="org.cryptomator.ui.AccessController" xmlns:fx="http://javafx.com/fxml">
|
||||
<stylesheets>
|
||||
<URL value="@main.css" />
|
||||
</stylesheets>
|
||||
|
||||
<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.UnlockedController" xmlns:fx="http://javafx.com/fxml">
|
||||
<padding>
|
||||
<Insets top="12.0" right="12.0" bottom="12.0" left="12.0" />
|
||||
<Insets top="24.0" right="24.0" bottom="24.0" left="24.0" />
|
||||
</padding>
|
||||
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints minWidth="300" />
|
||||
<ColumnConstraints percentWidth="38.2"/>
|
||||
<ColumnConstraints percentWidth="61.8"/>
|
||||
</columnConstraints>
|
||||
|
||||
<children>
|
||||
<!-- Row 0 -->
|
||||
<Label fx:id="messageLabel" GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.halignment="CENTER"/>
|
||||
<Label fx:id="messageLabel" GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" />
|
||||
|
||||
<!-- Row 1 -->
|
||||
<Button text="%access.button.close" GridPane.rowIndex="1" GridPane.columnIndex="0" prefWidth="120.0" GridPane.halignment="CENTER" onAction="#closeVault" focusTraversable="false">
|
||||
<styleClass>
|
||||
<String fx:value="red"/>
|
||||
</styleClass>
|
||||
</Button>
|
||||
<Button text="%unlocked.button.lock" defaultButton="true" GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#closeVault" focusTraversable="false"/>
|
||||
</children>
|
||||
</GridPane>
|
||||
|
26
main/ui/src/main/resources/welcome.fxml
Normal file
26
main/ui/src/main/resources/welcome.fxml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2014 Sebastian Stenzel
|
||||
This file is licensed under the terms of the MIT license.
|
||||
See the LICENSE.txt file for more info.
|
||||
|
||||
Contributors:
|
||||
Sebastian Stenzel - initial API and implementation
|
||||
-->
|
||||
<?import java.net.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import java.lang.String?>
|
||||
|
||||
|
||||
<AnchorPane xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<children>
|
||||
<Label fx:id="messageLabel" AnchorPane.leftAnchor="100.0" AnchorPane.topAnchor="50.0" text="%welcome.welcomeLabel"/>
|
||||
</children>
|
||||
|
||||
</AnchorPane>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user