mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-09 09:36:38 +00:00
Merge branch 'feature/jdk25' into feature/app-update
# Conflicts: # .idea/compiler.xml # pom.xml
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
package org.cryptomator.common.recovery;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
|
||||
|
||||
public final class BackupRestorer {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BackupRestorer.class);
|
||||
|
||||
private BackupRestorer() {}
|
||||
|
||||
public static void restoreIfBackupPresent(Path vaultPath, String filePrefix) {
|
||||
Path targetFile = vaultPath.resolve(filePrefix);
|
||||
|
||||
try (Stream<Path> files = Files.list(vaultPath)) {
|
||||
files.filter(file -> isFileMatchingPattern(file.getFileName().toString(), filePrefix))
|
||||
.max((f1, f2) -> {
|
||||
try {
|
||||
FileTime time1 = Files.getLastModifiedTime(f1);
|
||||
FileTime time2 = Files.getLastModifiedTime(f2);
|
||||
return time1.compareTo(time2);
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
.ifPresent(backupFile -> copyBackupFile(backupFile, targetFile));
|
||||
} catch (IOException e) {
|
||||
LOG.info("Unable to restore backup files in '{}'", vaultPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFileMatchingPattern(String fileName, String filePrefix) {
|
||||
return fileName.startsWith(filePrefix) && fileName.endsWith(MASTERKEY_BACKUP_SUFFIX);
|
||||
}
|
||||
|
||||
private static void copyBackupFile(Path backupFile, Path configPath) {
|
||||
try {
|
||||
Files.copy(backupFile, configPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
LOG.debug("Backup restored - file: '{}' path: '{}'", backupFile, configPath);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to copy backup file from '{}' to '{}'", backupFile, configPath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.cryptomator.common.recovery;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
|
||||
import static org.cryptomator.common.Constants.DEFAULT_KEY_ID;
|
||||
|
||||
public final class CryptoFsInitializer {
|
||||
|
||||
private CryptoFsInitializer() {}
|
||||
|
||||
public static void init(Path recoveryPath,
|
||||
Masterkey masterkey,
|
||||
int shorteningThreshold,
|
||||
CryptorProvider.Scheme scheme) throws IOException, CryptoException {
|
||||
|
||||
MasterkeyLoader loader = ignored -> masterkey.copy();
|
||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties //
|
||||
.cryptoFileSystemProperties() //
|
||||
.withCipherCombo(scheme) //
|
||||
.withKeyLoader(loader) //
|
||||
.withShorteningThreshold(shorteningThreshold) //
|
||||
.build();
|
||||
CryptoFileSystemProvider.initialize(recoveryPath, fsProps, DEFAULT_KEY_ID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.cryptomator.common.recovery;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME;
|
||||
|
||||
public final class MasterkeyService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyService.class);
|
||||
|
||||
private MasterkeyService() {}
|
||||
|
||||
public static void recoverFromRecoveryKey(String recoveryKey, RecoveryKeyFactory recoveryKeyFactory, Path recoveryPath, CharSequence newPassword) throws IOException {
|
||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(recoveryPath, recoveryKey, newPassword);
|
||||
}
|
||||
|
||||
public static Masterkey load(MasterkeyFileAccess masterkeyFileAccess, Path masterkeyFilePath, CharSequence password) throws IOException {
|
||||
return masterkeyFileAccess.load(masterkeyFilePath, password);
|
||||
}
|
||||
|
||||
public static CryptorProvider.Scheme validateRecoveryKeyAndDetectCombo(RecoveryKeyFactory recoveryKeyFactory, //
|
||||
Vault vault, String recoveryKey, //
|
||||
MasterkeyFileAccess masterkeyFileAccess) throws IOException, CryptoException, NoSuchElementException {
|
||||
String tmpPass = UUID.randomUUID().toString();
|
||||
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
|
||||
Path tempRecoveryPath = recoveryDirectory.getRecoveryPath();
|
||||
recoverFromRecoveryKey(recoveryKey, recoveryKeyFactory, tempRecoveryPath, tmpPass);
|
||||
Path masterkeyFilePath = tempRecoveryPath.resolve(MASTERKEY_FILENAME);
|
||||
|
||||
try (Masterkey mk = load(masterkeyFileAccess, masterkeyFilePath, tmpPass)) {
|
||||
return detect(mk, vault.getPath()).orElseThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<CryptorProvider.Scheme> detect(Masterkey masterkey, Path vaultPath) {
|
||||
try (Stream<Path> paths = Files.walk(vaultPath.resolve(DATA_DIR_NAME))) {
|
||||
Optional<Path> c9rFile = paths //
|
||||
.filter(p -> p.toString().endsWith(".c9r")) //
|
||||
.filter(p -> !p.endsWith("dir.c9r")) //
|
||||
.findFirst();
|
||||
if (c9rFile.isEmpty()) {
|
||||
LOG.info("Unable to detect Crypto scheme: No *.c9r file found in {}", vaultPath);
|
||||
return Optional.empty();
|
||||
}
|
||||
return determineScheme(c9rFile.get(), masterkey);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Unable to detect Crypto scheme: Failed to inspect vault", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<CryptorProvider.Scheme> determineScheme(Path c9rFile, Masterkey masterkey) {
|
||||
return Arrays.stream(CryptorProvider.Scheme.values()).filter(scheme -> {
|
||||
try (Cryptor cryptor = CryptorProvider.forScheme(scheme).provide(masterkey.copy(), SecureRandom.getInstanceStrong())) {
|
||||
int headerSize = cryptor.fileHeaderCryptor().headerSize();
|
||||
|
||||
ByteBuffer headerBuf = ByteBuffer.allocate(headerSize);
|
||||
|
||||
try (FileChannel channel = FileChannel.open(c9rFile, StandardOpenOption.READ)) {
|
||||
channel.read(headerBuf, 0);
|
||||
}
|
||||
|
||||
headerBuf.flip();
|
||||
cryptor.fileHeaderCryptor().decryptHeader(headerBuf.duplicate());
|
||||
LOG.debug("Detected Crypto scheme: {}", scheme);
|
||||
return true;
|
||||
} catch (IllegalArgumentException | CryptoException e) {
|
||||
LOG.debug("Could not decrypt with scheme: {}", scheme);
|
||||
return false;
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
LOG.warn("Unable to detect Crypto scheme: Failed to decrypt .c9r file", e);
|
||||
return false;
|
||||
}
|
||||
}).findFirst();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.cryptomator.common.recovery;
|
||||
|
||||
public enum RecoveryActionType {
|
||||
RESTORE_ALL,
|
||||
RESTORE_MASTERKEY,
|
||||
RESTORE_VAULT_CONFIG,
|
||||
RESET_PASSWORD,
|
||||
SHOW_KEY,
|
||||
CONVERT_VAULT
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.cryptomator.common.recovery;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class RecoveryDirectory implements AutoCloseable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryDirectory.class);
|
||||
|
||||
private final Path recoveryPath;
|
||||
private final Path vaultPath;
|
||||
|
||||
private RecoveryDirectory(Path vaultPath, Path recoveryPath) {
|
||||
this.vaultPath = vaultPath;
|
||||
this.recoveryPath = recoveryPath;
|
||||
}
|
||||
|
||||
public static RecoveryDirectory create(Path vaultPath) throws IOException {
|
||||
Path tempDir = Files.createTempDirectory("cryptomator");
|
||||
return new RecoveryDirectory(vaultPath, tempDir);
|
||||
}
|
||||
|
||||
public void moveRecoveredFile(String file) throws IOException {
|
||||
Files.move(recoveryPath.resolve(file), vaultPath.resolve(file), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
private void deleteRecoveryDirectory() {
|
||||
try (var paths = Files.walk(recoveryPath)) {
|
||||
paths.sorted(Comparator.reverseOrder()).forEach(p -> {
|
||||
try {
|
||||
Files.delete(p);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Unable to delete {}. Please delete it manually.", p);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to clean up recovery directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
deleteRecoveryDirectory();
|
||||
}
|
||||
|
||||
public Path getRecoveryPath() {
|
||||
return recoveryPath;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.cryptomator.common.recovery;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultComponent;
|
||||
import org.cryptomator.common.vaults.VaultConfigCache;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.integrations.mount.MountService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||
|
||||
public final class VaultPreparator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultPreparator.class);
|
||||
|
||||
private VaultPreparator() {}
|
||||
|
||||
public static Vault prepareVault(Path selectedDirectory, //
|
||||
VaultComponent.Factory vaultComponentFactory, //
|
||||
List<MountService> mountServices, //
|
||||
ResourceBundle resourceBundle) {
|
||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||
vaultSettings.path.set(selectedDirectory);
|
||||
if (selectedDirectory.getFileName() != null) {
|
||||
vaultSettings.displayName.set(selectedDirectory.getFileName().toString());
|
||||
} else {
|
||||
vaultSettings.displayName.set(resourceBundle.getString("defaults.vault.vaultName"));
|
||||
}
|
||||
|
||||
var wrapper = new VaultConfigCache(vaultSettings);
|
||||
Vault vault = vaultComponentFactory.create(vaultSettings, wrapper, LOCKED, null).vault();
|
||||
try {
|
||||
VaultListManager.determineVaultState(vault.getPath());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to determine vault state for {}", vaultSettings.path.get(), e);
|
||||
}
|
||||
|
||||
//due to https://github.com/cryptomator/cryptomator/issues/2880#issuecomment-1680313498
|
||||
var nameOfWinfspLocalMounter = "org.cryptomator.frontend.fuse.mount.WinFspMountProvider";
|
||||
if (SystemUtils.IS_OS_WINDOWS && vaultSettings.path.get().toString().contains("Dropbox") && mountServices.stream().anyMatch(s -> s.getClass().getName().equals(nameOfWinfspLocalMounter))) {
|
||||
vaultSettings.mountService.setValue(nameOfWinfspLocalMounter);
|
||||
}
|
||||
|
||||
return vault;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.event.VaultEvent;
|
||||
import org.cryptomator.integrations.mount.MountFailedException;
|
||||
import org.cryptomator.integrations.mount.Mountpoint;
|
||||
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||
@@ -35,7 +34,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
@@ -75,6 +73,7 @@ public class Vault {
|
||||
private final BooleanBinding missing;
|
||||
private final BooleanBinding needsMigration;
|
||||
private final BooleanBinding unknownError;
|
||||
private final BooleanBinding missingVaultConfig;
|
||||
private final ObjectBinding<Mountpoint> mountPoint;
|
||||
private final Mounter mounter;
|
||||
private final Settings settings;
|
||||
@@ -103,6 +102,7 @@ public class Vault {
|
||||
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
||||
this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state);
|
||||
this.missing = Bindings.createBooleanBinding(this::isMissing, state);
|
||||
this.missingVaultConfig = Bindings.createBooleanBinding(this::isMissingVaultConfig, state);
|
||||
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
|
||||
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
||||
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||
@@ -336,6 +336,14 @@ public class Vault {
|
||||
return state.get() == VaultState.Value.ERROR;
|
||||
}
|
||||
|
||||
public BooleanBinding missingVaultConfigProperty() {
|
||||
return missingVaultConfig;
|
||||
}
|
||||
|
||||
public boolean isMissingVaultConfig() {
|
||||
return state.get() == VaultState.Value.VAULT_CONFIG_MISSING || state.get() == VaultState.Value.ALL_MISSING;
|
||||
}
|
||||
|
||||
public ReadOnlyStringProperty displayNameProperty() {
|
||||
return vaultSettings.displayName;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public class VaultConfigCache {
|
||||
private final VaultSettings settings;
|
||||
private final AtomicReference<VaultConfig.UnverifiedVaultConfig> config;
|
||||
|
||||
VaultConfigCache(VaultSettings settings) {
|
||||
public VaultConfigCache(VaultSettings settings) {
|
||||
this.settings = settings;
|
||||
this.config = new AtomicReference<>(null);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.recovery.BackupRestorer;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
@@ -34,9 +35,7 @@ import java.util.ResourceBundle;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.*;
|
||||
|
||||
@Singleton
|
||||
public class VaultListManager {
|
||||
@@ -67,6 +66,12 @@ public class VaultListManager {
|
||||
autoLocker.init();
|
||||
}
|
||||
|
||||
public boolean isAlreadyAdded(Path vaultPath) {
|
||||
assert vaultPath.isAbsolute();
|
||||
assert vaultPath.normalize().equals(vaultPath);
|
||||
return vaultList.stream().anyMatch(v -> vaultPath.equals(v.getPath()));
|
||||
}
|
||||
|
||||
public Vault add(Path pathToVault) throws IOException {
|
||||
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
|
||||
if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) {
|
||||
@@ -114,59 +119,122 @@ public class VaultListManager {
|
||||
.findAny();
|
||||
}
|
||||
|
||||
public void addVault(Vault vault) {
|
||||
Path path = vault.getPath().normalize().toAbsolutePath();
|
||||
if (!isAlreadyAdded(path)) {
|
||||
vaultList.add(vault);
|
||||
}
|
||||
}
|
||||
|
||||
private Vault create(VaultSettings vaultSettings) {
|
||||
var wrapper = new VaultConfigCache(vaultSettings);
|
||||
try {
|
||||
var vaultState = determineVaultState(vaultSettings.path.get());
|
||||
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
|
||||
wrapper.reloadConfig();
|
||||
if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
|
||||
var keyIdScheme = wrapper.get().getKeyId().getScheme();
|
||||
vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
|
||||
}
|
||||
} else if (vaultState == NEEDS_MIGRATION) {
|
||||
vaultSettings.lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
|
||||
}
|
||||
initializeLastKnownKeyLoaderIfPossible(vaultSettings, vaultState, wrapper);
|
||||
|
||||
return vaultComponentFactory.create(vaultSettings, wrapper, vaultState, null).vault();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to determine vault state for " + vaultSettings.path.get(), e);
|
||||
LOG.warn("Failed to determine vault state for {}", vaultSettings.path.get(), e);
|
||||
return vaultComponentFactory.create(vaultSettings, wrapper, ERROR, e).vault();
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeLastKnownKeyLoaderIfPossible(VaultSettings vaultSettings, VaultState.Value vaultState, VaultConfigCache wrapper) throws IOException {
|
||||
if (vaultSettings.lastKnownKeyLoader.get() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (vaultState) {
|
||||
case LOCKED -> {
|
||||
wrapper.reloadConfig();
|
||||
vaultSettings.lastKnownKeyLoader.set(wrapper.get().getKeyId().getScheme());
|
||||
}
|
||||
case NEEDS_MIGRATION -> {
|
||||
//for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
|
||||
vaultSettings.lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
|
||||
}
|
||||
case VAULT_CONFIG_MISSING -> {
|
||||
//Nothing to do here, since there is no config to read
|
||||
}
|
||||
case MISSING, ALL_MISSING, ERROR, PROCESSING -> {
|
||||
// no config available or not safe to load
|
||||
}
|
||||
default -> {
|
||||
if (Files.exists(vaultSettings.path.get().resolve(VAULTCONFIG_FILENAME))) {
|
||||
try {
|
||||
wrapper.reloadConfig();
|
||||
vaultSettings.lastKnownKeyLoader.set(wrapper.get().getKeyId().getScheme());
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Unable to load config for {}", vaultSettings.path.get(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static VaultState.Value redetermineVaultState(Vault vault) {
|
||||
VaultState state = vault.stateProperty();
|
||||
VaultState.Value previousState = state.getValue();
|
||||
return switch (previousState) {
|
||||
case LOCKED, NEEDS_MIGRATION, MISSING -> {
|
||||
try {
|
||||
var determinedState = determineVaultState(vault.getPath());
|
||||
if (determinedState == LOCKED) {
|
||||
vault.getVaultConfigCache().reloadConfig();
|
||||
}
|
||||
state.set(determinedState);
|
||||
yield determinedState;
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
|
||||
state.set(ERROR);
|
||||
vault.setLastKnownException(e);
|
||||
yield ERROR;
|
||||
}
|
||||
VaultState.Value previous = state.getValue();
|
||||
|
||||
if (previous.equals(UNLOCKED) || previous.equals(PROCESSING)) {
|
||||
return previous;
|
||||
}
|
||||
|
||||
try {
|
||||
VaultState.Value determined = determineVaultState(vault.getPath());
|
||||
|
||||
if (determined == LOCKED) {
|
||||
vault.getVaultConfigCache().reloadConfig();
|
||||
}
|
||||
case ERROR, UNLOCKED, PROCESSING -> previousState;
|
||||
};
|
||||
|
||||
state.set(determined);
|
||||
return determined;
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to (re)determine vault state for {}", vault.getPath(), e);
|
||||
vault.setLastKnownException(e);
|
||||
state.set(ERROR);
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
|
||||
public static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
|
||||
if (!Files.exists(pathToVault)) {
|
||||
return VaultState.Value.MISSING;
|
||||
return MISSING;
|
||||
}
|
||||
|
||||
VaultState.Value structureResult = checkDirStructure(pathToVault);
|
||||
|
||||
if (structureResult == LOCKED || structureResult == NEEDS_MIGRATION) {
|
||||
return structureResult;
|
||||
}
|
||||
|
||||
Path pathToVaultConfig = pathToVault.resolve(VAULTCONFIG_FILENAME);
|
||||
Path pathToMasterkey = pathToVault.resolve(MASTERKEY_FILENAME);
|
||||
|
||||
if (!Files.exists(pathToVaultConfig)) {
|
||||
BackupRestorer.restoreIfBackupPresent(pathToVault, VAULTCONFIG_FILENAME);
|
||||
}
|
||||
if (!Files.exists(pathToMasterkey)) {
|
||||
BackupRestorer.restoreIfBackupPresent(pathToVault, MASTERKEY_FILENAME);
|
||||
}
|
||||
|
||||
boolean hasConfig = Files.exists(pathToVaultConfig);
|
||||
|
||||
if (!hasConfig && !Files.exists(pathToMasterkey)) {
|
||||
return ALL_MISSING;
|
||||
}
|
||||
if (!hasConfig) {
|
||||
return VAULT_CONFIG_MISSING;
|
||||
}
|
||||
|
||||
return checkDirStructure(pathToVault);
|
||||
}
|
||||
|
||||
private static VaultState.Value checkDirStructure(Path pathToVault) throws IOException {
|
||||
return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
|
||||
case VAULT -> VaultState.Value.LOCKED;
|
||||
case UNRELATED -> VaultState.Value.MISSING;
|
||||
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? //
|
||||
VaultState.Value.NEEDS_MIGRATION //
|
||||
: VaultState.Value.MISSING;
|
||||
case VAULT -> LOCKED;
|
||||
case UNRELATED -> MISSING;
|
||||
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? NEEDS_MIGRATION : MISSING;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,16 @@ public class VaultState extends ObservableValueBase<VaultState.Value> implements
|
||||
*/
|
||||
MISSING,
|
||||
|
||||
/**
|
||||
* No vault config found at the provided path
|
||||
*/
|
||||
VAULT_CONFIG_MISSING,
|
||||
|
||||
/**
|
||||
* No vault config and masterkey found at the provided path
|
||||
*/
|
||||
ALL_MISSING,
|
||||
|
||||
/**
|
||||
* Vault requires migration to a newer vault format
|
||||
*/
|
||||
|
||||
@@ -42,9 +42,10 @@ public enum FxmlFile {
|
||||
QUIT("/fxml/quit.fxml"), //
|
||||
QUIT_FORCED("/fxml/quit_forced.fxml"), //
|
||||
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
|
||||
RECOVERYKEY_EXPERT_SETTINGS("/fxml/recoverykey_expert_settings.fxml"), //
|
||||
RECOVERYKEY_ONBOARDING("/fxml/recoverykey_onboarding.fxml"), //
|
||||
RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), //
|
||||
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
|
||||
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
|
||||
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
|
||||
SHARE_VAULT("/fxml/share_vault.fxml"), //
|
||||
SIMPLE_DIALOG("/fxml/simple_dialog.fxml"), //
|
||||
|
||||
@@ -4,8 +4,10 @@ import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.changepassword.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
@@ -20,6 +22,7 @@ import org.cryptomator.ui.recoverykey.RecoveryKeyValidateController;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Scene;
|
||||
@@ -119,8 +122,8 @@ abstract class ConvertVaultModule {
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyValidateController.class)
|
||||
static FxController bindRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
|
||||
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory);
|
||||
static FxController provideRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, MasterkeyFileAccess masterkeyFileAccess) {
|
||||
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, masterkeyFileAccess, new SimpleObjectProperty<>(RecoveryActionType.CONVERT_VAULT), new SimpleObjectProperty<>(null));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ public class Dialogs {
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final StageFactory stageFactory;
|
||||
|
||||
private static final String BUTTON_KEY_CLOSE = "generic.button.close";
|
||||
|
||||
@Inject
|
||||
public Dialogs(ResourceBundle resourceBundle, StageFactory stageFactory) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
@@ -47,6 +49,43 @@ public class Dialogs {
|
||||
});
|
||||
}
|
||||
|
||||
public SimpleDialog.Builder prepareContactHubVaultOwner(Stage window) {
|
||||
return createDialogBuilder().setOwner(window) //
|
||||
.setTitleKey("contactHubVaultOwner.title") //
|
||||
.setMessageKey("contactHubVaultOwner.message") //
|
||||
.setDescriptionKey("contactHubVaultOwner.description") //
|
||||
.setIcon(FontAwesome5Icon.EXCLAMATION)//
|
||||
.setOkButtonKey(BUTTON_KEY_CLOSE);
|
||||
}
|
||||
|
||||
public SimpleDialog.Builder prepareRecoveryVaultAdded(Stage window, String displayName) {
|
||||
return createDialogBuilder().setOwner(window) //
|
||||
.setTitleKey("recover.existing.title") //
|
||||
.setMessageKey("recover.existing.message") //
|
||||
.setDescriptionKey("recover.existing.description", displayName) //
|
||||
.setIcon(FontAwesome5Icon.CHECK)//
|
||||
.setOkButtonKey(BUTTON_KEY_CLOSE);
|
||||
}
|
||||
public SimpleDialog.Builder prepareRecoveryVaultAlreadyExists(Stage window, String displayName) {
|
||||
return createDialogBuilder().setOwner(window) //
|
||||
.setTitleKey("recover.alreadyExists.title") //
|
||||
.setMessageKey("recover.alreadyExists.message") //
|
||||
.setDescriptionKey("recover.alreadyExists.description", displayName) //
|
||||
.setIcon(FontAwesome5Icon.EXCLAMATION)//
|
||||
.setOkButtonKey(BUTTON_KEY_CLOSE);
|
||||
}
|
||||
|
||||
public SimpleDialog.Builder prepareRecoverPasswordSuccess(Stage window) {
|
||||
return createDialogBuilder()
|
||||
.setOwner(window) //
|
||||
.setTitleKey("recoveryKey.recover.title") //
|
||||
.setMessageKey("recoveryKey.recover.resetSuccess.message") //
|
||||
.setDescriptionKey("recoveryKey.recover.resetSuccess.description") //
|
||||
.setIcon(FontAwesome5Icon.CHECK)
|
||||
.setOkAction(Stage::close)
|
||||
.setOkButtonKey(BUTTON_KEY_CLOSE);
|
||||
}
|
||||
|
||||
public SimpleDialog.Builder prepareRemoveCertDialog(Stage window, Settings settings) {
|
||||
return createDialogBuilder() //
|
||||
.setOwner(window) //
|
||||
@@ -69,7 +108,7 @@ public class Dialogs {
|
||||
.setMessageKey("dokanySupportEnd.message") //
|
||||
.setDescriptionKey("dokanySupportEnd.description") //
|
||||
.setIcon(FontAwesome5Icon.EXCLAMATION) //
|
||||
.setOkButtonKey("generic.button.close") //
|
||||
.setOkButtonKey(BUTTON_KEY_CLOSE) //
|
||||
.setCancelButtonKey("dokanySupportEnd.preferencesBtn") //
|
||||
.setOkAction(Stage::close) //
|
||||
.setCancelAction(cancelAction);
|
||||
@@ -83,8 +122,20 @@ public class Dialogs {
|
||||
.setDescriptionKey("retryIfReadonly.description") //
|
||||
.setIcon(FontAwesome5Icon.EXCLAMATION) //
|
||||
.setOkButtonKey("retryIfReadonly.retry") //
|
||||
.setCancelButtonKey("generic.button.close") //
|
||||
.setCancelButtonKey(BUTTON_KEY_CLOSE) //
|
||||
.setOkAction(okAction) //
|
||||
.setCancelAction(Stage::close);
|
||||
}
|
||||
|
||||
public SimpleDialog.Builder prepareNoDDirectorySelectedDialog(Stage window) {
|
||||
return createDialogBuilder() //
|
||||
.setOwner(window) //
|
||||
.setTitleKey("recover.invalidSelection.title") //
|
||||
.setMessageKey("recover.invalidSelection.message") //
|
||||
.setDescriptionKey("recover.invalidSelection.description") //
|
||||
.setIcon(FontAwesome5Icon.EXCLAMATION) //
|
||||
.setOkButtonKey("generic.button.change") //
|
||||
.setOkAction(Stage::close);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class SimpleDialog {
|
||||
|
||||
FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
|
||||
new SimpleDialogController(resolveText(builder.messageKey, null), //
|
||||
resolveText(builder.descriptionKey, null), //
|
||||
resolveText(builder.descriptionKey, builder.descriptionArgs), //
|
||||
builder.icon, //
|
||||
resolveText(builder.okButtonKey, null), //
|
||||
builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
|
||||
@@ -66,6 +66,7 @@ public class SimpleDialog {
|
||||
private String[] titleArgs;
|
||||
private String messageKey;
|
||||
private String descriptionKey;
|
||||
private String[] descriptionArgs;
|
||||
private String okButtonKey;
|
||||
private String cancelButtonKey;
|
||||
private FontAwesome5Icon icon;
|
||||
@@ -93,8 +94,9 @@ public class SimpleDialog {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDescriptionKey(String descriptionKey) {
|
||||
public Builder setDescriptionKey(String descriptionKey, String... args) {
|
||||
this.descriptionKey = descriptionKey;
|
||||
this.descriptionArgs = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public class EventViewController implements FxController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison method for the lru cache. During comparsion the map is accessed.
|
||||
* Comparison method for the lru cache. During comparison the map is accessed.
|
||||
* First the entries are compared by the event timestamp, then vaultId, then identifying path and lastly by class name.
|
||||
*
|
||||
* @param left an entry of a {@link FSEventBucket} and its content
|
||||
|
||||
@@ -15,15 +15,13 @@ import org.cryptomator.ui.lock.LockComponent;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||
import org.cryptomator.ui.quit.QuitComponent;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.cryptomator.ui.sharevault.ShareVaultComponent;
|
||||
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
||||
import org.cryptomator.ui.unlock.UnlockComponent;
|
||||
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
|
||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.image.Image;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -40,7 +38,8 @@ import java.io.InputStream;
|
||||
HealthCheckComponent.class, //
|
||||
UpdateReminderComponent.class, //
|
||||
ShareVaultComponent.class, //
|
||||
EventViewComponent.class})
|
||||
EventViewComponent.class, //
|
||||
RecoveryKeyComponent.class})
|
||||
abstract class FxApplicationModule {
|
||||
|
||||
private static Image createImageFromResource(String resourceName) throws IOException {
|
||||
|
||||
@@ -28,7 +28,7 @@ import static org.cryptomator.common.vaults.VaultState.Value.*;
|
||||
@FxApplicationScoped
|
||||
public class FxApplicationTerminator {
|
||||
|
||||
private static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
|
||||
private static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR, VAULT_CONFIG_MISSING, ALL_MISSING);
|
||||
private static final Set<VaultState.Value> STATES_PREVENT_TERMINATION = EnumSet.of(PROCESSING);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class);
|
||||
|
||||
@@ -110,7 +110,7 @@ public class FxApplicationTerminator {
|
||||
} else if (settings.autoCloseVaults.get() && !preventQuitWithGracefulLock.get()) {
|
||||
var lockAllTask = vaultService.createLockAllTask(vaults.filtered(Vault::isUnlocked), false);
|
||||
lockAllTask.setOnSucceeded(event -> {
|
||||
LOG.info("Locked remaining vaults was succesful.");
|
||||
LOG.info("Locked remaining vaults was successful.");
|
||||
exitingResponse.performQuit();
|
||||
});
|
||||
lockAllTask.setOnFailed(event -> {
|
||||
|
||||
+38
-3
@@ -1,14 +1,18 @@
|
||||
package org.cryptomator.ui.keyloading.masterkeyfile;
|
||||
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
@@ -27,17 +31,41 @@ public class ChooseMasterkeyFileController implements FxController {
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final CompletableFuture<Path> result;
|
||||
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
@FXML
|
||||
private CheckBox restoreInsteadCheckBox;
|
||||
@FXML
|
||||
private Button forwardButton;
|
||||
|
||||
@Inject
|
||||
public ChooseMasterkeyFileController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture<Path> result, ResourceBundle resourceBundle) {
|
||||
public ChooseMasterkeyFileController(@KeyLoading Stage window, //
|
||||
@KeyLoading Vault vault, //
|
||||
CompletableFuture<Path> result, //
|
||||
RecoveryKeyComponent.Factory recoveryKeyWindow, //
|
||||
ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.result = result;
|
||||
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.window.setOnHiding(this::windowClosed);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
restoreInsteadCheckBox.selectedProperty().addListener((_, _, newVal) -> {
|
||||
if (newVal) {
|
||||
forwardButton.setText(resourceBundle.getString("addvaultwizard.existing.restore"));
|
||||
forwardButton.setOnAction(_ -> restoreMasterkey());
|
||||
} else {
|
||||
forwardButton.setText(resourceBundle.getString("generic.button.choose"));
|
||||
forwardButton.setOnAction(_ -> proceed());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void cancel() {
|
||||
window.close();
|
||||
@@ -47,6 +75,13 @@ public class ChooseMasterkeyFileController implements FxController {
|
||||
result.cancel(true);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void restoreMasterkey() {
|
||||
Stage ownerStage = (Stage) window.getOwner();
|
||||
window.close();
|
||||
recoveryKeyWindow.create(vault, ownerStage, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_MASTERKEY)).showOnboardingDialogWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void proceed() {
|
||||
LOG.trace("proceed()");
|
||||
@@ -62,7 +97,7 @@ public class ChooseMasterkeyFileController implements FxController {
|
||||
|
||||
//--- Setter & Getter ---
|
||||
|
||||
public String getDisplayName(){
|
||||
public String getDisplayName() {
|
||||
return vault.getDisplayName();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationTerminator;
|
||||
import org.cryptomator.ui.fxapp.PrimaryStage;
|
||||
import org.cryptomator.ui.migration.MigrationComponent;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
||||
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||
@@ -32,7 +33,7 @@ import javafx.stage.Stage;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class})
|
||||
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class, RecoveryKeyComponent.class})
|
||||
abstract class MainWindowModule {
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -52,7 +52,7 @@ public class VaultDetailController implements FxController {
|
||||
case LOCKED -> FontAwesome5Icon.LOCK;
|
||||
case PROCESSING -> FontAwesome5Icon.SPINNER;
|
||||
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
|
||||
case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
};
|
||||
} else {
|
||||
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultDetailMissingVaultController implements FxController {
|
||||
@@ -23,6 +29,7 @@ public class VaultDetailMissingVaultController implements FxController {
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final Stage window;
|
||||
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
|
||||
private final Dialogs dialogs;
|
||||
|
||||
@Inject
|
||||
@@ -30,11 +37,13 @@ public class VaultDetailMissingVaultController implements FxController {
|
||||
ObservableList<Vault> vaults, //
|
||||
ResourceBundle resourceBundle, //
|
||||
@MainWindow Stage window, //
|
||||
Dialogs dialogs) {
|
||||
Dialogs dialogs, //
|
||||
RecoveryKeyComponent.Factory recoveryKeyWindow) {
|
||||
this.vault = vault;
|
||||
this.vaults = vaults;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.window = window;
|
||||
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||
this.dialogs = dialogs;
|
||||
}
|
||||
|
||||
@@ -48,6 +57,19 @@ public class VaultDetailMissingVaultController implements FxController {
|
||||
dialogs.prepareRemoveVaultDialog(window, vault.get(), vaults).build().showAndWait();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void restoreVaultConfig() {
|
||||
if(KeyLoadingStrategy.isHubVault(vault.get().getVaultSettings().lastKnownKeyLoader.get())){
|
||||
dialogs.prepareContactHubVaultOwner(window).build().showAndWait();
|
||||
}
|
||||
else if(Files.exists(vault.get().getPath().resolve(MASTERKEY_FILENAME))){
|
||||
recoveryKeyWindow.create(vault.get(), window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow();
|
||||
}
|
||||
else {
|
||||
recoveryKeyWindow.create(vault.get(), window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void changeLocation() {
|
||||
// copied from ChooseExistingVaultController class
|
||||
|
||||
@@ -55,7 +55,7 @@ public class VaultListCellController implements FxController {
|
||||
case LOCKED -> FontAwesome5Icon.LOCK;
|
||||
case PROCESSING -> FontAwesome5Icon.SPINNER;
|
||||
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
|
||||
case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
};
|
||||
} else {
|
||||
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
|
||||
@@ -20,11 +20,13 @@ import javafx.stage.Stage;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultListContextMenuController implements FxController {
|
||||
@@ -63,7 +65,7 @@ public class VaultListContextMenuController implements FxController {
|
||||
|
||||
this.selectedVaultState = selectedVault.flatMap(Vault::stateProperty).orElse(null);
|
||||
this.selectedVaultPassphraseStored = selectedVault.map(this::isPasswordStored).orElse(false);
|
||||
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION)::contains).orElse(false);
|
||||
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION, ALL_MISSING, VAULT_CONFIG_MISSING)::contains).orElse(false);
|
||||
this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false);
|
||||
this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false);
|
||||
}
|
||||
@@ -102,6 +104,12 @@ public class VaultListContextMenuController implements FxController {
|
||||
vaultService.reveal(vault);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void didClickShareVault() {
|
||||
var vault = Objects.requireNonNull(selectedVault.get());
|
||||
appWindows.showShareVaultWindow(vault);
|
||||
}
|
||||
|
||||
// Getter and Setter
|
||||
|
||||
public ObservableValue<Boolean> selectedVaultUnlockableProperty() {
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.recovery.VaultPreparator;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultComponent;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptofs.DirStructure;
|
||||
import org.cryptomator.cryptofs.common.Constants;
|
||||
import org.cryptomator.integrations.mount.MountService;
|
||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
@@ -13,6 +18,7 @@ import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.fxapp.FxFSEventList;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -22,6 +28,7 @@ import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -37,11 +44,14 @@ import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
@@ -50,10 +60,12 @@ import java.util.stream.Collectors;
|
||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultListController implements FxController {
|
||||
@@ -75,6 +87,10 @@ public class VaultListController implements FxController {
|
||||
private final ObservableValue<Double> cellSize;
|
||||
private final Dialogs dialogs;
|
||||
|
||||
private final VaultComponent.Factory vaultComponentFactory;
|
||||
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
|
||||
private final List<MountService> mountServices;
|
||||
|
||||
public ListView<Vault> vaultList;
|
||||
public StackPane root;
|
||||
@FXML
|
||||
@@ -94,6 +110,9 @@ public class VaultListController implements FxController {
|
||||
FxApplicationWindows appWindows, //
|
||||
Settings settings, //
|
||||
Dialogs dialogs, //
|
||||
RecoveryKeyComponent.Factory recoveryKeyWindow, //
|
||||
VaultComponent.Factory vaultComponentFactory, //
|
||||
List<MountService> mountServices, //
|
||||
FxFSEventList fxFSEventList) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.vaults = vaults;
|
||||
@@ -105,6 +124,9 @@ public class VaultListController implements FxController {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.appWindows = appWindows;
|
||||
this.dialogs = dialogs;
|
||||
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||
this.vaultComponentFactory = vaultComponentFactory;
|
||||
this.mountServices = mountServices;
|
||||
|
||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||
this.unreadEvents = fxFSEventList.unreadEventsProperty();
|
||||
@@ -204,6 +226,26 @@ public class VaultListController implements FxController {
|
||||
VaultListManager.redetermineVaultState(newValue);
|
||||
}
|
||||
|
||||
private Optional<Path> chooseValidVaultDirectory() {
|
||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||
File selectedDirectory;
|
||||
|
||||
do {
|
||||
selectedDirectory = directoryChooser.showDialog(mainWindow);
|
||||
if (selectedDirectory == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Path selectedPath = selectedDirectory.toPath();
|
||||
if (!Files.isDirectory(selectedPath.resolve(Constants.DATA_DIR_NAME))) {
|
||||
dialogs.prepareNoDDirectorySelectedDialog(mainWindow).build().showAndWait();
|
||||
selectedDirectory = null;
|
||||
}
|
||||
} while (selectedDirectory == null);
|
||||
|
||||
return Optional.of(selectedDirectory.toPath());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void didClickAddNewVault() {
|
||||
addVaultWizard.build().showAddNewVaultWizard(resourceBundle);
|
||||
@@ -214,9 +256,40 @@ public class VaultListController implements FxController {
|
||||
addVaultWizard.build().showAddExistingVaultWizard(resourceBundle);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void didClickRecoverExistingVault() {
|
||||
Optional<Path> selectedDirectory = chooseValidVaultDirectory();
|
||||
if (selectedDirectory.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path path = selectedDirectory.get();
|
||||
Optional<Vault> matchingVaultListEntry = vaultListManager.get(path);
|
||||
if (matchingVaultListEntry.isPresent()) {
|
||||
dialogs.prepareRecoveryVaultAlreadyExists(mainWindow, matchingVaultListEntry.get().getDisplayName()) //
|
||||
.setOkAction(Stage::close) //
|
||||
.build().showAndWait();
|
||||
return;
|
||||
}
|
||||
|
||||
Vault preparedVault = VaultPreparator.prepareVault(path, vaultComponentFactory, mountServices, resourceBundle);
|
||||
VaultListManager.redetermineVaultState(preparedVault);
|
||||
|
||||
switch (preparedVault.getState()) {
|
||||
case VAULT_CONFIG_MISSING -> recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow();
|
||||
case ALL_MISSING -> recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow();
|
||||
case LOCKED, NEEDS_MIGRATION -> {
|
||||
vaultListManager.addVault(preparedVault);
|
||||
dialogs.prepareRecoveryVaultAdded(mainWindow, preparedVault.getDisplayName()).setOkAction(Stage::close).build().showAndWait();
|
||||
}
|
||||
default -> LOG.warn("Unhandled vault state during recovery: {}", preparedVault.getState());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void pressedShortcutToRemoveVault() {
|
||||
final var vault = selectedVault.get();
|
||||
if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION).contains(vault.getState())) {
|
||||
if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION, ALL_MISSING, VAULT_CONFIG_MISSING).contains(vault.getState())) {
|
||||
dialogs.prepareRemoveVaultDialog(mainWindow, vault, vaults).build().showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package org.cryptomator.ui.recoverykey;
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@@ -24,6 +26,9 @@ public interface RecoveryKeyComponent {
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER)
|
||||
Lazy<Scene> recoverScene();
|
||||
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING)
|
||||
Lazy<Scene> recoverOnboardingScene();
|
||||
|
||||
default void showRecoveryKeyCreationWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(creationScene().get());
|
||||
@@ -38,11 +43,19 @@ public interface RecoveryKeyComponent {
|
||||
stage.show();
|
||||
}
|
||||
|
||||
default void showOnboardingDialogWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(recoverOnboardingScene().get());
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
}
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, @BindsInstance @Named("keyRecoveryOwner") Stage owner);
|
||||
RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, //
|
||||
@BindsInstance @Named("keyRecoveryOwner") Stage owner, //
|
||||
@BindsInstance @Named("recoverType") ObjectProperty<RecoveryActionType> recoverType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,28 +1,45 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.recovery.CryptoFsInitializer;
|
||||
import org.cryptomator.common.recovery.MasterkeyService;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.recovery.RecoveryDirectory;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.controls.FormattedLabel;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyCreationController implements FxController {
|
||||
|
||||
@@ -30,23 +47,71 @@ public class RecoveryKeyCreationController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final Lazy<Scene> recoverykeyExpertSettingsScene;
|
||||
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||
private final Vault vault;
|
||||
private final ExecutorService executor;
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final StringProperty recoveryKeyProperty;
|
||||
private final FxApplicationWindows appWindows;
|
||||
public NiceSecurePasswordField passwordField;
|
||||
private final IntegerProperty shorteningThreshold;
|
||||
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||
private final ResourceBundle resourceBundle;
|
||||
public FormattedLabel descriptionLabel;
|
||||
public Button cancelButton;
|
||||
public Button nextButton;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final Dialogs dialogs;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy<Scene> successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
|
||||
public RecoveryKeyCreationController(FxApplicationWindows appWindows, //
|
||||
@RecoveryKeyWindow Stage window, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy<Scene> successScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> recoverykeyExpertSettingsScene, //
|
||||
@RecoveryKeyWindow Vault vault, //
|
||||
RecoveryKeyFactory recoveryKeyFactory, //
|
||||
MasterkeyFileAccess masterkeyFileAccess, //
|
||||
ExecutorService executor, //
|
||||
@RecoveryKeyWindow StringProperty recoveryKey, //
|
||||
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
|
||||
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||
VaultListManager vaultListManager, //
|
||||
ResourceBundle resourceBundle, //
|
||||
Dialogs dialogs) {
|
||||
this.window = window;
|
||||
window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
|
||||
this.successScene = successScene;
|
||||
this.recoverykeyExpertSettingsScene = recoverykeyExpertSettingsScene;
|
||||
this.vault = vault;
|
||||
this.executor = executor;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.recoveryKeyProperty = recoveryKey;
|
||||
this.appWindows = appWindows;
|
||||
this.recoverType = recoverType;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||
this.shorteningThreshold = shorteningThreshold;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.dialogs = dialogs;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
if (recoverType.get() == RecoveryActionType.SHOW_KEY) {
|
||||
window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
|
||||
} else if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
|
||||
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
|
||||
descriptionLabel.formatProperty().set(resourceBundle.getString("recoveryKey.recover.description"));
|
||||
cancelButton.setOnAction((_) -> back());
|
||||
cancelButton.setText(resourceBundle.getString("generic.button.back"));
|
||||
nextButton.setOnAction((_) -> restoreWithPassword());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back() {
|
||||
window.setScene(recoverykeyExpertSettingsScene.get());
|
||||
window.centerOnScreen();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -71,6 +136,42 @@ public class RecoveryKeyCreationController implements FxController {
|
||||
executor.submit(task);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void restoreWithPassword() {
|
||||
|
||||
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
|
||||
Path recoveryPath = recoveryDirectory.getRecoveryPath();
|
||||
|
||||
Path masterkeyFilePath = vault.getPath().resolve(MASTERKEY_FILENAME);
|
||||
|
||||
try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, passwordField.getCharacters())) {
|
||||
var combo = MasterkeyService.detect(masterkey, vault.getPath())
|
||||
.orElseThrow(() -> new IllegalStateException("Could not detect combo for vault path: " + vault.getPath()));
|
||||
|
||||
CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), combo);
|
||||
}
|
||||
|
||||
recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
|
||||
|
||||
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
|
||||
vaultListManager.add(vault.getPath());
|
||||
}
|
||||
window.close();
|
||||
dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
|
||||
.setTitleKey("recover.recoverVaultConfig.title") //
|
||||
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
|
||||
.setDescriptionKey("recoveryKey.recover.resetMasterkeyFileSuccess.description")
|
||||
.build().showAndWait();
|
||||
|
||||
} catch (InvalidPassphraseException e) {
|
||||
LOG.info("Password invalid", e);
|
||||
Animations.createShakeWindowAnimation(window).play();
|
||||
} catch (IOException | CryptoException | IllegalStateException e) {
|
||||
LOG.error("Recovery process failed", e);
|
||||
appWindows.showErrorWindow(e, window, null);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.controls.NumericTextField;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyExpertSettingsController implements FxController {
|
||||
|
||||
public static final int MAX_SHORTENING_THRESHOLD = 220;
|
||||
public static final int MIN_SHORTENING_THRESHOLD = 36;
|
||||
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/security/vault/#name-shortening";
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Application> application;
|
||||
private final Vault vault;
|
||||
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||
private final IntegerProperty shorteningThreshold;
|
||||
private final Lazy<Scene> resetPasswordScene;
|
||||
private final Lazy<Scene> createScene;
|
||||
private final Lazy<Scene> onBoardingScene;
|
||||
private final Lazy<Scene> recoverScene;
|
||||
private final BooleanBinding validShorteningThreshold;
|
||||
|
||||
@FXML
|
||||
public CheckBox expertSettingsCheckBox;
|
||||
@FXML
|
||||
public NumericTextField shorteningThresholdTextField;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyExpertSettingsController(@RecoveryKeyWindow Stage window, //
|
||||
Lazy<Application> application, //
|
||||
@RecoveryKeyWindow Vault vault, //
|
||||
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE) Lazy<Scene> createScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy<Scene> onBoardingScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverScene) {
|
||||
this.window = window;
|
||||
this.application = application;
|
||||
this.vault = vault;
|
||||
this.recoverType = recoverType;
|
||||
this.shorteningThreshold = shorteningThreshold;
|
||||
this.resetPasswordScene = resetPasswordScene;
|
||||
this.createScene = createScene;
|
||||
this.onBoardingScene = onBoardingScene;
|
||||
this.recoverScene = recoverScene;
|
||||
this.validShorteningThreshold = Bindings.createBooleanBinding(this::isValidShorteningThreshold, shorteningThreshold);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
shorteningThresholdTextField.setPromptText(MIN_SHORTENING_THRESHOLD + "-" + MAX_SHORTENING_THRESHOLD);
|
||||
shorteningThresholdTextField.setText(Integer.toString(MAX_SHORTENING_THRESHOLD));
|
||||
shorteningThresholdTextField.textProperty().addListener((_, _, newValue) -> {
|
||||
try {
|
||||
int intValue = Integer.parseInt(newValue);
|
||||
shorteningThreshold.set(intValue);
|
||||
} catch (NumberFormatException e) {
|
||||
shorteningThreshold.set(0); //the value is set to 0 to ensure that an invalid value assignment is detected during a NumberFormatException
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void toggleUseExpertSettings() {
|
||||
if (!expertSettingsCheckBox.isSelected()) {
|
||||
shorteningThresholdTextField.setText(Integer.toString(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD));
|
||||
}
|
||||
}
|
||||
|
||||
public void openDocs() {
|
||||
application.get().getHostServices().showDocument(DOCS_NAME_SHORTENING_URL);
|
||||
}
|
||||
|
||||
public BooleanBinding validShorteningThresholdProperty() {
|
||||
return validShorteningThreshold;
|
||||
}
|
||||
|
||||
public boolean isValidShorteningThreshold() {
|
||||
var value = shorteningThreshold.get();
|
||||
return value >= MIN_SHORTENING_THRESHOLD && value <= MAX_SHORTENING_THRESHOLD;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back() {
|
||||
if (recoverType.get() == RecoveryActionType.RESTORE_ALL && vault.getState() == VaultState.Value.VAULT_CONFIG_MISSING) {
|
||||
window.setScene(recoverScene.get());
|
||||
} else if (recoverType.get() == RecoveryActionType.RESTORE_ALL && vault.getState() == VaultState.Value.ALL_MISSING) {
|
||||
window.setScene(recoverScene.get());
|
||||
} else if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
|
||||
window.setScene(onBoardingScene.get());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
|
||||
window.setScene(createScene.get());
|
||||
} else {
|
||||
window.setScene(resetPasswordScene.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,12 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
@@ -19,6 +23,10 @@ import org.cryptomator.ui.common.StageFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Scene;
|
||||
@@ -99,12 +107,18 @@ abstract class RecoveryKeyModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS)
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING)
|
||||
@RecoveryKeyScoped
|
||||
static Scene provideRecoveryKeyResetPasswordSuccessScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS);
|
||||
static Scene provideRecoveryKeyOnboardingScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_ONBOARDING);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS)
|
||||
@RecoveryKeyScoped
|
||||
static Scene provideRecoveryKeyExpertSettingsScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS);
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@@ -120,6 +134,25 @@ abstract class RecoveryKeyModule {
|
||||
return new RecoveryKeyDisplayController(window, vault.getDisplayName(), recoveryKey.get(), localization);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("shorteningThreshold")
|
||||
@RecoveryKeyScoped
|
||||
static IntegerProperty provideShorteningThreshold() {
|
||||
return new SimpleIntegerProperty(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("cipherCombo")
|
||||
@RecoveryKeyScoped
|
||||
static ObjectProperty<CryptorProvider.Scheme> provideCipherCombo() {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyExpertSettingsController.class)
|
||||
abstract FxController provideRecoveryKeyExpertSettingsController(RecoveryKeyExpertSettingsController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyRecoverController.class)
|
||||
@@ -137,14 +170,14 @@ abstract class RecoveryKeyModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyResetPasswordSuccessController.class)
|
||||
abstract FxController bindRecoveryKeyResetPasswordSuccessController(RecoveryKeyResetPasswordSuccessController controller);
|
||||
@FxControllerKey(RecoveryKeyOnboardingController.class)
|
||||
abstract FxController bindRecoveryKeyOnboardingController(RecoveryKeyOnboardingController controller);
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyValidateController.class)
|
||||
static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
|
||||
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory);
|
||||
static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, @Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) {
|
||||
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, masterkeyFileAccess, recoverType, cipherCombo);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static org.cryptomator.common.recovery.RecoveryActionType.RESTORE_ALL;
|
||||
import static org.cryptomator.common.recovery.RecoveryActionType.RESTORE_VAULT_CONFIG;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyOnboardingController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final Lazy<Scene> recoverykeyRecoverScene;
|
||||
private final Lazy<Scene> recoverykeyExpertSettingsScene;
|
||||
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
public Label titleLabel;
|
||||
public Label messageLabel;
|
||||
public Label pleaseConfirm;
|
||||
public Label secondTextDesc;
|
||||
|
||||
@FXML
|
||||
private CheckBox affirmationBox;
|
||||
@FXML
|
||||
private RadioButton recoveryKeyRadio;
|
||||
@FXML
|
||||
private RadioButton passwordRadio;
|
||||
@FXML
|
||||
private Button nextButton;
|
||||
@FXML
|
||||
private VBox chooseMethodeBox;
|
||||
@FXML
|
||||
private ToggleGroup methodToggleGroup = new ToggleGroup();
|
||||
@FXML
|
||||
private HBox hBox;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyOnboardingController(@RecoveryKeyWindow Stage window, //
|
||||
@RecoveryKeyWindow Vault vault, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverykeyRecoverScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> recoverykeyExpertSettingsScene, //
|
||||
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||
ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.recoverykeyRecoverScene = recoverykeyRecoverScene;
|
||||
this.recoverykeyExpertSettingsScene = recoverykeyExpertSettingsScene;
|
||||
this.recoverType = recoverType;
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
recoveryKeyRadio.setToggleGroup(methodToggleGroup);
|
||||
passwordRadio.setToggleGroup(methodToggleGroup);
|
||||
|
||||
BooleanBinding showMethodSelection = Bindings.createBooleanBinding(
|
||||
() -> recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG, recoverType);
|
||||
chooseMethodeBox.visibleProperty().bind(showMethodSelection);
|
||||
chooseMethodeBox.managedProperty().bind(showMethodSelection);
|
||||
|
||||
nextButton.disableProperty().bind(
|
||||
affirmationBox.selectedProperty().not()
|
||||
.or(methodToggleGroup.selectedToggleProperty().isNull().and(showMethodSelection))
|
||||
);
|
||||
|
||||
switch (recoverType.get()) {
|
||||
case RESTORE_MASTERKEY -> {
|
||||
window.setTitle(resourceBundle.getString("recover.recoverMasterkey.title"));
|
||||
messageLabel.setVisible(false);
|
||||
messageLabel.setManaged(false);
|
||||
pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.pleaseConfirm"));
|
||||
}
|
||||
case RESTORE_ALL -> {
|
||||
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
|
||||
messageLabel.setVisible(true);
|
||||
messageLabel.setManaged(true);
|
||||
pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.otherwisePleaseConfirm"));
|
||||
}
|
||||
case RESTORE_VAULT_CONFIG -> {
|
||||
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
|
||||
messageLabel.setVisible(false);
|
||||
messageLabel.setManaged(false);
|
||||
pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.pleaseConfirm"));
|
||||
}
|
||||
default -> window.setTitle("");
|
||||
}
|
||||
|
||||
if (vault.getState() == VaultState.Value.ALL_MISSING) {
|
||||
messageLabel.setText(resourceBundle.getString("recover.onBoarding.allMissing.intro"));
|
||||
} else {
|
||||
messageLabel.setText(resourceBundle.getString("recover.onBoarding.intro"));
|
||||
}
|
||||
|
||||
titleLabel.textProperty().bind(Bindings.createStringBinding(() ->
|
||||
recoverType.get() == RecoveryActionType.RESTORE_MASTERKEY
|
||||
? resourceBundle.getString("recover.recoverMasterkey.title")
|
||||
: resourceBundle.getString("recover.recoverVaultConfig.title"), recoverType));
|
||||
|
||||
BooleanBinding isRestoreMasterkey = Bindings.createBooleanBinding(
|
||||
() -> recoverType.get() == RecoveryActionType.RESTORE_MASTERKEY, recoverType);
|
||||
hBox.minHeightProperty().bind(Bindings.when(isRestoreMasterkey).then(206.0).otherwise(Region.USE_COMPUTED_SIZE));
|
||||
|
||||
secondTextDesc.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
RecoveryActionType type = recoverType.get();
|
||||
Toggle sel = methodToggleGroup.getSelectedToggle();
|
||||
return switch (type) {
|
||||
case RESTORE_VAULT_CONFIG -> resourceBundle.getString(sel == passwordRadio
|
||||
? "recover.onBoarding.intro.password"
|
||||
: "recover.onBoarding.intro.recoveryKey");
|
||||
case RESTORE_MASTERKEY -> resourceBundle.getString("recover.onBoarding.intro.masterkey.recoveryKey");
|
||||
case RESTORE_ALL -> resourceBundle.getString("recover.onBoarding.intro.recoveryKey");
|
||||
default -> "";
|
||||
};
|
||||
}, recoverType, methodToggleGroup.selectedToggleProperty()));
|
||||
|
||||
showMethodSelection.addListener((_, _, nowShown) -> {
|
||||
if (nowShown && methodToggleGroup.getSelectedToggle() == null) {
|
||||
methodToggleGroup.selectToggle(recoveryKeyRadio);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
switch (recoverType.get()) {
|
||||
case RESTORE_VAULT_CONFIG, RESTORE_ALL -> {
|
||||
Object selectedToggle = methodToggleGroup.getSelectedToggle();
|
||||
if (selectedToggle == recoveryKeyRadio) {
|
||||
recoverType.set(RESTORE_ALL);
|
||||
window.setScene(recoverykeyRecoverScene.get());
|
||||
} else if (selectedToggle == passwordRadio) {
|
||||
recoverType.set(RESTORE_VAULT_CONFIG);
|
||||
window.setScene(recoverykeyExpertSettingsScene.get());
|
||||
} else {
|
||||
window.setScene(recoverykeyRecoverScene.get());
|
||||
}
|
||||
}
|
||||
case RESTORE_MASTERKEY -> window.setScene(recoverykeyRecoverScene.get());
|
||||
default -> window.setScene(recoverykeyRecoverScene.get()); // Fallback
|
||||
}
|
||||
window.centerOnScreen();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +1,105 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyRecoverController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> resetPasswordScene;
|
||||
private final Vault vault;
|
||||
private final Lazy<Scene> nextScene;
|
||||
private final Lazy<Scene> onBoardingScene;
|
||||
private final ResourceBundle resourceBundle;
|
||||
public ObjectProperty<RecoveryActionType> recoverType;
|
||||
|
||||
@FXML
|
||||
private Button cancelButton;
|
||||
|
||||
@FXML
|
||||
RecoveryKeyValidateController recoveryKeyValidateController;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, ResourceBundle resourceBundle) {
|
||||
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, //
|
||||
@RecoveryKeyWindow Vault vault, //
|
||||
ResourceBundle resourceBundle, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> expertSettingsScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy<Scene> onBoardingScene, //
|
||||
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType) {
|
||||
this.window = window;
|
||||
window.setTitle(resourceBundle.getString("recoveryKey.recover.title"));
|
||||
this.resetPasswordScene = resetPasswordScene;
|
||||
this.vault = vault;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.onBoardingScene = onBoardingScene;
|
||||
this.recoverType = recoverType;
|
||||
this.nextScene = switch (recoverType.get()) {
|
||||
case RESTORE_ALL, RESTORE_VAULT_CONFIG -> {
|
||||
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
|
||||
yield expertSettingsScene;
|
||||
}
|
||||
case RESTORE_MASTERKEY -> {
|
||||
window.setTitle(resourceBundle.getString("recover.recoverMasterkey.title"));
|
||||
yield resetPasswordScene;
|
||||
}
|
||||
case RESET_PASSWORD -> {
|
||||
window.setTitle(resourceBundle.getString("recoveryKey.recover.title"));
|
||||
yield resetPasswordScene;
|
||||
}
|
||||
case SHOW_KEY -> {
|
||||
window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
|
||||
yield resetPasswordScene;
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unexpected recovery action type: " + recoverType.get());
|
||||
};
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
if (recoverType.get() == RecoveryActionType.RESET_PASSWORD) {
|
||||
cancelButton.setText(resourceBundle.getString("generic.button.cancel"));
|
||||
} else {
|
||||
cancelButton.setText(resourceBundle.getString("generic.button.back"));
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
public void closeOrReturn() {
|
||||
switch (recoverType.get()) {
|
||||
case RESET_PASSWORD -> window.close();
|
||||
case RESTORE_MASTERKEY -> {
|
||||
window.setScene(onBoardingScene.get());
|
||||
window.centerOnScreen();
|
||||
}
|
||||
default -> {
|
||||
if(vault.getState().equals(VaultState.Value.ALL_MISSING)){
|
||||
recoverType.set(RecoveryActionType.RESTORE_ALL);
|
||||
}
|
||||
else {
|
||||
recoverType.set(RecoveryActionType.RESTORE_VAULT_CONFIG);
|
||||
}
|
||||
window.setScene(onBoardingScene.get());
|
||||
window.centerOnScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void recover() {
|
||||
window.setScene(resetPasswordScene.get());
|
||||
window.setScene(nextScene.get());
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
+123
-13
@@ -1,25 +1,44 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.recovery.CryptoFsInitializer;
|
||||
import org.cryptomator.common.recovery.MasterkeyService;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.recovery.RecoveryDirectory;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyResetPasswordController implements FxController {
|
||||
|
||||
@@ -30,48 +49,140 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final ExecutorService executor;
|
||||
private final StringProperty recoveryKey;
|
||||
private final Lazy<Scene> recoverResetPasswordSuccessScene;
|
||||
private final Lazy<Scene> recoverExpertSettingsScene;
|
||||
private final Lazy<Scene> recoverykeyRecoverScene;
|
||||
private final FxApplicationWindows appWindows;
|
||||
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final IntegerProperty shorteningThreshold;
|
||||
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||
private final ObjectProperty<CryptorProvider.Scheme> cipherCombo;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final Dialogs dialogs;
|
||||
|
||||
public NewPasswordController newPasswordController;
|
||||
public Button nextButton;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy<Scene> recoverResetPasswordSuccessScene, FxApplicationWindows appWindows) {
|
||||
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, //
|
||||
@RecoveryKeyWindow Vault vault, //
|
||||
RecoveryKeyFactory recoveryKeyFactory, //
|
||||
ExecutorService executor, //
|
||||
@RecoveryKeyWindow StringProperty recoveryKey, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> recoverExpertSettingsScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverykeyRecoverScene, //
|
||||
FxApplicationWindows appWindows, //
|
||||
MasterkeyFileAccess masterkeyFileAccess, //
|
||||
VaultListManager vaultListManager, //
|
||||
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
|
||||
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||
@Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo, //
|
||||
ResourceBundle resourceBundle, //
|
||||
Dialogs dialogs) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.executor = executor;
|
||||
this.recoveryKey = recoveryKey;
|
||||
this.recoverResetPasswordSuccessScene = recoverResetPasswordSuccessScene;
|
||||
this.recoverExpertSettingsScene = recoverExpertSettingsScene;
|
||||
this.recoverykeyRecoverScene = recoverykeyRecoverScene;
|
||||
this.appWindows = appWindows;
|
||||
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.shorteningThreshold = shorteningThreshold;
|
||||
this.cipherCombo = cipherCombo;
|
||||
this.recoverType = recoverType;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.dialogs = dialogs;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
switch (recoverType.get()) {
|
||||
case RESTORE_MASTERKEY, RESTORE_ALL -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn"));
|
||||
case RESET_PASSWORD -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.resetBtn"));
|
||||
default -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn")); // Fallback
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
switch (recoverType.get()) {
|
||||
case RESTORE_ALL -> window.setScene(recoverExpertSettingsScene.get());
|
||||
case RESTORE_MASTERKEY, RESET_PASSWORD -> window.setScene(recoverykeyRecoverScene.get());
|
||||
default -> window.close();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
switch (recoverType.get()) {
|
||||
case RESTORE_ALL -> restorePassword();
|
||||
case RESTORE_MASTERKEY, RESET_PASSWORD -> resetPassword();
|
||||
default -> resetPassword(); // Fallback
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void restorePassword() {
|
||||
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
|
||||
Path recoveryPath = recoveryDirectory.getRecoveryPath();
|
||||
MasterkeyService.recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, recoveryPath, newPasswordController.passwordField.getCharacters());
|
||||
|
||||
try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, recoveryPath.resolve(MASTERKEY_FILENAME), newPasswordController.passwordField.getCharacters())) {
|
||||
CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), cipherCombo.get());
|
||||
}
|
||||
|
||||
recoveryDirectory.moveRecoveredFile(MASTERKEY_FILENAME);
|
||||
recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
|
||||
|
||||
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
|
||||
vaultListManager.add(vault.getPath());
|
||||
}
|
||||
window.close();
|
||||
dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
|
||||
.setTitleKey("recover.recoverVaultConfig.title") //
|
||||
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
|
||||
.build().showAndWait();
|
||||
|
||||
} catch (IOException | CryptoException e) {
|
||||
LOG.error("Recovery process failed", e);
|
||||
appWindows.showErrorWindow(e, window, null);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void resetPassword() {
|
||||
Task<Void> task = new ResetPasswordTask();
|
||||
task.setOnScheduled(event -> {
|
||||
|
||||
task.setOnScheduled(_ -> {
|
||||
LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
});
|
||||
task.setOnSucceeded(event -> {
|
||||
LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
window.setScene(recoverResetPasswordSuccessScene.get());
|
||||
|
||||
task.setOnSucceeded(_ -> {
|
||||
LOG.debug("Used recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
window.close();
|
||||
switch (recoverType.get()){
|
||||
case RESET_PASSWORD -> dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()).build().showAndWait();
|
||||
case RESTORE_MASTERKEY -> dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()).setTitleKey("recover.recoverMasterkey.title").setMessageKey("recoveryKey.recover.resetMasterkeyFileSuccess.message").build().showAndWait();
|
||||
default -> dialogs.prepareRecoverPasswordSuccess(window).build().showAndWait(); // Fallback
|
||||
}
|
||||
});
|
||||
task.setOnFailed(event -> {
|
||||
|
||||
task.setOnFailed(_ -> {
|
||||
LOG.error("Resetting password failed.", task.getException());
|
||||
appWindows.showErrorWindow(task.getException(), window, null);
|
||||
});
|
||||
|
||||
executor.submit(task);
|
||||
}
|
||||
|
||||
private class ResetPasswordTask extends Task<Void> {
|
||||
|
||||
private ResetPasswordTask() {
|
||||
setOnFailed(event -> LOG.error("Failed to reset password", getException()));
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResetPasswordTask.class);
|
||||
|
||||
public ResetPasswordTask() {
|
||||
setOnFailed(_ -> LOG.error("Failed to reset password", getException()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,7 +190,6 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyResetPasswordSuccessController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyResetPasswordSuccessController(@RecoveryKeyWindow Stage window) {
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +1,23 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Strings;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.ObservableUtil;
|
||||
import org.cryptomator.common.recovery.MasterkeyService;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.VaultConfigLoadException;
|
||||
import org.cryptomator.cryptofs.VaultKeyInvalidException;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
@@ -22,10 +27,12 @@ import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import java.io.IOException;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class RecoveryKeyValidateController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyValidateController.class);
|
||||
private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
|
||||
|
||||
private final Vault vault;
|
||||
@@ -36,13 +43,23 @@ public class RecoveryKeyValidateController implements FxController {
|
||||
private final ObservableValue<Boolean> recoveryKeyInvalid;
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final ObjectProperty<RecoveryKeyState> recoveryKeyState;
|
||||
private final ObjectProperty<CryptorProvider.Scheme> cipherCombo;
|
||||
private final AutoCompleter autoCompleter;
|
||||
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||
|
||||
private volatile boolean isWrongKey;
|
||||
|
||||
public TextArea textarea;
|
||||
|
||||
public RecoveryKeyValidateController(Vault vault, @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
|
||||
public RecoveryKeyValidateController(Vault vault, //
|
||||
@Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, //
|
||||
StringProperty recoveryKey, //
|
||||
RecoveryKeyFactory recoveryKeyFactory, //
|
||||
MasterkeyFileAccess masterkeyFileAccess, //
|
||||
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||
@Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo
|
||||
) {
|
||||
this.vault = vault;
|
||||
this.unverifiedVaultConfig = vaultConfig;
|
||||
this.recoveryKey = recoveryKey;
|
||||
@@ -52,6 +69,9 @@ public class RecoveryKeyValidateController implements FxController {
|
||||
this.recoveryKeyCorrect = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.CORRECT::equals, false);
|
||||
this.recoveryKeyWrong = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.WRONG::equals, false);
|
||||
this.recoveryKeyInvalid = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.INVALID::equals, false);
|
||||
this.recoverType = recoverType;
|
||||
this.cipherCombo = cipherCombo;
|
||||
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -117,14 +137,37 @@ public class RecoveryKeyValidateController implements FxController {
|
||||
}
|
||||
|
||||
private void validateRecoveryKey() {
|
||||
isWrongKey = false;
|
||||
var valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
|
||||
if (valid) {
|
||||
recoveryKeyState.set(RecoveryKeyState.CORRECT);
|
||||
} else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig()
|
||||
recoveryKeyState.set(RecoveryKeyState.WRONG);
|
||||
} else {
|
||||
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
||||
switch (recoverType.get()) {
|
||||
case RESTORE_ALL, RESTORE_VAULT_CONFIG -> {
|
||||
try {
|
||||
var scheme = MasterkeyService.validateRecoveryKeyAndDetectCombo(recoveryKeyFactory, vault, recoveryKey.get(), masterkeyFileAccess);
|
||||
cipherCombo.set(scheme);
|
||||
recoveryKeyState.set(RecoveryKeyState.CORRECT);
|
||||
} catch (CryptoException e) {
|
||||
LOG.info("Recovery key is valid but crypto scheme couldn't be determined", e);
|
||||
recoveryKeyState.set(RecoveryKeyState.WRONG);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.info("Recovery key is syntactically invalid", e);
|
||||
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("IO error while validating recovery key", e);
|
||||
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
||||
} catch (NoSuchElementException e) {
|
||||
LOG.warn("Could not determine scheme from masterkey during recovery key validation, because no valid *.c9r file is present in vault", e);
|
||||
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
||||
}
|
||||
}
|
||||
case RESTORE_MASTERKEY, RESET_PASSWORD, SHOW_KEY, CONVERT_VAULT -> {
|
||||
isWrongKey = false;
|
||||
boolean valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
|
||||
if (valid) {
|
||||
recoveryKeyState.set(RecoveryKeyState.CORRECT);
|
||||
} else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig()
|
||||
recoveryKeyState.set(RecoveryKeyState.WRONG);
|
||||
} else {
|
||||
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.inject.Named;
|
||||
@@ -27,7 +28,7 @@ import javafx.stage.Stage;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(subcomponents = {KeyLoadingComponent.class})
|
||||
@Module(subcomponents = {KeyLoadingComponent.class, RecoveryKeyComponent.class})
|
||||
abstract class UnlockModule {
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package org.cryptomator.ui.vaultoptions;
|
||||
|
||||
import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
@@ -18,8 +18,6 @@ import javafx.stage.Stage;
|
||||
@VaultOptionsScoped
|
||||
public class MasterkeyOptionsController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyOptionsController.class);
|
||||
|
||||
private final Vault vault;
|
||||
private final Stage window;
|
||||
private final ChangePasswordComponent.Builder changePasswordWindow;
|
||||
@@ -51,12 +49,12 @@ public class MasterkeyOptionsController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void showRecoveryKey() {
|
||||
recoveryKeyWindow.create(vault, window).showRecoveryKeyCreationWindow();
|
||||
recoveryKeyWindow.create(vault, window, new SimpleObjectProperty<>(RecoveryActionType.SHOW_KEY)).showRecoveryKeyCreationWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showRecoverVaultDialog() {
|
||||
recoveryKeyWindow.create(vault, window).showRecoveryKeyRecoverWindow();
|
||||
recoveryKeyWindow.create(vault, window, new SimpleObjectProperty<>(RecoveryActionType.RESET_PASSWORD)).showRecoveryKeyRecoverWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -6,7 +6,12 @@
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import javafx.scene.Group?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.convertvault.HubToPasswordConvertController"
|
||||
minWidth="400"
|
||||
@@ -17,7 +22,16 @@
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Group>
|
||||
<StackPane>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="KEY" glyphSize="24"/>
|
||||
</StackPane>
|
||||
</Group>
|
||||
<VBox spacing="12" HBox.hgrow="ALWAYS" alignment="TOP_CENTER">
|
||||
<fx:include fx:id="newPassword" source="new_password.fxml"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
@@ -26,7 +40,7 @@
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||
<Button fx:id="convertBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#convert"> <!-- for button logic, see controller -->
|
||||
<Button fx:id="convertBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#convert">
|
||||
<graphic>
|
||||
<FontAwesome5Spinner glyphSize="12"/>
|
||||
</graphic>
|
||||
@@ -34,5 +48,5 @@
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import javafx.scene.Group?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.convertvault.HubToPasswordStartController"
|
||||
minWidth="400"
|
||||
@@ -16,18 +19,26 @@
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Group>
|
||||
<StackPane>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="SYNC" glyphSize="24"/>
|
||||
</StackPane>
|
||||
</Group>
|
||||
|
||||
<VBox spacing="12" HBox.hgrow="ALWAYS" alignment="TOP_CENTER">
|
||||
<fx:include fx:id="recoveryKeyValidate" source="recoverykey_validate.fxml"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<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" defaultButton="true" onAction="#next" disable="${!controller.validateController.recoveryKeyCorrect}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
<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" defaultButton="true" onAction="#next" disable="${!controller.validateController.recoveryKeyCorrect}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<Insets bottom="6" top="6"/>
|
||||
</padding>
|
||||
</Label>
|
||||
<FormattedLabel format="%recoveryKey.create.description" arg1="${controller.vault.displayName}" wrapText="true">
|
||||
<FormattedLabel fx:id="descriptionLabel" format="%recoveryKey.create.description" arg1="${controller.vault.displayName}" wrapText="true">
|
||||
<padding>
|
||||
<Insets bottom="6"/>
|
||||
</padding>
|
||||
@@ -49,8 +49,8 @@
|
||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||
<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" defaultButton="true" onAction="#createRecoveryKey" disable="${passwordField.text.empty}"/>
|
||||
<Button fx:id="cancelButton" text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||
<Button fx:id="nextButton" text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#createRecoveryKey" disable="${passwordField.text.empty}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.control.Hyperlink?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.scene.Group?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.NumericTextField?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyExpertSettingsController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<Group>
|
||||
<StackPane>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="QUESTION" glyphSize="24"/>
|
||||
</StackPane>
|
||||
</Group>
|
||||
|
||||
<VBox HBox.hgrow="ALWAYS">
|
||||
<Label styleClass="label-large" text="Expert Settings" wrapText="true">
|
||||
<padding>
|
||||
<Insets bottom="6" top="6"/>
|
||||
</padding>
|
||||
</Label>
|
||||
|
||||
<CheckBox fx:id="expertSettingsCheckBox" text="%addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox" onAction="#toggleUseExpertSettings"/>
|
||||
<VBox spacing="6" visible="${expertSettingsCheckBox.selected}">
|
||||
<HBox spacing="2" HBox.hgrow="NEVER">
|
||||
<Label text="%addvaultwizard.new.expertSettings.shorteningThreshold.title"/>
|
||||
<Region prefWidth="2"/>
|
||||
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%addvaultwizard.new.expertSettings.shorteningThreshold.tooltip" showDelay="10ms"/>
|
||||
</tooltip>
|
||||
</Hyperlink>
|
||||
</HBox>
|
||||
<Label text="%recover.expertSettings.shorteningThreshold.title" wrapText="true"/>
|
||||
<NumericTextField fx:id="shorteningThresholdTextField"/>
|
||||
<HBox alignment="TOP_RIGHT">
|
||||
<Region minWidth="4" prefWidth="4" HBox.hgrow="NEVER"/>
|
||||
<StackPane>
|
||||
<Label styleClass="label-muted" text="%addvaultwizard.new.expertSettings.shorteningThreshold.invalid" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${!controller.validShorteningThreshold}" managed="${!controller.validShorteningThreshold}" graphicTextGap="6">
|
||||
<graphic>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-red" glyph="TIMES"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label styleClass="label-muted" text="%addvaultwizard.new.expertSettings.shorteningThreshold.valid" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.validShorteningThreshold}" managed="${controller.validShorteningThreshold}" graphicTextGap="6">
|
||||
<graphic>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-primary" glyph="CHECK"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
</StackPane>
|
||||
</HBox>
|
||||
</VBox>
|
||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||
<buttons>
|
||||
<Button text="%generic.button.back" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#back"/>
|
||||
<Button text="%generic.button.next" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" onAction="#next"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</HBox>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?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.RadioButton?>
|
||||
<?import javafx.scene.control.ToggleGroup?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.RowConstraints?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
|
||||
<?import javafx.scene.Group?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:id="hBox"
|
||||
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyOnboardingController"
|
||||
prefWidth="480"
|
||||
minHeight="242"
|
||||
spacing="12"
|
||||
VBox.vgrow="ALWAYS">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<Group>
|
||||
<StackPane>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="SYNC" glyphSize="24"/>
|
||||
</StackPane>
|
||||
</Group>
|
||||
<VBox HBox.hgrow="ALWAYS" spacing="12">
|
||||
<Label fx:id="titleLabel" text="%recover.recoverVaultConfig.title" styleClass="label-extra-large"/>
|
||||
<VBox spacing="6">
|
||||
<VBox fx:id="chooseMethodeBox" spacing="6">
|
||||
<Label text="%recover.onBoarding.chooseMethod"/>
|
||||
<RadioButton fx:id="recoveryKeyRadio" text="%recover.onBoarding.useRecoveryKey" selected="true">
|
||||
<toggleGroup>
|
||||
<ToggleGroup fx:id="methodToggleGroup"/>
|
||||
</toggleGroup>
|
||||
</RadioButton>
|
||||
<RadioButton fx:id="passwordRadio" text="%recover.onBoarding.usePassword"/>
|
||||
</VBox>
|
||||
|
||||
<Label fx:id="messageLabel" wrapText="true"/>
|
||||
<Label fx:id="pleaseConfirm" wrapText="true"/>
|
||||
<GridPane alignment="CENTER_LEFT">
|
||||
<padding>
|
||||
<Insets left="6"/>
|
||||
</padding>
|
||||
<columnConstraints>
|
||||
<ColumnConstraints minWidth="20" halignment="LEFT"/>
|
||||
<ColumnConstraints />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints valignment="TOP"/>
|
||||
<RowConstraints valignment="TOP"/>
|
||||
<RowConstraints valignment="TOP"/>
|
||||
</rowConstraints>
|
||||
<Label text="1." GridPane.rowIndex="0" GridPane.columnIndex="0"/>
|
||||
<Label text="%recover.onBoarding.intro.ensure" wrapText="true" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
|
||||
<Label text="2." GridPane.rowIndex="1" GridPane.columnIndex="0"/>
|
||||
<Label fx:id="secondTextDesc" text="%recover.onBoarding.intro.recoveryKey" wrapText="true" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
|
||||
</GridPane>
|
||||
<CheckBox fx:id="affirmationBox" text="%recover.onBoarding.affirmation"/>
|
||||
</VBox>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CX" VBox.vgrow="NEVER">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||
<Button fx:id="nextButton" text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD"
|
||||
disable="${!affirmationBox.selected}" defaultButton="true" onAction="#next"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
|
||||
</VBox>
|
||||
</HBox>
|
||||
@@ -3,32 +3,46 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.Group?>
|
||||
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyRecoverController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_CENTER">
|
||||
spacing="12">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Group>
|
||||
<StackPane>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="SYNC" glyphSize="24"/>
|
||||
</StackPane>
|
||||
</Group>
|
||||
|
||||
<fx:include fx:id="recoveryKeyValidate" source="recoverykey_validate.fxml"/>
|
||||
<VBox spacing="12" HBox.hgrow="ALWAYS" alignment="TOP_CENTER">
|
||||
<fx:include fx:id="recoveryKeyValidate" source="recoverykey_validate.fxml"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+BX">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||
<Button fx:id="cancelButton" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#closeOrReturn"/>
|
||||
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#recover" disable="${!controller.validateController.recoveryKeyCorrect}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
@@ -5,7 +5,12 @@
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import javafx.scene.Group?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyResetPasswordController"
|
||||
minWidth="400"
|
||||
@@ -17,17 +22,28 @@
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<fx:include fx:id="newPassword" source="new_password.fxml"/>
|
||||
<Group>
|
||||
<StackPane>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="KEY" glyphSize="24"/>
|
||||
</StackPane>
|
||||
</Group>
|
||||
<VBox spacing="12" HBox.hgrow="ALWAYS" alignment="TOP_CENTER">
|
||||
<fx:include fx:id="newPassword" source="new_password.fxml"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||
<Button text="%recoveryKey.recover.resetBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#resetPassword" disable="${!controller.passwordSufficientAndMatching}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||
<buttons>
|
||||
<Button text="%generic.button.back" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||
<Button fx:id="nextButton" ButtonBar.buttonData="FINISH" defaultButton="true" disable="${!controller.passwordSufficientAndMatching}" onAction="#next"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import javafx.scene.Group?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyResetPasswordSuccessController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_LEFT">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Group>
|
||||
<StackPane>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CHECK" glyphSize="24"/>
|
||||
</StackPane>
|
||||
</Group>
|
||||
|
||||
<VBox HBox.hgrow="ALWAYS">
|
||||
<Label styleClass="label-large" text="%recoveryKey.recover.resetSuccess.message" wrapText="true" textAlignment="LEFT">
|
||||
<padding>
|
||||
<Insets bottom="6" top="6"/>
|
||||
</padding>
|
||||
</Label>
|
||||
|
||||
<Label text="%recoveryKey.recover.resetSuccess.description" wrapText="true" textAlignment="LEFT"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||
<buttons>
|
||||
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" cancelButton="true" onAction="#close"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</HBox>
|
||||
@@ -2,48 +2,40 @@
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextArea?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyValidateController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
spacing="12">
|
||||
<children>
|
||||
<FormattedLabel format="%recoveryKey.recover.prompt" arg1="${controller.vault.displayName}" wrapText="true"/>
|
||||
|
||||
<TextArea wrapText="true" prefRowCount="4" fx:id="textarea" textFormatter="${controller.recoveryKeyTextFormatter}" onKeyPressed="#onKeyPressed"/>
|
||||
|
||||
<StackPane>
|
||||
<Label text="Just some Filler" visible="false" graphicTextGap="6">
|
||||
<VBox>
|
||||
<Label text="Just some Filler" visible="false" managed="${textarea.text.empty}" graphicTextGap="6">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="ANCHOR"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%recoveryKey.recover.correctKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) && controller.recoveryKeyCorrect}">
|
||||
<Label text="%recoveryKey.recover.correctKey" graphicTextGap="6" visible="${(!textarea.text.empty) && controller.recoveryKeyCorrect}" managed="${(!textarea.text.empty) && controller.recoveryKeyCorrect}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CHECK"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%recoveryKey.recover.wrongKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) && controller.recoveryKeyWrong}">
|
||||
<Label text="%recoveryKey.recover.wrongKey" graphicTextGap="6" visible="${(!textarea.text.empty) && controller.recoveryKeyWrong}" managed="${(!textarea.text.empty) && controller.recoveryKeyWrong}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%recoveryKey.recover.invalidKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) && controller.recoveryKeyInvalid}">
|
||||
<Label text="%recoveryKey.recover.invalidKey" graphicTextGap="6" visible="${(!textarea.text.empty) && controller.recoveryKeyInvalid}" managed="${(!textarea.text.empty) && controller.recoveryKeyInvalid}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
</StackPane>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<?import javafx.scene.Group?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.keyloading.masterkeyfile.ChooseMasterkeyFileController"
|
||||
@@ -40,12 +41,13 @@
|
||||
</padding>
|
||||
</Label>
|
||||
<FormattedLabel format="%unlock.chooseMasterkey.description" arg1="${controller.displayName}" wrapText="true"/>
|
||||
<CheckBox fx:id="restoreInsteadCheckBox" text="%unlock.chooseMasterkey.restoreInstead" wrapText="true"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#cancel"/>
|
||||
<Button text="%generic.button.choose" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#proceed"/>
|
||||
<Button fx:id="forwardButton" text="%generic.button.choose" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#proceed"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
|
||||
@@ -53,5 +53,6 @@
|
||||
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_missing.fxml" visible="${controller.vault.missing}" managed="${controller.vault.missing}"/>
|
||||
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_needsmigration.fxml" visible="${controller.vault.needsMigration}" managed="${controller.vault.needsMigration}"/>
|
||||
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_unknownerror.fxml" visible="${controller.vault.unknownError}" managed="${controller.vault.unknownError}"/>
|
||||
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_missing_vault_config.fxml" visible="${controller.vault.missingVaultConfig}" managed="${controller.vault.missingVaultConfig}"/>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailMissingVaultController"
|
||||
alignment="TOP_CENTER"
|
||||
spacing="9">
|
||||
<VBox spacing="9" alignment="CENTER">
|
||||
<StackPane>
|
||||
<Circle styleClass="glyph-icon-primary" radius="48"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="FILE" glyphSize="48"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-primary" glyph="SEARCH" glyphSize="24">
|
||||
<StackPane.margin>
|
||||
<Insets top="12"/>
|
||||
</StackPane.margin>
|
||||
</FontAwesome5IconView>
|
||||
</StackPane>
|
||||
<Label text="%main.vaultDetail.missingVaultConfig.info" wrapText="true"/>
|
||||
</VBox>
|
||||
<VBox spacing="6" alignment="CENTER">
|
||||
<Button text="%main.vaultDetail.missing.recheck" minWidth="120" onAction="#recheck">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="REDO"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button text="%main.vaultDetail.missingVaultConfig.restore" minWidth="120" onAction="#restoreVaultConfig">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="MAGIC"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button text="%main.vaultDetail.missing.remove" minWidth="120" onAction="#didClickRemoveVault">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TRASH"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</VBox>
|
||||
</VBox>
|
||||
@@ -13,8 +13,6 @@
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Arc?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
@@ -80,6 +78,11 @@
|
||||
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14"/>
|
||||
</graphic>
|
||||
</MenuItem>
|
||||
<MenuItem styleClass="dropdown-button-context-menu-item" text="%main.vaultlist.addVaultBtn.menuItemRecover" onAction="#didClickRecoverExistingVault">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="SYNC" textAlignment="CENTER" wrappingWidth="14"/>
|
||||
</graphic>
|
||||
</MenuItem>
|
||||
</items>
|
||||
</ContextMenu>
|
||||
</fx:define>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<MenuItem fx:id="lockEntry" text="%main.vaultlist.contextMenu.lock" onAction="#didClickLockVault" visible="${controller.selectedVaultLockable}"/>
|
||||
<MenuItem fx:id="unlockEntry" text="%main.vaultlist.contextMenu.unlock" onAction="#didClickUnlockVault" visible="${controller.selectedVaultUnlockable && !controller.selectedVaultPassphraseStored}"/>
|
||||
<MenuItem fx:id="unlockNowEntry" text="%main.vaultlist.contextMenu.unlockNow" onAction="#didClickUnlockVault" visible="${controller.selectedVaultUnlockable && controller.selectedVaultPassphraseStored}"/>
|
||||
<MenuItem fx:id="shareEntry" text="%main.vaultlist.contextMenu.share" onAction="#didClickShareVault" visible="${controller.selectedVaultUnlockable || controller.selectedVaultLockable}"/>
|
||||
<MenuItem fx:id="optionsEntry" text="%main.vaultlist.contextMenu.vaultoptions" onAction="#didClickShowVaultOptions" disable="${!controller.selectedVaultUnlockable}"/>
|
||||
<MenuItem fx:id="removeEntry" text="%main.vaultlist.contextMenu.remove" onAction="#didClickRemoveVault" disable="${!controller.selectedVaultRemovable}"/>
|
||||
</items>
|
||||
|
||||
@@ -97,6 +97,7 @@ addvault.new.readme.accessLocation.4=Feel free to remove this file.
|
||||
## Existing
|
||||
addvaultwizard.existing.title=Add Existing Vault
|
||||
addvaultwizard.existing.instruction=Choose the "vault.cryptomator" file of your existing vault. If only a file named "masterkey.cryptomator" exists, select that instead.
|
||||
addvaultwizard.existing.restore=Restore…
|
||||
addvaultwizard.existing.chooseBtn=Choose…
|
||||
addvaultwizard.existing.filePickerTitle=Select Vault File
|
||||
addvaultwizard.existing.filePickerMimeDesc=Cryptomator Vault
|
||||
@@ -128,6 +129,7 @@ unlock.unlockBtn=Unlock
|
||||
## Select
|
||||
unlock.chooseMasterkey.message=Masterkey file not found
|
||||
unlock.chooseMasterkey.description=Cryptomator could not find the masterkey file for vault "%s". Please choose the key file manually.
|
||||
unlock.chooseMasterkey.restoreInstead=Restore the masterkey file instead
|
||||
unlock.chooseMasterkey.filePickerTitle=Select Masterkey File
|
||||
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
|
||||
## Success
|
||||
@@ -394,8 +396,10 @@ main.vaultlist.contextMenu.unlock=Unlock…
|
||||
main.vaultlist.contextMenu.unlockNow=Unlock Now
|
||||
main.vaultlist.contextMenu.vaultoptions=Show Vault Options
|
||||
main.vaultlist.contextMenu.reveal=Reveal Drive
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Create New Vault...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault...
|
||||
main.vaultlist.contextMenu.share=Share…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Create New Vault…
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault…
|
||||
main.vaultlist.addVaultBtn.menuItemRecover=Recover Existing Vault…
|
||||
main.vaultlist.showEventsButton.tooltip=Open event view
|
||||
##Notificaition
|
||||
main.notification.updateAvailable=Update is available.
|
||||
@@ -433,6 +437,9 @@ main.vaultDetail.missing.info=Cryptomator could not find a vault at this path.
|
||||
main.vaultDetail.missing.recheck=Recheck
|
||||
main.vaultDetail.missing.remove=Remove from Vault List…
|
||||
main.vaultDetail.missing.changeLocation=Change Vault Location…
|
||||
### Missing Vault Config
|
||||
main.vaultDetail.missingVaultConfig.info=Vault config is missing.
|
||||
main.vaultDetail.missingVaultConfig.restore=Restore vault config
|
||||
### Needs Migration
|
||||
main.vaultDetail.migrateButton=Upgrade Vault
|
||||
main.vaultDetail.migratePrompt=Your vault needs to be upgraded to a new format, before you can access it
|
||||
@@ -497,6 +504,7 @@ vaultOptions.hub.convertBtn=Convert to Password-Based Vault
|
||||
recoveryKey.display.title=Show Recovery Key
|
||||
recoveryKey.create.message=Password required
|
||||
recoveryKey.create.description=Enter the password for "%s" to show its recovery key.
|
||||
recoveryKey.recover.description=Enter the password for "%s" to recover the vault config.
|
||||
recoveryKey.display.description=The following recovery key can be used to restore access to "%s":
|
||||
recoveryKey.display.StorageHints=Keep it somewhere very secure, e.g.:\n • Store it using a password manager\n • Save it on a USB flash drive\n • Print it on paper
|
||||
## Reset Password
|
||||
@@ -509,9 +517,59 @@ recoveryKey.recover.invalidKey=This recovery key is not valid
|
||||
recoveryKey.printout.heading=Cryptomator Recovery Key\n"%s"\n
|
||||
### Reset Password
|
||||
recoveryKey.recover.resetBtn=Reset
|
||||
recoveryKey.recover.recoverBtn=Recover
|
||||
### Recovery Key Password Reset Success
|
||||
recoveryKey.recover.resetSuccess.message=Password reset successful
|
||||
recoveryKey.recover.resetSuccess.description=You can unlock your vault with the new password.
|
||||
### Recovery Key Vault Config Reset Success
|
||||
recoveryKey.recover.resetVaultConfigSuccess.message=Vault config reset successful
|
||||
recoveryKey.recover.resetMasterkeyFileSuccess.message=Masterkey file reset successful
|
||||
recoveryKey.recover.resetMasterkeyFileSuccess.description=You can unlock your vault with your password now.
|
||||
|
||||
# Recover Vault Config File and/or Masterkey
|
||||
##Add Existing Vault without recovery - Dialog
|
||||
recover.existing.title=Vault Added
|
||||
recover.existing.message=The vault was added successfully
|
||||
recover.existing.description=Your vault "%s" has been added to the vault list. No recovery process was necessary.
|
||||
|
||||
##Vault Already Exists - Dialog
|
||||
recover.alreadyExists.title=Vault Already Exists
|
||||
recover.alreadyExists.message=This vault has already been added
|
||||
recover.alreadyExists.description=Your vault "%s" is already present in your vault list and was therefore not added again.
|
||||
|
||||
##Invalid Selection - Dialog
|
||||
recover.invalidSelection.title=Invalid Selection
|
||||
recover.invalidSelection.message=Your selection is not a vault
|
||||
recover.invalidSelection.description=The selected folder must be a valid Cryptomator vault.
|
||||
|
||||
## Contact Hub Vault Owner - Dialog
|
||||
contactHubVaultOwner.title=Hub Vault
|
||||
contactHubVaultOwner.message=This vault was created with Cryptomator Hub
|
||||
contactHubVaultOwner.description=Please reach out to the vault owner to restore the missing file. They can download the vault template from Cryptomator Hub.
|
||||
|
||||
##Dialog Title
|
||||
recover.recoverVaultConfig.title=Recover Vault Config
|
||||
recover.recoverMasterkey.title=Recover Masterkey
|
||||
|
||||
## OnBoarding
|
||||
recover.onBoarding.chooseMethod=Choose recovery method:
|
||||
recover.onBoarding.useRecoveryKey=Use Recovery Key
|
||||
recover.onBoarding.usePassword=Use Password
|
||||
recover.onBoarding.intro=Make sure to check the following:
|
||||
recover.onBoarding.pleaseConfirm=Before proceeding, please confirm that:
|
||||
recover.onBoarding.otherwisePleaseConfirm=Otherwise, please confirm that:
|
||||
recover.onBoarding.allMissing.intro=If this vault is managed by Cryptomator Hub, the vault owner must restore it for you.
|
||||
recover.onBoarding.intro.ensure=All files are fully synced.
|
||||
recover.onBoarding.affirmation=I have read and understood these requirements
|
||||
|
||||
###Vault Config Missing
|
||||
recover.onBoarding.intro.recoveryKey=You have the recovery key and know if expert settings were used.
|
||||
recover.onBoarding.intro.password=You have the vault password and know if expert settings were used.
|
||||
###Masterkey Missing
|
||||
recover.onBoarding.intro.masterkey.recoveryKey=You have the vault recovery key.
|
||||
|
||||
## Expert Settings
|
||||
recover.expertSettings.shorteningThreshold.title=This value must match the one used before recovery to ensure compatibility with previously encrypted data.
|
||||
|
||||
# Convert Vault
|
||||
convertVault.title=Convert Vault
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=فتح…
|
||||
main.vaultlist.contextMenu.unlockNow=افتح الان
|
||||
main.vaultlist.contextMenu.vaultoptions=إظهار خيارات المخزن
|
||||
main.vaultlist.contextMenu.reveal=اظهار القرص
|
||||
main.vaultlist.contextMenu.share=مشاركة…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=إنشاء مخزن جديد...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=افتح مخزن موجود...
|
||||
main.vaultlist.showEventsButton.tooltip=عرض الإشعارات
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Desbloca…
|
||||
main.vaultlist.contextMenu.unlockNow=Desbloqueja ara
|
||||
main.vaultlist.contextMenu.vaultoptions=Opcions de la caixa forta
|
||||
main.vaultlist.contextMenu.reveal=Mostra la unitat
|
||||
main.vaultlist.contextMenu.share=Compateix…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Crea una nova caixa forta…
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Obri una caixa forta existent...
|
||||
##Notificaition
|
||||
|
||||
@@ -364,6 +364,7 @@ main.vaultlist.contextMenu.unlock=Odemknout…
|
||||
main.vaultlist.contextMenu.unlockNow=Odemknout nyní
|
||||
main.vaultlist.contextMenu.vaultoptions=Zobrazit možnosti trezoru
|
||||
main.vaultlist.contextMenu.reveal=Zobrazit jednotku
|
||||
main.vaultlist.contextMenu.share=Sdílet…
|
||||
##Notificaition
|
||||
## Vault Detail
|
||||
### Welcome
|
||||
|
||||
@@ -283,6 +283,7 @@ preferences.title=Præferencer
|
||||
## General
|
||||
preferences.general=Generelt
|
||||
preferences.general.startHidden=Skjul vinduet når Cryptomator starter
|
||||
preferences.general.autoCloseVaults=Lås bokse uden at spørge ved afslutning af applikationen
|
||||
preferences.general.debugLogging=Aktivér fejllogning
|
||||
preferences.general.debugDirectory=Vis logfiler
|
||||
preferences.general.autoStart=Start Cryptomator automatisk ved opstart
|
||||
@@ -301,6 +302,7 @@ preferences.interface.interfaceOrientation=Brugerflade retning
|
||||
preferences.interface.interfaceOrientation.ltr=Venstre til højre
|
||||
preferences.interface.interfaceOrientation.rtl=Højre til venstre
|
||||
preferences.interface.showTrayIcon=Vis ikon i system-bakken (kræver genstart)
|
||||
preferences.interface.compactMode=Aktivér kompakt bokse-liste
|
||||
## Volume
|
||||
preferences.volume=Virtuelt drev
|
||||
preferences.volume.type=Standard Drev Type
|
||||
@@ -320,7 +322,13 @@ preferences.updates.currentVersion=Nuværende version: %s
|
||||
preferences.updates.autoUpdateCheck=Søg automatisk efter opdateringer
|
||||
preferences.updates.checkNowBtn=Kontrollér nu
|
||||
preferences.updates.updateAvailable=Opdatering til version %s er tilgængelig.
|
||||
preferences.updates.lastUpdateCheck=Seneste tjek: %s
|
||||
preferences.updates.lastUpdateCheck.never=aldrig
|
||||
preferences.updates.lastUpdateCheck.recently=for nylig
|
||||
preferences.updates.lastUpdateCheck.daysAgo=%s dage siden
|
||||
preferences.updates.lastUpdateCheck.hoursAgo=%s timer siden
|
||||
preferences.updates.checkFailed=Søgning efter opdateringer fejlede. Tjek din internetforbindelse eller forsøg igen senere.
|
||||
preferences.updates.upToDate=Cryptomator er opdateret.
|
||||
|
||||
## Contribution
|
||||
preferences.contribute=Støt os
|
||||
@@ -328,8 +336,14 @@ preferences.contribute.registeredFor=Registreret supporter-certifikat for %s
|
||||
preferences.contribute.noCertificate=Hjælp Cryptomator, og modtag et supporter-certifikat. Det er ligesom en license-nøgle, men til fantastiske mennesker som bruger fri software. ;-)
|
||||
preferences.contribute.getCertificate=Har du ikke et allerede? Se her hvordan du kan få et.
|
||||
preferences.contribute.promptText=Indsæt koden for supporter-certifikatet her
|
||||
preferences.contribute.thankYou=Tak fordi du støtter Cryptomators open source-udvikling!
|
||||
preferences.contribute.donate=Donér
|
||||
preferences.contribute.sponsor=Sponsor
|
||||
|
||||
### Remove License Key Dialog
|
||||
removeCert.title=Fjern certifikat
|
||||
removeCert.message=Fjern supporter-certifikat?
|
||||
removeCert.description=Cryptomators kernefunktioner påvirkes ikke af dette. Hverken adgangen er begrænset, eller sikkerhedsniveauet sænket til dine bokse.
|
||||
#<-- Add entries for donations and code/translation/documentation contribution -->
|
||||
|
||||
## About
|
||||
@@ -379,6 +393,10 @@ main.vaultlist.contextMenu.unlock=Lås op…
|
||||
main.vaultlist.contextMenu.unlockNow=Lås op nu
|
||||
main.vaultlist.contextMenu.vaultoptions=Vis boksindstillinger
|
||||
main.vaultlist.contextMenu.reveal=Vis drev
|
||||
main.vaultlist.contextMenu.share=Del…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Opret ny boks...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Åbn eksisterende boks...
|
||||
main.vaultlist.showEventsButton.tooltip=Åbn begivenhedsvisning
|
||||
##Notificaition
|
||||
main.notification.updateAvailable=Opdatering er tilgængelig.
|
||||
main.notification.support=Støt Cryptomator.
|
||||
@@ -404,9 +422,12 @@ main.vaultDetail.throughput.idle=afventer
|
||||
main.vaultDetail.throughput.kbps=%.1f KiB/s
|
||||
main.vaultDetail.throughput.mbps=%.1f MiB/s
|
||||
main.vaultDetail.stats=Boks statistik
|
||||
main.vaultDetail.locateEncryptedFileBtn=Find Krypteret Fil
|
||||
main.vaultDetail.locateEncryptedFileBtn=Find krypteret fil
|
||||
main.vaultDetail.locateEncryptedFileBtn.tooltip=Vælg en fil fra din boks for at finde dens krypterede modpart
|
||||
main.vaultDetail.encryptedPathsCopied=Stier kopieret!
|
||||
main.vaultDetail.locateEncrypted.filePickerTitle=Vælg fil inde i boks
|
||||
main.vaultDetail.decryptName.buttonLabel=Dekryptér filnavn
|
||||
main.vaultDetail.decryptName.tooltip=Vælg en krypteret fil i boks for at dekryptere dets navn
|
||||
### Missing
|
||||
main.vaultDetail.missing.info=Cryptomator kunne ikke finde en boks på denne sti.
|
||||
main.vaultDetail.missing.recheck=Kontrollér igen
|
||||
@@ -531,9 +552,15 @@ updateReminder.yesOnce=Ja, én gang
|
||||
updateReminder.yesAutomatically=Ja, automatisk
|
||||
|
||||
#Dokany Support End
|
||||
dokanySupportEnd.title=Meddelelse om udfasning
|
||||
dokanySupportEnd.message=Understøttelse slutter for Dokany
|
||||
dokanySupportEnd.description=Drevtypen Dokany understøttes ikke længere af Cryptomator. Dine indstillinger er justeret for at bruge standard drevtypen nu. Du kan se standardtypen i indstillingerne.
|
||||
dokanySupportEnd.preferencesBtn=Åbn Indstillinger
|
||||
|
||||
#Retry If Readonly
|
||||
retryIfReadonly.title=Begrænset boksadgang
|
||||
retryIfReadonly.message=Ingen skriveadgang til boksens mappe
|
||||
retryIfReadonly.description=Cryptomator kan ikke skrive til boksens mappe. Du kan ændre boksen til at være skrivebeskyttet og prøve igen. Denne indstilling kan være deaktiveret i boksens indstillinger.
|
||||
|
||||
# Share Vault
|
||||
shareVault.title=Del Boks
|
||||
@@ -556,7 +583,11 @@ shareVault.hub.instruction.2=2. Giv adgang til holdmedlem i Cryptomator Hub.
|
||||
shareVault.hub.openHub=Åben Cryptomator Hub
|
||||
|
||||
# Decrypt File Names
|
||||
decryptNames.title=Dekryptér Filnavne
|
||||
decryptNames.copyHint=Kopiér celleindhold med %s
|
||||
decryptNames.dropZone.message=Slip filer eller klik for at vælge
|
||||
|
||||
|
||||
# Event View
|
||||
eventView.title=Begivenheder
|
||||
## event list entries
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Entsperren …
|
||||
main.vaultlist.contextMenu.unlockNow=Jetzt entsperren
|
||||
main.vaultlist.contextMenu.vaultoptions=Tresoroptionen anzeigen
|
||||
main.vaultlist.contextMenu.reveal=Laufwerk anzeigen
|
||||
main.vaultlist.contextMenu.share=Teilen …
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Neuen Tresor erstellen...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Bestehenden Tresor öffnen...
|
||||
main.vaultlist.showEventsButton.tooltip=Ereignis-Ansicht öffnen
|
||||
|
||||
@@ -393,8 +393,10 @@ main.vaultlist.contextMenu.unlock=Ξεκλείδωμα…
|
||||
main.vaultlist.contextMenu.unlockNow=Ξεκλείδωμα τώρα
|
||||
main.vaultlist.contextMenu.vaultoptions=Εμφάνιση επιλογών Vault
|
||||
main.vaultlist.contextMenu.reveal=Αποκάλυψη εικονικού δίσκου
|
||||
main.vaultlist.contextMenu.share=Κοινοποίηση…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Δημιουργία Νέας Κρύπτης...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Άνοιγμα Υπάρχοντος Κρύπτης...
|
||||
main.vaultlist.showEventsButton.tooltip=Άνοιγμα προβολής συμβάντων
|
||||
##Notificaition
|
||||
main.notification.updateAvailable=Η ενημέρωση είναι διαθέσιμη.
|
||||
main.notification.support=Υποστήριξη Cryptomator.
|
||||
@@ -423,6 +425,9 @@ main.vaultDetail.stats=Στατιστικά Vault
|
||||
main.vaultDetail.locateEncryptedFileBtn=Εντοπισμός Κρυπτογραφημένου Αρχείου
|
||||
main.vaultDetail.locateEncryptedFileBtn.tooltip=Επιλέξτε ένα αρχείο από την κρύπτη σας για να εντοπίσετε το κρυπτογραφημένο αντίστοιχο
|
||||
main.vaultDetail.encryptedPathsCopied=Οι Διαδρομές Αντιγράφηκαν στο Πρόχειρο!
|
||||
main.vaultDetail.locateEncrypted.filePickerTitle=Επιλογή Αρχείου Μέσα Στην Κρύπτη
|
||||
main.vaultDetail.decryptName.buttonLabel=Αποκρυπτογράφηση Ονόματος Αρχείου
|
||||
main.vaultDetail.decryptName.tooltip=Επιλέξτε ένα κρυπτογραφημένο αρχείο κρύπτης για να αποκρυπτογραφήσετε το όνομά του
|
||||
### Missing
|
||||
main.vaultDetail.missing.info=Cryptomator δεν βρήκε vault σε αυτόν τον κατάλογο.
|
||||
main.vaultDetail.missing.recheck=Επανέλεγχος
|
||||
@@ -579,8 +584,40 @@ shareVault.hub.instruction.2=2. Παραχωρήστε πρόσβαση σε μ
|
||||
shareVault.hub.openHub=Ανοίξτε το Cryptomator Hub
|
||||
|
||||
# Decrypt File Names
|
||||
decryptNames.title=Αποκρυπτογράφηση Ονομάτων Αρχείων
|
||||
decryptNames.filePicker.title=Επιλογή κρυπτογραφημένου αρχείου
|
||||
decryptNames.filePicker.extensionDescription=Κρυπτογραφημένο κρυπτογραφημένου αρχείο Cryptomator
|
||||
decryptNames.copyTable.tooltip=Αντιγραφή πίνακα
|
||||
decryptNames.clearTable.tooltip=Εκκαθάριση πίνακα
|
||||
decryptNames.copyHint=Αντιγραφή περιεχομένου κελιού με %s
|
||||
decryptNames.dropZone.message=Απόθεση αρχείων ή κλικ για επιλογή
|
||||
decryptNames.dropZone.error.vaultInternalFiles=Εσωτερικά αρχεία κρύπτης χωρίς να επιλεχθεί όνομα αποκρυπτογράφησης
|
||||
decryptNames.dropZone.error.foreignFiles=Τα αρχεία δεν ανήκουν στην κρύπτη "%s"
|
||||
decryptNames.dropZone.error.noDirIdBackup=Ο κατάλογος των επιλεγμένων αρχείων δεν περιέχει αρχείο dirId.c9r
|
||||
decryptNames.dropZone.error.generic=Αποτυχία αποκρυπτογράφησης ονομάτων αρχείων
|
||||
|
||||
|
||||
# Event View
|
||||
eventView.title=Συμβάντα
|
||||
eventView.filter.allVaults=Όλες
|
||||
eventView.clearListButton.tooltip=Εκκαθάριση λίστας
|
||||
## event list entries
|
||||
eventView.entry.vaultLocked.description=Ξεκλείδωμα "%s" για λεπτομέρειες
|
||||
eventView.entry.conflictResolved.message=Επίλυση σύγκρουσης
|
||||
eventView.entry.conflictResolved.showDecrypted=Εμφάνιση αποκρυπτογραφημένου αρχείου
|
||||
eventView.entry.conflictResolved.copyDecrypted=Αντιγραφή αποκρυπτογραφημένης διαδρομής
|
||||
eventView.entry.conflict.message=Η επίλυση συγκρούσεων απέτυχε
|
||||
eventView.entry.conflict.showDecrypted=Εμφάνιση αποκρυπτογραφημένων, αρχικό αρχείο
|
||||
eventView.entry.conflict.copyDecrypted=Αντιγραφή αποκρυπτογραφημένων, αρχική διαδρομή
|
||||
eventView.entry.conflict.showEncrypted=Εμφάνιση αντικρουόμενου, κρυπτογραφημένο αρχείο
|
||||
eventView.entry.conflict.copyEncrypted=Αντιγραφή σύγκρουσης, κρυπτογραφημένη διαδρομή
|
||||
eventView.entry.decryptionFailed.message=Η αποκρυπτογράφηση απέτυχε
|
||||
eventView.entry.decryptionFailed.showEncrypted=Εμφάνιση κρυπτογραφημένου αρχείου
|
||||
eventView.entry.decryptionFailed.copyEncrypted=Αντιγραφή κρυπτογραφημένης διαδρομής
|
||||
eventView.entry.brokenDirFile.message=Μη λειτουργικός σύνδεσμος καταλόγου
|
||||
eventView.entry.brokenDirFile.showEncrypted=Εμφάνιση μη λειτουργικού, κρυπτογραφημένου συνδέσμου
|
||||
eventView.entry.brokenDirFile.copyEncrypted=Αντιγραφή διαδρομής του μη λειτουργικού συνδέσμου
|
||||
eventView.entry.brokenFileNode.message=Μη λειτουργικός κόμβος συστήματος αρχείων
|
||||
eventView.entry.brokenFileNode.showEncrypted=Εμφάνιση μη λειτουργικού, κρυπτογραφημένου κόμβου
|
||||
eventView.entry.brokenFileNode.copyEncrypted=Αντιγραφή διαδρομής του μη λειτουργικού, κρυπτογραφημένου κόμβου
|
||||
eventView.entry.brokenFileNode.copyDecrypted=Αντιγραφή αποκρυπτογραφημένης διαδρομής
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Desbloquear…
|
||||
main.vaultlist.contextMenu.unlockNow=Desbloquear ahora
|
||||
main.vaultlist.contextMenu.vaultoptions=Mostrar opciones de la bóveda
|
||||
main.vaultlist.contextMenu.reveal=Revelar unidad
|
||||
main.vaultlist.contextMenu.share=Compartir…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Crear Bóveda Nueva...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Abrir Bóveda Existente...
|
||||
main.vaultlist.showEventsButton.tooltip=Abrir vista de evento
|
||||
|
||||
@@ -392,6 +392,7 @@ main.vaultlist.contextMenu.unlock=Avaa…
|
||||
main.vaultlist.contextMenu.unlockNow=Avaa Nyt
|
||||
main.vaultlist.contextMenu.vaultoptions=Näytä holvin asetukset
|
||||
main.vaultlist.contextMenu.reveal=Paljasta Asema
|
||||
main.vaultlist.contextMenu.share=Jaa…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Luo uusi holvi...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Avaa olemassa oleva holvi...
|
||||
main.vaultlist.showEventsButton.tooltip=Avaa tapahtumanäkymä
|
||||
|
||||
@@ -384,6 +384,7 @@ main.vaultlist.contextMenu.unlock=I-unlock…
|
||||
main.vaultlist.contextMenu.unlockNow=I-unlock Ngayon
|
||||
main.vaultlist.contextMenu.vaultoptions=Ipakita ang Mga Opsyon sa Vault
|
||||
main.vaultlist.contextMenu.reveal=Ibunyag ang Drive
|
||||
main.vaultlist.contextMenu.share=Ibahagi…
|
||||
##Notificaition
|
||||
## Vault Detail
|
||||
### Welcome
|
||||
|
||||
@@ -361,7 +361,7 @@ stats.read.total.data.kib=Lecture des données : %.1f kio
|
||||
stats.read.total.data.mib=Lecture des données : %.1f MiO
|
||||
stats.read.total.data.gib=Lecture des données : %.1f GiO
|
||||
stats.decr.total.data.none=Données déchiffrées: -
|
||||
stats.decr.total.data.kib=Données déchiffrées: %.1f kio
|
||||
stats.decr.total.data.kib=Données déchiffrées: %.1f Kio
|
||||
stats.decr.total.data.mib=Données déchiffrées: %.1f MiO
|
||||
stats.decr.total.data.gib=Données déchiffrées: %.1f GiO
|
||||
stats.read.accessCount=Total des lectures: %d
|
||||
@@ -374,7 +374,7 @@ stats.write.total.data.kib=Données écrites : %.1f kio
|
||||
stats.write.total.data.mib=Données écrites : %.1f MiO
|
||||
stats.write.total.data.gib=Données écrites : %.1f GiO
|
||||
stats.encr.total.data.none=Données chiffrées : -
|
||||
stats.encr.total.data.kib=Données chiffrées: %.1f kio
|
||||
stats.encr.total.data.kib=Données chiffrées: %.1f Kio
|
||||
stats.encr.total.data.mib=Données chiffrées: %.1f MiO
|
||||
stats.encr.total.data.gib=Données chiffrées: %.1f GiO
|
||||
stats.write.accessCount=Total des écritures: %d
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Déverrouiller…
|
||||
main.vaultlist.contextMenu.unlockNow=Déverrouiller maintenant
|
||||
main.vaultlist.contextMenu.vaultoptions=Afficher les options du volume chiffré
|
||||
main.vaultlist.contextMenu.reveal=Afficher le lecteur
|
||||
main.vaultlist.contextMenu.share=Partager…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Créer un nouveau coffre...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Ouvrir un coffre existant...
|
||||
main.vaultlist.showEventsButton.tooltip=Ouvrir la vue Événements
|
||||
@@ -590,6 +591,7 @@ decryptNames.copyTable.tooltip=Copier le tableau
|
||||
decryptNames.clearTable.tooltip=Effacer le tableau
|
||||
decryptNames.copyHint=Copier le contenu de la cellule avec %s
|
||||
decryptNames.dropZone.message=Déposer des fichiers ou cliquer pour en sélectionner
|
||||
decryptNames.dropZone.error.vaultInternalFiles=Fichiers internes du coffre sans nom déchiffrable sélectionnés
|
||||
decryptNames.dropZone.error.foreignFiles=Les fichiers n'appartiennent pas au coffre « %s »
|
||||
decryptNames.dropZone.error.noDirIdBackup=Le répertoire des fichiers sélectionnés ne contient pas de fichier dirId.c9r
|
||||
decryptNames.dropZone.error.generic=Impossible de déchiffrer les noms de fichiers
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Feloldás…
|
||||
main.vaultlist.contextMenu.unlockNow=Azonnali feloldás
|
||||
main.vaultlist.contextMenu.vaultoptions=Széf beállítások
|
||||
main.vaultlist.contextMenu.reveal=Széf megjelenítése
|
||||
main.vaultlist.contextMenu.share=Megosztás…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Új széf létrehozása...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Meglévő széf megnyitása...
|
||||
##Notificaition
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Buka Kunci…
|
||||
main.vaultlist.contextMenu.unlockNow=Buka Kunci Sekarang
|
||||
main.vaultlist.contextMenu.vaultoptions=Tampilkan Opsi Vault
|
||||
main.vaultlist.contextMenu.reveal=Buka Drive
|
||||
main.vaultlist.contextMenu.share=Bagikan…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Buat Vault Baru...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Buka Vault yang Tersedia...
|
||||
##Notificaition
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Sblocca…
|
||||
main.vaultlist.contextMenu.unlockNow=Sblocca Ora
|
||||
main.vaultlist.contextMenu.vaultoptions=Mostra le Opzioni della Cassaforte
|
||||
main.vaultlist.contextMenu.reveal=Rivela Unità
|
||||
main.vaultlist.contextMenu.share=Condividi…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Crea una nuova cassaforte...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Apri una cassaforte esistente...
|
||||
main.vaultlist.showEventsButton.tooltip=Apri vista eventi
|
||||
|
||||
@@ -390,6 +390,7 @@ main.vaultlist.contextMenu.unlock=解錠...
|
||||
main.vaultlist.contextMenu.unlockNow=今すぐ解錠
|
||||
main.vaultlist.contextMenu.vaultoptions=金庫のオプションを表示
|
||||
main.vaultlist.contextMenu.reveal=ドライブを表示
|
||||
main.vaultlist.contextMenu.share=共有…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=新しい金庫を作成…
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=既存の金庫を開く…
|
||||
##Notificaition
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=잠금 해제...
|
||||
main.vaultlist.contextMenu.unlockNow=지금 잠금 해제
|
||||
main.vaultlist.contextMenu.vaultoptions=Vault 옵션 보기
|
||||
main.vaultlist.contextMenu.reveal=드라이브 표시
|
||||
main.vaultlist.contextMenu.share=공유하기…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=새 Vault 생성...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=기존 Vault 열기...
|
||||
main.vaultlist.showEventsButton.tooltip=이벤트 뷰어 열기
|
||||
@@ -588,8 +589,10 @@ decryptNames.filePicker.title=암호화된 파일 선택
|
||||
decryptNames.filePicker.extensionDescription=Cryptomator로 암호화된 파일
|
||||
decryptNames.copyTable.tooltip=테이블 복사
|
||||
decryptNames.clearTable.tooltip=테이블 비우기
|
||||
decryptNames.copyHint=%s로 셀 내용 복사하기
|
||||
decryptNames.dropZone.message=파일을 여기에 드롭하거나 클릭하세요
|
||||
decryptNames.dropZone.error.vaultInternalFiles=복호화 가능한 이름이 선택되지 않은 Vailt 내부 파일
|
||||
decryptNames.dropZone.error.foreignFiles=Vault "%s"에 포함되지 않은 파일
|
||||
decryptNames.dropZone.error.noDirIdBackup=선택된 파일 폴더에 dirId.c9r 파일이 포함되어 있지 않습니다
|
||||
decryptNames.dropZone.error.generic=파일 이름 복호화 실패
|
||||
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Atslēgt…
|
||||
main.vaultlist.contextMenu.unlockNow=Atslēgt tagad
|
||||
main.vaultlist.contextMenu.vaultoptions=Rādīt glabātavas iespējas
|
||||
main.vaultlist.contextMenu.reveal=Atklāt disku
|
||||
main.vaultlist.contextMenu.share=Kopīgot…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Izveidot jaunu glabātavu...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Atvērt esošu glabātavu...
|
||||
main.vaultlist.showEventsButton.tooltip=Atvērt notikumu skatu
|
||||
|
||||
@@ -386,6 +386,7 @@ main.vaultlist.contextMenu.unlock=Lås opp…
|
||||
main.vaultlist.contextMenu.unlockNow=Lås opp nå
|
||||
main.vaultlist.contextMenu.vaultoptions=Alternativer for hvelvet
|
||||
main.vaultlist.contextMenu.reveal=Vis enheten
|
||||
main.vaultlist.contextMenu.share=Del…
|
||||
##Notificaition
|
||||
main.notification.support=Støtt Cryptomator.
|
||||
## Vault Detail
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Ontgrendelen…
|
||||
main.vaultlist.contextMenu.unlockNow=Nu Ontgrendelen
|
||||
main.vaultlist.contextMenu.vaultoptions=Laat kluisinstellingen zien
|
||||
main.vaultlist.contextMenu.reveal=Toon Schijf
|
||||
main.vaultlist.contextMenu.share=Delen…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Nieuwe Kluis Aanmaken...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Open Bestaande Kluis...
|
||||
main.vaultlist.showEventsButton.tooltip=Afspraakweergave openen
|
||||
|
||||
@@ -328,6 +328,7 @@ main.vaultlist.contextMenu.unlock=ਅਣ-ਲਾਕ ਕਰੋ…
|
||||
main.vaultlist.contextMenu.unlockNow=ਹੁਣੇ ਅਣ-ਲਾਕ ਕਰੋ
|
||||
main.vaultlist.contextMenu.vaultoptions=ਵਾਲਟ ਚੋਣਾਂ ਨੂੰ ਵੇਖਾਓ
|
||||
main.vaultlist.contextMenu.reveal=ਡਰਾਇਵ ਦਿਖਾਓ
|
||||
main.vaultlist.contextMenu.share=…ਸਾਂਝਾ ਕਰੋ
|
||||
main.vaultlist.addVaultBtn.menuItemNew=...ਨਵਾਂ ਵਾਲਟ ਬਣਾਓ
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=...ਮੌਜੂਦਾ ਵਾਲਟ ਨੂੰ ਖੋਲ੍ਹੋ
|
||||
##Notificaition
|
||||
|
||||
@@ -393,8 +393,10 @@ main.vaultlist.contextMenu.unlock=Odblokuj…
|
||||
main.vaultlist.contextMenu.unlockNow=Odblokuj teraz
|
||||
main.vaultlist.contextMenu.vaultoptions=Pokaż opcje sejfu
|
||||
main.vaultlist.contextMenu.reveal=Otwórz lokalizację
|
||||
main.vaultlist.contextMenu.share=Udostępnij…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Utwórz Nowy Sejf...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Otwórz Istniejący Sejf...
|
||||
main.vaultlist.showEventsButton.tooltip=Otwórz widok wydarzeń
|
||||
##Notificaition
|
||||
main.notification.updateAvailable=Dostępna aktualizacja.
|
||||
main.notification.support=Wspomóż Cryptomatora.
|
||||
@@ -423,6 +425,9 @@ main.vaultDetail.stats=Statystyki sejfu
|
||||
main.vaultDetail.locateEncryptedFileBtn=Zlokalizuj zaszyfrowany plik
|
||||
main.vaultDetail.locateEncryptedFileBtn.tooltip=Wybierz plik z sejfu, aby zlokalizować jego zaszyfrowany odpowiednik
|
||||
main.vaultDetail.encryptedPathsCopied=Ścieżki skopiowane do schowka!
|
||||
main.vaultDetail.locateEncrypted.filePickerTitle=Wybierz plik wewnątrz sejfu
|
||||
main.vaultDetail.decryptName.buttonLabel=Odszyfruj nazwę pliku
|
||||
main.vaultDetail.decryptName.tooltip=Wybierz zaszyfrowany plik sejfu do odszyfrowania jego nazwy
|
||||
### Missing
|
||||
main.vaultDetail.missing.info=Cryptomator nie mógł znaleźć sejfu w tej lokalizacji.
|
||||
main.vaultDetail.missing.recheck=Ponów próbę
|
||||
@@ -579,7 +584,40 @@ shareVault.hub.instruction.2=2. Udziel dostępu członkowi zespołu w Cryptomato
|
||||
shareVault.hub.openHub=Otwórz Cryptomator Hub
|
||||
|
||||
# Decrypt File Names
|
||||
decryptNames.title=Odszyfruj nazwy plików
|
||||
decryptNames.filePicker.title=Wybierz zaszyfrowany plik
|
||||
decryptNames.filePicker.extensionDescription=Zaszyfrowany plik Cryptomator
|
||||
decryptNames.copyTable.tooltip=Kopiuj tabelę
|
||||
decryptNames.clearTable.tooltip=Wyczyść tabelę
|
||||
decryptNames.copyHint=Kopiuj zawartość komórki z %s
|
||||
decryptNames.dropZone.message=Upuść pliki lub kliknij, aby wybrać
|
||||
decryptNames.dropZone.error.vaultInternalFiles=Wybrano wewnętrzne pliki sejfu z nazwami niemożliwymi do odszyfrowania
|
||||
decryptNames.dropZone.error.foreignFiles=Pliki nie należą do sejfu "%s"
|
||||
decryptNames.dropZone.error.noDirIdBackup=Katalog wybranych plików nie zawiera pliku dirId.c9r
|
||||
decryptNames.dropZone.error.generic=Nie udało się odszyfrować nazw plików
|
||||
|
||||
|
||||
# Event View
|
||||
eventView.title=Zdarzenia
|
||||
eventView.filter.allVaults=Wszystkie
|
||||
eventView.clearListButton.tooltip=Wyczyść listę
|
||||
## event list entries
|
||||
eventView.entry.vaultLocked.description=Odblokuj "%s" po szczegóły
|
||||
eventView.entry.conflictResolved.message=Konflikt rozwiązany
|
||||
eventView.entry.conflictResolved.showDecrypted=Pokaż odszyfrowany plik
|
||||
eventView.entry.conflictResolved.copyDecrypted=Kopiuj odszyfrowaną ścieżkę
|
||||
eventView.entry.conflict.message=Rozwiązywanie konfliktu nie powiodło się
|
||||
eventView.entry.conflict.showDecrypted=Pokaż odszyfrowany, oryginalny plik
|
||||
eventView.entry.conflict.copyDecrypted=Kopiuj odszyfrowaną, oryginalną ścieżkę
|
||||
eventView.entry.conflict.showEncrypted=Pokaż sprzeczny, zaszyfrowany plik
|
||||
eventView.entry.conflict.copyEncrypted=Kopiuj sprzeczną, zaszyfrowaną ścieżkę
|
||||
eventView.entry.decryptionFailed.message=Odszyfrowywanie nie powiodło się
|
||||
eventView.entry.decryptionFailed.showEncrypted=Pokaż zaszyfrowany plik
|
||||
eventView.entry.decryptionFailed.copyEncrypted=Kopiuj zaszyfrowaną ścieżkę
|
||||
eventView.entry.brokenDirFile.message=Uszkodzony link do katalogu
|
||||
eventView.entry.brokenDirFile.showEncrypted=Pokaż uszkodzony, zaszyfrowany link
|
||||
eventView.entry.brokenDirFile.copyEncrypted=Kopiuj ścieżkę uszkodzonego linku
|
||||
eventView.entry.brokenFileNode.message=Uszkodzony węzeł systemu plików
|
||||
eventView.entry.brokenFileNode.showEncrypted=Pokaż uszkodzony, zaszyfrowany węzeł
|
||||
eventView.entry.brokenFileNode.copyEncrypted=Kopiuj ścieżkę uszkodzonego, zaszyfrowanego węzła
|
||||
eventView.entry.brokenFileNode.copyDecrypted=Kopiuj odszyfrowaną ścieżkę
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Desbloquear…
|
||||
main.vaultlist.contextMenu.unlockNow=Desbloquear agora
|
||||
main.vaultlist.contextMenu.vaultoptions=Mostrar opções do Cofre
|
||||
main.vaultlist.contextMenu.reveal=Revelar unidade
|
||||
main.vaultlist.contextMenu.share=Partilhar…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Criar novo cofre...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Abrir cofre existente...
|
||||
main.vaultlist.showEventsButton.tooltip=Abrir visualização do evento
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Desbloquear…
|
||||
main.vaultlist.contextMenu.unlockNow=Desbloquear Agora
|
||||
main.vaultlist.contextMenu.vaultoptions=Exibir Opções de Cofre
|
||||
main.vaultlist.contextMenu.reveal=Revelar Volume
|
||||
main.vaultlist.contextMenu.share=Compartilhar…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Novo Cofre...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Abrir Cofre Existente...
|
||||
main.vaultlist.showEventsButton.tooltip=Abrir visualização de evento
|
||||
|
||||
@@ -389,6 +389,7 @@ main.vaultlist.contextMenu.unlock=Deblochează…
|
||||
main.vaultlist.contextMenu.unlockNow=Deblochează acum
|
||||
main.vaultlist.contextMenu.vaultoptions=Arată opțiunile seifului
|
||||
main.vaultlist.contextMenu.reveal=Dezvăluie unitatea
|
||||
main.vaultlist.contextMenu.share=Distribuie…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Creare seif nou...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Deschide un seif existent...
|
||||
##Notificaition
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Разблокировать…
|
||||
main.vaultlist.contextMenu.unlockNow=Разблокировать
|
||||
main.vaultlist.contextMenu.vaultoptions=Параметры хранилища
|
||||
main.vaultlist.contextMenu.reveal=Показать диск
|
||||
main.vaultlist.contextMenu.share=Поделиться…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Создать хранилище...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Открыть имеющееся хранилище...
|
||||
main.vaultlist.showEventsButton.tooltip=Открыть просмотр события
|
||||
|
||||
@@ -386,6 +386,7 @@ main.vaultlist.contextMenu.unlock=Odomknúť…
|
||||
main.vaultlist.contextMenu.unlockNow=Odomknúť teraz
|
||||
main.vaultlist.contextMenu.vaultoptions=Ukáž možnosti trezora
|
||||
main.vaultlist.contextMenu.reveal=Odkry disk
|
||||
main.vaultlist.contextMenu.share=Zdieľať…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Vytvoriť Nový trezor…
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Otvoriť Existujúci trezor...
|
||||
main.vaultlist.showEventsButton.tooltip=Otvoriť zobrazenie udalosti
|
||||
|
||||
@@ -132,6 +132,7 @@ preferences.updates.lastUpdateCheck.daysAgo=%s dni nazaj
|
||||
## Vault List
|
||||
main.vaultlist.contextMenu.lock=Zakleni
|
||||
main.vaultlist.contextMenu.unlockNow=Odkleni zdaj
|
||||
main.vaultlist.contextMenu.share=Deli…
|
||||
##Notificaition
|
||||
## Vault Detail
|
||||
### Welcome
|
||||
|
||||
@@ -283,6 +283,7 @@ preferences.title=Inställningar
|
||||
## General
|
||||
preferences.general=Allmänt
|
||||
preferences.general.startHidden=Dölj fönster när Cryptomator startar
|
||||
preferences.general.autoCloseVaults=Lås valv utan att fråga när du avslutar applikationen
|
||||
preferences.general.debugLogging=Aktivera loggning för felsökning
|
||||
preferences.general.debugDirectory=Visa loggfiler
|
||||
preferences.general.autoStart=Starta Cryptomator vid systemstart
|
||||
@@ -392,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Lås upp…
|
||||
main.vaultlist.contextMenu.unlockNow=Lås upp nu
|
||||
main.vaultlist.contextMenu.vaultoptions=Visa inställningar för valv
|
||||
main.vaultlist.contextMenu.reveal=Visa enhet
|
||||
main.vaultlist.contextMenu.share=Dela…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Skapa nytt valv...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Öppna befintligt valv...
|
||||
main.vaultlist.showEventsButton.tooltip=Öppna händelsevy
|
||||
|
||||
@@ -335,6 +335,7 @@ main.vaultlist.contextMenu.unlock=ปลดล็อก...
|
||||
main.vaultlist.contextMenu.unlockNow=ปลดล็อกตอนนี้
|
||||
main.vaultlist.contextMenu.vaultoptions=แสดงตัวเลือก Vault
|
||||
main.vaultlist.contextMenu.reveal=เปิดไดรฟ์
|
||||
main.vaultlist.contextMenu.share=แชร์...
|
||||
##Notificaition
|
||||
## Vault Detail
|
||||
### Welcome
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Kilidi aç…
|
||||
main.vaultlist.contextMenu.unlockNow=Kilidi Şimdi Aç
|
||||
main.vaultlist.contextMenu.vaultoptions=Kasa Ayarlarını Göster
|
||||
main.vaultlist.contextMenu.reveal=Sürücüyü Göster
|
||||
main.vaultlist.contextMenu.share=Paylaş…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Yeni Kasa Oluştur...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Var Olan Kasayı Aç...
|
||||
##Notificaition
|
||||
|
||||
@@ -383,6 +383,7 @@ main.vaultlist.contextMenu.unlock=قۇلۇپسىزلا…
|
||||
main.vaultlist.contextMenu.unlockNow=ھازىر قۇلۇپسىزلا
|
||||
main.vaultlist.contextMenu.vaultoptions=ئامبار تاللانمىلىرىنى كۆرسەت
|
||||
main.vaultlist.contextMenu.reveal=دىسكىنى كۆرسەت
|
||||
main.vaultlist.contextMenu.share=ھەمبەھىرلە…
|
||||
##Notificaition
|
||||
## Vault Detail
|
||||
### Welcome
|
||||
|
||||
@@ -15,17 +15,17 @@ generic.button.copied=Скопійовано!
|
||||
generic.button.done=Завершити
|
||||
generic.button.next=Далі
|
||||
generic.button.print=Друкувати
|
||||
generic.button.remove=Прибрати
|
||||
generic.button.remove=Вилучити
|
||||
|
||||
# Error
|
||||
error.message=Сталася помилка
|
||||
error.description=Ой! Cryptomator не очікував, що таке трапиться. Ви можете знайти існуючі рішення цієї помилки. Або, якщо про це ще не повідомили, то не соромтеся зробити це.
|
||||
error.message=Виникла помилка
|
||||
error.description=Ой! Cryptomator не очікував, що це станеться. Ви можете спробувати знайти рішення для цієї помилки. Або, якщо про неї ще не повідомляли, не соромтеся зробити це.
|
||||
error.hyperlink.lookup=Дізнатися більше про цю помилку
|
||||
error.hyperlink.report=Повідомити про цю помилку
|
||||
error.technicalDetails=Докладно:
|
||||
error.existingSolutionDescription=Cryptomator не очікував, що це станеться. Але ми знайшли спосіб розв'язання цієї проблеми. Ознайомтесь з ним, будь ласка, далі.
|
||||
error.existingSolutionDescription=Cryptomator не очікував, що це станеться. Однак ми знайшли рішення для цієї помилки. Будь ласка, перегляньте наступне посилання.
|
||||
error.hyperlink.solution=Ознайомтесь із рішенням
|
||||
error.lookupPermissionMessage=Cryptomator може спробувати знайти спосіб розв'язання цієї проблеми онлайн. Це передбачає відправку запиту до нашої бази даних проблем з вашої ІР.
|
||||
error.lookupPermissionMessage=Cryptomator може спробувати знайти рішення для цієї проблеми онлайн. Це передбачає відправку запиту до нашої бази даних проблем з вашої IP-адреси.
|
||||
error.dismiss=Відхилити
|
||||
error.lookUpSolution=Пошук рішення
|
||||
|
||||
@@ -35,14 +35,14 @@ defaults.vault.vaultName=Сховище
|
||||
# Tray Menu
|
||||
traymenu.showMainWindow=Показати
|
||||
traymenu.showPreferencesWindow=Налаштування
|
||||
traymenu.lockAllVaults=Заблокувати все
|
||||
traymenu.lockAllVaults=Заблокувати всі
|
||||
traymenu.quitApplication=Вийти
|
||||
traymenu.vault.unlock=Розблокувати
|
||||
traymenu.vault.lock=Заблокувати
|
||||
traymenu.vault.reveal=Розгорнути вікно
|
||||
traymenu.vault.reveal=Відкрити диск
|
||||
|
||||
# Add Vault Wizard
|
||||
addvaultwizard.title=Додавання сховища
|
||||
addvaultwizard.title=Додати сховище
|
||||
## New
|
||||
addvaultwizard.new.title=Додавання нового сховища
|
||||
### Name
|
||||
@@ -50,68 +50,68 @@ addvaultwizard.new.nameInstruction=Оберіть назву для сховищ
|
||||
addvaultwizard.new.namePrompt=Назва сховища
|
||||
### Location
|
||||
addvaultwizard.new.locationInstruction=Де Cryptomator має зберігати зашифровані файли вашого сховища?
|
||||
addvaultwizard.new.locationLoading=Перевірка локальної файлової системи на предмет наявності папок клієнтів хмарних сховищ…
|
||||
addvaultwizard.new.locationLoading=Перевірка локальної файлової системи на наявність каталогів хмарних сховищ…
|
||||
addvaultwizard.new.locationLabel=Розташування сховища
|
||||
addvaultwizard.new.locationPrompt=…
|
||||
addvaultwizard.new.directoryPickerLabel=Власне розташування
|
||||
addvaultwizard.new.directoryPickerButton=Обрати…
|
||||
addvaultwizard.new.directoryPickerTitle=Обрати папку
|
||||
addvaultwizard.new.fileAlreadyExists=Файл чи папка з вказаним іменем сховища вже існує
|
||||
addvaultwizard.new.locationDoesNotExist=Папка недоступна за вказаною адресою або не існує
|
||||
addvaultwizard.new.locationIsNotWritable=Вказана адреса доступна лише для читання
|
||||
addvaultwizard.new.directoryPickerTitle=Обрати каталог
|
||||
addvaultwizard.new.fileAlreadyExists=Файл або каталог з такою назвою сховища вже існує
|
||||
addvaultwizard.new.locationDoesNotExist=Каталог за вказаним шляхом не існує або недоступний
|
||||
addvaultwizard.new.locationIsNotWritable=Немає дозволу на запис за вказаним шляхом
|
||||
addvaultwizard.new.locationIsOk=Це місце підходить для сховища
|
||||
addvaultwizard.new.invalidName=Недопустима назва сховища
|
||||
addvaultwizard.new.validName=Допустима назва сховища
|
||||
addvaultwizard.new.invalidName=Недійсна назва сховища
|
||||
addvaultwizard.new.validName=Дійсна назва сховища
|
||||
addvaultwizard.new.validCharacters.message=Назва сховища може містити такі символи:
|
||||
addvaultwizard.new.validCharacters.chars=Літери (напр. a, ж or 수)
|
||||
addvaultwizard.new.validCharacters.chars=Літери (напр. a, ж або 수)
|
||||
addvaultwizard.new.validCharacters.numbers=Цифри
|
||||
addvaultwizard.new.validCharacters.dashes=Дефіс (%s) або підкреслення (%s)
|
||||
### Expert Settings
|
||||
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Увімкнути експертні налаштування
|
||||
addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Введіть значення від 36 до 220 (типово 220)
|
||||
addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Введіть значення від 36 до 220 (за замовчуванням 220)
|
||||
addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Відкрийте документацію, щоб дізнатися більше.
|
||||
addvaultwizard.new.expertSettings.shorteningThreshold.title=Максимальна довжина зашифрованих імен файлів
|
||||
addvaultwizard.new.expertSettings.shorteningThreshold.title=Максимальна довжина зашифрованих назв файлів
|
||||
addvaultwizard.new.expertSettings.shorteningThreshold.valid=Вірний
|
||||
### Password
|
||||
addvaultwizard.new.createVaultBtn=Створити сховище
|
||||
addvaultwizard.new.generateRecoveryKeyChoice=Ви не зможете отримати доступ до своїх даних без пароля. Хочете створити ключ відновлення на випадок втрати паролю?
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Так, будь ласка, береженого Бог береже
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Так, будь ласка, краще перестрахуватися
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Ні, дякую, я не втрачу свій пароль
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=ВАЖЛИВО.rtf
|
||||
addvault.new.readme.storageLocation.1=⚠️ ФАЙЛИ СХОВИЩА ⚠️
|
||||
addvault.new.readme.storageLocation.2=Це місце, де зберігається ваше сховище.
|
||||
addvault.new.readme.storageLocation.3=НЕ
|
||||
addvault.new.readme.storageLocation.4=• змінюйте будь-які файли в цій папці, або
|
||||
addvault.new.readme.storageLocation.5=• додавайте безпосередньо до цієї папки будь-які файли для їх шифрування.
|
||||
addvault.new.readme.storageLocation.3=НЕ РОБІТЬ ЦЬОГО
|
||||
addvault.new.readme.storageLocation.4=• не змінюйте будь-які файли в цьому каталозі, та
|
||||
addvault.new.readme.storageLocation.5=• не додавайте безпосередньо до цього каталогу будь-які файли для їх шифрування.
|
||||
addvault.new.readme.storageLocation.6=Якщо ви хочете зашифрувати файли та переглянути вміст сховища, зробіть так:
|
||||
addvault.new.readme.storageLocation.7=1. Додайте це сховище (папку) у Cryptomator.
|
||||
addvault.new.readme.storageLocation.7=1. Додайте це сховище у Cryptomator.
|
||||
addvault.new.readme.storageLocation.8=2. Розблокуйте його через Cryptomator.
|
||||
addvault.new.readme.storageLocation.9=3. Отримайте доступ до вмісту сховища, натиснувши кнопку "Розгорнути".
|
||||
addvault.new.readme.storageLocation.10=Якщо вам потрібна допомога, почитайте документацію: %s
|
||||
addvault.new.readme.accessLocation.fileName=ПРОЧИТАЙ_МЕНЕ.rtf
|
||||
addvault.new.readme.storageLocation.9=3. Натисніть кнопку «Відкрити диск», щоб отримати доступ до вмісту сховища.
|
||||
addvault.new.readme.storageLocation.10=Якщо вам потрібна допомога, відвідайте документацію: %s
|
||||
addvault.new.readme.accessLocation.fileName=ПРИВІТ.rtf
|
||||
addvault.new.readme.accessLocation.1=🔐️ ЗАШИФРОВАНИЙ ТОМ 🔐️
|
||||
addvault.new.readme.accessLocation.2=Це місце розташування вашого сховища.
|
||||
addvault.new.readme.accessLocation.2=Це розташування для доступу до вашого сховища.
|
||||
addvault.new.readme.accessLocation.3=Будь-які файли, додані до цього тому, будуть зашифровані за допомогою Cryptomator. Ви можете працювати із ним як із будь-якою іншою директорією або накопичувачем. Це лише розшифрований вигляд вмісту сховища, ваші файли завжди знаходяться в зашифрованому вигляді на диску.
|
||||
addvault.new.readme.accessLocation.4=Цей файл можна видалити, якщо бажаєте.
|
||||
## Existing
|
||||
addvaultwizard.existing.title=Додати наявне сховище
|
||||
addvaultwizard.existing.instruction=Виберіть файл "vault.cryptomator" вашого наявного сховища. Якщо існує лише файл з назвою "masterkey.cryptomator", виберіть його.
|
||||
addvaultwizard.existing.instruction=Оберіть файл «vault.cryptomator» свого наявного сховища. Якщо існує лише файл з назвою «masterkey.cryptomator», оберіть його.
|
||||
addvaultwizard.existing.chooseBtn=Обрати…
|
||||
addvaultwizard.existing.filePickerTitle=Виберіть файл сховища
|
||||
addvaultwizard.existing.filePickerMimeDesc=Сховище Cryptomator
|
||||
## Success
|
||||
addvaultwizard.success.nextStepsInstructions=Додано сховище "%s".\nЦе сховище необхідно розблокувати для доступу або додавання вмісту. Але ви можете розблокувати його пізніше.
|
||||
addvaultwizard.success.nextStepsInstructions=Сховище «%s» додано.\nЩоб отримати доступ або додати вміст, вам потрібно розблокувати це сховище. Також ви можете розблокувати його пізніше в будь-який час.
|
||||
addvaultwizard.success.unlockNow=Розблокувати
|
||||
|
||||
# Remove Vault
|
||||
removeVault.title=Видалити "%s"
|
||||
removeVault.message=Видалити сховище?
|
||||
removeVault.description=Це лише змусить Cryptomator забути про це сховище. Його можна буде додати знову пізніше. Зашифровані файли на вашому жорсткому диску не буде втрачено.
|
||||
removeVault.title=Вилучити «%s»
|
||||
removeVault.message=Вилучити сховище?
|
||||
removeVault.description=Це лише змусить Cryptomator забути про сховище. Ви зможете додати його знову. Зашифровані файли не будуть видалені з вашого накопичувача.
|
||||
|
||||
# Change Password
|
||||
changepassword.title=Зміна паролю
|
||||
changepassword.enterOldPassword=Введіть поточний пароль для "%s"
|
||||
changepassword.enterOldPassword=Введіть поточний пароль для «%s»
|
||||
changepassword.finalConfirmation=Я розумію, що не зможу отримати доступ до даних, якщо забуду свій пароль
|
||||
|
||||
# Forget Password
|
||||
@@ -121,201 +121,202 @@ forgetPassword.description=Ця дія видалить збережений п
|
||||
forgetPassword.confirmBtn=Забути пароль
|
||||
|
||||
# Unlock
|
||||
unlock.title=Розблокування "%s"
|
||||
unlock.passwordPrompt=Введіть пароль для "%s":
|
||||
unlock.title=Розблокування «%s»
|
||||
unlock.passwordPrompt=Введіть пароль для «%s»:
|
||||
unlock.savePassword=Запам'ятати пароль
|
||||
unlock.unlockBtn=Розблокувати
|
||||
## Select
|
||||
unlock.chooseMasterkey.message=Файл майстер-ключа не знайдено
|
||||
unlock.chooseMasterkey.description=Cryptomator не зміг знайти файл майстер-ключа для сховища "%s". Будь ласка, виберіть ключ вручну.
|
||||
unlock.chooseMasterkey.description=Cryptomator не зміг знайти файл майстер-ключа для сховища «%s». Будь ласка, виберіть файл ключа вручну.
|
||||
unlock.chooseMasterkey.filePickerTitle=Виберіть файл майстер-ключа
|
||||
unlock.chooseMasterkey.filePickerMimeDesc=Майстер-ключ Cryptomator
|
||||
## Success
|
||||
unlock.success.message=Успішно розблоковано
|
||||
unlock.success.description=Вміст сховища "%s" тепер доступний через точку монтування.
|
||||
unlock.success.description=Вміст сховища «%s» тепер доступний через точку монтування.
|
||||
unlock.success.rememberChoice=Запам'ятайте мій вибір та більше не запитуйте
|
||||
unlock.success.revealBtn=Розгорнути диск
|
||||
unlock.success.revealBtn=Відкрити диск
|
||||
## Failure
|
||||
unlock.error.customPath.message=Не вдалося змонтувати сховище за вказаним шляхом
|
||||
unlock.error.customPath.description.notSupported=Якщо ви хочете надалі використовувати власний шлях, будь ласка, перейдіть до налаштувань та виберіть тип тому, що його підтримує. В іншому випадку перейдіть до параметрів сховища та оберіть точку монтування, що підтримується.
|
||||
unlock.error.customPath.description.notExists=Вказаний шлях монтування не існує. Створіть його в локальній файловій системі або змініть його в параметрах сховища.
|
||||
unlock.error.customPath.description.inUse=Зараз використовується літера або користувацький шлях монтування "%s".
|
||||
unlock.error.customPath.description.hideawayNotDir=Цей тимчасовий прихований файл "%3$s" використовується для розблокування і не може бути видалений. Будь ласка, перевірте файл та видаліть його вручну.
|
||||
unlock.error.customPath.description.couldNotBeCleaned=Ваше сховище неможливо змонтувати за шляхом "%s". Будь ласка, спробуйте знову або оберіть інший шлях.
|
||||
unlock.error.customPath.description.notEmptyDir=Користувацький шлях монтування "%s" не є порожньою папкою. Будь ласка, оберіть порожню папку і спробуйте ще раз.
|
||||
unlock.error.customPath.description.generic=Ви вибрали користувацький шлях монтування для цього сховища, але його використання призвело до помилки з текстом: %2$s
|
||||
unlock.error.customPath.description.inUse=Літера диска або власний шлях монтування «%s» уже використовується.
|
||||
unlock.error.customPath.description.hideawayNotDir=Тимчасовий прихований файл «%3$s», що використовувався для розблокування, не вдалося видалити. Будь ласка, перевірте файл, а потім видаліть його вручну.
|
||||
unlock.error.customPath.description.couldNotBeCleaned=Ваше сховище неможливо змонтувати за шляхом «%s». Будь ласка, спробуйте знову або оберіть інший шлях.
|
||||
unlock.error.customPath.description.notEmptyDir=Власний шлях монтування «%s» не є порожньою папкою. Будь ласка, оберіть порожню папку та спробуйте ще раз.
|
||||
unlock.error.customPath.description.generic=Ви обрали власний шлях монтування для цього сховища, але його використання не вдалося з таким повідомленням: %2$s
|
||||
unlock.error.restartRequired.message=Не вдалося розблокувати сховище
|
||||
unlock.error.restartRequired.description=Змініть тип носія в налаштуваннях сховища або перезапустіть Cryptomator.
|
||||
unlock.error.title=Розблокувати "%s" не вдалося
|
||||
unlock.error.restartRequired.description=Змініть тип тому в параметрах сховища або перезапустіть Cryptomator.
|
||||
unlock.error.title=Розблокувати «%s» не вдалося
|
||||
## Hub
|
||||
hub.noKeychain.message=Не вдалося отримати доступ до ключа пристрою
|
||||
hub.noKeychain.description=Щоб розблокувати сховища Hub, необхідний ключ пристрою, який захищено за допомогою зв'язки ключів. Щоб продовжити, увімкніть “%s” та виберіть зв'язку ключів у налаштуваннях.
|
||||
hub.noKeychain.description=Щоб розблокувати сховища Hub, необхідний ключ пристрою, який захищено за допомогою зв'язки ключів. Щоб продовжити, увімкніть «%s» та виберіть зв'язку ключів у налаштуваннях.
|
||||
hub.noKeychain.openBtn=Відкрити налаштування
|
||||
### Waiting
|
||||
hub.auth.message=Очікування завершення автентифікації…
|
||||
hub.auth.description=Вас буде автоматично переспрямовано на сторінку входу.
|
||||
hub.auth.loginLink=Не переспрямовано? Натисніть тут, щоб відкрити її.
|
||||
hub.auth.description=Вас буде автоматично перенаправлено на сторінку входу.
|
||||
hub.auth.loginLink=Не перенаправлено? Натисніть тут, щоб відкрити її.
|
||||
### Receive Key
|
||||
hub.receive.message=Обробка відповіді…
|
||||
hub.receive.description=Cryptomator отримує та опрацьовує відповідь від Hub. Будь ласка, зачекайте.
|
||||
### Register Device
|
||||
hub.register.message=Новий пристрій
|
||||
hub.register.description=Це перший доступ до Хабу Cryptomator з цього пристрою. Будь ласка, зареєструйтесь за допомогою вашого ключа облікового запису.
|
||||
hub.register.description=Це перший доступ до Hub з цього пристрою. Будь ласка, зареєструйтесь за допомогою вашого ключа облікового запису.
|
||||
hub.register.nameLabel=Назва пристрою
|
||||
hub.register.invalidAccountKeyLabel=Недійсний ключ облікового запису
|
||||
hub.register.registerBtn=Зареєструватись
|
||||
### Register Device Legacy
|
||||
hub.register.legacy.occupiedMsg=Таке ім’я уже існує
|
||||
hub.register.legacy.description=Це перший доступ до Хабу Cryptomator з цього пристрою. Будь ласка, зареєструйте його.
|
||||
hub.register.legacy.description=Це перший доступ до Hub з цього пристрою. Будь ласка, зареєструйте його.
|
||||
### Registration Success
|
||||
hub.registerSuccess.message=Пристрій зареєстровано
|
||||
hub.registerSuccess.description=Ваш пристрій успішно зареєстровано. Тепер ви можете продовжити розблокування сховища.
|
||||
hub.registerSuccess.unlockBtn=Розблокувати
|
||||
hub.registerSuccess.legacy.description=Щоб отримати доступ до сховища, ваш пристрій повинен бути додатково авторизованим власником сховища.
|
||||
hub.registerSuccess.legacy.description=Щоб отримати доступ до сховища, ваш пристрій має бути додатково авторизованим власником сховища.
|
||||
### Registration Failed
|
||||
hub.registerFailed.message=Помилка реєстрації пристрою
|
||||
hub.registerFailed.description.generic=Помилка виникла у процесі реєстрації. Для більш докладної інформації перегляньте журнал додатка.
|
||||
hub.registerFailed.description.deviceAlreadyExists=Цей пристрій вже зареєстровано для іншого користувача. Спробуйте змінити обліковий запис користувача або скористайтеся іншим пристроєм.
|
||||
hub.registerFailed.message=Не вдалося зареєструвати пристрій
|
||||
hub.registerFailed.description.generic=Під час реєстрації виникла помилка. Для отримання додаткової інформації перегляньте журнал програми.
|
||||
hub.registerFailed.description.deviceAlreadyExists=Цей пристрій вже зареєстровано для іншого користувача. Спробуйте змінити обліковий запис або скористайтеся іншим пристроєм.
|
||||
### Unauthorized
|
||||
hub.unauthorized.message=У доступі відмовлено
|
||||
hub.unauthorized.description=Ви не маєте права для відкриття цього сховища. Зверніться до власника сховища, щоб запитати доступ.
|
||||
hub.unauthorized.description=Ви не авторизовані для відкриття цього сховища. Зв'яжіться з його власником, щоб отримати доступ.
|
||||
### Requires Account Initialization
|
||||
hub.requireAccountInit.message=Необхідна дія
|
||||
hub.requireAccountInit.description.0=Для продовження, будь ласка, завершіть виконання обов'язкових кроків
|
||||
hub.requireAccountInit.description.1=Профіль користувача Хабу
|
||||
hub.requireAccountInit.description.0=Щоб продовжити, будь ласка, виконайте необхідні кроки у вашому
|
||||
hub.requireAccountInit.description.1=профілі користувача Hub
|
||||
hub.requireAccountInit.description.2=.
|
||||
### License Exceeded
|
||||
hub.invalidLicense.message=Недійсна ліцензія Hub
|
||||
hub.invalidLicense.description=У вашого Cryptomator Hub недійсна ліцензія. Будь ласка, зв'яжіться з адміністратором Hub, щоб оновити або продовжити дію ліцензії.
|
||||
hub.invalidLicense.description=Ваш екземпляр Cryptomator Hub має недійсну ліцензію. Будь ласка, повідомте адміністратора Hub для оновлення або поновлення ліцензії.
|
||||
|
||||
# Lock
|
||||
## Force
|
||||
lock.forced.message=Помилка блокування
|
||||
lock.forced.description=Блокуванню "%s" заважає виконання операцій або відкриті файли. Ви можете примусово заблокувати це сховище, однак переривання операцій чинання/запису може призвести до втрати незбережених даних.
|
||||
lock.forced.retryBtn=Повторити
|
||||
lock.forced.forceBtn=Примусове блокування
|
||||
lock.forced.description=Блокуванню «%s» заважає виконання операцій або відкриті файли. Ви можете примусово заблокувати це сховище, однак переривання операцій читання/запису може призвести до втрати незбережених даних.
|
||||
lock.forced.retryBtn=Повторити спробу
|
||||
lock.forced.forceBtn=Примусово заблокувати
|
||||
## Failure
|
||||
lock.fail.message=Не вдалося заблокувати сховище
|
||||
lock.fail.description=Сховище "%s" не можна заблокувати. Переконайтеся, що незавершену роботу збережено в іншому місці, а також важливі операції читання/запису закінчені. Щоб закрити сховище, завершіть процес Cryptomator.
|
||||
lock.fail.description=Не вдалося заблокувати сховище «%s». Переконайтеся, що незбережена робота збережена в іншому місці, а важливі операції читання/запису завершені. Щоб закрити сховище, завершіть процес Cryptomator.
|
||||
|
||||
# Migration
|
||||
migration.title=Покращення сховища
|
||||
## Start
|
||||
migration.start.header=Покращення сховища до наступної версії
|
||||
migration.start.text=Для того, щоб відкрити сховище "%s" у новій версії Cryptomator, воно має бути оновлене до новішого формату. Перед тим, як це зробити, ви повинні знати таке:
|
||||
migration.start.text=Для того, щоб відкрити сховище «%s» у новій версії Cryptomator, воно має бути оновлене до новішого формату. Перед тим, як це зробити, ви повинні знати таке:
|
||||
migration.start.remarkUndone=Це оновлення не може бути скасовано.
|
||||
migration.start.remarkVersions=Старіші версії Cryptomator не зможуть відкрити оновлене сховище.
|
||||
migration.start.remarkCanRun=Ви повинні бути впевнені, що кожен пристрій, з якого ви маєте доступ до сховища, підтримує цю версію Cryptomator.
|
||||
migration.start.remarkSynced=Ви маєте бути впевнені, що ваше сховище повністю синхронізовано на цьому пристрої та на інших ваших пристроях, перш ніж оновити його.
|
||||
migration.start.confirm=Я прочитав та зрозумів вищевказану інформацію
|
||||
migration.start.confirm=Я прочитав(ла) і зрозумів(ла) наведену вище інформацію
|
||||
## Run
|
||||
migration.run.enterPassword=Введіть пароль для "%s"
|
||||
migration.run.startMigrationBtn=Розпочати міграцію
|
||||
migration.run.enterPassword=Введіть пароль для «%s»
|
||||
migration.run.startMigrationBtn=Розпочати оновлення
|
||||
migration.run.progressHint=Це може зайняти деякий час…
|
||||
## Success
|
||||
migration.success.nextStepsInstructions=Міграцію "%s" успішно завершено.\nТепер ви можете розблокувати ваше сховище.
|
||||
migration.success.nextStepsInstructions=Оновлення «%s» успішно завершено.\nТепер ви можете розблокувати ваше сховище.
|
||||
migration.success.unlockNow=Розблокувати
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Файлова система не підтримується
|
||||
migration.error.missingFileSystemCapabilities.description=Перенесення не було розпочато, оскільки ваше сховище знаходиться в несумісній файловій системі.
|
||||
migration.error.missingFileSystemCapabilities.description=Оновлення не було розпочато, оскільки ваше сховище знаходиться в несумісній файловій системі.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Файлова система не підтримує довгі назви файлів.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Файлова система не підтримує довгі шляхи.
|
||||
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Файлова система не дозволяє зчитувати дані.
|
||||
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Файлова система не дозволяє записувати дані.
|
||||
## Impossible
|
||||
migration.impossible.heading=Не вдалося перенести сховище
|
||||
migration.impossible.reason=Сховище не можливо автоматично перенести через його розташуванням на диску або несумісність точки доступу.
|
||||
migration.impossible.moreInfo=Сховище все ще може бути відкрите старішою версією. Для інструкцій, як вручну перенести сховище, перейдіть
|
||||
migration.impossible.heading=Не вдалося оновити сховище
|
||||
migration.impossible.reason=Сховище не можливо автоматично оновити через його розташування на диску або несумісність точки доступу.
|
||||
migration.impossible.moreInfo=Сховище все ще можна відкрити старішою версією. Для інструкцій, як вручну оновити сховище, відвідайте
|
||||
|
||||
# Health Check
|
||||
## Start
|
||||
health.title=Перевірка працездатності "%s"
|
||||
health.intro.header=Перевірка працездатності
|
||||
health.intro.text=Перевірка працездатності передбачає ряд тестів для виявлення та за можливості виправлення проблем у внутрішній структурі вашого сховища. Будь ласка, пам'ятайте:
|
||||
health.title=Перевірка стану «%s»
|
||||
health.intro.header=Перевірка стану
|
||||
health.intro.text=«Перевірка стану» — це набір перевірок для виявлення та, можливо, виправлення проблем у внутрішній структурі вашого сховища. Будь ласка, врахуйте:
|
||||
health.intro.remarkSync=Переконайтеся, що всі пристрої повністю синхронізовані, це вирішує більшість проблем.
|
||||
health.intro.remarkFix=Не всі проблеми можна виправити.
|
||||
health.intro.remarkBackup=Якщо дані пошкоджено, то може допомогти тільки резервна копія.
|
||||
health.intro.affirmation=Я прочитав та зрозумів вищевказану інформацію
|
||||
health.intro.remarkBackup=Якщо дані пошкоджено, допоможе лише резервна копія.
|
||||
health.intro.affirmation=Я прочитав(ла) і зрозумів(ла) наведену вище інформацію
|
||||
## Start Failure
|
||||
health.fail.header=Помилка при завантаженні конфігурації сховища
|
||||
health.fail.ioError=Сталася помилка під час спроби доступу до файлу конфігурації.
|
||||
health.fail.parseError=Сталася помилка під час опрацювання конфігурації сховища.
|
||||
health.fail.ioError=Під час доступу та читання файлу конфігурації сталася помилка.
|
||||
health.fail.parseError=Під час аналізу конфігурації сховища сталася помилка.
|
||||
health.fail.moreInfo=Більше інформації
|
||||
## Check Selection
|
||||
health.checkList.description=Оберіть прапорець зі списку ліворуч або використовуйте кнопки нижче.
|
||||
health.checkList.description=Виберіть перевірки зі списку ліворуч або скористайтеся кнопками нижче.
|
||||
health.checkList.selectAllButton=Вибрати всі перевірки
|
||||
health.checkList.deselectAllButton=Зняти вибір з усіх перевірок
|
||||
health.check.runBatchBtn=Розпочати обрані перевірки
|
||||
## Detail view
|
||||
health.check.detail.noSelectedCheck=Для перегляду результатів оберіть завершену перевірку працездатності зі списку ліворуч.
|
||||
health.check.detail.noSelectedCheck=Щоб переглянути результати, виберіть завершену перевірку стану зі списку ліворуч.
|
||||
health.check.detail.checkScheduled=Перевірку заплановано.
|
||||
health.check.detail.checkRunning=Перевірка триває…
|
||||
health.check.detail.checkSkipped=Перевірку не вибрано для запуску.
|
||||
health.check.detail.checkSkipped=Цю перевірку не було обрано для запуску.
|
||||
health.check.detail.checkFinished=Перевірку завершено успішно.
|
||||
health.check.detail.checkFinishedAndFound=Перевірку завершено. Будь ласка, ознайомтесь з результатами.
|
||||
health.check.detail.checkFailed=Перевірка завершилась через помилку.
|
||||
health.check.detail.checkFailed=Перевірку перервано через помилку.
|
||||
health.check.detail.checkCancelled=Перевірку було скасовано.
|
||||
health.check.detail.listFilters.label=Фільтр
|
||||
health.check.detail.fixAllSpecificBtn=Виправити все
|
||||
health.check.detail.fixAllSpecificBtn=Виправити всі цього типу
|
||||
health.check.exportBtn=Експортувати звіт
|
||||
## Result view
|
||||
health.result.severityFilter.all=Рівень критичності - Усі
|
||||
health.result.severityFilter.all=Ступінь серйозності — Усі
|
||||
health.result.severityFilter.good=В нормі
|
||||
health.result.severityFilter.info=Інформація
|
||||
health.result.severityFilter.warn=Попередження
|
||||
health.result.severityFilter.crit=Критично
|
||||
health.result.severityTip.good=Рівень критичності: в нормі\nЗвичайна структура сховища.
|
||||
health.result.severityTip.info=Рівень критичності: інформація\nСтруктура сховища цілісна, виправлення рекомендовані.
|
||||
health.result.severityTip.warn=Рівень критичності: попередження\nСтруктуру сховища пошкоджено, це слід виправити.
|
||||
health.result.severityTip.crit=Рівень критичності: критичний\nСтруктуру сховища пошкоджено, виявлено втрату даних.
|
||||
health.result.fixStateFilter.all=Статус виправлення - Всі
|
||||
health.result.fixStateFilter.fixable=Поправні
|
||||
health.result.fixStateFilter.notFixable=Не поправні
|
||||
health.result.severityTip.good=Ступінь серйозності: В нормі.\nЗвичайна структура сховища.
|
||||
health.result.severityTip.info=Ступінь серйозності: Інформація.\nСтруктура сховища ціла, запропоновано виправлення.
|
||||
health.result.severityTip.warn=Ступінь серйозності: Попередження.\nСтруктура сховища пошкоджена, наполегливо рекомендуємо виправити.
|
||||
health.result.severityTip.crit=Ступінь серйозності: Критичний.\nСтруктура сховища пошкоджена, виявлено втрату даних.
|
||||
health.result.fixStateFilter.all=Стан виправлення — Усі
|
||||
health.result.fixStateFilter.fixable=Можна виправити
|
||||
health.result.fixStateFilter.notFixable=Неможливо виправити
|
||||
health.result.fixStateFilter.fixing=Виправляються…
|
||||
health.result.fixStateFilter.fixed=Виправлені
|
||||
health.result.fixStateFilter.fixFailed=Невдале виправлення
|
||||
health.result.fixStateFilter.fixed=Виправлено
|
||||
health.result.fixStateFilter.fixFailed=Не вдалося виправити
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Виправити
|
||||
health.fix.successTip=Виправлення успішне
|
||||
health.fix.failTip=Виправити не вдалось, перегляньте журнал для ознайомлення з подробицями
|
||||
health.fix.successTip=Успішно виправлено
|
||||
health.fix.failTip=Не вдалося виправити, перегляньте журнал для отримання подробиць
|
||||
|
||||
# Preferences
|
||||
preferences.title=Налаштування
|
||||
## General
|
||||
preferences.general=Загальні
|
||||
preferences.general.startHidden=Приховати вікно під час запуску Cryptomator
|
||||
preferences.general.startHidden=Приховувати вікно під час запуску Cryptomator
|
||||
preferences.general.autoCloseVaults=Блокувати сховища без запиту при виході з програми
|
||||
preferences.general.debugLogging=Увімкнути ведення журналу налагодження
|
||||
preferences.general.debugDirectory=Показати файли журналу
|
||||
preferences.general.autoStart=Запускати Cryptomator під час запуску системи
|
||||
preferences.general.keychainBackend=Зберігати паролі за допомогою
|
||||
preferences.general.quickAccessService=Додати до області швидкого доступу розблоковані сховища
|
||||
preferences.general.quickAccessService=Додавати розблоковані сховища до області швидкого доступу
|
||||
## Interface
|
||||
preferences.interface=Вигляд
|
||||
preferences.interface.theme=Колір і стиль
|
||||
preferences.interface.theme=Вигляд і поведінка
|
||||
preferences.interface.theme.automatic=Автоматично
|
||||
preferences.interface.theme.dark=Темний
|
||||
preferences.interface.theme.light=Світлий
|
||||
preferences.interface.unlockThemes=Розблокувати темний режим
|
||||
preferences.interface.language=Мова (потребує перезавантаження)
|
||||
preferences.interface.language=Мова (потрібен перезапуск)
|
||||
preferences.interface.language.auto=Мова системи
|
||||
preferences.interface.interfaceOrientation=Відображення елементів
|
||||
preferences.interface.interfaceOrientation=Орієнтація інтерфейсу
|
||||
preferences.interface.interfaceOrientation.ltr=Зліва направо
|
||||
preferences.interface.interfaceOrientation.rtl=Справа наліво
|
||||
preferences.interface.showTrayIcon=Показувати піктограму на панелі завдань (потрібен перезапуск)
|
||||
preferences.interface.showTrayIcon=Показувати іконку в треї (потрібен перезапуск)
|
||||
preferences.interface.compactMode=Активувати компактний список сховищ
|
||||
## Volume
|
||||
preferences.volume=Віртуальний диск
|
||||
preferences.volume.type=Тип тому за замовчуванням
|
||||
preferences.volume.type.automatic=Автоматично
|
||||
preferences.volume.docsTooltip=Відкрийте документацію, щоб дізнатися більше про різні типи томів.
|
||||
preferences.volume.fuseRestartRequired=Слід перезапустити Cryptomator, щоб зміни набули чинності.
|
||||
preferences.volume.fuseRestartRequired=Щоб застосувати зміни, Cryptomator потрібно перезапустити.
|
||||
preferences.volume.tcp.port=TCP порт за замовчуванням
|
||||
preferences.volume.supportedFeatures=Обраний тип тому підтримує такі функції:
|
||||
preferences.volume.supportedFeatures=Обраний тип тому підтримує такі можливості:
|
||||
preferences.volume.feature.mountAuto=Автоматичний вибір точки монтування
|
||||
preferences.volume.feature.mountToDir=Користувацька папка як точка монтування
|
||||
preferences.volume.feature.mountToDriveLetter=Точка монтування у вигляді букви диска
|
||||
preferences.volume.feature.mountFlags=Користувацькі параметри монтування
|
||||
preferences.volume.feature.readOnly=Монтування в режимі "лише для читання"
|
||||
preferences.volume.feature.mountToDir=Власний каталог як точка монтування
|
||||
preferences.volume.feature.mountToDriveLetter=Літера диска як точка монтування
|
||||
preferences.volume.feature.mountFlags=Власні параметри монтування
|
||||
preferences.volume.feature.readOnly=Монтування лише для читання
|
||||
## Updates
|
||||
preferences.updates=Оновлення
|
||||
preferences.updates.currentVersion=Поточна версія: %s
|
||||
@@ -326,24 +327,24 @@ preferences.updates.lastUpdateCheck=Остання перевірка: %s
|
||||
preferences.updates.lastUpdateCheck.never=ніколи
|
||||
preferences.updates.lastUpdateCheck.recently=нещодавно
|
||||
preferences.updates.lastUpdateCheck.daysAgo=%s дні(в) тому
|
||||
preferences.updates.lastUpdateCheck.hoursAgo=%s годин тому
|
||||
preferences.updates.lastUpdateCheck.hoursAgo=%s годин(и) тому
|
||||
preferences.updates.checkFailed=Не вдалось перевірити наявність оновлень. Будь ласка, перевірте підключення до Інтернету або спробуйте ще раз пізніше.
|
||||
preferences.updates.upToDate=Ваш Cryptomator не потребує оновлення.
|
||||
|
||||
## Contribution
|
||||
preferences.contribute=Підтримайте нас
|
||||
preferences.contribute.registeredFor=Сертифікат помічника зареєстровано для %s
|
||||
preferences.contribute.noCertificate=Підтримайте Cryptomator та отримайте сертифікат помічника - це як ліцензійний ключ, але для чудових людей, які користуються безкоштовним програмним забезпеченням. ;-)
|
||||
preferences.contribute.noCertificate=Підтримайте Cryptomator та отримайте сертифікат помічника. Це як ліцензійний ключ, але для чудових людей, які користуються безплатним програмним забезпеченням. ;-)
|
||||
preferences.contribute.getCertificate=Ще немає такого? Дізнайтеся, як його отримати.
|
||||
preferences.contribute.promptText=Вставте код сертифікату помічника тут
|
||||
preferences.contribute.thankYou=Дякуємо, що підтримуєте розробку Cryptomator з відкритим вихідним кодом!
|
||||
preferences.contribute.donate=Підтримати
|
||||
preferences.contribute.sponsor=Спонсор
|
||||
preferences.contribute.donate=Зробити пожертву
|
||||
preferences.contribute.sponsor=Спонсори
|
||||
|
||||
### Remove License Key Dialog
|
||||
removeCert.title=Видалити сертифікат
|
||||
removeCert.message=Видалити сертифікат помічника?
|
||||
removeCert.description=Основний функціонал Cryptomator не змінюється. Ні доступ до вашого сховища, а ні рівень безпеки не буде понижено/обмежено.
|
||||
removeCert.title=Вилучити сертифікат
|
||||
removeCert.message=Вилучити сертифікат помічника?
|
||||
removeCert.description=Основні функції Cryptomator не будуть зачеплені. Ані доступ до ваших сховищ не буде обмежено, ані рівень безпеки не буде знижено.
|
||||
#<-- Add entries for donations and code/translation/documentation contribution -->
|
||||
|
||||
## About
|
||||
@@ -351,33 +352,33 @@ preferences.about=Про додаток
|
||||
|
||||
# Vault Statistics
|
||||
stats.title=Статистика %s
|
||||
stats.cacheHitRate=Відсоток потрапляння до кешу
|
||||
stats.cacheHitRate=Коефіцієнт влучань кеша
|
||||
## Read
|
||||
stats.read.throughput.idle=Читання: очікування
|
||||
stats.read.throughput.kibs=Зчитування: %.2f КіБ/с
|
||||
stats.read.throughput.mibs=Зчитування: %.2f МіБ/с
|
||||
stats.read.total.data.none=Зчитано даних: -
|
||||
stats.read.total.data.kib=Зчитано даних: %.1f Кб
|
||||
stats.read.total.data.mib=Зчитано даних: %.1f Мб
|
||||
stats.read.total.data.gib=Зчитано даних: %.1f Гб
|
||||
stats.decr.total.data.none=Розшифровано даних: -
|
||||
stats.decr.total.data.kib=Розшифровано даних: %.1f Кб
|
||||
stats.decr.total.data.mib=Розшифровано даних: %.1f Мб
|
||||
stats.decr.total.data.gib=Розшифровано даних: %.1f Гб
|
||||
stats.read.accessCount=Всього зчитувань: %d
|
||||
stats.read.throughput.idle=Читання: простоює
|
||||
stats.read.throughput.kibs=Читання: %.2f КіБ/с
|
||||
stats.read.throughput.mibs=Читання: %.2f МіБ/с
|
||||
stats.read.total.data.none=Прочитано даних: —
|
||||
stats.read.total.data.kib=Прочитано даних: %.1f КіБ
|
||||
stats.read.total.data.mib=Прочитано даних: %.1f МіБ
|
||||
stats.read.total.data.gib=Прочитано даних: %.1f ГіБ
|
||||
stats.decr.total.data.none=Розшифровано даних: —
|
||||
stats.decr.total.data.kib=Розшифровано даних: %.1f КіБ
|
||||
stats.decr.total.data.mib=Розшифровано даних: %.1f МіБ
|
||||
stats.decr.total.data.gib=Розшифровано даних: %.1f ГіБ
|
||||
stats.read.accessCount=Усього прочитань: %d
|
||||
## Write
|
||||
stats.write.throughput.idle=Запис: очікування
|
||||
stats.write.throughput.idle=Запис: простоює
|
||||
stats.write.throughput.kibs=Запис: %.2f KіБ/с
|
||||
stats.write.throughput.mibs=Запис: %.2f MіБ/с
|
||||
stats.write.total.data.none=Записано даних: -
|
||||
stats.write.total.data.kib=Записано даних: %.1f Кб
|
||||
stats.write.total.data.mib=Записано даних: %.1f Мб
|
||||
stats.write.total.data.gib=Записано даних: %.1f Гб
|
||||
stats.encr.total.data.none=Зашифровано даних: -
|
||||
stats.encr.total.data.kib=Зашифровано даних: %.1f Кб
|
||||
stats.encr.total.data.mib=Зашифровано даних: %.1f Мб
|
||||
stats.encr.total.data.gib=Зашифровано даних: %.1f Гб
|
||||
stats.write.accessCount=Всього записувань: %d
|
||||
stats.write.total.data.none=Записано даних: —
|
||||
stats.write.total.data.kib=Записано даних: %.1f КіБ
|
||||
stats.write.total.data.mib=Записано даних: %.1f МіБ
|
||||
stats.write.total.data.gib=Записано даних: %.1f ГіБ
|
||||
stats.encr.total.data.none=Зашифровано даних: —
|
||||
stats.encr.total.data.kib=Зашифровано даних: %.1f КіБ
|
||||
stats.encr.total.data.mib=Зашифровано даних: %.1f МіБ
|
||||
stats.encr.total.data.gib=Зашифровано даних: %.1f ГіБ
|
||||
stats.write.accessCount=Усього записів: %d
|
||||
|
||||
## Accesses
|
||||
stats.access.current=Отримано доступ: %d
|
||||
@@ -387,42 +388,47 @@ stats.access.total=Усього отримано доступ: %d
|
||||
# Main Window
|
||||
## Vault List
|
||||
main.vaultlist.emptyList.onboardingInstruction=Натисніть тут, щоб додати сховище
|
||||
main.vaultlist.contextMenu.remove=Видалити…
|
||||
main.vaultlist.contextMenu.remove=Вилучити…
|
||||
main.vaultlist.contextMenu.lock=Заблокувати
|
||||
main.vaultlist.contextMenu.unlock=Розблокувати…
|
||||
main.vaultlist.contextMenu.unlockNow=Розблокувати
|
||||
main.vaultlist.contextMenu.vaultoptions=Показати параметри сховища
|
||||
main.vaultlist.contextMenu.reveal=Розгорнути диск
|
||||
main.vaultlist.contextMenu.reveal=Відкрити диск
|
||||
main.vaultlist.contextMenu.share=Поділитися…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Створити нове сховище...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Відкрити наявне сховище...
|
||||
main.vaultlist.showEventsButton.tooltip=Відкрити журнал подій
|
||||
##Notificaition
|
||||
main.notification.updateAvailable=Оновлення доступне.
|
||||
main.notification.updateAvailable=Доступне оновлення.
|
||||
main.notification.support=Підтримайте Cryptomator.
|
||||
## Vault Detail
|
||||
### Welcome
|
||||
main.vaultDetail.welcomeOnboarding=Дякуємо, що обрали Cryptomator для захисту ваших файлів. Якщо вам потрібна допомога, перегляньте наші інструкції з роботи:
|
||||
main.vaultDetail.welcomeOnboarding=Дякуємо, що обрали Cryptomator для захисту ваших файлів. Якщо вам потрібна допомога, ознайомтеся з нашими посібниками для початку роботи:
|
||||
### Locked
|
||||
main.vaultDetail.lockedStatus=ЗАБЛОКОВАНО
|
||||
main.vaultDetail.unlockBtn=Розблокувати…
|
||||
main.vaultDetail.unlockNowBtn=Розблокувати
|
||||
main.vaultDetail.optionsBtn=Налаштування сховища
|
||||
main.vaultDetail.optionsBtn=Параметри сховища
|
||||
main.vaultDetail.passwordSavedInKeychain=Пароль збережено
|
||||
main.vaultDetail.share=Поділитися…
|
||||
### Unlocked
|
||||
main.vaultDetail.unlockedStatus=РОЗБЛОКОВАНО
|
||||
main.vaultDetail.accessLocation=Щоб отримати доступ до вмісту сховища - натисніть кнопку:
|
||||
main.vaultDetail.revealBtn=Розгорнути диск
|
||||
main.vaultDetail.copyUri=Скопіювати адресу
|
||||
main.vaultDetail.accessLocation=Вміст вашого сховища доступний тут:
|
||||
main.vaultDetail.revealBtn=Відкрити диск
|
||||
main.vaultDetail.copyUri=Копіювати URI
|
||||
main.vaultDetail.lockBtn=Заблокувати
|
||||
main.vaultDetail.bytesPerSecondRead=Зчитування:
|
||||
main.vaultDetail.bytesPerSecondRead=Читання:
|
||||
main.vaultDetail.bytesPerSecondWritten=Запис:
|
||||
main.vaultDetail.throughput.idle=простоювання
|
||||
main.vaultDetail.throughput.idle=простоює
|
||||
main.vaultDetail.throughput.kbps=%.1f КіБ/c
|
||||
main.vaultDetail.throughput.mbps=%.1f МіБ/c
|
||||
main.vaultDetail.stats=Статистика сховища
|
||||
main.vaultDetail.locateEncryptedFileBtn=Знайти зашифрований файл
|
||||
main.vaultDetail.locateEncryptedFileBtn.tooltip=Виберіть файл із вашого сховища, щоб знайти розташування його зашифрованого відповідника
|
||||
main.vaultDetail.locateEncryptedFileBtn.tooltip=Виберіть файл зі сховища, щоб знайти його зашифрований відповідник
|
||||
main.vaultDetail.encryptedPathsCopied=Шляхи скопійовано в буфер обміну!
|
||||
main.vaultDetail.locateEncrypted.filePickerTitle=Виберіть файл у сховищі
|
||||
main.vaultDetail.decryptName.buttonLabel=Розшифрувати назву файлу
|
||||
main.vaultDetail.decryptName.tooltip=Виберіть зашифрований файл сховища, щоб розшифрувати його назву
|
||||
### Missing
|
||||
main.vaultDetail.missing.info=Cryptomator не зміг знайти сховище за цим шляхом.
|
||||
main.vaultDetail.missing.recheck=Перевірити знову
|
||||
@@ -430,7 +436,7 @@ main.vaultDetail.missing.remove=Вилучити зі списку сховищ
|
||||
main.vaultDetail.missing.changeLocation=Змінити розташування сховища…
|
||||
### Needs Migration
|
||||
main.vaultDetail.migrateButton=Покращити сховище
|
||||
main.vaultDetail.migratePrompt=Ваше сховище слід покращити (перетворити) у новий формат, перш ніж ви зможете отримати до нього доступ
|
||||
main.vaultDetail.migratePrompt=Ваше сховище потрібно оновити до нового формату, перш ніж ви зможете отримати до нього доступ
|
||||
### Error
|
||||
main.vaultDetail.error.info=Виникла помилка під час завантаження сховища з диска.
|
||||
main.vaultDetail.error.reload=Перезавантажити
|
||||
@@ -439,10 +445,10 @@ main.vaultDetail.error.windowTitle=Помилка завантаження сх
|
||||
# Wrong File Alert
|
||||
wrongFileAlert.title=Як зашифрувати файли
|
||||
wrongFileAlert.message=Ви намагалися зашифрувати ці файли?
|
||||
wrongFileAlert.description=Для цього Cryptomator додає том у ваш системний файловий менеджер.
|
||||
wrongFileAlert.description=Для цього Cryptomator надає том у системному файловому менеджері.
|
||||
wrongFileAlert.instruction.0=Щоб зашифрувати файли, виконайте такі кроки:
|
||||
wrongFileAlert.instruction.1=1. Розблокуйте ваше сховище.
|
||||
wrongFileAlert.instruction.2=2. Натисніть "Розгорнути", щоб відкрити том у вашому файловому менеджері.
|
||||
wrongFileAlert.instruction.2=2. Натисніть «Відкрити диск», щоб відкрити том у вашому файловому менеджері.
|
||||
wrongFileAlert.instruction.3=3. Додайте ваші файли до цього тому.
|
||||
wrongFileAlert.link=Для отримання подальшої допомоги, відвідайте
|
||||
|
||||
@@ -450,27 +456,27 @@ wrongFileAlert.link=Для отримання подальшої допомог
|
||||
## General
|
||||
vaultOptions.general=Загальні
|
||||
vaultOptions.general.vaultName=Назва сховища
|
||||
vaultOptions.general.autoLock.lockAfterTimePart1=Заблокувати коли не використовується протягом
|
||||
vaultOptions.general.autoLock.lockAfterTimePart1=Блокувати після простою протягом
|
||||
vaultOptions.general.autoLock.lockAfterTimePart2=хвилин
|
||||
vaultOptions.general.unlockAfterStartup=Розблоковувати сховище під час запуску Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=Після успішного розблокування
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Нічого не робити
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Розгорнути диск
|
||||
vaultOptions.general.actionAfterUnlock.ask=Запитати
|
||||
vaultOptions.general.startHealthCheckBtn=Розпочати перевірку працездатності
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Відкривати диск
|
||||
vaultOptions.general.actionAfterUnlock.ask=Запитувати
|
||||
vaultOptions.general.startHealthCheckBtn=Розпочати перевірку стану
|
||||
|
||||
## Mount
|
||||
vaultOptions.mount=Монтування
|
||||
vaultOptions.mount.info=Відкрийте вибір віртуального носія, щоб змінити налаштування за замовчуванням.
|
||||
vaultOptions.mount.info=Відкрийте налаштування віртуального диска, щоб змінити параметри за замовчуванням.
|
||||
vaultOptions.mount.readonly=Тільки для перегляду
|
||||
vaultOptions.mount.customMountFlags=Користувацькі опції монтування
|
||||
vaultOptions.mount.customMountFlags=Власні прапори монтування
|
||||
vaultOptions.mount.winDriveLetterOccupied=зайнято
|
||||
vaultOptions.mount.mountPoint=Точка монтування
|
||||
vaultOptions.mount.mountPoint.auto=Автоматично підбирати зручне розташування
|
||||
vaultOptions.mount.mountPoint.driveLetter=Використовувати призначену літеру для диска
|
||||
vaultOptions.mount.mountPoint.custom=Використовувати обрану папку
|
||||
vaultOptions.mount.mountPoint.auto=Автоматично вибрати відповідне розташування
|
||||
vaultOptions.mount.mountPoint.driveLetter=Використовувати призначену літеру диска
|
||||
vaultOptions.mount.mountPoint.custom=Використовувати обраний каталог
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Обрати…
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Виберіть папку
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Виберіть каталог
|
||||
vaultOptions.mount.volumeType.default=За замовчуванням (%s)
|
||||
vaultOptions.mount.volumeType.restartRequired=Щоб використовувати цей тип тому, необхідно перезапустити Cryptomator.
|
||||
vaultOptions.mount.volume.tcp.port=TCP порт
|
||||
@@ -479,107 +485,140 @@ vaultOptions.mount.volume.type=Тип тому
|
||||
vaultOptions.masterkey=Пароль
|
||||
vaultOptions.masterkey.changePasswordBtn=Змінити пароль
|
||||
vaultOptions.masterkey.forgetSavedPasswordBtn=Забути збережений пароль
|
||||
vaultOptions.masterkey.recoveryKeyExplanation=У разі втрати пароля, лише ключ відновлення залишиться єдиним способом отримання доступу до сховища.
|
||||
vaultOptions.masterkey.recoveryKeyExplanation=Ключ відновлення — це ваш єдиний спосіб відновити доступ до сховища, якщо ви втратите пароль.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Показати ключ відновлення
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Скинути пароль
|
||||
## Hub
|
||||
vaultOptions.hub=Відновлення
|
||||
vaultOptions.hub.convertInfo=Ви можете використати ключ відновлення, щоб перетворити це сховище Hub у сховище на основі пароля в разі крайньої потреби.
|
||||
vaultOptions.hub.convertBtn=Перетворити на сховище з паролем
|
||||
vaultOptions.hub.convertInfo=Ви можете використати ключ відновлення, щоб у надзвичайній ситуації перетворити це Hub-сховище на сховище, захищене паролем.
|
||||
vaultOptions.hub.convertBtn=Перетворити на сховище, захищене паролем
|
||||
|
||||
# Recovery Key
|
||||
## Display Recovery Key
|
||||
recoveryKey.display.title=Показати ключ відновлення
|
||||
recoveryKey.create.message=Потрібен пароль
|
||||
recoveryKey.create.description=Введіть пароль для "%s", щоб показати його ключ відновлення.
|
||||
recoveryKey.display.description=Цей ключ можна використати для відновлення доступу до "%s":
|
||||
recoveryKey.display.StorageHints=Зберігайте його у дуже безпечному місці, наприклад:\n • в менеджері паролів\n • на захищеному USB-носії\n • роздрукуйте на папері та зберігайте у недоступному для інших місці
|
||||
recoveryKey.create.description=Введіть пароль для «%s», щоб показати його ключ відновлення.
|
||||
recoveryKey.display.description=Зазначений ключ відновлення можна використовувати для відновлення доступу до «%s»:
|
||||
recoveryKey.display.StorageHints=Зберігайте його в дуже надійному місці, наприклад:\n • Збережіть його за допомогою менеджера паролів\n • Збережіть його на USB-накопичувачі\n • Роздрукуйте його на папері
|
||||
## Reset Password
|
||||
### Enter Recovery Key
|
||||
recoveryKey.recover.title=Скинути пароль
|
||||
recoveryKey.recover.prompt=Введіть ключ відновлення для "%s:
|
||||
recoveryKey.recover.correctKey=Цей ключ відновлення є правильним
|
||||
recoveryKey.recover.wrongKey=Цей ключ відновлення належить до іншого сховища
|
||||
recoveryKey.recover.prompt=Введіть ключ відновлення для «%s»:
|
||||
recoveryKey.recover.correctKey=Цей ключ відновлення правильний
|
||||
recoveryKey.recover.wrongKey=Цей ключ відновлення належить іншому сховищу
|
||||
recoveryKey.recover.invalidKey=Невірний ключ відновлення
|
||||
recoveryKey.printout.heading=Ключ відновлення Cryptomator\n"%s"\n
|
||||
recoveryKey.printout.heading=Ключ відновлення Cryptomator\n«%s»\n
|
||||
### Reset Password
|
||||
recoveryKey.recover.resetBtn=Скинути
|
||||
### Recovery Key Password Reset Success
|
||||
recoveryKey.recover.resetSuccess.message=Пароль скинуто успішно
|
||||
recoveryKey.recover.resetSuccess.message=Пароль успішно скинуто
|
||||
recoveryKey.recover.resetSuccess.description=Ви можете розблокувати своє сховище за допомогою нового пароля.
|
||||
|
||||
# Convert Vault
|
||||
convertVault.title=Перетворити сховище
|
||||
convertVault.convert.convertBtn.before=Перетворити
|
||||
convertVault.convert.convertBtn.processing=Перетворення…
|
||||
convertVault.success.message=Перетворення виконано успішно
|
||||
convertVault.hubToPassword.success.description=Тепер ви можете розблокувати сховище за допомогою обраного пароля без необхідності доступу до Hub.
|
||||
convertVault.success.message=Перетворення успішно виконано
|
||||
convertVault.hubToPassword.success.description=Тепер ви можете розблокувати сховище за допомогою обраного пароля, не вимагаючи доступу до Hub.
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Введіть новий пароль
|
||||
newPassword.reenterPassword=Підтвердіть новий пароль
|
||||
newPassword.passwordsMatch=Паролі збігаються!
|
||||
newPassword.passwordsDoNotMatch=Паролі не збігаються
|
||||
passwordStrength.messageLabel.tooShort=Введіть принаймні %d символів
|
||||
passwordStrength.messageLabel.tooShort=Використайте щонайменше %d символів
|
||||
passwordStrength.messageLabel.0=Дуже слабкий
|
||||
passwordStrength.messageLabel.1=Слабкий
|
||||
passwordStrength.messageLabel.2=Посередній
|
||||
passwordStrength.messageLabel.2=Задовільний
|
||||
passwordStrength.messageLabel.3=Надійний
|
||||
passwordStrength.messageLabel.4=Дуже надiйний
|
||||
|
||||
# Quit
|
||||
quit.title=Вийти з додатка
|
||||
quit.message=Є розблоковані сховища
|
||||
quit.description=Будь ласка, підтвердіть що ви хочете вийти. Cryptomator правильно заблокує всі розблоковані сховища, щоб запобігти втраті даних.
|
||||
quit.lockAndQuitBtn=Заблокувати і вийти
|
||||
quit.description=Ви дійсно хочете вийти? Cryptomator коректно заблокує всі розблоковані сховища, щоб запобігти втраті даних.
|
||||
quit.lockAndQuitBtn=Заблокувати та вийти
|
||||
|
||||
# Forced Quit
|
||||
quit.forced.message=Деякі сховища неможливо заблокувати
|
||||
quit.forced.description=Блокування сховищ було перерване через операції з ними або відкриті файли. Ви можете примусово заблокувати сховища, проте переривання операцій зчитування-запису може призвести до втрати незбережених даних.
|
||||
quit.forced.forceAndQuitBtn=Завершити роботу примусово
|
||||
quit.forced.message=Деякі сховища не вдалося заблокувати
|
||||
quit.forced.description=Блокування сховищ було заблоковано через незавершені операції або відкриті файли. Ви можете примусово заблокувати сховища, що залишилися, однак переривання операцій читання/запису може призвести до втрати незбережених даних.
|
||||
quit.forced.forceAndQuitBtn=Примусово заблокувати та вийти
|
||||
|
||||
# Update Reminder
|
||||
updateReminder.title=Перевірити наявність оновлень
|
||||
updateReminder.message=Перевірити наявність оновлень?
|
||||
updateReminder.description=Будьте в курсі нових функцій, виправлення помилок і вдосконалення безпеки. Ми рекомендуємо автоматично перевіряти наявність оновлень.
|
||||
updateReminder.description=Залишайтеся в курсі нових функцій, виправлень помилок та вдосконалень безпеки. Ми рекомендуємо автоматично перевіряти наявність оновлень.
|
||||
updateReminder.notNow=Не зараз
|
||||
updateReminder.yesOnce=Так, лише раз
|
||||
updateReminder.yesAutomatically=Так, автоматично
|
||||
|
||||
#Dokany Support End
|
||||
dokanySupportEnd.title=Застаріле повідомлення
|
||||
dokanySupportEnd.title=Повідомлення про припинення підтримки
|
||||
dokanySupportEnd.message=Закінчення підтримки Dokany
|
||||
dokanySupportEnd.description=Тип сховища Dokany більше не підтримується в Cryptomator. Тепер ваші налаштування змінено для використання типового типу сховища. Ви можете переглянути тип сховища за замовчуванням в налаштуваннях.
|
||||
dokanySupportEnd.description=Тип тому Dokany більше не підтримується Cryptomator. Ваші налаштування змінено на використання типу тому за замовчуванням. Переглянути стандартний тип можна в налаштуваннях.
|
||||
dokanySupportEnd.preferencesBtn=Відкрити налаштування
|
||||
|
||||
#Retry If Readonly
|
||||
retryIfReadonly.title=Обмежений доступ до сховища
|
||||
retryIfReadonly.message=Немає прав на запис до папки сховища
|
||||
retryIfReadonly.description=Cryptomator не може записувати в папку сховища. Ви можете змінити режим доступу до сховища на "лише для читання" і повторити спробу. Цей параметр можна вимкнути у налаштуваннях сховища.
|
||||
retryIfReadonly.retry=Змінити і повторити спробу
|
||||
retryIfReadonly.message=Немає доступу для запису до каталогу сховища
|
||||
retryIfReadonly.description=Cryptomator не може здійснити запис до каталогу сховища. Ви можете перевести сховище в режим «лише для читання» і спробувати знову. Цю опцію можна вимкнути в параметрах сховища.
|
||||
retryIfReadonly.retry=Змінити та повторити спробу
|
||||
|
||||
# Share Vault
|
||||
shareVault.title=Поділитися сховищем
|
||||
shareVault.message=Ви хочете поділитися своїм сховищем з іншими?
|
||||
shareVault.description=Завжди будьте обережні, коли ділитесь своїм сховищем з іншими людьми. Якщо коротко, виконайте ці кроки:
|
||||
shareVault.instruction.1=1. Діліться доступом до зашифрованої папки сховища через хмарне сховище.
|
||||
shareVault.instruction.2=2. Передавайте пароль сховища безпечним способом.
|
||||
shareVault.remarkBestPractices=Для отримання додаткової інформації - ознайомтесь з кращим досвідом з наших документів.
|
||||
shareVault.docsTooltip=Відкрийте документацію, щоб дізнатися більше про надання доступу до сховищ.
|
||||
shareVault.hubAd.description=Безпечний спосіб роботи в командах
|
||||
shareVault.hubAd.keyManagement=• Управління ключами без інформації про їх значення
|
||||
shareVault.hubAd.authentication=• Сильні механізми перевірки особи (автентифікації)
|
||||
shareVault.message=Хочете поділитися сховищем з іншими?
|
||||
shareVault.description=Завжди будьте обережні, коли ділитесь своїм сховищем з іншими людьми. Якщо коротко, дотримуйтеся таких кроків:
|
||||
shareVault.instruction.1=1. Надайте спільний доступ до зашифрованої папки сховища через хмарне сховище.
|
||||
shareVault.instruction.2=2. Передайте пароль сховища безпечним способом.
|
||||
shareVault.remarkBestPractices=Для отримання додаткової інформації перегляньте поради щодо найкращих практик у нашій документації.
|
||||
shareVault.docsTooltip=Відкрийте документацію, щоб дізнатися більше про спільний доступ до сховищ.
|
||||
shareVault.hubAd.description=Надійний спосіб для командної роботи
|
||||
shareVault.hubAd.keyManagement=• Управління ключами за принципом «zero-knowledge»
|
||||
shareVault.hubAd.authentication=• Надійна автентифікація
|
||||
shareVault.hubAd.encryption=• Наскрізне шифрування
|
||||
shareVault.visitHub=Відвідати Cryptomator Hub
|
||||
|
||||
shareVault.hub.message=Як поділитись сховищем у хабі (Hub)
|
||||
shareVault.hub.description=Щоб поділитися вмістом сховища з іншим членом команди, ви повинні виконати два кроки:
|
||||
shareVault.hub.instruction.1=1. Поділіться доступом до зашифрованої папки сховища через хмарне сховище.
|
||||
shareVault.hub.message=Як надати спільний доступ до сховища Hub
|
||||
shareVault.hub.description=Щоб надати спільний доступ до вмісту сховища іншому учаснику команди, потрібно виконати два кроки:
|
||||
shareVault.hub.instruction.1=1. Надайте спільний доступ до зашифрованої папки сховища через хмарне сховище.
|
||||
shareVault.hub.instruction.2=2. Надайте доступ учаснику команди у Cryptomator Hub.
|
||||
shareVault.hub.openHub=Відкрити Cryptomator Hub
|
||||
|
||||
# Decrypt File Names
|
||||
decryptNames.title=Розшифрування назв файлів
|
||||
decryptNames.filePicker.title=Виберіть зашифрований файл
|
||||
decryptNames.filePicker.extensionDescription=Зашифрований файл Cryptomator
|
||||
decryptNames.copyTable.tooltip=Копіювати таблицю
|
||||
decryptNames.clearTable.tooltip=Очистити таблицю
|
||||
decryptNames.copyHint=Скопіювати вміст комірки за допомогою %s
|
||||
decryptNames.dropZone.message=Перетягніть файли або натисніть, щоб вибрати
|
||||
decryptNames.dropZone.error.vaultInternalFiles=Вибрано службові файли сховища, які не можна розшифрувати
|
||||
decryptNames.dropZone.error.foreignFiles=Файли не належать до сховища «%s»
|
||||
decryptNames.dropZone.error.noDirIdBackup=Каталог вибраних файлів не містить файл dirId.c9r
|
||||
decryptNames.dropZone.error.generic=Не вдалося розшифрувати назви файлів
|
||||
|
||||
|
||||
# Event View
|
||||
eventView.title=Події
|
||||
eventView.filter.allVaults=Усі
|
||||
eventView.clearListButton.tooltip=Очистити список
|
||||
## event list entries
|
||||
eventView.entry.vaultLocked.description=Розблокуйте «%s» для отримання деталей
|
||||
eventView.entry.conflictResolved.message=Конфлікт вирішено
|
||||
eventView.entry.conflictResolved.showDecrypted=Показати розшифрований файл
|
||||
eventView.entry.conflictResolved.copyDecrypted=Копіювати розшифрований шлях
|
||||
eventView.entry.conflict.message=Не вдалося вирішити конфлікт
|
||||
eventView.entry.conflict.showDecrypted=Показати розшифрований оригінальний файл
|
||||
eventView.entry.conflict.copyDecrypted=Скопіювати розшифрований оригінальний шлях
|
||||
eventView.entry.conflict.showEncrypted=Показати конфліктуючий зашифрований файл
|
||||
eventView.entry.conflict.copyEncrypted=Скопіювати конфліктуючий зашифрований шлях
|
||||
eventView.entry.decryptionFailed.message=Не вдалося розшифрувати
|
||||
eventView.entry.decryptionFailed.showEncrypted=Показати зашифрований файл
|
||||
eventView.entry.decryptionFailed.copyEncrypted=Копіювати зашифрований шлях
|
||||
eventView.entry.brokenDirFile.message=Пошкоджене посилання на каталог
|
||||
eventView.entry.brokenDirFile.showEncrypted=Показати пошкоджене зашифроване посилання
|
||||
eventView.entry.brokenDirFile.copyEncrypted=Копіювати шлях пошкодженого посилання
|
||||
eventView.entry.brokenFileNode.message=Пошкоджений вузол файлової системи
|
||||
eventView.entry.brokenFileNode.showEncrypted=Показати пошкоджений зашифрований вузол
|
||||
eventView.entry.brokenFileNode.copyEncrypted=Скопіювати шлях до пошкодженого зашифрованого вузла
|
||||
eventView.entry.brokenFileNode.copyDecrypted=Копіювати розшифрований шлях
|
||||
|
||||
@@ -291,7 +291,7 @@ preferences.general.keychainBackend=Lưu mật khẩu với
|
||||
preferences.general.quickAccessService=Thêm các vault đã mở khóa vào khu vực truy cập nhanh
|
||||
## Interface
|
||||
preferences.interface=Giao diện
|
||||
preferences.interface.theme=Cái nhìn và cảm nhận
|
||||
preferences.interface.theme=Chủ đề giao diện
|
||||
preferences.interface.theme.automatic=Tự động
|
||||
preferences.interface.theme.dark=Tối
|
||||
preferences.interface.theme.light=Sáng
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Mở khoá…
|
||||
main.vaultlist.contextMenu.unlockNow=Mở khóa bây giờ
|
||||
main.vaultlist.contextMenu.vaultoptions=Hiện tùy chọn vault
|
||||
main.vaultlist.contextMenu.reveal=Hiển thị Ổ đĩa
|
||||
main.vaultlist.contextMenu.share=Chia sẻ…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Tạo Vault Mới...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Mở Vault Hiện Có...
|
||||
main.vaultlist.showEventsButton.tooltip=Mở xem sự kiện
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=解锁…
|
||||
main.vaultlist.contextMenu.unlockNow=立即解锁
|
||||
main.vaultlist.contextMenu.vaultoptions=显示保险库选项
|
||||
main.vaultlist.contextMenu.reveal=显示驱动器
|
||||
main.vaultlist.contextMenu.share=共享…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=新建保险库...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=打开现有的保险库...
|
||||
##Notificaition
|
||||
|
||||
@@ -391,6 +391,7 @@ main.vaultlist.contextMenu.unlock=解鎖…
|
||||
main.vaultlist.contextMenu.unlockNow=立即解鎖
|
||||
main.vaultlist.contextMenu.vaultoptions=顯示加密庫選項
|
||||
main.vaultlist.contextMenu.reveal=展示磁碟
|
||||
main.vaultlist.contextMenu.share=分享…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=新建加密檔案庫...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=開啟現有的加密檔案庫...
|
||||
##Notificaition
|
||||
|
||||
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=解鎖…
|
||||
main.vaultlist.contextMenu.unlockNow=立即解鎖
|
||||
main.vaultlist.contextMenu.vaultoptions=顯示加密檔案庫選項
|
||||
main.vaultlist.contextMenu.reveal=顯示磁碟
|
||||
main.vaultlist.contextMenu.share=分享…
|
||||
main.vaultlist.addVaultBtn.menuItemNew=新建加密檔案庫...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=開啟現有的加密檔案庫...
|
||||
main.vaultlist.showEventsButton.tooltip=打開事件檢視
|
||||
@@ -583,21 +584,39 @@ shareVault.hub.instruction.2=2. 在Cryptomator Hub中允許團隊成員對加密
|
||||
shareVault.hub.openHub=打開 Cryptomator Hub
|
||||
|
||||
# Decrypt File Names
|
||||
decryptNames.title=解密檔案名稱
|
||||
decryptNames.filePicker.title=選擇已加密的檔案
|
||||
decryptNames.filePicker.extensionDescription=Cryptomator 加密檔案
|
||||
decryptNames.copyTable.tooltip=複製表格
|
||||
decryptNames.clearTable.tooltip=清除表格
|
||||
decryptNames.dropZone.message=拖放檔案或點擊選取
|
||||
decryptNames.dropZone.error.vaultInternalFiles=所選取的加密檔案庫內部檔案並沒有可解密的檔名
|
||||
decryptNames.dropZone.error.foreignFiles=檔案不屬於加密檔案庫「%s」
|
||||
decryptNames.dropZone.error.noDirIdBackup=選取的檔案的資料夾並不包含dirId.c9r檔案
|
||||
decryptNames.dropZone.error.generic=解密檔案名稱失敗
|
||||
|
||||
|
||||
# Event View
|
||||
eventView.title=事件
|
||||
eventView.filter.allVaults=全部
|
||||
eventView.clearListButton.tooltip=清空列表
|
||||
## event list entries
|
||||
eventView.entry.vaultLocked.description=解鎖「%s」以顯示詳細資訊
|
||||
eventView.entry.conflictResolved.message=以解決的衝突
|
||||
eventView.entry.conflictResolved.showDecrypted=顯示解密的檔案
|
||||
eventView.entry.conflictResolved.copyDecrypted=複製解密路徑
|
||||
eventView.entry.conflict.message=解決衝突失敗
|
||||
eventView.entry.conflict.showDecrypted=顯示解密、原始的檔案
|
||||
eventView.entry.conflict.copyDecrypted=複製解密、原始的路徑
|
||||
eventView.entry.conflict.showEncrypted=顯示衝突的加密檔案
|
||||
eventView.entry.conflict.copyEncrypted=複製衝突的加密檔案
|
||||
eventView.entry.decryptionFailed.message=解密失敗
|
||||
eventView.entry.decryptionFailed.showEncrypted=顯示加密的檔案
|
||||
eventView.entry.decryptionFailed.copyEncrypted=複製加密路徑
|
||||
eventView.entry.brokenDirFile.message=損壞的目錄連結
|
||||
eventView.entry.brokenDirFile.showEncrypted=顯示損壞的加密路徑
|
||||
eventView.entry.brokenDirFile.copyEncrypted=複製損壞的路徑連結
|
||||
eventView.entry.brokenFileNode.message=損壞的檔案系統節點
|
||||
eventView.entry.brokenFileNode.showEncrypted=顯示損壞的加密節點
|
||||
eventView.entry.brokenFileNode.copyEncrypted=複製損壞的加密節點路徑
|
||||
eventView.entry.brokenFileNode.copyDecrypted=複製解密路徑
|
||||
|
||||
Reference in New Issue
Block a user