diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b76ffabde..f089ca673 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -14,8 +14,8 @@ jobs: runs-on: ubuntu-20.04 env: - QT_VERSION: 6.5.1 - QIF_VERSION: 4.6 + QT_VERSION: 6.6.2 + QIF_VERSION: 4.7 steps: - name: 'Install Qt' @@ -72,8 +72,8 @@ jobs: runs-on: windows-latest env: - QT_VERSION: 6.5.1 - QIF_VERSION: 4.6 + QT_VERSION: 6.6.2 + QIF_VERSION: 4.7 BUILD_ARCH: 64 steps: @@ -134,7 +134,7 @@ jobs: runs-on: macos-13 env: - QT_VERSION: 6.5.2 + QT_VERSION: 6.6.2 CC: cc CXX: c++ @@ -227,7 +227,7 @@ jobs: env: # Keep compat with MacOS 10.15 aka Catalina by Qt 6.4 QT_VERSION: 6.4.3 - QIF_VERSION: 4.6 + QIF_VERSION: 4.7 steps: - name: 'Setup xcode' @@ -286,7 +286,7 @@ jobs: env: ANDROID_BUILD_PLATFORM: android-34 - QT_VERSION: 6.6.1 + QT_VERSION: 6.6.2 QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools qtcharts' steps: diff --git a/.gitignore b/.gitignore index 7de64e4b2..5b90fd55d 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,6 @@ client/3rd/ShadowSocks/ss_ios.xcconfig # UML generated pics out/ + +# CMake files +CMakeFiles/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a17d37635..2c1a19343 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 45) +set(APP_ANDROID_VERSION_CODE 46) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index c476588b7..f40c3ddb5 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -15,6 +15,15 @@ set(PACKAGES Core5Compat Concurrent LinguistTools Charts ) +execute_process( + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + COMMAND git rev-parse --short HEAD + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}") + if(IOS) set(PACKAGES ${PACKAGES} Multimedia) endif() @@ -57,6 +66,7 @@ set(AMNEZIAVPN_TS_FILES ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts + ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar.ts ) file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 289b474b6..0831b1a22 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -286,10 +286,16 @@ void AmneziaApplication::initModels() m_containersModel.reset(new ContainersModel(this)); m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + m_defaultServerContainersModel.reset(new ContainersModel(this)); + m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get()); + m_serversModel.reset(new ServersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel); + connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(), + &ContainersModel::updateModel); + m_serversModel->resetModel(); m_languageModel.reset(new LanguageModel(m_settings, this)); m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); @@ -333,7 +339,7 @@ void AmneziaApplication::initModels() connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this, [this](const QString &clientId, const QString &clientName, const DockerContainer container, ServerCredentials credentials) { - m_serversModel->reloadContainerConfig(); + m_serversModel->reloadDefaultServerContainerConfig(); m_clientManagementModel->appendClient(clientId, clientName, container, credentials); emit m_configurator->clientModelUpdated(); }); @@ -385,7 +391,13 @@ void AmneziaApplication::initControllers() m_engine->rootContext()->setContextProperty("ApiController", m_apiController.get()); connect(m_apiController.get(), &ApiController::updateStarted, this, [this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Connecting); }); - connect(m_apiController.get(), &ApiController::errorOccurred, this, - [this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); }); - connect(m_apiController.get(), &ApiController::updateFinished, m_connectionController.get(), &ConnectionController::toggleConnection); + connect(m_apiController.get(), &ApiController::errorOccurred, this, [this](const QString &errorMessage) { + if (m_connectionController->isConnectionInProgress()) { + emit m_pageController->showErrorMessage(errorMessage); + } + + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); + }); + connect(m_apiController.get(), &ApiController::updateFinished, m_connectionController.get(), + &ConnectionController::toggleConnection); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index fdc2ca0a1..2f5e54127 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -94,6 +94,7 @@ private: QCommandLineParser m_parser; QSharedPointer m_containersModel; + QSharedPointer m_defaultServerContainersModel; QSharedPointer m_serversModel; QSharedPointer m_languageModel; QSharedPointer m_protocolsModel; diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index fb417f05f..22eed003c 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -22,7 +22,7 @@ - + @@ -137,14 +137,13 @@ android:name=".AmneziaVpnService" android:process=":amneziaVpnService" android:permission="android.permission.BIND_VPN_SERVICE" - android:foregroundServiceType="specialUse" - android:exported="false"> + android:foregroundServiceType="systemExempted" + android:exported="false" + tools:ignore="ForegroundServicePermission"> - - = Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> FOREGROUND_SERVICE_TYPE_SPECIAL_USE + Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> FOREGROUND_SERVICE_TYPE_MANIFEST else -> 0 } diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index 8b250f185..9a170a85e 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -211,13 +211,7 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential localFile.write(data); localFile.close(); -#ifdef Q_OS_WINDOWS - error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toLocal8Bit().toStdString(), remotePath.toStdString(), - "non_desc"); -#else - error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), - "non_desc"); -#endif + error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc"); if (error != ErrorCode::NoError) { return error; diff --git a/client/core/sshclient.cpp b/client/core/sshclient.cpp index 0ac95662d..03670b304 100644 --- a/client/core/sshclient.cpp +++ b/client/core/sshclient.cpp @@ -222,7 +222,7 @@ namespace libssh { return fromLibsshErrorCode(); } - ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const std::string& localPath, const std::string& remotePath, const std::string& fileDesc) + ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc) { m_sftpSession = sftp_new(m_session); @@ -245,40 +245,38 @@ namespace libssh { const size_t bufferSize = 16384; char buffer[bufferSize]; - file = sftp_open(m_sftpSession, remotePath.c_str(), accessType, S_IRWXU); + file = sftp_open(m_sftpSession, remotePath.toStdString().c_str(), accessType, S_IRWXU); if (file == nullptr) { return closeSftpSession(); } - int localFileSize = std::filesystem::file_size(localPath); + int localFileSize = QFileInfo(localPath).size(); int chunksCount = localFileSize / (bufferSize); - std::ifstream fin(localPath, std::ios::binary | std::ios::in); + QFile fin(localPath); - if (fin.is_open()) { + if (fin.open(QIODevice::ReadOnly)) { for (int currentChunkId = 0; currentChunkId < chunksCount; currentChunkId++) { - fin.read(buffer, bufferSize); + QByteArray chunk = fin.read(bufferSize); + if (chunk.size() != bufferSize) return ErrorCode::SshSftpEofError; - int bytesWritten = sftp_write(file, buffer, bufferSize); + int bytesWritten = sftp_write(file, chunk.data(), chunk.size()); - std::string chunk(buffer, bufferSize); - - if (bytesWritten != bufferSize) { + if (bytesWritten != chunk.size()) { fin.close(); sftp_close(file); return closeSftpSession(); } } - int lastChunkSize = localFileSize % (bufferSize); + int lastChunkSize = localFileSize % bufferSize; if (lastChunkSize != 0) { - fin.read(buffer, lastChunkSize); + QByteArray lastChunk = fin.read(lastChunkSize); + if (lastChunk.size() != lastChunkSize) return ErrorCode::SshSftpEofError; - std::string chunk(buffer, lastChunkSize); - - int bytesWritten = sftp_write(file, buffer, lastChunkSize); + int bytesWritten = sftp_write(file, lastChunk.data(), lastChunkSize); if (bytesWritten != lastChunkSize) { fin.close(); diff --git a/client/core/sshclient.h b/client/core/sshclient.h index 4e08faaa1..74c3b7240 100644 --- a/client/core/sshclient.h +++ b/client/core/sshclient.h @@ -33,9 +33,9 @@ namespace libssh { const std::function &cbReadStdErr); ErrorCode writeResponse(const QString &data); ErrorCode sftpFileCopy(const SftpOverwriteMode overwriteMode, - const std::string& localPath, - const std::string& remotePath, - const std::string& fileDesc); + const QString &localPath, + const QString &remotePath, + const QString& fileDesc); ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &passphraseCallback); private: ErrorCode closeChannel(); diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index b85b2c33a..5b2e3cf11 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -500,7 +500,6 @@ bool Daemon::switchServer(const InterfaceConfig& config) { QJsonObject Daemon::getStatus() { Q_ASSERT(wgutils() != nullptr); QJsonObject json; - logger.debug() << "Status request"; if (!wgutils()->interfaceExists() || m_connections.isEmpty()) { json.insert("connected", QJsonValue(false)); diff --git a/client/daemon/daemonlocalserverconnection.cpp b/client/daemon/daemonlocalserverconnection.cpp index 1a49b7e5d..ed44d482c 100644 --- a/client/daemon/daemonlocalserverconnection.cpp +++ b/client/daemon/daemonlocalserverconnection.cpp @@ -46,8 +46,6 @@ DaemonLocalServerConnection::~DaemonLocalServerConnection() { } void DaemonLocalServerConnection::readData() { - logger.debug() << "Read Data"; - Q_ASSERT(m_socket); while (true) { @@ -90,8 +88,6 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) { } QString type = typeValue.toString(); - logger.debug() << "Command received:" << type; - if (type == "activate") { InterfaceConfig config; if (!Daemon::parseConfig(obj, config)) { diff --git a/client/images/controls/split-tunneling.svg b/client/images/controls/split-tunneling.svg new file mode 100644 index 000000000..3062054df --- /dev/null +++ b/client/images/controls/split-tunneling.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/ios/networkextension/CMakeLists.txt b/client/ios/networkextension/CMakeLists.txt index b09753c9b..e48215f68 100644 --- a/client/ios/networkextension/CMakeLists.txt +++ b/client/ios/networkextension/CMakeLists.txt @@ -85,6 +85,7 @@ target_sources(networkextension PRIVATE ${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift ${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift ${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPNAdapterDelegate.swift + ${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift ${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm ) diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index d75006859..ba95b295f 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -124,7 +124,10 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { // json.insert("hopindex", QJsonValue((double)hop.m_hopindex)); json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key)); json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip)); + // todo review wg ipv6 +#ifndef Q_OS_WINDOWS json.insert("deviceIpv6Address", "dead::1"); +#endif json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key)); json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key)); json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName)); @@ -247,8 +250,6 @@ void LocalSocketController::deactivate() { } void LocalSocketController::checkStatus() { - logger.debug() << "Check status"; - if (m_daemonState == eReady || m_daemonState == eInitializing) { Q_ASSERT(m_socket); @@ -298,7 +299,6 @@ void LocalSocketController::cleanupBackendLogs() { } void LocalSocketController::readData() { - logger.debug() << "Reading"; Q_ASSERT(m_socket); Q_ASSERT(m_daemonState == eInitializing || m_daemonState == eReady); @@ -340,8 +340,6 @@ void LocalSocketController::parseCommand(const QByteArray& command) { } QString type = typeValue.toString(); - logger.debug() << "Parse command:" << type; - if (m_daemonState == eInitializing && type == "status") { m_daemonState = eReady; @@ -367,6 +365,7 @@ void LocalSocketController::parseCommand(const QByteArray& command) { } emit initialized(true, connected.toBool(), datetime); + checkStatus(); return; } diff --git a/client/platforms/ios/PacketTunnelProvider.swift b/client/platforms/ios/PacketTunnelProvider.swift index 52f7084b3..dc0403e34 100644 --- a/client/platforms/ios/PacketTunnelProvider.swift +++ b/client/platforms/ios/PacketTunnelProvider.swift @@ -59,10 +59,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { var stopHandler: (() -> Void)? var protoType: TunnelProtoType = .none - override init() { - super.init() - } - override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { let tmpStr = String(data: messageData, encoding: .utf8)! wg_log(.error, message: tmpStr) @@ -71,7 +67,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { return } - guard let completionHandler = completionHandler else { + guard let completionHandler else { log(.error, message: "Missing message completion handler") return } @@ -179,14 +175,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider { return } - let wgConfigStr = String(data: wgConfig, encoding: .utf8)! - - guard let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: wgConfigStr) else { + guard let wgConfigStr = try? JSONDecoder().decode(WGConfig.self, from: wgConfig).str, + let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: wgConfigStr) + else { wg_log(.error, message: "Can't parse WireGuard config") completionHandler(nil) return } + log(.info, message: "wgConfig: \(wgConfigStr.replacingOccurrences(of: "\n", with: " "))") + if tunnelConfiguration.peers.first!.allowedIPs .map({ $0.stringRepresentation }) .joined(separator: ", ") == "0.0.0.0/0, ::/0" { diff --git a/client/platforms/ios/WGConfig.swift b/client/platforms/ios/WGConfig.swift new file mode 100644 index 000000000..9593d289c --- /dev/null +++ b/client/platforms/ios/WGConfig.swift @@ -0,0 +1,135 @@ +import Foundation + +struct WGConfigData: Decodable { + let initPacketMagicHeader, responsePacketMagicHeader: String? + let underloadPacketMagicHeader, transportPacketMagicHeader: String? + let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String? + let initPacketJunkSize, responsePacketJunkSize: String? + + var settings: String { + junkPacketCount == nil ? "" : + """ + Jc = \(junkPacketCount!) + Jmin = \(junkPacketMinSize!) + Jmax = \(junkPacketMaxSize!) + S1 = \(initPacketJunkSize!) + S2 = \(responsePacketJunkSize!) + H1 = \(initPacketMagicHeader!) + H2 = \(responsePacketMagicHeader!) + H3 = \(underloadPacketMagicHeader!) + H4 = \(transportPacketMagicHeader!) + + """ + } + + let clientIP: String + let clientPrivateKey: String + let clientPublicKey: String + let serverPublicKey: String + let presharedKey: String + let hostName: String + let port: Int + + var allowedIPs: [String] + var persistentKeepAlive: String + + enum CodingKeys: String, CodingKey { + case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2" + case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4" + case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax" + case initPacketJunkSize = "S1", responsePacketJunkSize = "S2" + + case clientIP = "client_ip" // "10.8.1.16" + case clientPrivateKey = "client_priv_key" + case clientPublicKey = "client_pub_key" + case serverPublicKey = "server_pub_key" + case presharedKey = "psk_key" + + case allowedIPs = "allowed_ips" + case persistentKeepAlive = "persistent_keep_alive" + case hostName + case port + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.initPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .initPacketMagicHeader) + self.responsePacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .responsePacketMagicHeader) + self.underloadPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .underloadPacketMagicHeader) + self.transportPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .transportPacketMagicHeader) + self.junkPacketCount = try container.decodeIfPresent(String.self, forKey: .junkPacketCount) + self.junkPacketMinSize = try container.decodeIfPresent(String.self, forKey: .junkPacketMinSize) + self.junkPacketMaxSize = try container.decodeIfPresent(String.self, forKey: .junkPacketMaxSize) + self.initPacketJunkSize = try container.decodeIfPresent(String.self, forKey: .initPacketJunkSize) + self.responsePacketJunkSize = try container.decodeIfPresent(String.self, forKey: .responsePacketJunkSize) + self.clientIP = try container.decode(String.self, forKey: .clientIP) + self.clientPrivateKey = try container.decode(String.self, forKey: .clientPrivateKey) + self.clientPublicKey = try container.decode(String.self, forKey: .clientPublicKey) + self.serverPublicKey = try container.decode(String.self, forKey: .serverPublicKey) + self.presharedKey = try container.decode(String.self, forKey: .presharedKey) + self.allowedIPs = try container.decodeIfPresent([String].self, forKey: .allowedIPs) ?? ["0.0.0.0/0", "::/0"] + self.persistentKeepAlive = try container.decodeIfPresent(String.self, forKey: .persistentKeepAlive) ?? "25" + self.hostName = try container.decode(String.self, forKey: .hostName) + self.port = try container.decode(Int.self, forKey: .port) + } +} + +struct WGConfig: Decodable { + let data: WGConfigData + let configVersion: Int + let description: String + let dns1: String + let dns2: String + let hostName: String + let `protocol`: String + let splitTunnelSites: [String] + let splitTunnelType: Int + + enum CodingKeys: String, CodingKey { + case awgConfigData = "awg_config_data", wgConfigData = "wireguard_config_data" + case configData + case configVersion = "config_version" + case description + case dns1 + case dns2 + case hostName + case `protocol` + case splitTunnelSites + case splitTunnelType + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + if container.contains(.awgConfigData) { + self.data = try container.decode(WGConfigData.self, forKey: .awgConfigData) + } else { + self.data = try container.decode(WGConfigData.self, forKey: .wgConfigData) + } + + self.configVersion = try container.decode(Int.self, forKey: .configVersion) + self.description = try container.decode(String.self, forKey: .description) + self.dns1 = try container.decode(String.self, forKey: .dns1) + self.dns2 = try container.decode(String.self, forKey: .dns2) + self.hostName = try container.decode(String.self, forKey: .hostName) + self.protocol = try container.decode(String.self, forKey: .protocol) + self.splitTunnelSites = try container.decode([String].self, forKey: .splitTunnelSites) + self.splitTunnelType = try container.decode(Int.self, forKey: .splitTunnelType) + } + + var str: String { + """ + [Interface] + Address = \(data.clientIP)/32 + DNS = \(dns1), \(dns2) + PrivateKey = \(data.clientPrivateKey) + \(data.settings) + [Peer] + PublicKey = \(data.serverPublicKey) + PresharedKey = \(data.presharedKey) + AllowedIPs = \(data.allowedIPs.joined(separator: ", ")) + Endpoint = \(data.hostName):\(data.port) + PersistentKeepalive = \(data.persistentKeepAlive) + """ + } +} diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 8fb8283d6..0d5eadbcb 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -400,9 +400,10 @@ bool IosController::setupCloak() bool IosController::setupWireGuard() { QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::WireGuard)].toObject(); - - QString wgConfig = config[config_key::config].toString(); + QJsonDocument doc(m_rawConfig); + QString wgConfig(doc.toJson(QJsonDocument::Compact)); + return startWireGuard(wgConfig); } @@ -410,8 +411,9 @@ bool IosController::setupAwg() { QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject(); - QString wgConfig = config[config_key::config].toString(); - + QJsonDocument doc(m_rawConfig); + QString wgConfig(doc.toJson(QJsonDocument::Compact)); + return startWireGuard(wgConfig); } diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index 61b2e261d..a574c0546 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -20,9 +20,20 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject * }); connect(m_impl.get(), &ControllerImpl::disconnected, this, [this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }); + + connect(m_impl.get(), &ControllerImpl::statusUpdated, this, + &WireguardProtocol::statusUpdated); + m_impl->initialize(nullptr, nullptr); } +void WireguardProtocol::statusUpdated(const QString& serverIpv4Gateway, const QString& deviceIpv4Address, + uint64_t txBytes, uint64_t rxBytes) { + setBytesChanged(rxBytes, txBytes); + QThread::msleep(1000); + m_impl->checkStatus(); +} + WireguardProtocol::~WireguardProtocol() { WireguardProtocol::stop(); diff --git a/client/protocols/wireguardprotocol.h b/client/protocols/wireguardprotocol.h index 6d1a05187..e0e98c801 100644 --- a/client/protocols/wireguardprotocol.h +++ b/client/protocols/wireguardprotocol.h @@ -21,7 +21,8 @@ public: ErrorCode start() override; void stop() override; - + void statusUpdated(const QString& serverIpv4Gateway, const QString& deviceIpv4Address, + uint64_t txBytes, uint64_t rxBytes); ErrorCode startMzImpl(); ErrorCode stopMzImpl(); diff --git a/client/resources.qrc b/client/resources.qrc index c098ef754..cb965aa9c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -160,7 +160,6 @@ ui/qml/Components/SettingsContainersListView.qml ui/qml/Controls2/TextTypes/ListItemTitleType.qml ui/qml/Controls2/DividerType.qml - ui/qml/Controls2/DrawerType.qml ui/qml/Controls2/StackViewType.qml ui/qml/Pages2/PageSettings.qml images/controls/amnezia.svg @@ -226,5 +225,8 @@ images/controls/close.svg images/controls/search.svg ui/qml/Controls2/GraphViewType.qml + ui/qml/Components/HomeSplitTunnelingDrawer.qml + images/controls/split-tunneling.svg + ui/qml/Controls2/DrawerType2.qml diff --git a/client/translations/amneziavpn_ar.ts b/client/translations/amneziavpn_ar.ts new file mode 100644 index 000000000..18af56121 --- /dev/null +++ b/client/translations/amneziavpn_ar.ts @@ -0,0 +1,3248 @@ + + + + + ConnectionController + + + + + + Connect + اتصل + + + + VPN Protocols is not installed. + Please install VPN container at first + لم يتم تثبيت بروتوكولات VPN, من فضلك قم بتنزيل حاوية VPN اولاً + + + + Connection... + اتصال... + + + + Connected + تم الاتصال + + + + Reconnection... + إعادة الاتصال... + + + + Disconnection... + إنهاء الاتصال... + + + + Settings updated successfully, Reconnnection... + تم تحديث الاعدادات بنجاح, جاري إعادة الاتصال... + + + + Settings updated successfully + تم تحديث الاعدادات بنجاح + + + + ConnectionTypeSelectionDrawer + + + Add new connection + إضافة اتصال جديد + + + + Configure your server + قم بتهيئة الخادم الخاص بك + + + + Open config file, key or QR code + افتح ملف تعريف, مفتاح تعريف او رمز QR + + + + ContextMenuType + + + C&ut + ق&طع + + + + &Copy + &استنتاخ + + + + &Paste + &لصق + + + + &SelectAll + &تحديد الكل + + + + ExportController + + + Access error! + خطأ في الوصول! + + + + HomeContainersListView + + + Unable change protocol while there is an active connection + قم بتغيير البروتوكول عند تواجد اتصال + + + + The selected protocol is not supported on the current platform + البروتوكول المحدد غير مدعوم علي المنصة الحالية + + + + ImportController + + + Scanned %1 of %2. + تم فحص%1 من %2. + + + + InstallController + + installed successfully. + تم التثبيت بنجاح + + + is already installed on the server. + بالفعل مٌثبت علي الخادم + + + + + %1 installed successfully. + %1 تم التثبيت بنجاح. + + + + + %1 is already installed on the server. + %1 بالفعل مٌثبت علي الخادم. + + + + +Added containers that were already installed on the server + +تمت إضافة الحاويات التي كانت مٌثبتة بالفعل علي الخادم + + + + +Already installed containers were found on the server. All installed containers have been added to the application + +تم العثور علي حاويات مٌثبتة بالفعل علي الخادم +تمت إضافة جميع الحاويات المٌثبتة إلي التطبيق + + + + Settings updated successfully + تم تحديث الاعدادات بنجاح + + + + Server '%1' was rebooted + تمت إعادة تشغيل الخادم%1 + + + + Server '%1' was removed + تمت إزالة الخادم '%1' + + + + All containers from server '%1' have been removed + قد تم حذفها '%1' جميع الحاويات من الخادم + + + + %1 has been removed from the server '%2' + %1 تم حدف '%2' اسم الخادم + + + 1% has been removed from the server '%2' + %1 من الخادم '%2' تم مسحة + + + Server ' + خادم + + + ' was removed + تم حذفة + + + has been removed from the server ' + قد تمت إزالتة من الخادم + + + + Please login as the user + من فضلك قم بتسجيل الدخول كمستخدم + + + + Server added successfully + تمت إضافة الخادم بنجاح + + + + KeyChainClass + + + Read key failed: %1 + فشل مفتاح القراءة: %1 + + + + Write key failed: %1 + فشل مفتاح الكتابة: %1 + + + + Delete key failed: %1 + فشل مفتاح الحذف: %1 + + + + NotificationHandler + + + + AmneziaVPN + AmneziaVPN + + + + VPN Connected + تم الاتصال + + + + VPN Disconnected + تم إنهاء الاتصال + + + + AmneziaVPN notification + إشعار من AmneziaVPN + + + + Unsecured network detected: + تم العثور علي شبكة غير مؤمنة: + + + + PageDeinstalling + + + Removing services from %1 + من %1 مسح الخدمة + + + + Usually it takes no more than 5 minutes + في العادة تستغرق اقل من 5 دقائق + + + + PageHome + + + VPN protocol + بروتوكول VPN + + + + Servers + الخوادم + + + + Unable change server while there is an active connection + لا يمكن تغير الخادم بينما هناك اتصال مفعل + + + + PageProtocolAwgSettings + + + AmneziaWG settings + اعدادات AmneziaWG + + + + Port + منفذ + + + + Junk packet count + عدد الحزم الغير مرغوب فيها + + + + Junk packet minimum size + الحد الادني لحجم الحزمة الغير مرغوب فيها + + + + Junk packet maximum size + الحجم الاقصي للحزمة الغير مرغوب فيها + + + + Init packet junk size + Init packet junk size + + + + Response packet junk size + حجم حزمة الاستجابة الغير مرغوب فيها + + + + Init packet magic header + إطلاق حزمة magic header + + + + Response packet magic header + حزمة الرد magic header + + + + Transport packet magic header + نقل حزمة magic header + + + + Underload packet magic header + تحميل حزمة magic header + + + + Remove AmneziaWG + قم بحذف AmneziaWG + + + + Remove AmneziaWG from server? + قم بحذف AmneziaWG من الخادم؟ + + + + All users with whom you shared a connection will no longer be able to connect to it. + جميع المستخدمين الذين قمت بمشاركة اتصال معهم لن يستطيعو الاتصال. + + + + Continue + واصل + + + + Cancel + إلغاء + + + + Save and Restart Amnezia + احفظ و اعِد تشغيل Amnezia + + + + PageProtocolCloakSettings + + + Cloak settings + Cloak إعدادات + + + + Disguised as traffic from + متنكراً في حركة مرور من + + + + Port + منفذ + + + + + Cipher + الشفرة + + + + Save and Restart Amnezia + احفظ و اعِد تشغيل Amnezia + + + + PageProtocolOpenVpnSettings + + + OpenVPN settings + OpenVPN اعدادات + + + + VPN address subnet + الشبكة الفرعية لعنوان VPN + + + + Network protocol + بروتوكول الشبكة + + + + Port + منفذ + + + + Auto-negotiate encryption + التفاوض التلقائي علي الشبكة + + + + + Hash + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + + Cipher + شفرة + + + + AES-256-GCM + + + + + AES-192-GCM + + + + + AES-128-GCM + + + + + AES-256-CBC + + + + + AES-192-CBC + + + + + AES-128-CBC + + + + + ChaCha20-Poly1305 + + + + + ARIA-256-CBC + + + + + CAMELLIA-256-CBC + + + + + none + لا شئ + + + + TLS auth + TLS مصادقة + + + + Block DNS requests outside of VPN + احظر طلبات DNS خارج ال VPN + + + + Additional client configuration commands + اوامر تكوين العميل الاضافية + + + + + Commands: + الاوامر: + + + + Additional server configuration commands + اوامر تكوين الخادم الاضافية + + + + Remove OpenVPN + احذف OpenVPN + + + + Remove OpenVpn from server? + احذف OpenVPN من الخادم? + + + + All users with whom you shared a connection will no longer be able to connect to it. + جميع المستخدمين الذين شاركت معهم الاتصال لن يستطيعو الاتصال مجدداً. + + + All users with whom you shared a connection will no longer be able to connect to it + جميع المستخدمين الذين شاركت اتصال معهم لن يستطيعو الاتصال بعد الان + + + + Continue + واصل + + + + Cancel + إلغاء + + + + Save and Restart Amnezia + احفظ واعِد تشغيل Amnezia + + + + PageProtocolRaw + + + settings + إعدادات + + + + Show connection options + اظهر اختيارات الاتصال + + + Connection options + اختيارات الاتصال + + + + Connection options %1 + %1 اختيارات الاتصال + + + + Remove + احذف + + + + Remove %1 from server? + احذف %1 من الخادم ? + + + + All users with whom you shared a connection will no longer be able to connect to it. + جميع المستخدمين الذين شاركت معهم اتصال لن يستطيعو الاتصال بعد الان. + + + from server? + من الخادم + + + All users with whom you shared a connection will no longer be able to connect to it + جميع المستخدمين الذين شاركت اتصال معهم لن يستطيعو الاتصال بعد الان + + + + Continue + واصل + + + + Cancel + إلغاء + + + + PageProtocolShadowSocksSettings + + + ShadowSocks settings + ShadowSocks إعدادات + + + + Port + منفذ + + + + + Cipher + تشفير + + + + Save and Restart Amnezia + احفظ واعِد تشغيل Amnezia + + + + PageServiceDnsSettings + + + A DNS service is installed on your server, and it is only accessible via VPN. + + تم تثبيت خدمة DNS علي الخادم الخاص بك, و فقط متاح من خلال VPN. + + + + + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. + عنوان ال DNS متطابق لنفس عنوان الخادم بك, يمكنك تهيئة DNS في الاعدادات, تحت علامة تبويب الاتصال. + + + + Remove + احذف + + + + Remove %1 from server? + احذف %1 ? + + + from server? + من الخادم + + + + Continue + واصل + + + + Cancel + إلغاء + + + + PageServiceSftpSettings + + + Settings updated successfully + تم تحديث الإعدادات بنجاح + + + + SFTP settings + SFTP إعدادات + + + + Host + استضافة + + + + + + + Copied + تم الاستنساخ + + + + Port + منفذ + + + + Login + تسجيل الدخول + + + + Password + كلمة المرور + + + + Mount folder on device + قم بتثبيت المجلد علي الجهاز + + + + In order to mount remote SFTP folder as local drive, perform following steps: <br> + لتثبيت مجلد SFTP كمحرك اقراص محلي, اتبع هذه الخطوات : <br> + + + + + <br>1. Install the latest version of + <br>1. تحميل اخر اصدار من + + + + + <br>2. Install the latest version of + <br>2. تحمير اخر اصدار من + + + + Detailed instructions + تعليمات مفصلة + + + + Remove SFTP and all data stored there + امسح SFTP وجميع البيانات المخزنة + + + + Remove SFTP and all data stored there? + امسح SFTP وجميع البيانات المخزنة؟ + + + + Continue + واصل + + + + Cancel + إلغاء + + + + PageServiceTorWebsiteSettings + + + Settings updated successfully + تم تحديث الإعدادات بنجاح + + + + Tor website settings + Tor إعدادات متصفح + + + + Website address + عنوان المتصفح + + + + Copied + تم الاستنساخ + + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this URL. + + + + + After creating your onion site, it takes a few minutes for the Tor network to make it available for use. + + + + + When configuring WordPress set the this onion address as domain. + عند تكوين WordPress قم بتعيين عنوان ال onion هذا ك domain. + + + + Remove website + احذف متصفح + + + + The site with all data will be removed from the tor network. + سيتم حذف الموقع وجميع البيانات من الشبكة. + + + + Continue + واصل + + + + Cancel + إلغاء + + + + PageSettings + + + Settings + إعدادات + + + + Servers + الخوادم + + + + Connection + الاتصال + + + + Application + تطبيق + + + + Backup + نسخة احتياطية + + + + About AmneziaVPN + عن AmneziaVPN + + + + Close application + إغلاق التطبيق + + + + PageSettingsAbout + + This is a free and open source application. If you like it, support the developers with a donation. +And if you don't like the app, all the more support it - the donation will be used to improve the app. + هذا تطبيق مجاني و مفتوح المصدر. إذا عجبك التطبيق, ادعم المطورين ب تبرع. + وإذا لما يعجبك, فهذا سبب اكبر لدعمة - تستخدم التبرعات في تطوير التطبيق + + + + Support Amnezia + دعم Amenzia + + + + This is a free and open source application. If you like it, support the developers with a donation. + هذا تطبيق مجاني ومفتوح المصدر. إذا عجبك التطبيق, ادعم المطورين ب تبرع. + + + + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. + وإذا لم يعجبك التطبيق,، فهذا سبب إضافي لدعمه - سيتم استخدام التبرع لتحسين التطبيق. + + + + Card on Patreon + البطاقة علي Patreon + + + + https://www.patreon.com/amneziavpn + + + + + Show other methods on Github + اظهر المزيد علي GitHub + + + + https://github.com/amnezia-vpn/amnezia-client#donate + + + + + Contacts + التواصل + + + + Telegram group + مجموعة ال Telegram + + + + To discuss features + لمناقشة الميزات + + + + https://t.me/amnezia_vpn_en + + + + + Mail + البريد + + + + For reviews and bug reports + لل مراجعات والابلاغات عن المشاكل + + + + Github + + + + + https://github.com/amnezia-vpn/amnezia-client + + + + + Website + موقع + + + + https://amnezia.org + + + + + Software version: %1 + %1 :إصدار البرنامج + + + + Check for updates + تحقق من وجود تحديثات + + + + PageSettingsApplication + + + Application + تطبيق + + + + Allow application screenshots + اسمح بلقطات شاشة التطبيق + + + + Auto start + تشغيل تلقائي + + + Launch the application every time + شغل البرنامج كل مرة + + + starts + يبدأ + + + + Launch the application every time the device is starts + قم بتشغيل التطبيق فكل مرة يتم فيها تشغيل الجهاز + + + + Start minimized + ابدأ ب الحجم الادني + + + + Launch application minimized + تشغيل التطبيق في الحد الادني + + + + Language + اللغة + + + + Logging + تسجيل + + + + Enabled + مٌفعل + + + + Disabled + مٌعطل + + + + Reset settings and remove all data from the application + إعادة ضبط الاعدادات ومسح جميع البيانات من التطبيق + + + + Reset settings and remove all data from the application? + إعادة ضبط الاعدادات ومسح جميع البيانات من التطبيق؟ + + + + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. + سيتم ضبط الاعدادات الافتراضية. جميع خدمات AmneziaVPN المٌثبتة ستبقي علي الخادم. + + + + Continue + واصل + + + + Cancel + إلغاء + + + + PageSettingsBackup + + + Settings restored from backup file + تم إعادة الاعدادات من ملف نسخة احتياطية + + + + Backup + نسخة احتياطية + + + + Configuration backup + نسخ احتياطي للإعدادات + + + It will help you instantly restore connection settings at the next installation + سيساعدك علي إعادة إعدادات الاتصال بسرعة عند إعادة تثبيت التطبيق + + + + You can save your settings to a backup file to restore them the next time you install the application. + يمكنك حفظ الإعدادات في ملف نسخة احتياطية لأعادتهم في المرة القادمة التي تثبت فيها التطبيق. + + + + Make a backup + إضافة نسخة احتياطية + + + + Save backup file + احفظ ملف النسخه الاحتياطيه + + + + + Backup files (*.backup) + ملفات نٌسخ احتياطية (*.backup) + + + + Backup file saved + تم حفظ ملف النسخ الاحتياطي + + + + Restore from backup + استرجاع من ملف يحتوي علي نسخة احتياطية + + + + Open backup file + افتح ملف نسخ احتياطي + + + + Import settings from a backup file? + استرد الإعدادات من ملف نسخ احتياطي؟ + + + + All current settings will be reset + ستتم إعادة ضبط جميع الإعدادات الحالية + + + + Continue + واصل + + + + Cancel + إلغاء + + + + PageSettingsConnection + + + Connection + الاتصال + + + + Auto connect + الاتصال التلقائي + + + + Connect to VPN on app start + اتصل ب ال VPN عند تشغيل التطبيق + + + + When AmneziaDNS is not used or installed + عندما يكون AmneziaDNS غير مٌثبت او غير مستخدم + + + + Allows you to use the VPN only for certain Apps + يسمح لك بأستخدام ال VPN علي تطبيقات معينة + + + Use AmneziaDNS if installed on the server + استخدم AmneziaDNS إذا كان مٌثبت علي الخادم + + + + Use AmneziaDNS + استخدم AmneziaDNS + + + + If AmneziaDNS is installed on the server + في حالة كان AmneziaDNS مٌثبت علي الخادم + + + + DNS servers + خوادم DNS + + + + Site-based split tunneling + انقسام الانفاق القائم علي الموقع + + + + Allows you to select which sites you want to access through the VPN + يسمح لك بتحديد اي موقع تريد الوصول له عن طريق ال VPN + + + + App-based split tunneling + انقسام الانفاق القائم علي التطبيق + + + Split site tunneling + قسم نفق الموقع + + + Allows you to connect to some sites through a secure connection, and to others bypassing it + يسمحلك بألاتصال ببعض المواقع بسرية, وعلي الاخرين تجاوزه + + + Separate application tunneling + فرق نفق التطبيق + + + + PageSettingsDns + + + Default server does not support custom dns + الخادم الافتراضي لا يدعم DNS مخصص + + + + DNS servers + خوادم ال DNS + + + + If AmneziaDNS is not used or installed + AmneziaVPN ليس مٌستخدم او مٌثبت + + + + Primary DNS + الرئيسي DNS + + + + Secondary DNS + الثانوي DNS + + + + Restore default + استعادة الافتراضي + + + + Restore default DNS settings? + قم بأعادة ضبط إعدادات ال DNS الافتراضية؟ + + + + Continue + واصل + + + + Cancel + إلغاء + + + + Settings have been reset + لم يتم إعادة ضبط الإعدادات + + + + Save + احفظ + + + + Settings saved + تم حفظ الإعدادات + + + + PageSettingsLogging + + + Logging + التسجيل + + + + Save logs + احفظ السجلات + + + + Open folder with logs + افتح مجلد يحتوي علي سجلات + + + + Save + احفظ + + + + Logs files (*.log) + ملفات الولوج (*.log) + + + + Logs file saved + تم حفظ ملف السجل + + + + Save logs to file + احفظ السجلات في ملف + + + + Clear logs? + مسح السجلات؟ + + + + Continue + واصل + + + + Cancel + إلغاء + + + + Logs have been cleaned up + تم مسح السجلات + + + + Clear logs + احذف السجلات + + + + PageSettingsServerData + + + All installed containers have been added to the application + تمت إضافة جميع الحاويات المٌثبتة للتطبيق + + + + No new installed containers found + لم يتم العثور علي اي حاويات جديدة مٌثبتة + + + + Clear Amnezia cache + حذف ذاكرة تخزين Amnezia المؤقتة + + + + May be needed when changing other settings + قد يكون ضروري عند تغير الإعدادات الاخري + + + + Clear cached profiles? + حذف الملفات الشخصية المخزنة مؤقتاً؟ + + + + Do you want to reboot the server? + هل تريد إعادة تشغيل الخادم؟ + + + + + Do you want to clear server from Amnezia software? + هل تريد حذف الخادم من Amnezia? + + + + + + + + + + + + + + Continue + واصل + + + + + + + + + Cancel + إلغاء + + + + Check the server for previously installed Amnezia services + افحص الخادم عن اي خدمات Amnezia مٌثبتة سابقاُ + + + + Add them to the application if they were not displayed + اضفهم إلي التطبيق إذا لم يكونو ظاهرين + + + + Reboot server + إعادة تشغيل الخادم + + + + The reboot process may take approximately 30 seconds. Are you sure you wish to proceed? + عملية إعادة التشغيل قد تستغرق 30 ثانية, هل تريد الاستكمال؟ + + + + Remove server from application + احذف خادم من التطبيق + + + + Do you want to remove the server from application? + هل تريد حذف الخادم من التطبيق؟ + + + + Reset API config + إعادة تكوين API + + + + Do you want to reset API config? + هل تريد إعادة تكوين API? + + + + All installed AmneziaVPN services will still remain on the server. + جميع خدمات AmneziaVPN المٌثبتة ستظل علي الخادم. + + + + + Clear server from Amnezia software + احذف خادم من Amnezia + + + + + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. + سيتم حذف جميع الحاويات, هذا يعني ان جميع ملفات التكوين, شهادات و مفاتيح التعريف سيتم حذفهم. + + + + PageSettingsServerInfo + + + Server name + اسم الخادم + + + + Save + احفظ + + + + Protocols + البروتوكولات + + + + Services + الخدمات + + + + Data + البيانات + + + + PageSettingsServerProtocol + + + settings + الإعدادات + + + + Remove + احذف + + + + All users with whom you shared a connection will no longer be able to connect to it. + جميع المستخدمين الذين شاركت معاهم اتصال لن يستطيعو الاتصال بعد الان. + + + from server? + من الخادم؟ + + + + Remove %1 from server? + احذف %1 من الخادم ? + + + All users with whom you shared a connection will no longer be able to connect to it + جميع المستخدمين الذين شاركت اتصال معهم لن يستطيعو الاتصال بعد الان + + + + Continue + واصل + + + + Cancel + إلغاء + + + + PageSettingsServersList + + + Servers + الخوادم + + + + PageSettingsSplitTunneling + + Only the addresses in the list must be opened via VPN + يجب فتح العنواين التي في القائمة عبر VPN + + + Addresses from the list should never be opened via VPN + لا يجب ابداً فتح العنواين التي في القائمة عن طريق VPN + + + Split site tunneling + قسم نفق الموقع + + + + Default server does not support split tunneling function + السرفر الافتراضي لا يدعم ميزة تقسيم الانفاق + + + + Addresses from the list should not be accessed via VPN + لا يجب الولوج للعنواين المذكورة هنا من خلال ال VPN + + + + Split tunneling + تقسيم الانفاق + + + + Mode + وضع + + + + Remove + احذف + + + + Continue + واصل + + + + Cancel + إلغاء + + + + Only the sites listed here will be accessed through the VPN + سيتم الولوج للمواقع المذكورة هنا فقط عن طريق ال VPN + + + + website or IP + موقع او IP + + + + Import / Export Sites + + + + + Import + استرد + + + + Save site list + احفظ قائمة المواقع + + + + Save sites + احفظ المواقع + + + + + + Sites files (*.json) + + + + + Import a list of sites + استرد قائمة من المواقع + + + + Replace site list + تبديل قائمة المواقع + + + + + Open sites file + افتح ملف المواقع + + + + Add imported sites to existing ones + إضافة المواقع المستردة للمواقع الموجودة + + + + PageSetupWizardConfigSource + + + Server connection + اتصال الخادم + + + + Do not use connection code from public sources. It may have been created to intercept your data. + +It's okay as long as it's from someone you trust. + لا تستخدم رمز الاتصال من المصادر العامة. ربما تم إنشاؤه لاعتراض بياناتك + +لا بأس طالما انه من شخص تثق به. + + + + What do you have? + ماذا لديك؟ + + + + File with connection settings or backup + ملف إعدادات اتصال او نسخ احتياطي + + + + File with connection settings + ملف إعدادات اتصال + + + + Open config file + افتح ملف تكوين + + + + QR-code + رمز QR + + + + Key as text + مفتاح كنص + + + + PageSetupWizardCredentials + + + Configure your server + تكوين الخادم الخاص بك + + + + Server IP address [:port] + عنوان خادم IP [:منفذ] + + + + Login to connect via SSH + قم بتسجيل الدخول للأتصال عن طريق SSH + + + + Continue + واصل + + + + All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties + ستظل جميع البيانات التي تدخلها سرية للغاية ولن تتم مشاركتها أو الكشف عنها ل Amnezia أو أي طرف ثالث + + + + 255.255.255.255:22 + + + + + Password or SSH private key + كلمة مرور او مفتاح SSH خاص + + + + Ip address cannot be empty + لا يمكن لعنوان IP ان يكون فارغ + + + + Enter the address in the format 255.255.255.255:88 + ادخل العنوان في شكل 255.255.255.255:88 + + + + Login cannot be empty + تسجيل دخول لا يمكن ان يكون فارغ + + + + Password/private key cannot be empty + كلمة مرور/مفتاح خاص لأ يمكن ان يكونو فارغين + + + + PageSetupWizardEasy + + + What is the level of internet control in your region? + ما هو مستوي التحكم في الانترنت في منطقتك؟ + + + + Set up a VPN yourself + قم بإعداد VPN بنفسك + + + + I want to choose a VPN protocol + اريد اختيار بروتوكول VPN + + + + Continue + واصل + + + + Set up later + إعداد في وقت لاحق + + + + PageSetupWizardInstalling + + + + Usually it takes no more than 5 minutes + عادة لا تستغرق اكثر من 5 دقائق + + + + The server has already been added to the application + تمت إضافة الخادم بالفعل للتطبيق + + + + Amnezia has detected that your server is currently + اكتشف Amnezia الخادم الخاص بك موجود حاليًا + + + + busy installing other software. Amnezia installation + مشغول بتثبيت برامج اخري, تثبيت Amnezia + + + + Cancel installation + إلغاء التثبيت + + + + will pause until the server finishes installing other software + سيتوقف مؤقتًا حتى ينتهي الخادم من تثبيت البرامج الأخرى + + + + Installing + جاري التثبيت + + + + PageSetupWizardProtocolSettings + + + Installing %1 + جاري تثبيت %1 + + + + More detailed + اكثر تفصيلاً + + + + Close + اغلق + + + + Network protocol + بروتوكول شبكة + + + + Port + منفذ + + + + Install + تثبيت + + + + PageSetupWizardProtocols + + + VPN protocol + VPN بروتوكول + + + + Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. + اختر بالنسبة للأولوية القصوى بالنسبة لك. ويمكنك لاحقًا تثبيت بروتوكولات وخدمات إضافية أخرى، مثل وكيل DNS وSFTP. + + + + PageSetupWizardQrReader + + + Point the camera at the QR code and hold for a couple of seconds. + قم بتوجيه الكاميرا نحو رمز QR و اثبت لبضع ثوان. + + + + PageSetupWizardStart + + + Settings restored from backup file + تم استرداد الإعدادات من ملف نسخة احتياطية + + + + Free service for creating a personal VPN on your server. + خدمة مجانية لأنشاء VPN شخصي علي الخادم الشخصي. + + + + Helps you access blocked content without revealing your privacy, even to VPN providers. + يساعدك في الولوج للمحتوي المحظور بدون إظهار خصوصيات, حتي لمزود ال VPN. + + + + I have the data to connect + لدي البيانات المطلوبة للأتصال + + + + I have nothing + ليس لدي اي شئ + + + + https://amnezia.org/instructions/0_starter-guide + + + + + PageSetupWizardTextKey + + + Connection key + مفتاح اتصال + + + + A line that starts with vpn://... + يجب ان تٌكتب بهذه الطريقة حتي بوجود الخطأ كي تظهر بشكل صحيح داخل التطبيق + سطر يبدأ ب ...//:vpn + + + + Key + مفتاح + + + + Insert + ادخل + + + + Continue + واصل + + + + PageSetupWizardViewConfig + + + New connection + اتصال جديد + + + + Do not use connection code from public sources. It could be created to intercept your data. + لا تستخدم رمز الاتصال من مصادر مفتوحة, قد تكون مصنوعة للتعارض مع بياناتك. + + + + Collapse content + طي المحتوي + + + + Show content + اظهر المحتوي + + + + Connect + اتصل + + + + PageShare + + + Save OpenVPN config + احفظ تكوين OpenVPN + + + + Save WireGuard config + احفظ تكوين WireGuard + + + + Save ShadowSocks config + احفظ تكوين ShadowSocks + + + + Save Cloak config + احفظ تكوين Cloak + + + + For the AmneziaVPN app + AmneziaVPN من اجل تطبيق + + + + OpenVpn native format + تنسيق OpenVpn الاصلي + + + + WireGuard native format + تنسيق WireGuard الاصلي + + + + ShadowSocks native format + تنسيق ShadowSocks الاصلي + + + + Cloak native format + تنسيق Cloak الاصلي + + + + Share VPN Access + شارك اتصال VPN + + + + Share full access to the server and VPN + شارك ولوج كامل للخادم و ال VPN + + + + Use for your own devices, or share with those you trust to manage the server. + استخدمه للأجهزة الخاصة بك، أو شاركه مع من تثق بهم لإدارة الخادم. + + + + + Users + المستخدمين + + + + Share VPN access without the ability to manage the server + شارك اتصال VPN بدون القدرة علي إدارة الخادم + + + + Search + ابحث + + + + Creation date: + تاريخ الإنشاء: + + + + Rename + إعادة التسمية + + + + Client name + اسم العميل + + + + Save + احفظ + + + + Revoke + سحب وإبطال + + + + Revoke the config for a user - %1? + سحب وإبطال للمستخدم - %1? + + + + The user will no longer be able to connect to your server. + المستخدم لن يكون قادر علي الاتصال بعد الان. + + + + Continue + واصل + + + + Cancel + إلغاء + + + + Connection + الاتصال + + + Full access to server + ولوج كامل للخادم + + + Servers + الخوادم + + + + + Server + خادم + + + + File with connection settings to + ملف بإعدادات إلي + + + Protocols + البروتوكولات + + + + + Protocol + بروتوكول + + + + Connection to + اتصال إلي + + + + Config revoked + تم سحب وإبطال التكوين + + + + User name + اسم المستخدم + + + + + Connection format + تنسيق الاتصال + + + + + Share + شارك + + + + PageShareFullAccess + + + Full access to the server and VPN + ولوج كامل للخادم و ال VPN + + + + We recommend that you use full access to the server only for your own additional devices. + + نحن ننصحك بأستخدام ولوج كامل للخادم فقط لأجهزتك الاضافية. + + + + + If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. + إذا شاركت ولوج كامل مع الاشخاص, سيكونو قادرين علي حذف وإضافة بروتوكولات و خدمات إلي الخادم, والذي سيجعل VPN يعمل بشكل غير صحيح لجميع المستخدمين. + + + + + Server + خادم + + + + Accessing + التواصل + + + + File with accessing settings to + ملف مع إعدادات الوصول إلي + + + + Share + مشاركة + + + + Connection to + اتصال إلي + + + + File with connection settings to + معلف مع إعدادات الاتصال إلي + + + + PopupType + + + Close + اغلاق + + + + QKeychain::DeletePasswordJobPrivate + + + Password entry not found + لم يتم العثور علي مدخلات كلمة المرور + + + + Could not decrypt data + فشل فك تشفير البيانات + + + + + Unknown error + خطأ غير معروف + + + + Could not open wallet: %1; %2 + فشل فتح المحفظة: %1; %2 + + + + Password not found + لم يتم العثور علي كلمة المرور + + + + Could not open keystore + فشل فتح مخزن المفاتيح + + + + Could not remove private key from keystore + فشل حذف المفتاح الخاص من مخزن المفاتيح + + + + QKeychain::JobPrivate + + + Unknown error + خطأ غير معروف + + + + Access to keychain denied + الولو سلسلة المفاتيح محظور + + + + QKeychain::PlainTextStore + + + Could not store data in settings: access error + فشل تخزين البيانات في الإعدادات: خطأ ولوج + + + + Could not store data in settings: format error + فشل تخزين البيانات في الإعدادات: خطأ في التنسيق + + + + Could not delete data from settings: access error + فشل في حذف البيانات من الإعدادات: خطأ ولوج + + + + Could not delete data from settings: format error + فشل حذف البيانات من الإعدادات: خطأ في التنسيق + + + + Entry not found + لم يتم العثور علي المدخلات + + + + QKeychain::ReadPasswordJobPrivate + + + Password entry not found + لم يتم العثور علي مدخلات كلمة المرور + + + + + Could not decrypt data + فشل فك تشفير البيانات + + + + D-Bus is not running + D-Bus لا يعمل + + + + + Unknown error + خطأ غير معروف + + + + No keychain service available + + + + + Could not open wallet: %1; %2 + فشل فتح المحفظة: %1; %2 + + + + Access to keychain denied + الولوج إلي سلسة المفاتيح محظور + + + + Could not determine data type: %1; %2 + فشل تحديد نوع البيانات: %1; %2 + + + + + Entry not found + لم يتم العثور علي المدخلات + + + + Unsupported entry type 'Map' + نوع مدخلات غير مٌدعم 'Map' + + + + Unknown kwallet entry type '%1' + نوع المدخلات kwaller غير معروف '%1' + + + + Password not found + لم يتم العثور علي كلمة المرور + + + + Could not open keystore + فشل في فتح مخزن المفاتيح + + + + Could not retrieve private key from keystore + فشل استرداد المفتاح الخاص من مخزن المفاتيح + + + + Could not create decryption cipher + فشل في إنشاء شفرة فك تشفير + + + + QKeychain::WritePasswordJobPrivate + + + Credential size exceeds maximum size of %1 + حجم الاعتماد يتجاوز الحجم الاقصي ل: %1 + + + + Credential key exceeds maximum size of %1 + مفتاح الاعتماد يتجاوز الحد الاقصي ل: %1 + + + + Writing credentials failed: Win32 error code %1 + فشل في كتابة الاعتماد: Win32 خطأ: %1 + + + + Encryption failed + فشل التشفير + + + + D-Bus is not running + D-Bus لا يعمل + + + + + Unknown error + خطأ غير معروف + + + + Could not open wallet: %1; %2 + فشل في فتح المحفظة: %1; %2 + + + + Password not found + لم يتم العثور علي كلمة المرور + + + + Could not open keystore + فشل في فتح مخزن المفاتيح + + + + Could not create private key generator + فشل ف إنشاء مولد المفاتيح الخاصة + + + + Could not generate new private key + فشل في إنشاء مفتاح خاص جديد + + + + Could not retrieve private key from keystore + فشل في استرداد مفتاح خاص من مخزن المفاتيح + + + + Could not create encryption cipher + فشل في إنشاء شفرة التشفير + + + + Could not encrypt data + فشل في تشفير الداتا + + + + QObject + + + Sftp service + خدمة Sftp + + + + No error + لا يوجد خطأ + + + + Unknown Error + خطأ غير معروف + + + + Function not implemented + لم يتم تنفيذ الوظيفة + + + + Server check failed + فشل في فحص الخادم + + + + Server port already used. Check for another software + منفذ الخادم بالفعل مٌستخدم, تحقق من باقي التطبيقات + + + + Server error: Docker container missing + خطأ من الخادم: حاوية Docker مفقودة + + + + Server error: Docker failed + خطأ من الخادم: فشل Docker + + + + Installation canceled by user + تم اغلاق التثبيت بواسطة المستخدم + + + + The user does not have permission to use sudo + ليس لدي المستخدم الصلحيات لأستخدام sudo + + + + Ssh request was denied + طلب Ssh محظو + + + + Ssh request was interrupted + إنقطع طلب Ssh + + + + Ssh internal error + مشكلة داخلية Ssh + + + + Invalid private key or invalid passphrase entered + مفتا ح خاص غير صحيح او عبارة مرور غير صحيحة + + + + The selected private key format is not supported, use openssh ED25519 key types or PEM key types + التنسيق المٌحدد للمفتاح الخاص غير مدعوم, استخدم نوع مفتاح openssh ED25519 او نوع مفتاح PEM + + + + Timeout connecting to server + انتهت مدة الاتصال بالخادم + + + + Sftp error: End-of-file encountered + + + + + Sftp error: File does not exist + خطأ Sftp: الملف غير موجود + + + + Sftp error: Permission denied + خطأ Sftp: تم حظر الصلحيات + + + + Sftp error: Generic failure + خطأ Sftp: فشل عام + + + + Sftp error: Garbage received from server + خطأ Sftp: تم استلام نفايات من الخادم + + + + Sftp error: No connection has been set up + خطأ Sftp: لم يتم إعداد اتصال + + + + Sftp error: There was a connection, but we lost it + خطأ Sftp: كان هناك اتصال, ولكن خسرناه + + + + Sftp error: Operation not supported by libssh yet + خطأ Sftp: العملية ليست مدعومة من libssh بعد + + + + Sftp error: Invalid file handle + + + + + Sftp error: No such file or directory path exists + خطأ Sftp: لا يوجد مسار ملف او مجلد مثل هذا + + + + Sftp error: An attempt to create an already existing file or directory has been made + خطأ Sftp: محاولة إنشاء ملف او مجلد موجود بالفعل + + + + Sftp error: Write-protected filesystem + خطأ Sftp: نظام كتابة الملفات محمي + + + + Sftp error: No media was in remote drive + خطأ Sftp: لا يوجد وسائط في القرص البعيد + + + + VPN connection error + + + + + Error when retrieving configuration from API + خطأ عند استرداد التكوين من API + + + + This config has already been added to the application + هذا التكوين بالفعل تمت إضافتة للبرنامج + + + + ErrorCode: %1. + + + + + OpenVPN config missing + OpenVpn تكوين مفقود + + + + OpenVPN management server error + OpenVpn خطأ في إدارة الخادم + + + + OpenVPN executable missing + OpenVpn executeable مفقود + + + + ShadowSocks (ss-local) executable missing + ShadowSocks (ss-local) executable مفقود + + + + Cloak (ck-client) executable missing + Cloak (ck-client) executable مفقود + + + + Amnezia helper service error + خطأ في خدمة مٌساعد Amnezia + + + + OpenSSL failed + فشل OpenSSL + + + + Can't connect: another VPN connection is active + لا يمكن الاتصال: هناك اتصال VPN اخر بالفعل يعمل + + + + Can't setup OpenVPN TAP network adapter + لا يمك نتثبيت محول شبكة OpenVPN TAP + + + + VPN pool error: no available addresses + VPN pool error: لا يوجد عنواين مٌتاحة + + + + The config does not contain any containers and credentials for connecting to the server + التكوين لا يحتوي علي اي حاويات و اعتماد للأتصال بالخادم + + + + Internal error + خطأ داخلي + + + + IPsec + + + + + + Website in Tor network + موقع في شبكة Tor + + + + Amnezia DNS + + + + + Sftp file sharing service + ملف Sftp: خدمة المشاركة + + + + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. + بروتوكول OpenVPN احد اشهر بروتوكولات VPN, مع مرونة في إعدادات التكوين. يستخدم بروتوكول امان خاص به مع SSL/TLS لتغير المفاتيح. + + + + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but it may be recognized by analysis systems in some highly censored regions. + بروتوكول ShadowSocks- يتنكر في حركة مرور VPN, يبدو ك حركة مرور الويب العادية +ولكن قد يتم التعرف عليه من خلال أنظمة التحليل في بعض المناطق شديدة الرقابة. + + + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + بروتوكول OpenVPN over Cloak هو OpenVPN مع VPN يتنكر كحركة مرور على الويب ويوفر الحماية + ضد عمليات الكشف النشط. مثالية لتجاوز الحجب في المناطق ذات أعلى مستويات الرقابة. + + + + Create a file vault on your server to securely store and transfer files. + انشأ مخزن ملفات علي الخادم الخاص بك حتي تخزن الملفات و تنقلها بسرية. + + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for protecting against blocking. + +OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works over TCP network protocol, 443 port. + + هذه مجموعة من بروتوكول OpenVPN و برنامج Cloak المساعد مٌصمم خصيصاً للحماية ضد الحجب + +يوفر OpenVPN اتصال VPN امن عن طريق تشفير جميع حركات المرور بين العميل والخادم + +Cloak يحمي OpenVPN من ان يٌكتشف والحجب + +يمكن ان يعدل Cloak حزمة البيانات حتي يجعل حركة مرور VPN تبدو بالكامل كحركة ويب طبيعية, +وايضاُ يحمي ال VPN من ان يٌكتشف عن طريق انظمة الكشف الفعالة. هذا يجعلة مقاوم جداُ لأن يٌكتشف + +فوراُ بعد استلام اول حزمة بيانات, يصادق Cloak الاتصال القادم. +إذا فشل التصادق, البرنامج المساعد يجعل الخادم يبدو ك موقع مزيف ويصبح ال VPN مخفي لأنظمة التحليل. + +إذا كان هناك رقابة شديدة علي الانترنت في منطقتك, نحن ننصحك بأن تستخدم OpenVPN over Cloak من اول اتصال + +* مٌتاح في AmneziaVPN عبر جميع المنصات +* استهلاك طاقة عالية علي اجهزة المحمول +* مرونة في الإعدادات +* لا يٌكتشف بواسطة انظمة تحليل DPI +* يعمل عبر بروتوكول شبكة TCK, منفذ 443. + + + + + A relatively new popular VPN protocol with a simplified architecture. +WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. + بروتوكول VPN جديد وشارع ذو بنية مبسطة. +يوفر WireGuard اتصال VPN مستقر و اداء عالي علي جميع الاجهزة. يستعمل إعدادات تشفير معقدة. WireGuard مٌقارنة مع OpenVPN يتمتع بزمن وصول أقل وتحسين إنتاجية نقل البيانات. +بسبب توقيعات الحزمة المميزة WireGuard عرضة جداُ للحجب. علي عكس باقي برتوكولات VPN التي تستعمل تقنيات تشويش. حزمة أنماط التوقيع المتسقة الخاصة ب WireGuard يمكن التعرف عليها بسهولة ولذلك تٌحجب بواسطة أنظمة الفحص العميق للحزم (DPI) المتقدمة وأدوات مراقبة الشبكة الأخرى. + +* مٌتاح في AmneziaVPN عبر جميع المنصات +* استهلاك قليل للطاقة +* عدد قليل من الإعدادات +سهل التعرف علية بواسطة انظمة تحليل DPI, عرضة للحجب +* يعمل عبر بروتوكول شبكة UDP. + + + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + بروتوكول WireGuard - بروتوكول شائع ب اداء عالي, سرعة عالية واستهلاك قليل للطاقة. ينصح للمناطق ذات مستوي منخفض من الرقابة. + + + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. + بروتوكول AmneziaWG - بروتوكول خاص من Amnezia, يعتمد علي WireGuard. سريع مثل WireGuard, لكن مقاوم جداً للحجب. ينصح للمناطق ذات مستوي عالي من الرقابة. + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. + بروتوكول IKEv2 - بروتوكول مستقر حديث, اسرع بقليل من الباقي, يسترجع الاتصال بعد خسارة الاشارة. لدية يتمتع بدعم أصلي على أحدث إصدارات Android وiOS. + + + + Deploy a WordPress site on the Tor network in two clicks. + انشر موقع WordPress علي شبكة Tor في ضغطتين. + + + + Replace the current DNS server with your own. This will increase your privacy level. + استبدل خادم ال DNS الحالي مع الخادم الخاص بك, هذا سيزيد من خصوصيتك. + + + + OpenVPN stands as one of the most popular and time-tested VPN protocols available. +It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. + +* Available in the AmneziaVPN across all platforms +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices +* Recognised by DPI analysis systems and therefore susceptible to blocking +* Can operate over both TCP and UDP network protocols. + يبقا OpenVPN كأحد اشهر بروتوكولات VPN و التي تم اختبارها عبر الزمن. +ينشأ بروتوكول امان مميز, يستفيد من SSL/TLS للتشفير و تغير المفاتيح. واكثر من ذلك, OpenVpn يدعم تعدد طرق المصادقة يجعلة متعدد الاستخدامات وقابلة للتكيف, تلبية مجموعة واسعة من الأجهزة وأنظمة التشغيل. بسبب طبيعتة مفتوحة المصدر, يستفيد OpenVPN من التدقيق الشامل من قبل المجتمع العالمي, مما يعزز أمنها باستمرار. مع توازن قوي بين الأداء والأمان والتوافق, يظل OpenVPN الخيار الأفضل للأفراد والشركات المهتمين بالخصوصية على حدٍ سواء. + +* مٌتاح في AmneziaVPN عبر جميع المنصات +* استهلاك طاقة عادي علي اجهزة المحمول +* مرونة في التخصيص كي يلائم احتياجات المستخدم حتي يعمل مع انظمة تشغيل واجهزة مختلفة +* يٌلاحظ بواسطة انظمة تحليل DPI و لذلك عرضة للحجب +* يمكن ان يعمل علي بروتوكولات شبكة TCP و UDP. + + + + Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. + +* Available in the AmneziaVPN only on desktop platforms +* Normal power consumption on mobile devices + +* Configurable encryption protocol +* Detectable by some DPI systems +* Works over TCP network protocol. + Shadowsocks, مستوحي من بروتوكول SOCKS5, يحمي الاتصال بأستعمال شفرة AEAD. كذلك Shadowsocks صٌمم كي يكون متحفظاً ويصعب تحديدة, إنه ليس مطابقًا لاتصال HTTPS القياسي. عمتاُ. بعض انظمة تحليل حركات المرور قد تتعرف علي اتصال Shadowsocks. بسبب الدعم المحدود في Amnezia, يٌنصح بأستخدام بروتوكول AmneziaWG. + +* مٌتاح في AmneziaVPN عبر جميع المنصات +* استهلاك طاقة عادي علي اجهزة المحمول + +* بروتوكول تشفير قابل للتكوين +* قابل للكشف بواسطة بعض انظمة DPI +* يعمل عبر بروتوكول شبكة TCP. + + + + A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. +While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. +This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Not recognised by DPI analysis systems, resistant to blocking +* Works over UDP network protocol. + لفة سريعة من بروتوكولات VPN الحديثة والشائعة, يٌبني AmneziaWG علي الاساس الموضع من قبل WireGuard, مع الاحتفاظ ببنيته المبسطة وقدرات الأداء العالي عبر الاجهزة. +بينما WireGuard معروف بأدائة العالي. لدية مشاكل مع سهولة التعرف علية بسبب توقيعات الحزمة المميزة الخاصة بة. يٌصلح AmneziaWG هذه المشكلة عن طريق استخدام طرق تشويش افضل, يجعل حركة المرور تبقا مع حركة مرور انترنت عادية. +هذا يعني ان AmneziaWG يبقا الاداء العالي الاساسي بينما يضيف طبقة من العزل, هذا يجعلة اختيار ممتاز لهولاء الذين يريدون اتصال VPN سريع و متخفي. + +* مٌتاح في AmneziaVPN علي جميع المنصات +* استهلاك طاقة قليل +* اقل عدد من الإعدادات +* لا يٌكتشف من قبل انظمة تحليل DPI, مقاوم للحجب +* يعمل عبر بروتوكول شبكة UDP. + + + + IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol. +One of its distinguishing features is its ability to swiftly switch between networks and devices, making it particularly adaptive in dynamic network environments. +While it offers a blend of security, stability, and speed, it's essential to note that IKEv2 can be easily detected and is susceptible to blocking. + +* Available in the AmneziaVPN only on Windows +* Low power consumption, on mobile devices +* Minimal configuration +* Recognised by DPI analysis systems +* Works over UDP network protocol, ports 500 and 4500. + IKEv2, مقترن مع طبقة التشفير IPSec, يبقا بروتوكول VPN مستقر و حديث. +من مميزاتةقدرته على التبديل بسرعة بين الشبكات والأجهزة، مما يجعله قابلاً للتكيف بشكل خاص في بيئات الشبكات الديناميكية. + +*. مٌتاح في AmneziaVPN فقط علي منصة وندوز +* استهلاك طاقة قليل, علي اجهزة المحمول +* اقل تكوين +* يٌلاحظ بواسطة انظمة تحليل DPI +* يعمل عبر بروتوكول شبكة UDP, منفذ 500 و منفذ 4500. + + + + DNS Service + خدمة ال DNS + + + + Sftp file sharing service - is secure FTP service + خدمة نشر ملف Sftp - هي خدمة FTP امنة + + + + Entry not found + لم يتم العثور علي مدخلات + + + + Access to keychain denied + الولوج ل سلسلة المفاتيح محظور + + + + No keyring daemon + + + + + Already unlocked + بالفعل تم فتحة + + + + No such keyring + لا يوجد مثل هذه المفاتيح + + + + Bad arguments + معطيات سيئة + + + + I/O error + I/0 خطأ + + + + Cancelled + تم إغلاقة + + + + Keyring already exists + المفتاح موجود بالفعل + + + + No match + لا تطباق + + + + Unknown error + خطأ غير معروف + + + + error 0x%1: %2 + خطأ %1: %2 + + + + SelectLanguageDrawer + + + Choose language + اختر لغة + + + + Settings + + + Server #1 + خادم #1 + + + + + Server + خادم + + + + SettingsController + + + Backup file is corrupted + ملف النسخه الاحتياطيه تالف + + + + All settings have been reset to default values + تم استرجاع جميع الإعدادات للإعدادات الافتراضية + + + + Cached profiles cleared + تم حذف الملفات الشخصية المٌخزنة مؤقتاُ + + + + ShareConnectionDrawer + + + + Save AmneziaVPN config + احفظ تكوين AmneziaVPN + + + + Share + شارك + + + + Copy + انسخ + + + + + Copied + تم النسخ + + + + Copy config string + انسخ نص التكوين + + + + Show connection settings + اظهر إعدادات الاتصال + + + Show content + 展示内容 + + + + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" + حتي تقرأ رمز ال QR في تطبيق Amnezia, اختار "إضافة خادم" - "لدي بيانات الاتصال" - "رمز Qr, او مفتاح تعريف او ملف إعدادات" + + + + SitesController + + + Hostname not look like ip adress or domain name + اسم المضيف لا يشبه عنوان IP أو اسم ال domain + + + + New site added: %1 + تمت إضافة موقع جديد: %1 + + + + Site removed: %1 + تم حذف الموقع: %1 + + + + Can't open file: %1 + لا يمكن فتح ملف: %1 + + + + Failed to parse JSON data from file: %1 + فشل قراءه بيانات JSON من الملف: %1 + + + + The JSON data is not an array in file: %1 + بيانات ال JSON ليست مصفوفة في الملف: %1 + + + + Import completed + اكتمل الاستيراد + + + + Export completed + اكتمل التصدير + + + + SystemTrayNotificationHandler + + + + Show + اظهر + + + + + Connect + اتصل + + + + + Disconnect + اقطع الاتصال + + + + + Visit Website + زور الموقع + + + + + Quit + اغلاق + + + + TextFieldWithHeaderType + + + The field can't be empty + الحقل لا يمكن ان يكون فارغ + + + + VpnConnection + + + Mbps + + + + + VpnProtocol + + + Unknown + غير معرف + + + + Disconnected + انقطع الاتصال + + + + Preparing + جاري التحضير + + + + Connecting... + جاري الاتصال... + + + + Connected + تم الاتصال + + + + Disconnecting... + جاري قطع الاتصال... + + + + Reconnecting... + جاري إعادة الاتصال... + + + + Error + خطأ + + + + amnezia::ContainerProps + + + Low + منخفض + + + + Medium or High + متوسط او عالي + + + + Extreme + شديد + + + + I just want to increase the level of my privacy. + انا فقط اريد زيادة مستوي الخصوصية. + + + + I want to bypass censorship. This option recommended in most cases. + أريد تجاوز الرقابة. يوصى بهذا الخيار في معظم الحالات. + + + + Most VPN protocols are blocked. Recommended if other options are not working. + يتم حظر معظم بروتوكولات VPN. يوصى به إذا كانت الخيارات الأخرى لا تعمل. + + + + main2 + + + Private key passphrase + عبارة المرور الخاصة بالمفتاح + + + + Save + احفظ + + + diff --git a/client/translations/amneziavpn_fa_IR.ts b/client/translations/amneziavpn_fa_IR.ts index 923e71802..9ae98d19c 100644 --- a/client/translations/amneziavpn_fa_IR.ts +++ b/client/translations/amneziavpn_fa_IR.ts @@ -1,51 +1,84 @@ + + AmneziaApplication + + Split tunneling for WireGuard is not implemented, the option was disabled + Раздельное туннелирование для "Wireguard" не реализовано,опция отключена + + + Split tunneling for %1 is not implemented, the option was disabled + جداسازی ترافیک برای %1 پیاده سازی نشده، این گزینه غیرفعال است + + + + AndroidController + + AmneziaVPN + AmneziaVPN + + + VPN Connected + Refers to the app - which is currently running the background and waiting + وی‎پی‎ان متصل است + + + + ApiController + + + Error when retrieving configuration from cloud server + خطا در حین دریافت پیکربندی از سمت سرور + + ConnectionController - - - - - Connect - + + VPN Protocols is not installed. + Please install VPN container at first + پروتکل وی‎پی‎ان نصب نشده است +لطفا کانتینر وی‎پی‎ان را نصب کنید - VPN Protocols is not installed. - Please install VPN container at first - + Connection... + در حال ارتباط... + + + + Connected + متصل + + + + Settings updated successfully, Reconnnection... + تنظیمات به روز رسانی شد +در حال اتصال دوباره... + + + + Settings updated successfully + تنظیمات با موفقیت به‎روز‎رسانی شدند + + + + Reconnection... + اتصال دوباره... + + + + + + + Connect + اتصال - Connection... - - - - - Connected - - - - - Reconnection... - - - - Disconnection... - - - - - Settings updated successfully, Reconnnection... - - - - - Settings updated successfully - + قطع ارتباط... @@ -53,17 +86,17 @@ Add new connection - + ایجاد ارتباط جدید Configure your server - + تنظیم سرور Open config file, key or QR code - + بارگذاری فایل تنظیمات، کلید یا QR Code @@ -71,22 +104,22 @@ C&ut - + &بریدن &Copy - + &کپی &Paste - + &پیوست &SelectAll - + &انتخاب همه @@ -94,7 +127,7 @@ Access error! - + خطای دسترسی! @@ -102,20 +135,24 @@ Unable change protocol while there is an active connection - + امکان تغییر پروتکل در هنگام متصل بودن وجود ندارد The selected protocol is not supported on the current platform - + پروتکل انتخاب شده بر روی این پلتفرم پشتیبانی نمی‎‎شود + + + Reconnect via VPN Procotol: + پروتکل VPN را متصل مجدد کنید" ImportController - + Scanned %1 of %2. - + ارزیابی %1 از %2. @@ -124,30 +161,32 @@ %1 installed successfully. - + %1 با موفقیت نصب شد %1 is already installed on the server. - + %1 در حال حاضر بر روی سرور نصب شده است Added containers that were already installed on the server - + +کانتینرهایی که بر روی سرور موجود بودند اضافه شدند Already installed containers were found on the server. All installed containers have been added to the application - + +کانتینرهای نصب شده بر روی سرور شناسایی شدند. تمام کانتینترهای نصب شده به نرم افزار اضافه شدند Settings updated successfully - + تنظیمات با موفقیت به‎روز‎رسانی شدند @@ -157,27 +196,27 @@ Already installed containers were found on the server. All installed containers Server '%1' was removed - + سرور %1 حذف شد All containers from server '%1' have been removed - + تمام کانتینترها از سرور %1 حذف شدند %1 has been removed from the server '%2' - + %1 از سرور %2 حذف شد Please login as the user - + لطفا به عنوان کاربر وارد شوید Server added successfully - + سرور با موفقیت اضافه شد @@ -185,17 +224,17 @@ Already installed containers were found on the server. All installed containers Read key failed: %1 - + خواندن کلید انجام نشد: %1 Write key failed: %1 - + نوشتن کلید انجام نشد: %1 Delete key failed: %1 - + حذف کلید انجام نشد: %1 @@ -204,27 +243,27 @@ Already installed containers were found on the server. All installed containers AmneziaVPN - + AmneziaVPN VPN Connected - + وی‎پی‎ان وصل شد VPN Disconnected - + وی‎پی‎ان قطع شد AmneziaVPN notification - + اخطار AmneziaVPN Unsecured network detected: - + شبکه ناامن شناسایی شد: @@ -232,30 +271,30 @@ Already installed containers were found on the server. All installed containers Removing services from %1 - + حذف سرویس‎ها از %1 Usually it takes no more than 5 minutes - + معمولا بیش از 5 دقیقه طول نمی‎کشد PageHome - + VPN protocol - + پروتکل وی‎پی‎ان - + Servers - + سرورها - + Unable change server while there is an active connection - + امکان تغییر سرور در هنگام متصل بودن وجود ندارد @@ -263,87 +302,91 @@ Already installed containers were found on the server. All installed containers AmneziaWG settings - + تنظیمات AmneziaWG Port - + پورت Junk packet count - + تعداد بسته‎های ناخواسته Junk packet minimum size - + Junk packet minimum size Junk packet maximum size - + Junk packet maximum size Init packet junk size - + Init packet junk size Response packet junk size - + Response packet junk size Init packet magic header - + Init packet magic header Response packet magic header - + Response packet magic header Transport packet magic header - + Transport packet magic header Underload packet magic header - + Underload packet magic header Remove AmneziaWG - + حذف AmneziaWG Remove AmneziaWG from server? - + آیا میخواهید AmneziaWG از سرور حذف شود؟ All users with whom you shared a connection will no longer be able to connect to it. - + تمام کاربرانی که این اتصال را با آن‎ها با اشتراک گذاشته‎اید دیگر نمی‎توانند به آن متصل شوند. + + + All users who you shared a connection with will no longer be able to connect to it. + همه کاربرانی که با آن‌ها این پروتکل VPN را به اشتراک گذاشته‌اید دیگر نمی‌توانند به آن متصل شوند. Continue - + ادامه Cancel - + کنسل Save and Restart Amnezia - + ذخیره و راه اندازی مجدد Amnezia @@ -351,28 +394,28 @@ Already installed containers were found on the server. All installed containers Cloak settings - + تنظیمات Cloak Disguised as traffic from - + پنهان کردن به عنوان ترافیک از Port - + پورت Cipher - + رمزگذاری Save and Restart Amnezia - + ذخیره و راه اندازی دوباره Amnezia @@ -380,7 +423,11 @@ Already installed containers were found on the server. All installed containers OpenVPN settings - + تنظیمات OpenVPN + + + VPN Addresses Subnet + آدرس زیرشبکه وی‎پی‎ان @@ -390,185 +437,189 @@ Already installed containers were found on the server. All installed containers Network protocol - + پروتکل شبکه Port - + پورت Auto-negotiate encryption - + رمزگذاری خودکار Hash - + هش SHA512 - + SHA512 SHA384 - + SHA384 SHA256 - + SHA256 SHA3-512 - + SHA3-512 SHA3-384 - + SHA3-384 SHA3-256 - + SHA3-256 whirlpool - + whirlpool BLAKE2b512 - + BLAKE2b512 BLAKE2s256 - + BLAKE2s256 SHA1 - + SHA1 Cipher - + رمزگذاری AES-256-GCM - + AES-256-GCM AES-192-GCM - + AES-192-GCM AES-128-GCM - + AES-128-GCM AES-256-CBC - + AES-256-CBC AES-192-CBC - + AES-192-CBC AES-128-CBC - + AES-128-CBC ChaCha20-Poly1305 - + ChaCha20-Poly1305 ARIA-256-CBC - + ARIA-256-CBC CAMELLIA-256-CBC - + CAMELLIA-256-CBC none - + none TLS auth - + اعتبار TLS Block DNS requests outside of VPN - + مسدود کردن درخواست‎های DNS خارج از وی‎پی‎ان Additional client configuration commands - + تنظیمات و دستورات اضافه برنامه متصل شونده Commands: - + دستورات: Additional server configuration commands - + تنظیمات و دستورات اضافه سرور Remove OpenVPN - + حذف OpenVPN Remove OpenVpn from server? - + آیا میخواهید OpenVPN از سرور حذف شود؟ All users with whom you shared a connection will no longer be able to connect to it. - + تمام کاربرانی که این اتصال را با آن‎ها با اشتراک گذاشته‎اید دیگر نمی‎توانند به آن متصل شوند. + + + All users who you shared a connection with will no longer be able to connect to it. + همه کاربرانی که با آن این پروتکل VPN را به اشتراک گذاشته‌اید دیگر نمی‌توانند به آن متصل شوند. Continue - + ادامه Cancel - + کنسل Save and Restart Amnezia - + ذخیره و راه اندازی دوباره Amnezia @@ -576,42 +627,46 @@ Already installed containers were found on the server. All installed containers settings - + تنظیمات Show connection options - + نمایش تنظیمات اتصال Connection options %1 - + تنظیمات اتصال %1 Remove - + حذف Remove %1 from server? - + %1 از سرور حذف شود؟ All users with whom you shared a connection will no longer be able to connect to it. - + تمام کاربرانی که این اتصال را با آن‎ها با اشتراک گذاشته‎اید دیگر نمی‎توانند به آن متصل شوند. + + + All users who you shared a connection with will no longer be able to connect to it. + همه کاربرانی که با آن این پروتکل VPN را به اشتراک گذاشته‌اید دیگر نمی‌توانند به آن متصل شوند. Continue - + ادامه Cancel - + کنسل @@ -619,23 +674,30 @@ Already installed containers were found on the server. All installed containers ShadowSocks settings - + تنظیمات ShadowSocks Port - + پورت Cipher - + رمزگذاری Save and Restart Amnezia - + ذخیره و راه اندازی دوباره Amnezia + + + + PageServerContainers + + Continue + ادامه دهید @@ -644,32 +706,33 @@ Already installed containers were found on the server. All installed containers A DNS service is installed on your server, and it is only accessible via VPN. - + یک سرویس DSN بر روی سرور شما نصب شده و فقط از طریق وی‎پی‎ان قابل دسترسی می‎باشد + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. - + آدرس DSN همان آدرس سرور شماست. میتوانید از قسمت تنظیمات و تب اتصالات DSN خود را تنظیم کنید Remove - + جذف Remove %1 from server? - + %1 از سرور حذف شود؟ Continue - + ادامه Cancel - + کنسل @@ -677,17 +740,17 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + تنظیمات با موفقیت به‎روز‎رسانی شد SFTP settings - + تنظیمات SFTP Host - + هاست @@ -695,69 +758,69 @@ Already installed containers were found on the server. All installed containers Copied - + کپی شد Port - + پورت Login - + ورود Password - + رمز عبور Mount folder on device - + بارگذاری پوشه بر روی دستگاه In order to mount remote SFTP folder as local drive, perform following steps: <br> - + برای بارگذاری پوشه SFTP بر روی درایو محلی قدم‎های زیر را انجام دهید: <br> <br>1. Install the latest version of - + <br> 1. آخرین نسخه را نصب کنید: <br>2. Install the latest version of - + <br> 2. آخرین نسخه را نصب کنید: Detailed instructions - + جزییات دستورالعمل‎ها Remove SFTP and all data stored there - + حذف SFTP و تمام داده‎های ذخیره شده در آن Remove SFTP and all data stored there? - + پوشه SFTP و تمام داده‎های آن حذف شوند؟ Continue - + ادامه Cancel - + کنسل @@ -765,57 +828,69 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + تنظیمات با موفقیت به‎روز‎‌رسانی شد Tor website settings - + تنظیمات وب‎سایت Tor Website address - + آدرس وب‎سایت Copied - + کپی شد - Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this URL. - + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this URL. + استفاده <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> برای باز کردن این نشانی. After creating your onion site, it takes a few minutes for the Tor network to make it available for use. - + پس از ایجاد سایت پیاز خود، چند دقیقه طول می‌کشد تا شبکه تور آن را برای استفاده فراهم کند. + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. + از <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> برای باز کردن این url استفاده کنید. + + + After installation it takes several minutes while your onion site will become available in the Tor Network. + بعد از نصب چند دقیقه طول میکشد که سایت پیازی شما در شبکه Tor در دسترس قرار گیرد. When configuring WordPress set the this onion address as domain. - + زمانی که سایت وردپرس را تنظیم میکنید این آدرس پیازی را به عنوان دامنه قرار دهید. + + + When configuring WordPress set the this address as domain. + هنگام تنظیم وردپرس، این آدرس پیاز را به عنوان دامنه مشخص کنید. Remove website - + حذف وب سایت The site with all data will be removed from the tor network. - + سایت با تمام داده‎ها از شبکه Tor حذف خواهد شد. Continue - + ادامه Cancel - + کنسل @@ -823,70 +898,74 @@ Already installed containers were found on the server. All installed containers Settings - + تنظیمات Servers - + سرورها Connection - + ارتباط Application - + نرم‎افزار Backup - + بک‎آپ About AmneziaVPN - + درباره Amnezia Close application - + بستن نرم‎افزار PageSettingsAbout + + Support the project with a donation + حمایت از پروژه با کمک‎های مالی + Support Amnezia - + پشتیبانی از Amnezia This is a free and open source application. If you like it, support the developers with a donation. - + این نرم‎افزار یک پروژه رایگان است. اگر آن را دوست دارید با کمک‎های مالی از توسعه‎دهندگان آن حمایت کنید. And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. - + و اگر آن‎را دوست ندارید دلایل بیشتری برای کمک به نرم‎افزار است، کمک‎های مالی شما برای بهبود نرم‎افزار استفاده میشود. Card on Patreon - + کارت روی Patreon https://www.patreon.com/amneziavpn - + https://www.patreon.com/amneziavpn Show other methods on Github - + نمایش متد‎های دیگر در گیت هاب @@ -896,57 +975,62 @@ Already installed containers were found on the server. All installed containers Contacts - + مخاطب Telegram group - + گروه تلگرام To discuss features - + برای گفتگو در مورد ویژگی‎ها https://t.me/amnezia_vpn_en - + https://t.me/amnezia_vpn Mail - + ایمیل For reviews and bug reports - + برای ارائه نظرات و گزارشات باگ Github - + Github https://github.com/amnezia-vpn/amnezia-client - + https://github.com/amnezia-vpn/amnezia-client Website - + وب سایت https://amnezia.org - + https://amnezia.org + + + + Software version: %1 + %1 :نسخه نرم‎افزار Check for updates - + بررسی بروز‎رسانی @@ -954,151 +1038,151 @@ Already installed containers were found on the server. All installed containers Application - + نرم افزار Allow application screenshots - + مجوز اسکرین‎شات در برنامه Auto start - + شروع خودکار Launch the application every time the device is starts - + راه‎اندازی نرم‎افزار با هر بار روشن شدن دستگاه Start minimized - + شروع به صورت کوچک Launch application minimized - + راه‎اندازی برنامه به صورت کوچک Language - + زبان Logging - + گزارشات Enabled - + فعال Disabled - + غیر فعال Reset settings and remove all data from the application - + ریست کردن تنظیمات و حذف تمام داده‎ها از نرم‎افزار Reset settings and remove all data from the application? - + ریست کردن تنظیمات و حذف تمام داده‎ها از نرم‎افزار؟ All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - + تمام تنظیمات به حالت پیش‎فرض ریست می‎شوند. تمام سرویس‎های Amnezia بر روی سرور باقی می‎مانند. Continue - + ادامه Cancel - + کنسل PageSettingsBackup - - - Settings restored from backup file - - Backup - + پشتیبان‎گیری + + + + Settings restored from backup file + تنظیمات از فایل پشتیبان بازیابی شد Configuration backup - + پشتیبان‎گیری از پیکربندی You can save your settings to a backup file to restore them the next time you install the application. - + می‎توانید تنظیمات را در یک فایل پشتیبان ذخیره کرده و دفعه بعد که نرم‎افزار را نصب کردید آن‎ها را بازیابی کنید. Make a backup - + ایجاد یک پشتیبان Save backup file - + ذخیره فایل پشتیبان Backup files (*.backup) - + Backup files (*.backup) Backup file saved - + فایل پشتیبان ذخیره شد Restore from backup - + بازیابی از پشتیبان Open backup file - + باز کردن فایل پشتیبان Import settings from a backup file? - + ورود تنظیمات از فایل پشتیبان؟ All current settings will be reset - + تمام تنظیمات جاری ریست خواهد شد Continue - + ادامه Cancel - + کنسل @@ -1106,120 +1190,128 @@ Already installed containers were found on the server. All installed containers Connection - + ارتباط Auto connect - + اتصال خودکار Connect to VPN on app start - + اتصال به وی‎‎پی‎ان با شروع نرم‎افزار Use AmneziaDNS - + استفاده از AmneziaDNS If AmneziaDNS is installed on the server - + اگر AmneziaDNS بر روی سرور نصب شده باشد DNS servers - + سرورهای DNS When AmneziaDNS is not used or installed - - - - - Site-based split tunneling - - - - - Allows you to select which sites you want to access through the VPN - - - - - App-based split tunneling - + وقتی AmneziaDNS استفاده نشده یا نصب نشده است. Allows you to use the VPN only for certain Apps + + If AmneziaDNS is not used or installed + اگر AmneziaDNS نصب نشده یا استفاده نشود + + + + Site-based split tunneling + جداسازی ترافیک بر اساس سایت + + + + Allows you to select which sites you want to access through the VPN + میتوانید مشخص کنید که چه سایت‎هایی از وی‎پی‎ان استفاده کنند + + + + App-based split tunneling + جداسازی ترافیک بر اساس نرم‎افزار + + + Allows you to use the VPN only for certain applications + میتوانید مشخص کنید که چه نرم‎افزارهایی از وی‎پی‎ان استفاده کنند + PageSettingsDns Default server does not support custom dns - + سرور پیش‌فرض از دی‌ان‌اس سفارشی پشتیبانی نمی‌کند. DNS servers - + سرورهای DNS If AmneziaDNS is not used or installed - + اگر AmneziaDNS نصب نباشد یا استفاده نشود Primary DNS - + DNS اصلی Secondary DNS - + DNS ثانویه Restore default - + بازگشت به پیش‎فرض Restore default DNS settings? - + بازگشت به تنظیمات پیش‎فرض DNS؟ Continue - + ادامه Cancel - + کنسل Settings have been reset - + تنظیمات ریست شد Save - + ذخیره Settings saved - + ذخیره تنظیمات @@ -1227,62 +1319,62 @@ Already installed containers were found on the server. All installed containers Logging - + گزارشات Save logs - + ذخیره گزارشات Open folder with logs - + باز کردن پوشه گزارشات Save - + ذخیره Logs files (*.log) - + Logs files (*.log) Logs file saved - + فایل گزارشات ذخیره شد Save logs to file - + ذخیره گزارشات در فایل Clear logs? - + پاک کردن گزارشات؟ Continue - + ادامه Cancel - + کنسل Logs have been cleaned up - + گزارشات پاک شدند Clear logs - + پاک کردن گزارشات @@ -1290,30 +1382,30 @@ Already installed containers were found on the server. All installed containers All installed containers have been added to the application - - - - - No new installed containers found - + تمام کانتینرهای نصب شده به نرم‎افزار اضافه شدند Clear Amnezia cache - + پاک کردن حافظه داخلی Amnezia May be needed when changing other settings - + وقتی تنظیمات دیگر را تغییر دهید ممکن است نیاز باشد Clear cached profiles? - + پاک کردن پروفایل ذخیره شده؟ - + + No new installed containers found + کانتینر نصب شده جدیدی پیدا نشد + + + @@ -1322,88 +1414,79 @@ Already installed containers were found on the server. All installed containers - - Continue - + ادامه - - Cancel - + کنسل Check the server for previously installed Amnezia services - + چک کردن سرویس‎های نصب شده Amnezia بر روی سرور Add them to the application if they were not displayed - + اضافه کردن آنها به نرم‎افزار اگر نمایش داده نشده‎اند Reboot server - + سرور را دوباره راه‌اندازی کنید. Do you want to reboot the server? - + آیا می‌خواهید سرور را دوباره راه‌اندازی کنید؟ The reboot process may take approximately 30 seconds. Are you sure you wish to proceed? - - - - - Remove server from application - + فرآیند راه‌اندازی ممکن است حدود ۳۰ ثانیه طول بکشد. آیا مطمئن هستید که می‌خواهید ادامه دهید؟ Do you want to remove the server from application? - + آیا می‌خواهید سرور را از برنامه حذف کنید؟ + + + + Do you want to clear server from Amnezia software? + آیا می‌خواهید سرور را از نرم‌افزار Amnezia پاک کنید؟ + + + + Remove server from application + حذف کردن سرور از نرم‎افزار + + + Remove server? + حذف سرور؟ All installed AmneziaVPN services will still remain on the server. - + تمام سرویس‎های نصب‎شده Amnezia همچنان بر روی سرور باقی خواهند ماند. - Clear server from Amnezia software - + پاک کردن سرور از نرم‎افزار Amnezia - - - Do you want to clear server from Amnezia software? - + Clear server from Amnezia software? + سرور از نرم‎افزار Amnezia پاک شود؟ - All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. - - - - - Reset API config - - - - - Do you want to reset API config? - + تمام کانتینرها از سرور پاک شوند، به این معنی که تمام فایل‎های پیکربندی، کلیدها و مجوزها حذف خواهند شد. @@ -1411,27 +1494,27 @@ Already installed containers were found on the server. All installed containers Server name - + نام سرور Save - + ذخیره Protocols - + پروتکل‎ها Services - + سرویس‎ها Data - + داده @@ -1439,32 +1522,36 @@ Already installed containers were found on the server. All installed containers settings - + تنظیمات Remove - + حذف Remove %1 from server? - + حذف %1 از سرور؟ All users with whom you shared a connection will no longer be able to connect to it. - + تمام کاربرانی که این ارتباط را با آنها به اشتراک گذاشته‎اید دیگر نمی‎توانند به آن متصل شوند. + + + All users who you shared a connection with will no longer be able to connect to it. + تمام کاربرانی که با آن VPN را به اشتراک گذاشته‌اید، دیگر نمی‌توانند به آن متصل شوند. Continue - + ادامه Cancel - + کنسل @@ -1472,7 +1559,7 @@ Already installed containers were found on the server. All installed containers Servers - + سرورها @@ -1480,95 +1567,107 @@ Already installed containers were found on the server. All installed containers Default server does not support split tunneling function - + سرور پیش‌فرض از عملکرد تونل‌سازی تقسیم شده پشتیبانی نمی‌کند. - - Only the sites listed here will be accessed through the VPN - + Addresses from the list should be accessed via VPN + دسترسی به آدرس‎های لیست از طریق وی‎پی‎ان Addresses from the list should not be accessed via VPN - + دسترسی به آدرس‎های لیست بدون وی‎پی‎ان Split tunneling - + جداسازی ترافیک Mode - + حالت Remove - + حذف Continue - + ادامه Cancel - + کنسل + + + Site or IP + سایت یا آی‎پی + + + Import/Export Sites + بارگذاری / خروجی‎گرفتن از سایت‎ها + + + + Only the sites listed here will be accessed through the VPN + تنها سایت‌های موجود در اینجا از طریق VPN دسترسی داده خواهند شد. website or IP - + وب‌سایت یا آدرس IP Import / Export Sites - + وارد کردن / صادر کردن وب‌سایت‌ها Import - + بارگذاری Save site list - + ذخیره لیست سایت‎ها Save sites - + ذخیره سایت‎ها Sites files (*.json) - + Sites files (*.json) Import a list of sites - + بارگذاری لیست سایت‎ها Replace site list - + جایگزین کردن لیست سایت Open sites file - + باز کردن فایل سایت‎ها Add imported sites to existing ones - + اضافه کردن سایت‎های بارگذاری شده به سایت‎های موجود @@ -1576,66 +1675,95 @@ Already installed containers were found on the server. All installed containers Server connection - + ارتباط سرور Do not use connection code from public sources. It may have been created to intercept your data. It's okay as long as it's from someone you trust. - + از کد اتصالاتی که در منابع عمومی هستند استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشند. + +ایرادی ندارد که از طرف کسی باشد که به او اعتماد دارید. What do you have? - - - - - File with connection settings or backup - + چی داری؟ File with connection settings - + فایل شامل تنظیمات اتصال + + + + File with connection settings or backup + فایل شامل تنظیمات اتصال یا بک‎آپ Open config file - + باز کردن فایل تنظیمات QR-code - + QR-Code Key as text - + متن شامل کلید PageSetupWizardCredentials - - Configure your server - + Server connection + اتصال به سرور Server IP address [:port] - + آدرس آی‎پی سرور (:پورت) - - 255.255.255.255:22 - + 255.255.255.255:88 + 255.255.255.255:88 + + + Password / SSH private key + Password / SSH private key + + + + Continue + ادامه + + + All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties + تمام داده‎هایی که شما وارد می‎کنید به شدت محرمانه‎ است و با Amnezia یا هر شخص ثالث دیگری به اشتراک گذاشته نمی‎شود + + + + Enter the address in the format 255.255.255.255:88 + آدرس را با فرمت 255.255.255.255:88 وارد کنید Login to connect via SSH + ورود و اتصال با استفاده از SSH + + + + Configure your server + سرور خود را پیکربندی کنید + + + + 255.255.255.255:22 @@ -1643,35 +1771,25 @@ It's okay as long as it's from someone you trust. Password or SSH private key - - - Continue - - All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties - + تمام داده‎هایی که شما وارد می‎کنید به شدت محرمانه‎ است و با Amnezia یا هر شخص ثالث دیگری به اشتراک گذاشته نمی‎شود Ip address cannot be empty - - - - - Enter the address in the format 255.255.255.255:88 - + آدرس آی‎پی نمی‎تواند خالی باشد Login cannot be empty - + نام‎کاربری نمی‎تواند خالی باشد Password/private key cannot be empty - + پسورد یا کلید خصوصی نمی‎تواند خالی باشد @@ -1679,66 +1797,74 @@ It's okay as long as it's from someone you trust. What is the level of internet control in your region? - + سطح کنترل اینترنت در منطقه شما چگونه است؟ Set up a VPN yourself - + یک وی‎پی‎ان برای خودتان بسازید I want to choose a VPN protocol - + می‎خواهم پروتکل وی‎پی‎ان را انتخاب کنم Continue - + ادامه Set up later - + بعدا تنظیم شود PageSetupWizardInstalling - - - - Usually it takes no more than 5 minutes - - The server has already been added to the application - + سرور در حال حاضر به نرم‎افزار اضافه شده است + + + Amnezia has detected that your server is currently + Amnezia has detected that your server is currently + + + busy installing other software. Amnezia installation + busy installing other software. Amnezia installation Amnezia has detected that your server is currently - + برنامه Amnezia تشخیص داده است که سرور در حال حاضر busy installing other software. Amnezia installation - + مشغول نصب نرم‎افزار دیگری است. نصب Amnezia will pause until the server finishes installing other software - + متوقف شده تا زمانی که سرور نصب نرم‎افزار دیگر را تمام کند Installing - + در حال نصب Cancel installation - + لغو عملیات نصب + + + + + Usually it takes no more than 5 minutes + معمولا بیش از 5 دقیقه طول نمی‎کشد @@ -1746,32 +1872,32 @@ It's okay as long as it's from someone you trust. Installing %1 - + در حال نصب %1 More detailed - + جزییات بیشتر Close - + بستن Network protocol - + پروتکل شبکه Port - + پورت Install - + نصب @@ -1779,12 +1905,12 @@ It's okay as long as it's from someone you trust. VPN protocol - + پروتکل وی‎پی‎ان Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. - + پروتکلی که بیشترین اولویت را برای شما دارد انتخاب کنید. بعدا، میتوانید پروتکل‎ها و سرویس‎های اضافه مانند پروکسی DNS و SFTP را هم نصب کنید @@ -1792,7 +1918,7 @@ It's okay as long as it's from someone you trust. Point the camera at the QR code and hold for a couple of seconds. - + دوربین را روی QR Code بگیرید و برای چند ثانیه آن را نگه دارید. @@ -1800,27 +1926,27 @@ It's okay as long as it's from someone you trust. Settings restored from backup file - + تنظیمات از فایل بک‎آپ بازیابی شدند Free service for creating a personal VPN on your server. - + سرویس رایگان برای ایجاد وی‎پی‎ان شخصی بر روی سرور خودتان. Helps you access blocked content without revealing your privacy, even to VPN providers. - + به شما کمک می‎کند که بدون فاش کردن حریم شخصی خودتان حتی برای ارائه دهنده وی‎پی‎ان به محتوای مسدود شده دسترسی پیدا کنید. I have the data to connect - + من داده برای اتصال دارم I have nothing - + من هیچی ندارم @@ -1833,228 +1959,260 @@ It's okay as long as it's from someone you trust. Connection key - + کلید ارتباط A line that starts with vpn://... - + یک کلید متنی که با vpn:// شروع می‎شود Key - + کلید Insert - + وارد کردن Continue - + ادامه PageSetupWizardViewConfig - + New connection - + ارتباط جدید - + Do not use connection code from public sources. It could be created to intercept your data. - + از کد اتصالی که در منابع عمومی هست استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشد. - + Collapse content - + جمع کردن محتوا - + Show content - + نمایش محتوا - + Connect - + اتصال PageShare - - - Config revoked - - - - - Connection to - - - - - File with connection settings to - - - - - Save OpenVPN config - - - - - Save WireGuard config - - - - - Save ShadowSocks config - - - - - Save Cloak config - - - - - For the AmneziaVPN app - - OpenVpn native format - + فرمت OpenVPN WireGuard native format - + فرمت WireGuard - - ShadowSocks native format - - - - - Cloak native format - - - - - Share VPN Access - - - - - Share full access to the server and VPN - - - - - Use for your own devices, or share with those you trust to manage the server. - - - - - - Share - + VPN Access + دسترسی VPN Connection - + ارتباط - - - Users - + VPN access without the ability to manage the server + دسترسی به VPN بدون امکان مدیریت سرور - - Share VPN access without the ability to manage the server - - - - - User name - + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. + دسترسی به مدیریت سرور. کاربری که با او دسترسی کامل به اتصال را به اشتراک می‌گذارید، می‌تواند پروتکل‌ها و سرویس‌های شما را در سرور اضافه و حذف کند، همچنین تنظیمات را تغییر دهد. Server - + سرور + + + Accessing + در حال دسترسی به + + + File with accessing settings to + فایل شامل تنظیمات دسترسی به + + + + Config revoked + تنظیمات ابطال‎شد + + + + Connection to + ارتباط با + + + + File with connection settings to + فایل شامل تنظیمات ارتباط با + + + + Save OpenVPN config + ذخیره تنظیمات OpenVPN + + + + Save WireGuard config + ذخیره تنظیمات WireGuard + + + + Save ShadowSocks config + ذخیره تنظیمات ShadowSocks + + + + Save Cloak config + ذخیره تنظیمات Cloak + + + + For the AmneziaVPN app + برای نرم‎افزار AmneziaVPN + + + + ShadowSocks native format + فرمت ShadowSocks + + + + Cloak native format + فرمت Cloak + + + + Share VPN Access + به اشتراک گذاشتن دسترسی وی‎پی‎ان + + + + Share full access to the server and VPN + به اشتراک گذاشتن دسترسی کامل به سرور و وی‎پی‎ان + + + + Use for your own devices, or share with those you trust to manage the server. + برای دستگاه‎های خودتان استفاده کنید یا با آنهایی که برای مدیریت سرور به آن‎ها اعتماد دارید به اشتراک بگذارید. + + + + + Users + کاربران + + + + User name + نام کاربری + + + + Search + جستجو + + + + Creation date: + تاریخ ایجاد: + + + + Rename + تغییر نام + + + + Client name + نام کلاینت + + + + Save + ذخیره + + + + Revoke + ابطال + + + + Revoke the config for a user - %1? + لغو پیکربندی برای یک کاربر %1؟ + + + Revoke the config for a user - + ابطال تنظیمات برای کاربر + + + + The user will no longer be able to connect to your server. + کاربر دیگر نمی‎تواند به سرور وصل شود. + + + + Continue + ادامه + + + + Cancel + کنسل + + + Full access + دسترسی کامل + + + + Share VPN access without the ability to manage the server + به اشتراک گذاشتن دسترسی وی‎پی‎ان بدون امکان مدیریت سرور + + + Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings. + به اشتراک گذاری دسترسی به مدیریت سرور. کاربری که دسترسی کامل سرور با او به اشتراک گذاشته می‎شود می‎تواند پروتکل‌‎ها و سرویس‎ها را در سرور حذف یا اضافه کند و یا تنظیمات سرور را تغییر دهد. Protocol - + پروتکل Connection format - + فرمت ارتباط - - Search - - - - - Creation date: - - - - - Rename - - - - - Client name - - - - - Save - - - - - Revoke - - - - - Revoke the config for a user - %1? - - - - - The user will no longer be able to connect to your server. - - - - - Continue - - - - - Cancel - + + + Share + اشتراک‎گذاری @@ -2062,49 +2220,50 @@ It's okay as long as it's from someone you trust. Full access to the server and VPN - + دسترسی کامل به سرور و وی‎پی‎ان We recommend that you use full access to the server only for your own additional devices. - + ما پیشنهاد میکنیم که ازحالت دسترسی کامل به سرور فقط برای دستگاه‎های دیگر خودتان استفاده کنید + If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. - + اگر دسترسی کامل را با دیگران به اشتراک بگذارید، آن‎ها می‎توانند پروتکل‎ها و سرویس‎ها را حذف یا اضافه کنند که باعث می‎شود که وی‎پی‎ان دیگر برای سایر کاربران کار نکند. Server - + سرور Accessing - + در حال دسترسی به File with accessing settings to - + فایل شامل تنظیمات دسترسی به Share - + اشتراک‎گذاری Connection to - + ارتباط با File with connection settings to - + فایل شامل تنظیمات ارتباط با @@ -2112,7 +2271,7 @@ It's okay as long as it's from someone you trust. Close - + بستن @@ -2120,38 +2279,38 @@ It's okay as long as it's from someone you trust. Password entry not found - + Password entry not found Could not decrypt data - + Could not decrypt data Unknown error - + Unknown error Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Password not found - + Password not found Could not open keystore - + Could not open keystore Could not remove private key from keystore - + Could not remove private key from keystore @@ -2159,12 +2318,12 @@ It's okay as long as it's from someone you trust. Unknown error - + Unknown error Access to keychain denied - + Access to keychain denied @@ -2172,27 +2331,27 @@ It's okay as long as it's from someone you trust. Could not store data in settings: access error - + Could not store data in settings: access error Could not store data in settings: format error - + Could not store data in settings: format error Could not delete data from settings: access error - + Could not delete data from settings: access error Could not delete data from settings: format error - + Could not delete data from settings: format error Entry not found - + Entry not found @@ -2200,80 +2359,80 @@ It's okay as long as it's from someone you trust. Password entry not found - + Password entry not found Could not decrypt data - + Could not decrypt data D-Bus is not running - + D-Bus is not running Unknown error - + Unknown error No keychain service available - + No keychain service available Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Access to keychain denied - + Access to keychain denied Could not determine data type: %1; %2 - + Could not determine data type: %1; %2 Entry not found - + Entry not found Unsupported entry type 'Map' - + Unsupported entry type 'Map' Unknown kwallet entry type '%1' - + Unknown kwallet entry type '%1' Password not found - + Password not found Could not open keystore - + Could not open keystore Could not retrieve private key from keystore - + Could not retrieve private key from keystore Could not create decryption cipher - + Could not create decryption cipher @@ -2281,276 +2440,221 @@ It's okay as long as it's from someone you trust. Credential size exceeds maximum size of %1 - + Credential size exceeds maximum size of %1 Credential key exceeds maximum size of %1 - + Credential key exceeds maximum size of %1 Writing credentials failed: Win32 error code %1 - + Writing credentials failed: Win32 error code %1 Encryption failed - + Encryption failed D-Bus is not running - + D-Bus is not running Unknown error - + Unknown error Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Password not found - + Password not found Could not open keystore - + Could not open keystore Could not create private key generator - + Could not create private key generator Could not generate new private key - + Could not generate new private key Could not retrieve private key from keystore - + Could not retrieve private key from keystore Could not create encryption cipher - + Could not create encryption cipher Could not encrypt data - + Could not encrypt data QObject - - - Sftp service - - No error - + No error Unknown Error - + Unknown Error Function not implemented - + Function not implemented Server check failed - + Server check failed Server port already used. Check for another software - + Server port already used. Check for another software Server error: Docker container missing - + Server error: Docker container missing Server error: Docker failed - + Server error: Docker failed Installation canceled by user - + Installation canceled by user The user does not have permission to use sudo - + The user does not have permission to use sudo Ssh request was denied - + Ssh request was denied Ssh request was interrupted - + Ssh request was interrupted Ssh internal error - + Ssh internal error Invalid private key or invalid passphrase entered - + Invalid private key or invalid passphrase entered The selected private key format is not supported, use openssh ED25519 key types or PEM key types - + The selected private key format is not supported, use openssh ED25519 key types or PEM key types Timeout connecting to server - + Timeout connecting to server Sftp error: End-of-file encountered - + Sftp error: End-of-file encountered Sftp error: File does not exist - + Sftp error: File does not exist Sftp error: Permission denied - + Sftp error: Permission denied Sftp error: Generic failure - + Sftp error: Generic failure Sftp error: Garbage received from server - + Sftp error: Garbage received from server Sftp error: No connection has been set up - + Sftp error: No connection has been set up Sftp error: There was a connection, but we lost it - + Sftp error: There was a connection, but we lost it Sftp error: Operation not supported by libssh yet - + Sftp error: Operation not supported by libssh yet Sftp error: Invalid file handle - + Sftp error: Invalid file handle Sftp error: No such file or directory path exists - + Sftp error: No such file or directory path exists Sftp error: An attempt to create an already existing file or directory has been made - + Sftp error: An attempt to create an already existing file or directory has been made Sftp error: Write-protected filesystem - + Sftp error: Write-protected filesystem Sftp error: No media was in remote drive - - - - - OpenVPN config missing - - - - - OpenVPN management server error - - - - - OpenVPN executable missing - - - - - ShadowSocks (ss-local) executable missing - - - - - Cloak (ck-client) executable missing - - - - - Amnezia helper service error - - - - - OpenSSL failed - - - - - Can't connect: another VPN connection is active - - - - - Can't setup OpenVPN TAP network adapter - - - - - VPN pool error: no available addresses - + Sftp error: No media was in remote drive The config does not contain any containers and credentials for connecting to the server - + تنظیمات شامل هیچ کانتینر یا اعتبارنامه‎ای برای اتصال به سرور نیست @@ -2558,114 +2662,92 @@ It's okay as long as it's from someone you trust. - - Error when retrieving configuration from API - - - - - This config has already been added to the application - - - - - Internal error - - - - + ErrorCode: %1. + + Failed to save config to disk + Failed to save config to disk + + + + OpenVPN config missing + OpenVPN config missing + + + + OpenVPN management server error + OpenVPN management server error + + + + OpenVPN executable missing + OpenVPN executable missing + + + + ShadowSocks (ss-local) executable missing + ShadowSocks (ss-local) executable missing + + + + Cloak (ck-client) executable missing + Cloak (ck-client) executable missing + + + + Amnezia helper service error + Amnezia helper service error + + + + OpenSSL failed + OpenSSL failed + + + + Can't connect: another VPN connection is active + Can't connect: another VPN connection is active + + + + Can't setup OpenVPN TAP network adapter + Can't setup OpenVPN TAP network adapter + + + + VPN pool error: no available addresses + VPN pool error: no available addresses + + + The config does not contain any containers and credentiaks for connecting to the server + The config does not contain any containers and credentiaks for connecting to the server + + + + Internal error + Internal error + IPsec - - - - - - Website in Tor network - - - - - Amnezia DNS - - - - - Sftp file sharing service - - - - - OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. - + IPsec ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but it may be recognized by analysis systems in some highly censored regions. - + شدوساکس - ترافیک VPN را پنهان می کند، به طوری که مشابه ترافیک وب عادی می شود، اما ممکن است توسط سیستم های تجزیه و تحلیل در برخی از مناطق با سانسور شدید شناسایی شود. OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. - - - - - WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. - - - - - AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. - - - - - IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. - - - - - Deploy a WordPress site on the Tor network in two clicks. - - - - - Replace the current DNS server with your own. This will increase your privacy level. - + OpenVPN روی Cloak - OpenVPN با VPN که به عنوان ترافیک وب پنهان می‌شود و مقاومت در برابر تشخیص فعال از طریق پیشرفته. ایده‌آل برای دور زدن مسدود کردن در مناطق با بالاترین سطوح سانسور Create a file vault on your server to securely store and transfer files. - - - - - OpenVPN stands as one of the most popular and time-tested VPN protocols available. -It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. - -* Available in the AmneziaVPN across all platforms -* Normal power consumption on mobile devices -* Flexible customisation to suit user needs to work with different operating systems and devices -* Recognised by DPI analysis systems and therefore susceptible to blocking -* Can operate over both TCP and UDP network protocols. - - - - - Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. - -* Available in the AmneziaVPN only on desktop platforms -* Normal power consumption on mobile devices - -* Configurable encryption protocol -* Detectable by some DPI systems -* Works over TCP network protocol. - + ساختن یک گنجانده فایل بر روی سرور شما برای ذخیره و انتقال ایمن فایل‌ها @@ -2687,7 +2769,23 @@ If there is a extreme level of Internet censorship in your region, we advise you * Not recognised by DPI analysis systems * Works over TCP network protocol, 443 port. - + این ترکیبی از پروتکل OpenVPN و پلاگین Cloak به طور خاص برای محافظت در برابر مسدود کردن طراحی شده است. + +OpenVPN ارتباط امن VPN را با رمزگذاری تمام ترافیک اینترنتی بین مشتری و سرور فراهم می‌کند. + +Cloak OpenVPN را از شناسایی و مسدود کردن محافظت می‌کند. + +Cloak می‌تواند اطلاعات فراداده بسته را تغییر دهد تا ترافیک VPN را به طور کامل به عنوان ترافیک وب عادی پنهان کند و همچنین VPN را از شناسایی توسط Active Probing محافظت کند. این باعث می‌شود این سیستم بسیار مقاوم در برابر شناسایی شود. + +فوراً پس از دریافت اولین بسته داده، Cloak اتصال ورودی را تأیید می‌کند. اگر تأیید اعتبار ناموفق باشد، پلاگین سرور را به عنوان یک وب‌سایت جعلی پنهان می‌کند و VPN شما برای سیستم‌های تجزیه و تحلیل غیر قابل دسترسی می‌شود. + +اگر در منطقه شما سطح بسیار بالایی از سانسور اینترنت وجود دارد، به شما توصیه می‌شود که از اولین اتصال فقط از OpenVPN over Cloak استفاده کنید. + + در دسترس در AmneziaVPN بر روی تمام پلتفرم‌ها + مصرف بالای برق در دستگاه‌های تلفن همراه + تنظیمات انعطاف پذیر + توسط سیستم‌های تجزیه و تحلیل DPI شناخته نمی‌شود + بر روی پروتکل شبکه TCP، پورت 443 کار می‌کند. @@ -2699,19 +2797,6 @@ WireGuard is very susceptible to blocking due to its distinct packet signatures. * Low power consumption * Minimum number of settings * Easily recognised by DPI analysis systems, susceptible to blocking -* Works over UDP network protocol. - - - - - A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. -While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. -This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. - -* Available in the AmneziaVPN across all platforms -* Low power consumption -* Minimum number of settings -* Not recognised by DPI analysis systems, resistant to blocking * Works over UDP network protocol. @@ -2726,77 +2811,281 @@ While it offers a blend of security, stability, and speed, it's essential t * Minimal configuration * Recognised by DPI analysis systems * Works over UDP network protocol, ports 500 and 4500. - + پروتکل IKEv2 به همراه لایه رمزنگاری IPSec به عنوان پروتکل وی‎پی‎ان مدرن و پایدار است. +یکی از قابلیت‎‎های متمایز این پروتکل قابلیت سوییچ بین شبکه‎ها و دستگاه‎هاست که قابلیت انطباق بالایی در محیط شبکه‎های دینامیک را دارد +در حالیکه ترکیبی از امنیت، پایداری و سرعت را ارائه میدهد اما مهم است که اشاره کنیم IKEv2 به راحتی قابل تشخیص در شبکه و بلاک شدن میباشد. + +* در AmneziaVPN فقط بر روی ویندوز در دسترس است +* مصرف باتری کم روی دستگاه‎های موبایل +* تنظیمات ساده +* امکان شناسایی شدن در شبکه‎های تحلیل DPI +* روی پروتکل شبکه UDP، پورت‎های 500 و 4500 کار می‎کند. DNS Service - + سرویس DNS + + + + Sftp file sharing service + سرویس اشتراک گذاری فایل Sftp + + + + + Website in Tor network + وب سایت در شبکه Tor + + + + Amnezia DNS + Amnezia DNS + + + + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. + پروتکل OpenVPN یکی از پروتکل‎های وی‎پی‎ان محبوب می‎باشد با تنظیمات و پیکربندی‎های قابل تغییر. از پروتکل امنیتی داخلی خود با تبادل کلید SSL/TLS استفاده می‎کند. + + + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. + پروتکل ShadowSocks ترافیک وی‎پی‎ان را پنهان و آن را شبیه ترافیک عادی وب می‎کند، اما در مناطقی که سانسور شدیدی اعمال می‎شود با سیستم‎های تحلیلی قابل شناسایی است. + + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + پروتکل OpenVPN over Cloak که همان پروتکل OpenVPN با قابلیت پنهان کردن ترافیک از سیستم‎های تحلیل فعال برروی شبکه. ایده‎آل برای گذر از ممنوعیت در مناطقی که سانسور شدیدی اعمال می‎کنند. + + + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + پروتکل WireGuard یک پروتکل وی‎پی‎ان جدید با عملکرد بسیار خوب، سرعت بالا و مصرف انرژی پایین. برای مناطقی که سطح سانسور پایینی دارند پیشنهاد می‎شود. + + + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. + پروتکل AmneziaWG یک پروتکل اختصاصی Amnezia که بر اساس WireGaurd کار میکند. به اندازه WireGaurd پرسرعت است و در عین حال بسیار مقاوم به بلاک شدن توسط شبکه ست. مناسب برای مناطق با سطح سانسور بالاست. + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. + پروتکل IKEv2 پروتکلی پایدار و مدرن که مقداری سریعتر از سایر پروتکل‎هاست. بعد از قطع سیگنال دوباره اتصال را بازیابی می‎کند. به صورت پیش‎فرض بر روی آخرین نسخه دستگاه‎های اندروید و iOS پیشتیبانی می‎شود. + + + + Deploy a WordPress site on the Tor network in two clicks. + با دو کلیک یک سایت وردپرس در شبکه Tor راه‎اندازی کنید. + + + + Replace the current DNS server with your own. This will increase your privacy level. + سرور DNS را با مال خودتان جایگزین کنید. این کار سطح حریم خصوصی شما را افزایش می‎دهد. + + + Creates a file vault on your server to securely store and transfer files. + یک محفظه ایمن بر روی سرور خودتان ایجاد کنید که به طور امن بتوانید فایل‎ها را ذخیره و جابجا کنید. + + + + OpenVPN stands as one of the most popular and time-tested VPN protocols available. +It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. + +* Available in the AmneziaVPN across all platforms +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices +* Recognised by DPI analysis systems and therefore susceptible to blocking +* Can operate over both TCP and UDP network protocols. + پروتکل OpenVPN یکی از پروتکل‎های محبوب و تست شده در دسترس می‎باشد که از پروتکل امنیتی مخصوص خودش استفاده میکند. +از امتیازات SSL/TLS برای رمزنگاری و تبادل کلید استفاده میکند. +همچنین OpenVPN از روش‎های چندگانه‎ای برای احراز هویت پشتیبانی می‎کند که آن را قابل انطباق و منعطف میکند. +از طیف وسیعی از دستگاه‎ها و سیستم عامل‎ها نیز پشتیبانی می‎کند. +به دلیل طبیعت متن-باز آن، OpenVPN از بررسی گسترده توسط یک جامعه جهانی سود می‎برد که باعث بهتر شدن وضعیت امنیتی آن می‎شود. +به دلیل تعادل قوی بین عملکرد، امنیت و سازگاری OpenVPN تبدیل به یکی از انتخاب‎های اصلی برای اشخاص آگاه بر حریم خصوصی و تجارت‎های مشابه شده است. + +* بر روی تمام سیستم‎عامل‎ها در AmneziaVPN در دسترس است. +* مصرف انرژی عادی بر روی دستگاه‎های موبایل +* قابلیت شخصی‎سازی منعطف مطابق با نیاز شما که امکان کار بر روی دستگاه‎ها و سیستم عامل‎های مختلف را می‎دهد. +* قابل شناسایی توسط سیستم‎های تحلیل عمیق DPI در شبکه و در نتیجه امکان بلاک شدن +* امکان کار بر روی دو پروتکل TCP و UDP + + + + Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. + +* Available in the AmneziaVPN only on desktop platforms +* Normal power consumption on mobile devices + +* Configurable encryption protocol +* Detectable by some DPI systems +* Works over TCP network protocol. + پروتکل Shadowsocks، الهام گرفته از پروتکل Socks5، اتصال را با استفاده از رمزگذاری AEAD امن میکند. اگرچه Shadowsocks طوری طراحی شده که برای شناسایی در شبکه چالش‎برانگیز باشد و محتاط عمل کند اما این پروتکل مانند یک اتصال استاندارد HTTPS نیست و برخی از سیستم‎های تحلیل ترافیک مشخص ممکن است بتوانند اتصال Shadowsocks را شناسایی کنند. به دلیل محدودیت پشتیبانی در Amnezia پیشنهاد می‎شود که از َAmneziaWG استفاده شود. + +* فقط بر روی پلتفرم دسکتاپ بر روی Amnezia قابل دسترس است +* مصرف انرژی عادی در دستگاه‎های موبایل +* پروتکل رمزنگاری قابل پیکربندی +* قابل شناسایی توسط برخی سیستم‎های تحلیل عمیق DPI +* عملکرد بر روی پروتکل شبکه TCP + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. + +OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works over TCP network protocol, 443 port. + + این یک ترکیب از پروتکل OpenVPN و افزونه Cloak می‎باشد که به طور خاص برای محافظت از بلاک شدن طراحی شده است. + +پروتکل OpenVPN با رمزنگاری تمام ترافیک اینترنت بین دستگاه و سرور یک اتصال وی‎پی‎ان امن را فراهم می‎کند. + +افزونه Cloak از OpenVPN در مقابل شناسایی و بلاک شدن محافظت می‎کند + +افزونه Cloak می‎تواند داده‎های بسته ترافیکی را تغییر دهد و در نتیجه ترافیک وی‎پی‎ان شبیه ترافیک عادی وب می‎شود و همچنین از وی‎پی‎ان در مقابل شناسایی شدن توسط DPI محافظت می‎کند. این باعث می‎شود که این پروتکل به شناسایی‎شدن بسیار مقاوم باشد + +درست بعد از دریافت اولین بسته داده،افزونه Cloak اتصال ورودی را احراز هویت می‎کند و اگر عملیات احراز هویت انجام نشود Cloak سرور را به عنوان یک وب سایت جعلی در‎ می‎آورد و وی‎پی‎ان شما را از تحلیل شبکه پنهان می‎کند. + +اگر در منطقه شما سطح بالایی از سانسور وجود دارد ما به شما پیشنهاد می‎کنیم از اولین ارتباط تنها از OpenVPN over Cloak استفاده کنید. + + +* بر روی تمام پلتفرم‎ها در AmneziaVPN در دسترس است +* مصرف بالای انرژی در دستگاه‎های موبایل +* تنظیمات منطعف +* غیرقابل تشخیص و شناسایی توسط سیستم‎های تحلیل عمیق DPI +* کار کردن روی پروتکل شبکه TCP، پورت 443 + + + + A relatively new popular VPN protocol with a simplified architecture. +Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. + یک پروتکل نسبتا محبوب وی‎پی‎ان با معماری ساده +اتصال وی‎پی‎‎ان پایدار با عملکرد بالا بر روی تمام دستگاه‎‌ها فراهم می‎کند. از تنظیمات ثابت برای رمزنگاری استفاده می‎کند و در مقایسه با OpenVPN سرعت بهتری در انتقال اطلاعات دارد. +پروتکل WireGaurd به دلیل امضای بسته داده مخصوص، احتمال بسیار بالایی برای شناسایی و بلاک شدن دارد.برعکس سایر پروتکل‎های وی‎پی‎ان که از روش‎های مخفی کردن استفاده می‎کنند، امضای ثابت WireGuard به راحتی می‎تواند توسط سیستم‎های تحلیل عمیق DPI یا سایر روش‎های بررسی شبکه شناسایی و بلاک شود. + +* بر روی تمام پلتفرم‌ها در AmneziaVPN قابل دسترسی است. +* مصرف انرژی پایین +* کمترین میزان تنظیمات +* امکان شناسایی شدن توسط سیستم‎های تحلیل عمیق DPI به آسانی و بلاک شدن +* کار بر روی پروتکل شبکه UDP + + + + A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. +While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. +This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Not recognised by DPI analysis systems, resistant to blocking +* Works over UDP network protocol. + یک نسخه مدرن از پروتکل وی‎پی‎ان محبوب، AmneziaWG بر روی پایه‎های WireGuard ساخته شده و معماری ساده و عملکرد بالای آن را بر روی تمام دستگاه‎ها حفظ کرده است. +در حالی‎که WireGuard به دلیل بازدهی آن شناخته می‎شود اما امکان شناسایی شدن بالا به دلیل امضای ثابت بسته داده‎های آن یکی از مشکلات آن است. AmneziaWG این مشکل را با استفاده از متدهای مخفی سازی حل کرده و در نتیجه ترافیک آن همانند با ترافیک عادی اینترنت است. +این بدین معنی است که AmneziaWG عملکرد سریع اصلی را حفظ کرده و یک لایه پنهان سازی به آن اضافه کرده که باعث می‎شود که به انتخابی عالی برای آنها که وی‎پی‎ان امن و سریع می‎خواهند تبدیل شود. + +* بر روی تمام پلتفرم‌ها در AmneziaVPN قابل دسترسی است. +* مصرف انرژی پایین +* کمترین میزان تنظیمات +* غیرقابل تشخیص توسط سیستم‎های تحلیل عمیق DPI و مقاوم به بلاک شدن +* کار بر روی پروتکل شبکه UDP + + + AmneziaWG container + AmneziaWG протокол Sftp file sharing service - is secure FTP service - + سرویس اشتراک فایل Sftp یک سرویس امن FTP می‎باشد + + + + Sftp service + سرویس Sftp Entry not found - + Entry not found Access to keychain denied - + Access to keychain denied No keyring daemon - + No keyring daemon Already unlocked - + Already unlocked No such keyring - + No such keyring Bad arguments - + Bad arguments I/O error - + I/O error Cancelled - + Cancelled Keyring already exists - + Keyring already exists No match - + No match Unknown error - + Unknown error error 0x%1: %2 - + error 0x%1: %2 + + + WireGuard Configuration Highlighter + هایلایتر پیکربندی WireGuard + + + &Randomize colors + رنگ‎های تصادفی @@ -2804,7 +3093,7 @@ While it offers a blend of security, stability, and speed, it's essential t Choose language - + انتخاب زبان @@ -2812,36 +3101,31 @@ While it offers a blend of security, stability, and speed, it's essential t Server #1 - + Server #1 Server - + Server SettingsController - - Software version - - - - - Backup file is corrupted - - - - + All settings have been reset to default values - + تمام تنظیمات به مقادیر پیش فرض ریست شد - + Cached profiles cleared - + پروفایل ذخیره شده پاک شد + + + + Backup file is corrupted + فایل بک‎آپ خراب شده است @@ -2850,38 +3134,38 @@ While it offers a blend of security, stability, and speed, it's essential t Save AmneziaVPN config - + ذخیره تنظیمات AmneziaVPN Share - + اشتراک‎گذاری Copy - + کپی Copied - + کپی شد Copy config string - + کپی‎کردن متن تنظیمات Show connection settings - + نمایش تنظیمات ارتباط To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" - + برای خواندن QR Code در نرم‎افزار AmneziaVPN "اضافه کردن سرور" -> "من داده برای اتصال دارم" -> "QR Code، کلید یا فایل تنظیمات" @@ -2889,42 +3173,42 @@ While it offers a blend of security, stability, and speed, it's essential t Hostname not look like ip adress or domain name - + فرمت هاست شبیه آدرس آی‎پی یا نام دامنه نیست New site added: %1 - + سایت جدید اضافه‎شد: %1 Site removed: %1 - + سایت حذف شد: %1 Can't open file: %1 - + فایل باز نشد: %1 Failed to parse JSON data from file: %1 - + مشکل در تحلیل داده‎های JSON در فایل: %1 The JSON data is not an array in file: %1 - + داده‎های JSON در فایل به صورت آرایه نیستند: %1 Import completed - + بارگذاری کامل شد Export completed - + خروجی گرفتن کامل شد @@ -2933,31 +3217,31 @@ While it offers a blend of security, stability, and speed, it's essential t Show - + نمایش Connect - + اتصال Disconnect - + قطع ارتباط Visit Website - + بازدید وب سایت Quit - + خروج @@ -2965,7 +3249,7 @@ While it offers a blend of security, stability, and speed, it's essential t The field can't be empty - + این فیلد نمی‌تواند خالی باشد. @@ -2973,7 +3257,7 @@ While it offers a blend of security, stability, and speed, it's essential t Mbps - + Mbps @@ -2981,42 +3265,42 @@ While it offers a blend of security, stability, and speed, it's essential t Unknown - + ناشناخته Disconnected - + قطع شده Preparing - + درحال آماده‎سازی Connecting... - + برقراری ارتباط... Connected - + وصل شد Disconnecting... - + در حال قطع شدن... Reconnecting... - + برقراری ارتباط دوباره... Error - + خطا @@ -3024,45 +3308,65 @@ While it offers a blend of security, stability, and speed, it's essential t Low - + پایین Medium or High - + متوسط یا بالا Extreme - + شدید I just want to increase the level of my privacy. - + من فقط میخواهم سطح حریم شخصی خودم را بالا ببرم I want to bypass censorship. This option recommended in most cases. - + من میخواهم از سانسور عبور کنم. این گزینه در اکثر موارد توصیه می‎‌شود Most VPN protocols are blocked. Recommended if other options are not working. - + اکثر پروتکل‎های وی‎پی‎ان مسدود شده‎اند. در مواردی که بقیه گزینه‎ها کار نمی‎کنند توصی می‎شود. + + + High + بالایی + + + Medium + متوسط + + + Many foreign websites and VPN providers are blocked + بسیاری از وب‌سایت‌ها و ارائه‌دهندگان VPN خارجی مسدود شده‌اند. + + + Some foreign sites are blocked, but VPN providers are not blocked + بعضی از وب‌سایت‌های خارجی مسدود شده‌اند، اما ارائه‌دهندگان VPN مسدود نمی‌شوند. + + + I just want to increase the level of privacy + من فقط می‌خواهم سطح حریم خصوصی خود را افزایش دهم. main2 - + Private key passphrase - + عبارت کلید خصوصی - + Save - + ذخیره diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index c532b28c2..1425197e8 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -1,51 +1,83 @@ + + AmneziaApplication + + Split tunneling for WireGuard is not implemented, the option was disabled + Раздельное туннелирование для "Wireguard" не реализовано,опция отключена + + + Split tunneling for %1 is not implemented, the option was disabled + Раздельное туннелирование для %1 не реализовано, опция отключена + + + + AndroidController + + AmneziaVPN + AmneziaVPN + + + VPN Connected + Refers to the app - which is currently running the background and waiting + VPN Подключен + + + + ApiController + + + Error when retrieving configuration from cloud server + + + ConnectionController - - - - - Connect - + + VPN Protocols is not installed. + Please install VPN container at first + VPN протоколы не установлены. + Пожалуйста, установите протокол - VPN Protocols is not installed. - Please install VPN container at first - + Connection... + Подключение... + + + + Connected + Подключено + + + + Settings updated successfully, Reconnnection... + Настройки успешно обновлены. Подключение... + + + + Settings updated successfully + Настройки успешно обновлены + + + + Reconnection... + Переподключение... + + + + + + + Connect + Подключиться - Connection... - - - - - Connected - - - - - Reconnection... - - - - Disconnection... - - - - - Settings updated successfully, Reconnnection... - - - - - Settings updated successfully - + Отключение... @@ -53,17 +85,17 @@ Add new connection - + Добавить новое соединение Configure your server - + Настроить свой сервер Open config file, key or QR code - + Открыть файл конфига, ключ или QR код @@ -71,22 +103,22 @@ C&ut - + &Вырезать &Copy - + &Копировать &Paste - + &Вставить &SelectAll - + &ВыбратьВсе @@ -94,7 +126,7 @@ Access error! - + Ошибка доступа! @@ -102,20 +134,24 @@ Unable change protocol while there is an active connection - + Невозможно изменить протокол при активном соединении The selected protocol is not supported on the current platform - + Выбранный протокол не поддерживается на данном устройстве + + + Reconnect via VPN Procotol: + Переподключение через VPN протокол: ImportController - + Scanned %1 of %2. - + Отсканировано %1 из%2. @@ -124,30 +160,31 @@ %1 installed successfully. - + %1 успешно установлен. %1 is already installed on the server. - + %1 уже установлен на сервер. Added containers that were already installed on the server - + Добавлены сервисы и протоколы, которые были ранее установлены на сервер Already installed containers were found on the server. All installed containers have been added to the application - + +На сервере обнаружены установленные протоколы и сервисы, все они добавлены в приложение Settings updated successfully - + Настройки успешно обновлены @@ -157,27 +194,27 @@ Already installed containers were found on the server. All installed containers Server '%1' was removed - + Сервер '%1' был удален All containers from server '%1' have been removed - + Все протоколы и сервисы были удалены с сервера '%1' %1 has been removed from the server '%2' - + %1 был удален с сервера '%2' Please login as the user - + Пожалуйста, войдите в систему от имени пользователя Server added successfully - + Сервер успешно добавлен @@ -185,17 +222,17 @@ Already installed containers were found on the server. All installed containers Read key failed: %1 - + Не удалось считать ключ: %1 Write key failed: %1 - + Не удалось записать ключ: %1 Delete key failed: %1 - + Не удалось удалить ключ: %1 @@ -204,27 +241,27 @@ Already installed containers were found on the server. All installed containers AmneziaVPN - + AmneziaVPN VPN Connected - + VPN Подключен VPN Disconnected - + VPN Выключен AmneziaVPN notification - + Уведомление AmneziaVPN Unsecured network detected: - + Обнаружена незащищенная сеть: @@ -232,30 +269,30 @@ Already installed containers were found on the server. All installed containers Removing services from %1 - + Удаление сервисов c %1 Usually it takes no more than 5 minutes - + Обычно это занимает не более 5 минут PageHome - + VPN protocol - + VPN протокол - + Servers - + Серверы - + Unable change server while there is an active connection - + Невозможно изменить сервер при активном соединении @@ -263,87 +300,91 @@ Already installed containers were found on the server. All installed containers AmneziaWG settings - + AmneziaWG настройки Port - + Порт Junk packet count - + Junk packet count Junk packet minimum size - + Junk packet minimum size Junk packet maximum size - + Junk packet maximum size Init packet junk size - + Init packet junk size Response packet junk size - + Response packet junk size Init packet magic header - + Init packet magic header Response packet magic header - + Response packet magic header Transport packet magic header - + Transport packet magic header Underload packet magic header - + Underload packet magic header Remove AmneziaWG - + Удалить AmneziaWG Remove AmneziaWG from server? - + Удалить AmneziaWG с сервера? All users with whom you shared a connection will no longer be able to connect to it. - + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. Continue - + Продолжить Cancel - + Отменить Save and Restart Amnezia - + Сохранить и перезагрузить Amnezia @@ -351,28 +392,28 @@ Already installed containers were found on the server. All installed containers Cloak settings - + Настройки Cloak Disguised as traffic from - + Замаскировать трафик под Port - + Порт Cipher - + Шифрование Save and Restart Amnezia - + Сохранить и перезагрузить Amnezia @@ -380,7 +421,11 @@ Already installed containers were found on the server. All installed containers OpenVPN settings - + OpenVPN настройки + + + VPN Addresses Subnet + Подсеть для VPN @@ -390,185 +435,189 @@ Already installed containers were found on the server. All installed containers Network protocol - + Сетевой протокол Port - + Порт Auto-negotiate encryption - + Шифрование с автоматическим согласованием Hash - + Хэш SHA512 - + SHA512 SHA384 - + SHA384 SHA256 - + SHA256 SHA3-512 - + SHA3-512 SHA3-384 - + SHA3-384 SHA3-256 - + SHA3-256 whirlpool - + whirlpool BLAKE2b512 - + BLAKE2b512 BLAKE2s256 - + BLAKE2s256 SHA1 - + SHA1 Cipher - + Шифрование AES-256-GCM - + AES-256-GCM AES-192-GCM - + AES-192-GCM AES-128-GCM - + AES-128-GCM AES-256-CBC - + AES-256-CBC AES-192-CBC - + AES-192-CBC AES-128-CBC - + AES-128-CBC ChaCha20-Poly1305 - + ChaCha20-Poly1305 ARIA-256-CBC - + ARIA-256-CBC CAMELLIA-256-CBC - + CAMELLIA-256-CBC none - + none TLS auth - + TLS авторизация Block DNS requests outside of VPN - + Блокировать DNS запросы за пределами VPN Additional client configuration commands - + Дополнительные команды конфигурации клиента Commands: - + Commands: Additional server configuration commands - + Дополнительные команды конфигурации сервера Remove OpenVPN - + Удалить OpenVPN Remove OpenVpn from server? - + Удалить OpenVpn с сервера? All users with whom you shared a connection will no longer be able to connect to it. - + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. Continue - + Продолжить Cancel - + Отменить Save and Restart Amnezia - + Сохранить и перезагрузить @@ -576,42 +625,46 @@ Already installed containers were found on the server. All installed containers settings - + настройки Show connection options - + Показать параметры подключения Connection options %1 - + Параметры подключения %1 Remove - + Удалить Remove %1 from server? - + Удалить %1 с сервера? All users with whom you shared a connection will no longer be able to connect to it. - + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. Continue - + Продолжить Cancel - + Отменить @@ -619,23 +672,30 @@ Already installed containers were found on the server. All installed containers ShadowSocks settings - + Настройки ShadowSocks Port - + Порт Cipher - + Шифрование Save and Restart Amnezia - + Сохранить и перезагрузить Amnezia + + + + PageServerContainers + + Continue + Продолжить @@ -644,32 +704,33 @@ Already installed containers were found on the server. All installed containers A DNS service is installed on your server, and it is only accessible via VPN. - + На вашем сервере установлен DNS-сервис, доступ к нему возможен только через VPN. + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. - + Адрес DNS совпадает с адресом вашего сервера. Настроить DNS можно во вкладке "Соединения" настроек приложения Remove - + Удалить Remove %1 from server? - + Удалить %1 с сервера? Continue - + Продолжить Cancel - + Отменить @@ -677,17 +738,17 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + Настройки успешно обновлены SFTP settings - + Настройки SFTP Host - + Хост @@ -695,69 +756,69 @@ Already installed containers were found on the server. All installed containers Copied - + Скопировано Port - + Порт Login - + Логин Password - + Пароль Mount folder on device - + Смонтировать папку на вашем устройстве In order to mount remote SFTP folder as local drive, perform following steps: <br> - + Чтобы смонтировать SFTP-папку как локальный диск на вашем устройстве, выполните следующие действия <br>1. Install the latest version of - + <br>1. Установите последнюю версию <br>2. Install the latest version of - + <br>2. Установите последнюю версию Detailed instructions - + Подробные инструкции Remove SFTP and all data stored there - + Удалить SFTP-хранилище со всеми данными Remove SFTP and all data stored there? - + Удалить SFTP-хранилище и все хранящиеся на нем данные? Continue - + Продолжить Cancel - + Отменить @@ -765,22 +826,22 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + Настройки успешно обновлены Tor website settings - + Настройки сайта в сети Тоr Website address - + Адрес сайта Copied - + Скопировано @@ -792,30 +853,42 @@ Already installed containers were found on the server. All installed containers After creating your onion site, it takes a few minutes for the Tor network to make it available for use. + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this URL. + Используйте <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> для открытия этой ссылки. + + + After creating your onion site, it takes a few minutes for the Tor network to make it available for use. + Через несколько минут после установки ваш Onion сайт станет доступен в сети Tor. + When configuring WordPress set the this onion address as domain. - + При настройке WordPress укажите этот onion адрес в качестве домена. + + + When configuring WordPress set the this address as domain. + При настройке WordPress укажите этот onion адрес в качестве домена. Remove website - + Удалить сайт The site with all data will be removed from the tor network. - + Сайт со всеми данными будет удален из сети Tor. Continue - + Продолжить Cancel - + Отменить @@ -823,70 +896,74 @@ Already installed containers were found on the server. All installed containers Settings - + Настройки Servers - + Серверы Connection - + Соединение Application - + Приложение Backup - + Резервное копирование About AmneziaVPN - + Об AmneziaVPN Close application - + Закрыть приложение PageSettingsAbout + + Support the project with a donation + Поддержите проект пожертвованием + Support Amnezia - + Поддержите Amnezia This is a free and open source application. If you like it, support the developers with a donation. - + Это бесплатное приложение с открытым исходным кодом. Если, оно вам нравится - поддержите разработчиков пожертвованием. And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. - + А, если оно вам не нравится, тем более поддержите-пожертвование пойдет на улучшение приложения. Card on Patreon - + Картой на Patreon https://www.patreon.com/amneziavpn - + https://www.patreon.com/amneziavpn Show other methods on Github - + Показать другие способы на Github @@ -896,57 +973,62 @@ Already installed containers were found on the server. All installed containers Contacts - + Контакты Telegram group - + Группа в Telegram To discuss features - + Для обсуждений https://t.me/amnezia_vpn_en - + https://t.me/amnezia_vpn Mail - + Почта For reviews and bug reports - + Для отзывов и сообщений об ошибках Github - + Github https://github.com/amnezia-vpn/amnezia-client - + https://github.com/amnezia-vpn/amnezia-client Website - + Веб-сайт https://amnezia.org - + https://amnezia.org + + + + Software version: %1 + Версия ПО: %1 Check for updates - + Проверить обновления @@ -954,151 +1036,151 @@ Already installed containers were found on the server. All installed containers Application - + Приложение Allow application screenshots - + Разрешить скриншоты приложения Auto start - + Автозапуск Launch the application every time the device is starts - + Открывать приложение при запуске устройства Start minimized - + Запускать в свернутом виде Launch application minimized - + Запускать приложение в свернутом виде Language - + Язык Logging - + Логирование Enabled - + Включено Disabled - + Отключено Reset settings and remove all data from the application - + Сбросить настройки и удалить все данные из приложения Reset settings and remove all data from the application? - + Сбросить настройки и удалить все данные из приложения? All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - + Все данные из приложения будут удалены, все установленные сервисы AmneziaVPN останутся на сервере. Continue - + Продолжить Cancel - + Отменить PageSettingsBackup - - - Settings restored from backup file - - Backup - + Резервное копирование + + + + Settings restored from backup file + Восстановление настроек из бэкап файла Configuration backup - + Бэкап конфигурация You can save your settings to a backup file to restore them the next time you install the application. - + Поможет мгновенно восстановить настройки соединений при следующей установке. Make a backup - + Сделать бэкап Save backup file - + Сохранить бэкап файл Backup files (*.backup) - + Файлы резервного копирования (*.backup) Backup file saved - + Бэкап файл сохранен Restore from backup - + Восстановить из бэкапа Open backup file - + Открыть бэкап файл Import settings from a backup file? - + Импортировать настройки из бэкап файла? All current settings will be reset - + Все текущие настройки будут сброшены Continue - + Продолжить Cancel - + Отменить @@ -1106,57 +1188,65 @@ Already installed containers were found on the server. All installed containers Connection - + Соединение Auto connect - + Автоподключение Connect to VPN on app start - + Подключение к VPN при запуске приложения Use AmneziaDNS - + Использовать Amnezia DNS If AmneziaDNS is installed on the server - + Если он уставновлен на сервере DNS servers - + DNS сервер When AmneziaDNS is not used or installed + + + Allows you to use the VPN only for certain Apps + + + + When AmneziaDNS is not used or installed + Эти адреса будут использоваться, если не включен AmneziaDNS + Site-based split tunneling - + Раздельное туннелирование сайтов Allows you to select which sites you want to access through the VPN - + Позволяет подключаться к одним сайтам через VPN, а к другим в обход него App-based split tunneling - + Раздельное VPN-туннелирование приложений - - Allows you to use the VPN only for certain Apps - + Allows you to use the VPN only for certain applications + Позволяет использовать VPN только для определённых приложений @@ -1169,57 +1259,57 @@ Already installed containers were found on the server. All installed containers DNS servers - + DNS сервер - If AmneziaDNS is not used or installed - + When AmneziaDNS is not used or installed + Эти адреса будут использоваться, если не включен или не установлен AmneziaDNS Primary DNS - + Первичный DNS Secondary DNS - + Вторичный DNS Restore default - + Восстановить по умолчанию Restore default DNS settings? - + Восстановить настройки DNS по умолчанию? Continue - + Продолжить Cancel - + Отменить Settings have been reset - + Настройки сброшены Save - + Сохранить Settings saved - + Сохранить настройки @@ -1227,62 +1317,62 @@ Already installed containers were found on the server. All installed containers Logging - + Логирование Save logs - + Сохранять логи Open folder with logs - + Открыть папку с логами Save - + Сохранить Logs files (*.log) - + Logs files (*.log) Logs file saved - + Файл с логами сохранен Save logs to file - + Сохранить логи в файл Clear logs? - + Очистить логи? Continue - + Продолжить Cancel - + Отменить Logs have been cleaned up - + Логи удалены Clear logs - + Удалить логи @@ -1290,30 +1380,30 @@ Already installed containers were found on the server. All installed containers All installed containers have been added to the application - - - - - No new installed containers found - + Все установленные протоколы и сервисы были добавлены в приложение Clear Amnezia cache - + Очистить кэш Amnezia May be needed when changing other settings - + Может понадобиться при изменении других настроек Clear cached profiles? - + Удалить кэш Amnezia? - + + No new installed containers found + Новые установленные протоколы и сервисы не обнаружены + + + @@ -1322,88 +1412,79 @@ Already installed containers were found on the server. All installed containers - - Continue - + Продолжить - - Cancel - + Отменить Check the server for previously installed Amnezia services - + Проверить сервер на наличие ранее установленных сервисов Amnezia Add them to the application if they were not displayed - + Добавить их в приложение, если они не были отображены Reboot server - + Перезагрузить сервер Do you want to reboot the server? - + Вы уверены, что хотите перезагрузить сервер? The reboot process may take approximately 30 seconds. Are you sure you wish to proceed? - - - - - Remove server from application - + Процесс перезагрузки может занять около 30 секунд. Вы уверены, что хотите продолжить? Do you want to remove the server from application? - + Вы уверена что хотите удалить сервер из приложения? + + + + Do you want to clear server from Amnezia software? + Вы хотите очистить сервер от всех сервисов Amnezia? + + + + Remove server from application + Удалить сервер из приложения + + + Remove server? + Удалить сервер? All installed AmneziaVPN services will still remain on the server. - + Все установленные сервисы и протоколы Amnezia всё ещё останутся на сервере. - Clear server from Amnezia software - + Очистить сервер от протоколов и сервисов Amnezia - - - Do you want to clear server from Amnezia software? - + Clear server from Amnezia software? + Удалить все сервисы и протоколы Amnezia с сервера? - All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. - - - - - Reset API config - - - - - Do you want to reset API config? - + На сервере будут удалены все данные, связанные с Amnezia: протоколы, сервисы, конфигурационные файлы, ключи и сертификаты. @@ -1411,27 +1492,27 @@ Already installed containers were found on the server. All installed containers Server name - + Имя сервера Save - + Сохранить Protocols - + Протоколы Services - + Сервисы Data - + Данные @@ -1439,32 +1520,36 @@ Already installed containers were found on the server. All installed containers settings - + настройки Remove - + Удалить Remove %1 from server? - + Удалить %1 с сервера? All users with whom you shared a connection will no longer be able to connect to it. - + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, которым вы поделились VPN, больше не смогут к нему подключаться. Continue - + Продолжить Cancel - + Отменить @@ -1472,7 +1557,7 @@ Already installed containers were found on the server. All installed containers Servers - + Серверы @@ -1483,38 +1568,50 @@ Already installed containers were found on the server. All installed containers - - Only the sites listed here will be accessed through the VPN - + Only the sites listed here will be accesed via VPN + Только адреса из списка должны открываться через VPN Addresses from the list should not be accessed via VPN - + Адреса из списка не должны открываться через VPN Split tunneling - + Раздельное VPN-туннелирование Mode - + Режим Remove - + Удалить Continue - + Продолжить Cancel + Отменить + + + Website or IP + Сайт или IP + + + Import / Export Sites + Импорт/экспорт Сайтов + + + + Only the sites listed here will be accessed through the VPN @@ -1530,45 +1627,45 @@ Already installed containers were found on the server. All installed containers Import - + Импорт Save site list - + Сохранить список сайтов Save sites - + Сохранить Sites files (*.json) - + Sites files (*.json) Import a list of sites - + Импортировать список с сайтами Replace site list - + Заменить список сайтов Open sites file - + Открыть список с сайтами Add imported sites to existing ones - + Добавить импортированные сайты к существующим @@ -1576,66 +1673,95 @@ Already installed containers were found on the server. All installed containers Server connection - + Подключение к серверу Do not use connection code from public sources. It may have been created to intercept your data. It's okay as long as it's from someone you trust. - + Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.. + +Всё в порядке, если кодом поделился пользователь, которому вы доверяете. What do you have? - - - - - File with connection settings or backup - + Выберете что у вас есть File with connection settings - + Файл с настройками подключения + + + + File with connection settings or backup + Файл с настройками подключения или бэкап Open config file - + Открыть файл с конфигурацией QR-code - + QR-код Key as text - + Ключ в виде текста PageSetupWizardCredentials - - Configure your server - + Server connection + Подключение к серверу Server IP address [:port] - + Server IP address [:port] - - 255.255.255.255:22 - + 255.255.255.255:88 + 255.255.255.255:88 + + + Password / SSH private key + Password / SSH private key + + + + Continue + Продолжить + + + All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties + Все данные, которые вы вводите, останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или каким-либо третьим сторонам + + + + Enter the address in the format 255.255.255.255:88 + Введите адрес в формате 255.255.255.255:88 Login to connect via SSH + Login to connect via SSH + + + + Configure your server + Настроить ваш сервер + + + + 255.255.255.255:22 @@ -1643,35 +1769,25 @@ It's okay as long as it's from someone you trust. Password or SSH private key - - - Continue - - All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties - + Все введенные вами данные останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или третьим лицам Ip address cannot be empty - - - - - Enter the address in the format 255.255.255.255:88 - + Поле Ip address не может быть пустым Login cannot be empty - + Поле Login не может быть пустым Password/private key cannot be empty - + Поле Password/private key не может быть пустым @@ -1679,99 +1795,107 @@ It's okay as long as it's from someone you trust. What is the level of internet control in your region? - + Какой уровень контроля интернета в вашем регионе? Set up a VPN yourself - + Настроить VPN самостоятельно I want to choose a VPN protocol - + Выбрать VPN-протокол Continue - + Продолжить Set up later - + Настроить позднее PageSetupWizardInstalling - - - - Usually it takes no more than 5 minutes - - The server has already been added to the application - + Сервер уже был добавлен в приложение + + + Amnesia has detected that your server is currently + Amnesia обнаружила, что ваш сервер в настоящее время + + + busy installing other software. Amnesia installation + занят установкой других протоколов или сервисов. Установка Amnesia Amnezia has detected that your server is currently - + Amnezia обнаружила, что ваш сервер в настоящее время busy installing other software. Amnezia installation - + занят установкой другого программного обеспечения. Установка Amnezia will pause until the server finishes installing other software - + будет приостановлена до тех пор, пока сервер не завершит установку Installing - + Установка Cancel installation + + + + Usually it takes no more than 5 minutes + Обычно это занимает не более 5 минут + PageSetupWizardProtocolSettings Installing %1 - + Установить %1 More detailed - + Подробнее Close - + Закрыть Network protocol - + Сетевой протокол Port - + Порт Install - + Установить @@ -1779,12 +1903,12 @@ It's okay as long as it's from someone you trust. VPN protocol - + VPN протокол Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. - + Выберите протокол, который вам больше подходит. В дальнейшем можно установить другие протоколы и дополнительные сервисы, такие как DNS-прокси, TOR-сайт и SFTP. @@ -1792,7 +1916,7 @@ It's okay as long as it's from someone you trust. Point the camera at the QR code and hold for a couple of seconds. - + Наведите камеру на QR-код и удерживайте ее в течение нескольких секунд. @@ -1800,27 +1924,27 @@ It's okay as long as it's from someone you trust. Settings restored from backup file - + Восстановление настроек из бэкап файла Free service for creating a personal VPN on your server. - + Простое и бесплатное приложение для запуска self-hosted VPN с высокими требованиями к приватности. Helps you access blocked content without revealing your privacy, even to VPN providers. - + Помогает получить доступ к заблокированному контенту, не раскрывая вашу конфиденциальность даже провайдерам VPN. I have the data to connect - + У меня есть данные для подключения I have nothing - + У меня ничего нет @@ -1833,59 +1957,96 @@ It's okay as long as it's from someone you trust. Connection key - + Ключ для подключения A line that starts with vpn://... - + Строка, которая начинается с vpn://... Key - + Ключ Insert - + Вставить Continue - + Продолжить PageSetupWizardViewConfig - + New connection - + Новое соединение - + Do not use connection code from public sources. It could be created to intercept your data. - + Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные. - + Collapse content - + Свернуть - + Show content - + Показать содержимое ключа - + Connect - + Подключиться PageShare + + + OpenVpn native format + OpenVpn нативный формат + + + + WireGuard native format + WireGuard нативный формат + + + VPN Access + VPN-Доступ + + + + Connection + Соединение + + + VPN access without the ability to manage the server + Доступ к VPN, без возможности управления сервером + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. + Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки. + + + + + Server + Сервер + + + Accessing + Доступ + Config revoked @@ -1894,117 +2055,73 @@ It's okay as long as it's from someone you trust. Connection to - + Подключение к File with connection settings to - + Файл с настройками доступа к Save OpenVPN config - + Сохранить OpenVPN config Save WireGuard config - + Сохранить WireGuard config Save ShadowSocks config - + Сохранить конфигурацию ShadowSocks Save Cloak config - + Сохранить конфигурацию Cloak For the AmneziaVPN app - - - - - OpenVpn native format - - - - - WireGuard native format - + Для AmneziaVPN ShadowSocks native format - + ShadowSocks нативный формат Cloak native format - + Cloak нативный формат Share VPN Access - + Поделиться VPN Share full access to the server and VPN - + Поделиться полным доступом к серверу Use for your own devices, or share with those you trust to manage the server. - - - - - - Share - - - - - Connection - + Используйте для собственных устройств или передайте управление сервером тем, кому вы доверяете. Users - - - - - Share VPN access without the ability to manage the server - + Пользователи User name - - - - - - Server - - - - - - Protocol - - - - - - Connection format - + Имя пользователя @@ -2014,12 +2131,12 @@ It's okay as long as it's from someone you trust. Creation date: - + Дата создания Rename - + Переименовать @@ -2029,32 +2146,59 @@ It's okay as long as it's from someone you trust. Save - + Сохранить Revoke - + Отозвать Revoke the config for a user - %1? - + Отозвать доступ для пользователя - %1? The user will no longer be able to connect to your server. - + Пользователь больше не сможет подключаться к вашему серверу Continue - + Продолжить Cancel - + Отменить + + + Full access + Полный доступ + + + + Share VPN access without the ability to manage the server + Поделиться доступом к VPN, без возможности управления сервером + + + + + Protocol + Протокол + + + + + Connection format + Формат подключения + + + + + Share + Поделиться @@ -2062,29 +2206,29 @@ It's okay as long as it's from someone you trust. Full access to the server and VPN - + Полный доступ к серверу и VPN We recommend that you use full access to the server only for your own additional devices. - + Мы рекомендуем использовать полный доступ к серверу только для собственных устройств. If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. - + Если вы поделитесь полным доступом с другими людьми, то они смогут удалять и добавлять протоколы и сервисы на сервер, что приведет к некорректной работе VPN для всех пользователей. Server - + Сервер Accessing - + Доступ @@ -2094,17 +2238,17 @@ It's okay as long as it's from someone you trust. Share - + Поделиться Connection to - + Подключение к File with connection settings to - + Файл с настройками доступа к @@ -2112,7 +2256,7 @@ It's okay as long as it's from someone you trust. Close - + Закрыть @@ -2120,38 +2264,38 @@ It's okay as long as it's from someone you trust. Password entry not found - + Password entry not found Could not decrypt data - + Could not decrypt data Unknown error - + Unknown error Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Password not found - + Password not found Could not open keystore - + Could not open keystore Could not remove private key from keystore - + Could not remove private key from keystore @@ -2159,12 +2303,12 @@ It's okay as long as it's from someone you trust. Unknown error - + Unknown error Access to keychain denied - + Access to keychain denied @@ -2172,27 +2316,27 @@ It's okay as long as it's from someone you trust. Could not store data in settings: access error - + Could not store data in settings: access error Could not store data in settings: format error - + Could not store data in settings: format error Could not delete data from settings: access error - + Could not delete data from settings: access error Could not delete data from settings: format error - + Could not delete data from settings: format error Entry not found - + Entry not found @@ -2200,80 +2344,80 @@ It's okay as long as it's from someone you trust. Password entry not found - + Password entry not found Could not decrypt data - + Could not decrypt data D-Bus is not running - + D-Bus is not running Unknown error - + Unknown error No keychain service available - + No keychain service available Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Access to keychain denied - + Access to keychain denied Could not determine data type: %1; %2 - + Could not determine data type: %1; %2 Entry not found - + Entry not found Unsupported entry type 'Map' - + Unsupported entry type 'Map' Unknown kwallet entry type '%1' - + Unknown kwallet entry type '%1' Password not found - + Password not found Could not open keystore - + Could not open keystore Could not retrieve private key from keystore - + Could not retrieve private key from keystore Could not create decryption cipher - + Could not create decryption cipher @@ -2281,327 +2425,295 @@ It's okay as long as it's from someone you trust. Credential size exceeds maximum size of %1 - + Credential size exceeds maximum size of %1 Credential key exceeds maximum size of %1 - + Credential key exceeds maximum size of %1 Writing credentials failed: Win32 error code %1 - + Writing credentials failed: Win32 error code %1 Encryption failed - + Encryption failed D-Bus is not running - + D-Bus is not running Unknown error - + Unknown error Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Password not found - + Password not found Could not open keystore - + Could not open keystore Could not create private key generator - + Could not create private key generator Could not generate new private key - + Could not generate new private key Could not retrieve private key from keystore - + Could not retrieve private key from keystore Could not create encryption cipher - + Could not create encryption cipher Could not encrypt data - + Could not encrypt data QObject - - - Sftp service - - No error - + No error Unknown Error - + Unknown Error Function not implemented - + Function not implemented Server check failed - + Server check failed Server port already used. Check for another software - + Server port already used. Check for another software Server error: Docker container missing - + Server error: Docker container missing Server error: Docker failed - + Server error: Docker failed Installation canceled by user - + Installation canceled by user The user does not have permission to use sudo - + The user does not have permission to use sudo Ssh request was denied - + Ssh request was denied Ssh request was interrupted - + Ssh request was interrupted Ssh internal error - + Ssh internal error Invalid private key or invalid passphrase entered - + Invalid private key or invalid passphrase entered The selected private key format is not supported, use openssh ED25519 key types or PEM key types - + The selected private key format is not supported, use openssh ED25519 key types or PEM key types Timeout connecting to server - + Timeout connecting to server Sftp error: End-of-file encountered - + Sftp error: End-of-file encountered Sftp error: File does not exist - + Sftp error: File does not exist Sftp error: Permission denied - + Sftp error: Permission denied Sftp error: Generic failure - + Sftp error: Generic failure Sftp error: Garbage received from server - + Sftp error: Garbage received from server Sftp error: No connection has been set up - + Sftp error: No connection has been set up Sftp error: There was a connection, but we lost it - + Sftp error: There was a connection, but we lost it Sftp error: Operation not supported by libssh yet - + Sftp error: Operation not supported by libssh yet Sftp error: Invalid file handle - + Sftp error: Invalid file handle Sftp error: No such file or directory path exists - + Sftp error: No such file or directory path exists Sftp error: An attempt to create an already existing file or directory has been made - + Sftp error: An attempt to create an already existing file or directory has been made Sftp error: Write-protected filesystem - + Sftp error: Write-protected filesystem Sftp error: No media was in remote drive - - - - - OpenVPN config missing - - - - - OpenVPN management server error - - - - - OpenVPN executable missing - - - - - ShadowSocks (ss-local) executable missing - - - - - Cloak (ck-client) executable missing - - - - - Amnezia helper service error - - - - - OpenSSL failed - - - - - Can't connect: another VPN connection is active - - - - - Can't setup OpenVPN TAP network adapter - - - - - VPN pool error: no available addresses - + Sftp error: No media was in remote drive The config does not contain any containers and credentials for connecting to the server + Конфиг не содержит контейнеров и учетных данных для подключения к серверу + + + + ErrorCode: %1. + + Failed to save config to disk + Failed to save config to disk + + + + OpenVPN config missing + OpenVPN config missing + + + + OpenVPN management server error + OpenVPN management server error + + + + OpenVPN executable missing + OpenVPN executable missing + + + + ShadowSocks (ss-local) executable missing + ShadowSocks (ss-local) executable missing + + + + Cloak (ck-client) executable missing + Cloak (ck-client) executable missing + + + + Amnezia helper service error + Amnezia helper service error + + + + OpenSSL failed + OpenSSL failed + + + + Can't connect: another VPN connection is active + Can't connect: another VPN connection is active + + + + Can't setup OpenVPN TAP network adapter + Can't setup OpenVPN TAP network adapter + + + + VPN pool error: no available addresses + VPN pool error: no available addresses + VPN connection error - - - Error when retrieving configuration from API - - - This config has already been added to the application - - - - Internal error - - - - - ErrorCode: %1. - + Internal error IPsec - - - - - - Website in Tor network - - - - - Amnezia DNS - - - - - Sftp file sharing service - - - - - OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. - + IPsec @@ -2613,60 +2725,11 @@ It's okay as long as it's from someone you trust. OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. - - - WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. - - - - - AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. - - - - - IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. - - - - - Deploy a WordPress site on the Tor network in two clicks. - - - - - Replace the current DNS server with your own. This will increase your privacy level. - - Create a file vault on your server to securely store and transfer files. - - - OpenVPN stands as one of the most popular and time-tested VPN protocols available. -It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. - -* Available in the AmneziaVPN across all platforms -* Normal power consumption on mobile devices -* Flexible customisation to suit user needs to work with different operating systems and devices -* Recognised by DPI analysis systems and therefore susceptible to blocking -* Can operate over both TCP and UDP network protocols. - - - - - Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. - -* Available in the AmneziaVPN only on desktop platforms -* Normal power consumption on mobile devices - -* Configurable encryption protocol -* Detectable by some DPI systems -* Works over TCP network protocol. - - This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for protecting against blocking. @@ -2699,19 +2762,6 @@ WireGuard is very susceptible to blocking due to its distinct packet signatures. * Low power consumption * Minimum number of settings * Easily recognised by DPI analysis systems, susceptible to blocking -* Works over UDP network protocol. - - - - - A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. -While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. -This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. - -* Available in the AmneziaVPN across all platforms -* Low power consumption -* Minimum number of settings -* Not recognised by DPI analysis systems, resistant to blocking * Works over UDP network protocol. @@ -2726,77 +2776,267 @@ While it offers a blend of security, stability, and speed, it's essential t * Minimal configuration * Recognised by DPI analysis systems * Works over UDP network protocol, ports 500 and 4500. - + IKEv2 в сочетании с уровнем шифрования IPSec это современный и стабильный протокол VPN. +Он может быстро переключаться между сетями и устройствами, что делает его особенно адаптивным в динамичных сетевых средах. +Несмотря на сочетание безопасности, стабильности и скорости, необходимо отметить, что IKEv2 легко обнаруживается и подвержен блокировке. + +* Доступно в AmneziaVPN только для Windows. +* Низкое энергопотребление, на мобильных устройствах +* Минимальная конфигурация +* Распознается системами DPI-анализа +* Работает по сетевому протоколу UDP, порты 500 и 4500. DNS Service - + DNS Сервис + + + + Sftp file sharing service + Сервис обмена файлами Sftp + + + + + Website in Tor network + Веб-сайт в сети Tor + + + + Amnezia DNS + Amnezia DNS + + + + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. + OpenVPN - популярный VPN-протокол, с гибкой настройкой. Имеет собственный протокол безопасности с SSL/TLS для обмена ключами. + + + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but it may be recognized by analysis systems in some highly censored regions. + ShadowSocks - маскирует VPN-трафик под обычный веб-трафик, но распознается системами анализа в некоторых регионах с высоким уровнем цензуры. + + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + OpenVPN over Cloak - OpenVPN с маскировкой VPN под web-трафик и защитой от обнаружения active-probbing. Подходит для регионов с самым высоким уровнем цензуры. + + + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + WireGuard - Популярный VPN-протокол с высокой производительностью, высокой скоростью и низким энергопотреблением. Для регионов с низким уровнем цензуры. + + + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. + AmneziaWG - Специальный протокол от Amnezia, основанный на протоколе WireGuard. Он такой же быстрый, как WireGuard, но очень устойчив к блокировкам. Рекомендуется для регионов с высоким уровнем цензуры. + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. + IKEv2 Современный стабильный протокол, немного быстрее других восстанавливает соединение после потери сигнала. Имеет нативную поддержку последних версиий Android и iOS. + + + + Deploy a WordPress site on the Tor network in two clicks. + Разверните сайт на WordPress в сети Tor в два клика. + + + + Replace the current DNS server with your own. This will increase your privacy level. + Замените DNS-сервер на Amnezia DNS. Это повысит уровень конфиденциальности. + + + Create a file vault on your server to securely store and transfer files. + Создайте на сервере файловое хранилище для безопасного хранения и передачи файлов. + + + + OpenVPN stands as one of the most popular and time-tested VPN protocols available. +It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. + +* Available in the AmneziaVPN across all platforms +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices +* Recognised by DPI analysis systems and therefore susceptible to blocking +* Can operate over both TCP and UDP network protocols. + OpenVPN однин из самых популярных и проверенных временем VPN-протоколов. +В нем используется уникальный протокол безопасности, опирающийся на протокол SSL/TLS для шифрования и обмена ключами. Кроме того, поддержка OpenVPN множества методов аутентификации делает его универсальным и адаптируемым к широкому спектру устройств и операционных систем. Благодаря открытому исходному коду OpenVPN подвергается тщательному анализу со стороны мирового сообщества, что постоянно повышает его безопасность. Благодаря оптимальному соотношению производительности, безопасности и совместимости OpenVPN остается лучшим выбором как для частных лиц, так и для компаний, заботящихся о конфиденциальности. + +* Доступность AmneziaVPN для всех платформ +* Нормальное энергопотребление на мобильных устройствах +* Гибкая настройка под нужды пользователя для работы с различными операционными системами и устройствами +* Распознается системами DPI-анализа и поэтому подвержен блокировке +* Может работать по сетевым протоколам TCP и UDP. + + + + Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. + +* Available in the AmneziaVPN only on desktop platforms +* Normal power consumption on mobile devices + +* Configurable encryption protocol +* Detectable by some DPI systems +* Works over TCP network protocol. + Shadowsocks, создан на основе протокола SOCKS5, защищает соединение с помощью шифра AEAD. Несмотря на то, что протокол Shadowsocks разработан таким образом, чтобы быть незаметным и сложным для идентификации, он не идентичен стандартному HTTPS-соединению. Однако некоторые системы анализа трафика все же могут обнаружить соединение Shadowsocks. В связи с ограниченной поддержкой в Amnezia рекомендуется использовать протокол AmneziaWG, или OpenVPN over Cloak. + +* Доступен в AmneziaVPN только на ПК ноутбуках. +* Настраиваемый протокол шифрования +* Обнаруживается некоторыми DPI-системами +* Работает по сетевому протоколу TCP. + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. + +OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works over TCP network protocol, 443 port. + + OpenVPN over Cloak - это комбинация протокола OpenVPN и плагина Cloak, разработанного специально для защиты от блокировок. + +OpenVPN обеспечивает безопасное VPN-соединение за счет шифрования всего интернет-трафика между клиентом и сервером. + +Cloak защищает OpenVPN от обнаружения и блокировок. + +Cloak может изменять метаданные пакетов. Он полностью маскирует VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью Active Probing. Это делает его очень устойчивым к обнаружению + +Сразу же после получения первого пакета данных Cloak проверяет подлинность входящего соединения. Если аутентификация не проходит, плагин маскирует сервер под поддельный сайт, и ваш VPN становится невидимым для аналитических систем. + +Если в вашем регионе существует экстремальный уровень цензуры в Интернете, мы советуем вам при первом подключении использовать только OpenVPN через Cloak + +* Доступность AmneziaVPN на всех платформах +* Высокое энергопотребление на мобильных устройствах +* Гибкие настройки +* Не распознается системами DPI-анализа +* Работает по сетевому протоколу TCP, 443 порт. + + + + A relatively new popular VPN protocol with a simplified architecture. +Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. + WireGuard - относительно новый популярный VPN-протокол с упрощенной архитектурой. +Обеспечивает стабильное VPN-соединение, высокую производительность на всех устройствах. Использует жестко заданные настройки шифрования. WireGuard по сравнению с OpenVPN имеет меньшую задержку и лучшую пропускную способность при передаче данных. +WireGuard очень восприимчив к блокированию из-за особенностей сигнатур пакетов. В отличие от некоторых других VPN-протоколов, использующих методы обфускации, последовательные сигнатуры пакетов WireGuard легче выявляются и, соответственно, блокируются современными системами глубокой проверки пакетов (DPI) и другими средствами сетевого мониторинга. + +* Доступность AmneziaVPN для всех платформ +* Низкое энергопотребление +* Минимальное количество настроек +* Легко распознается системами DPI-анализа, подвержен блокировке +* Работает по сетевому протоколу UDP. + + + + A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. +While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. +This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Not recognised by DPI analysis systems, resistant to blocking +* Works over UDP network protocol. + AmneziaWG - усовершенствованная версия популярного VPN-протокола Wireguard. AmneziaWG опирается на фундамент, заложенный WireGuard, сохраняя упрощенную архитектуру и высокопроизводительные возможности работы на разных устройствах. +Хотя WireGuard известен своей эффективностью, у него были проблемы с обнаружением из-за характерных сигнатур пакетов. AmneziaWG решает эту проблему за счет использования более совершенных методов обфускации, благодаря чему его трафик сливается с обычным интернет-трафиком. +Таким образом, AmneziaWG сохраняет высокую производительность оригинала, добавляя при этом дополнительный уровень скрытности, что делает его отличным выбором для тех, кому нужно быстрое и незаметное VPN-соединение. + +* Доступность AmneziaVPN на всех платформах +* Низкое энергопотребление +* Минимальное количество настроек +* Не распознается системами DPI-анализа, устойчив к блокировке +* Работает по сетевому протоколу UDP. + + + AmneziaWG container + AmneziaWG протокол Sftp file sharing service - is secure FTP service - + Файловое хранилище для безопасного хранения данных + + + + Sftp service + Сервис SFTP Entry not found - + Entry not found Access to keychain denied - + Access to keychain denied No keyring daemon - + No keyring daemon Already unlocked - + Already unlocked No such keyring - + No such keyring Bad arguments - + Bad arguments I/O error - + I/O error Cancelled - + Cancelled Keyring already exists - + Keyring already exists No match - + No match Unknown error - + Unknown error error 0x%1: %2 - + error 0x%1: %2 @@ -2804,7 +3044,7 @@ While it offers a blend of security, stability, and speed, it's essential t Choose language - + Выберите язык @@ -2812,36 +3052,31 @@ While it offers a blend of security, stability, and speed, it's essential t Server #1 - + Server #1 Server - + Server SettingsController - - Software version - - - - - Backup file is corrupted - - - - + All settings have been reset to default values - + Все настройки были сброшены к значению "По умолчанию" - + Cached profiles cleared - + Кэш профиля очищен + + + + Backup file is corrupted + Backup файл поврежден @@ -2850,23 +3085,23 @@ While it offers a blend of security, stability, and speed, it's essential t Save AmneziaVPN config - + Сохранить config AmneziaVPN Share - + Поделиться Copy - + Скопировать Copied - + Скопировано @@ -2876,12 +3111,12 @@ While it offers a blend of security, stability, and speed, it's essential t Show connection settings - + Показать настройки подключения To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" - + Для считывания QR-кода в приложении Amnezia выберите "Добавить сервер" → "У меня есть данные для подключения" → "QR-код, ключ или файл настроек" @@ -2889,42 +3124,42 @@ While it offers a blend of security, stability, and speed, it's essential t Hostname not look like ip adress or domain name - + Имя хоста не похоже на ip-адрес или доменное имя New site added: %1 - + Добавлен новый сайт %1 Site removed: %1 - + Сайт удален %1 Can't open file: %1 - + Невозможно открыть файл: %1 Failed to parse JSON data from file: %1 - + Не удалось разобрать JSON-данные из файла: %1 The JSON data is not an array in file: %1 - + Данные JSON не являются массивом в файле: %1 Import completed - + Импорт завершен Export completed - + Экспорт завершен @@ -2933,31 +3168,31 @@ While it offers a blend of security, stability, and speed, it's essential t Show - + Показать Connect - + Подключиться Disconnect - + Отключиться Visit Website - + Посетить сайт Quit - + Закрыть @@ -2965,7 +3200,7 @@ While it offers a blend of security, stability, and speed, it's essential t The field can't be empty - + Поле не может быть пустым @@ -2973,7 +3208,7 @@ While it offers a blend of security, stability, and speed, it's essential t Mbps - + Mbps @@ -2981,42 +3216,42 @@ While it offers a blend of security, stability, and speed, it's essential t Unknown - + Неизвестный Disconnected - + Отключен Preparing - + Подготовка Connecting... - + Подключение... Connected - + Подключено Disconnecting... - + Отключение... Reconnecting... - + Переподключение... Error - + Ошибка @@ -3024,45 +3259,65 @@ While it offers a blend of security, stability, and speed, it's essential t Low - + Низкий Medium or High - + Средний или Высокий Extreme - + Экстремальный I just want to increase the level of my privacy. - + Я просто хочу повысить уровень своей приватности. I want to bypass censorship. This option recommended in most cases. - + Я хочу обойти блокировки. Этот вариант рекомендуется в большинстве случаев. Most VPN protocols are blocked. Recommended if other options are not working. - + Большинство VPN протоколов заблокированы. Рекомендуется, если другие варианты не работают. + + + High + Высокий + + + Medium + Средний + + + Many foreign websites and VPN providers are blocked + Многие иностранные сайты и VPN-провайдеры заблокированы + + + Some foreign sites are blocked, but VPN providers are not blocked + Некоторые иностранные сайты заблокированы, но VPN-провайдеры не блокируются + + + I just want to increase the level of privacy + Хочу просто повысить уровень приватности main2 - + Private key passphrase - + Кодовая фраза для закрытого ключа - + Save - + Сохранить diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 6c8ae4d24..ad996ced3 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -1,69 +1,104 @@ + + AmneziaApplication + + Split tunneling for WireGuard is not implemented, the option was disabled + 未启用选项,还未实现基于WireGuard协议的VPN分离 + + + + AndroidController + + VPN Connected + Refers to the app - which is currently running the background and waiting + VPN已连接 + + + + ApiController + + + Error when retrieving configuration from cloud server + + + ConnectionController - - - - + + + + Connect - + 连接 + + + + VPN Protocols is not installed. + Please install VPN container at first + 请先安装VPN协议 - VPN Protocols is not installed. - Please install VPN container at first - + Connection... + 连接中 + + + + Connected + 已连接 + + + + Reconnection... + 重连中 - Connection... - - - - - Connected - - - - - Reconnection... - - - - Disconnection... - + 断开中 - + Settings updated successfully, Reconnnection... - + 配置已更新,重连中 - + Settings updated successfully - + 配置更新成功 ConnectionTypeSelectionDrawer + + Connection data + 连接方式 + Add new connection - + 添加新连接 Configure your server - + 配置您的服务器 Open config file, key or QR code - + 配置文件,授权码或二维码 + + + Server IP, login and password + 服务器IP,用户名和密码 + + + QR code, key or configuration file + 二维码,授权码或者配置文件 @@ -71,22 +106,22 @@ C&ut - + 剪切 &Copy - + 拷贝 &Paste - + 粘贴 &SelectAll - + 全选 @@ -94,7 +129,7 @@ Access error! - + 访问错误 @@ -102,82 +137,111 @@ Unable change protocol while there is an active connection - + 已建立连接时无法更改服务器配置 The selected protocol is not supported on the current platform - + 当前平台不支持所选协议 + + + Reconnect via VPN Procotol: + 重连VPN基于协议: ImportController - + Scanned %1 of %2. - + 扫描 %1 of %2. InstallController + + installed successfully. + 安装成功 + + + is already installed on the server. + 已安装在服务器上 + %1 installed successfully. - + %1 安装成功。 %1 is already installed on the server. - + 服务器上已经安装 %1。 Added containers that were already installed on the server - + 添加已安装在服务器上的容器 Already installed containers were found on the server. All installed containers have been added to the application - + +在服务上发现已经安装协议并添加至应用 Settings updated successfully - + 配置更新成功 Server '%1' was rebooted - + 服务器 '%1' 已重新启动 Server '%1' was removed - + 已移除服务器 '%1' All containers from server '%1' have been removed - + 服务器 '%1' 的所有容器已移除 %1 has been removed from the server '%2' - + %1 已从服务器 '%2' 上移除 + + + 1% has been removed from the server '%2' + %1 已从服务器 '%2' 上移除 + + + Server ' + 服务器 + + + ' was removed + 已经移除 + + + has been removed from the server ' + 协议已从 Please login as the user - + 请以用户身份登录 Server added successfully - + 增加服务器成功 @@ -185,17 +249,17 @@ Already installed containers were found on the server. All installed containers Read key failed: %1 - + 获取授权码失败: %1 Write key failed: %1 - + 写入授权码失败: %1 Delete key failed: %1 - + 删除授权码失败: %1 @@ -204,27 +268,27 @@ Already installed containers were found on the server. All installed containers AmneziaVPN - + VPN Connected - + 已连接到VPN VPN Disconnected - + 已从VPN断开 AmneziaVPN notification - + AmneziaVPN 提示 Unsecured network detected: - + 发现不安全网络 @@ -232,30 +296,30 @@ Already installed containers were found on the server. All installed containers Removing services from %1 - + 正从 %1 移除服务 Usually it takes no more than 5 minutes - + 大约5分钟之内完成 PageHome - + VPN protocol - + VPN协议 - + Servers - + 服务器 - + Unable change server while there is an active connection - + 已建立连接时无法更改服务器配置 @@ -263,87 +327,91 @@ Already installed containers were found on the server. All installed containers AmneziaWG settings - + AmneziaWG 配置 Port - + 端口 Junk packet count - + 垃圾包数量 Junk packet minimum size - + 垃圾包最小值 Junk packet maximum size - + 垃圾包最大值 Init packet junk size - + 初始化垃圾包大小 Response packet junk size - + 响应垃圾包大小 Init packet magic header - + 初始化数据包魔数头 Response packet magic header - + 响应包魔数头 Transport packet magic header - + 传输包魔数头 Underload packet magic header - + 低负载数据包魔数头 Remove AmneziaWG - + 移除AmneziaWG Remove AmneziaWG from server? - + 从服务上移除AmneziaWG? All users with whom you shared a connection will no longer be able to connect to it. - + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 Continue - + 继续 Cancel - + 取消 Save and Restart Amnezia - + 保存并重启Amnezia @@ -351,28 +419,28 @@ Already installed containers were found on the server. All installed containers Cloak settings - + Cloak 配置 Disguised as traffic from - + 伪装流量为 Port - + 端口 Cipher - + 加密算法 Save and Restart Amnezia - + 保存并重启Amnezia @@ -380,7 +448,11 @@ Already installed containers were found on the server. All installed containers OpenVPN settings - + OpenVPN 配置 + + + VPN Addresses Subnet + VPN子网掩码 @@ -390,185 +462,193 @@ Already installed containers were found on the server. All installed containers Network protocol - + 网络协议 Port - + 端口 Auto-negotiate encryption - + 自定义加密方式 Hash - + SHA512 - + SHA384 - + SHA256 - + SHA3-512 - + SHA3-384 - + SHA3-256 - + whirlpool - + BLAKE2b512 - + BLAKE2s256 - + SHA1 - + Cipher - + AES-256-GCM - + AES-192-GCM - + AES-128-GCM - + AES-256-CBC - + AES-192-CBC - + AES-128-CBC - + ChaCha20-Poly1305 - + ARIA-256-CBC - + CAMELLIA-256-CBC - + none - + TLS auth - + TLS认证 Block DNS requests outside of VPN - + 阻止VPN外的DNS请求 Additional client configuration commands - + 附加客户端配置命令 Commands: - + 命令: Additional server configuration commands - + 附加服务器端配置命令 Remove OpenVPN - + 移除OpenVPN Remove OpenVpn from server? - + 从服务器移除OpenVPN吗? All users with whom you shared a connection will no longer be able to connect to it. - + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 + + + All users with whom you shared a connection will no longer be able to connect to it + 与您共享连接的所有用户将无法再连接到此链接 Continue - + 继续 Cancel - + 取消 Save and Restart Amnezia - + 保存并重启Amnezia @@ -576,42 +656,58 @@ Already installed containers were found on the server. All installed containers settings - + 配置 Show connection options - + 显示连接选项 + + + Connection options + 连接选项 Connection options %1 - + %1 连接选项 Remove - + 移除 Remove %1 from server? - + 从服务器移除 %1 ? All users with whom you shared a connection will no longer be able to connect to it. - + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 + + + from server? + 从服务器 + + + All users with whom you shared a connection will no longer be able to connect to it + 与您共享连接的所有用户将无法再连接到此链接 Continue - + 继续 Cancel - + 取消 @@ -619,23 +715,23 @@ Already installed containers were found on the server. All installed containers ShadowSocks settings - + ShadowSocks 配置 Port - + 端口 Cipher - + 加密算法 Save and Restart Amnezia - + 保存并重启Amnezia @@ -644,32 +740,37 @@ Already installed containers were found on the server. All installed containers A DNS service is installed on your server, and it is only accessible via VPN. - + 您的服务器已安装DNS服务,仅能通过VPN访问。 + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. - + 其地址与您的服务器地址相同。您可以在 设置 连接 中进行配置。 Remove - + 移除 Remove %1 from server? - + 从服务器移除 %1 ? + + + from server? + 从服务器 Continue - + 继续 Cancel - + 取消 @@ -677,17 +778,17 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + 配置更新成功 SFTP settings - + SFTP 配置 Host - + 主机 @@ -695,69 +796,69 @@ Already installed containers were found on the server. All installed containers Copied - + 拷贝 Port - + 端口 Login - + 用户 Password - + 密码 Mount folder on device - + 挂载文件夹 In order to mount remote SFTP folder as local drive, perform following steps: <br> - + 为将远程 SFTP 文件夹挂载到本地,请执行以下步骤: <br> <br>1. Install the latest version of - + <br>1. 安装最新版的 <br>2. Install the latest version of - + <br>2. 安装最新版的 Detailed instructions - + 详细说明 Remove SFTP and all data stored there - + 移除SFTP和其本地所有数据 Remove SFTP and all data stored there? - + 移除SFTP和其本地所有数据? Continue - + 继续 Cancel - + 取消 @@ -765,22 +866,22 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + 配置更新成功 Tor website settings - + Tor网站配置 Website address - + 网址 Copied - + 已拷贝 @@ -790,32 +891,44 @@ Already installed containers were found on the server. All installed containers After creating your onion site, it takes a few minutes for the Tor network to make it available for use. - + 创建您的洋葱网站后,需要几分钟时间,才能使其在Tor网络上可用 + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this URL. + 用 <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> 打开上面网址 + + + After installation it takes several minutes while your onion site will become available in the Tor Network. + 完成安装几分钟后,洋葱站点才会在 Tor 网络中生效。 When configuring WordPress set the this onion address as domain. - + 配置 WordPress 时,将此洋葱地址设置为域。 + + + When configuring WordPress set the domain as this onion address. + 配置 WordPress 时,将域设置为此洋葱地址。 Remove website - + 移除网站 The site with all data will be removed from the tor network. - + 网站及其所有数据将从 Tor 网络中删除 Continue - + 继续 Cancel - + 取消 @@ -823,70 +936,81 @@ Already installed containers were found on the server. All installed containers Settings - + 设置 Servers - + 服务器 Connection - + 连接 Application - + 应用 Backup - + 备份 About AmneziaVPN - + 关于 Close application - + 关闭应用 PageSettingsAbout + + Support the project with a donation + 捐款 + + + This is a free and open source application. If you like it, support the developers with a donation. +And if you don't like the app, all the more support it - the donation will be used to improve the app. + 这是一个免费且开源的应用软件。如果您喜欢它,请捐助支持我们继续研发。 +如果您不喜欢,请捐助支持我们改进它。 + Support Amnezia - + 支持Amnezia This is a free and open source application. If you like it, support the developers with a donation. - + 这是一个免费且开源的软件。如果您喜欢它,请捐助开发者们。 + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. - + 如果您不喜欢,请捐助支持我们改进它。 Card on Patreon - + Patreon订阅 https://www.patreon.com/amneziavpn - + Show other methods on Github - + 其他捐款途径 @@ -896,57 +1020,62 @@ Already installed containers were found on the server. All installed containers Contacts - + 联系方式 Telegram group - + 电报群 To discuss features - + 用于功能讨论 https://t.me/amnezia_vpn_en - + Mail - + 邮件 For reviews and bug reports - + 用于评论和提交软件的缺陷 Github - + https://github.com/amnezia-vpn/amnezia-client - + Website - + 官网 https://amnezia.org - + + + + + Software version: %1 + 软件版本: %1 Check for updates - + 检查更新 @@ -954,77 +1083,89 @@ Already installed containers were found on the server. All installed containers Application - + 应用 Allow application screenshots - + 允许截屏 Auto start - + 自动运行 + + + Launch the application every time + 总是在系统 + + + starts + 启动时自动运行运用程序 + + + Launch the application every time %1 starts + 运行应用软件在%1系统启动时 Launch the application every time the device is starts - + 每次设备启动时启动应用程序 Start minimized - + 最小化 Launch application minimized - + 开启应用软件时窗口最小化 Language - + 语言 Logging - + 日志 Enabled - + 开启 Disabled - + 禁用 Reset settings and remove all data from the application - + 重置并清理应用的所有数据 Reset settings and remove all data from the application? - + 重置并清理应用的所有数据? All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - + 所有配置恢复为默认值。服务器已安装的AmneziaVPN服务将被保留。 Continue - + 继续 Cancel - + 取消 @@ -1032,73 +1173,77 @@ Already installed containers were found on the server. All installed containers Settings restored from backup file - + 从备份文件还原配置 Backup - + 备份 Configuration backup - + 备份设置 + + + It will help you instantly restore connection settings at the next installation + 帮助您在下次安装时立即恢复连接设置 You can save your settings to a backup file to restore them the next time you install the application. - + 您可以将配置信息备份到文件中,以便在下次安装应用软件时恢复配置 Make a backup - + 进行备份 Save backup file - + 保存备份 Backup files (*.backup) - + Backup file saved - + 备份文件已保存 Restore from backup - + 从备份还原 Open backup file - + 打开备份文件 Import settings from a backup file? - + 从备份文件导入设置? All current settings will be reset - + 当前所有设置将重置 Continue - + 继续 Cancel - + 取消 @@ -1106,58 +1251,82 @@ Already installed containers were found on the server. All installed containers Connection - + 连接 Auto connect - + 自动连接 Connect to VPN on app start - - - - - Use AmneziaDNS - - - - - If AmneziaDNS is installed on the server - - - - - DNS servers - + 应用开启时连接VPN When AmneziaDNS is not used or installed - - - - - Site-based split tunneling - - - - - Allows you to select which sites you want to access through the VPN - - - - - App-based split tunneling - + 当未使用或未安装AmneziaDNS时 Allows you to use the VPN only for certain Apps + + Use AmneziaDNS if installed on the server + 使用AmneziaDNS,如其已安装在服务器上 + + + + Use AmneziaDNS + 使用AmneziaDNS + + + + If AmneziaDNS is installed on the server + 如果已在服务器安装AmneziaDNS + + + + DNS servers + DNS服务器 + + + If AmneziaDNS is not used or installed + 如果未使用或未安装AmneziaDNS + + + + Site-based split tunneling + 基于网站的隧道分离 + + + + Allows you to select which sites you want to access through the VPN + 配置想要通过VPN访问网站 + + + + App-based split tunneling + 基于应用的隧道分离 + + + Split site tunneling + 网站级VPN分流 + + + Allows you to connect to some sites through a secure connection, and to others bypassing it + 使用VPN访问指定网站,其他的则绕过 + + + Separate application tunneling + 应用级VPN分流 + + + Allows you to use the VPN only for certain applications + 仅指定应用使用VPN + PageSettingsDns @@ -1169,57 +1338,57 @@ Already installed containers were found on the server. All installed containers DNS servers - + DNS服务器 If AmneziaDNS is not used or installed - + 如果未使用或未安装AmneziaDNS Primary DNS - + 首选 DNS Secondary DNS - + 备用 DNS Restore default - + 恢复默认配置 Restore default DNS settings? - + 是否恢复默认DNS配置? Continue - + 继续 Cancel - + 取消 Settings have been reset - + 已重置 Save - + 保存 Settings saved - + 配置已保存 @@ -1227,62 +1396,62 @@ Already installed containers were found on the server. All installed containers Logging - + 日志 Save logs - + 记录日志 Open folder with logs - + 打开日志文件夹 Save - + 保存 Logs files (*.log) - + Logs file saved - + 日志文件已保存 Save logs to file - + 保存日志到文件 Clear logs? - + 清理日志? Continue - + 继续 Cancel - + 取消 Logs have been cleaned up - + 日志已清理 Clear logs - + 清理日志 @@ -1290,30 +1459,40 @@ Already installed containers were found on the server. All installed containers All installed containers have been added to the application - + 所有已安装的容器,已被添加到应用软件 No new installed containers found - + 未发现新安装的容器 Clear Amnezia cache - + 清除 Amnezia 缓存 May be needed when changing other settings - + 更改其他设置时可能需要缓存 Clear cached profiles? - + 清除缓存? - + + Do you want to reboot the server? + 您想重新启动服务器吗? + + + + Do you want to clear server from Amnezia software? + 您要清除服务器上的Amnezia软件吗? + + + @@ -1322,88 +1501,69 @@ Already installed containers were found on the server. All installed containers - - Continue - + 继续 - - Cancel - + 取消 Check the server for previously installed Amnezia services - + 检查服务器上,是否存在之前安装的 Amnezia 服务 Add them to the application if they were not displayed - + 如果存在且未显示,则添加到应用软件 Reboot server - - - - - Do you want to reboot the server? - + 重新启动服务器 The reboot process may take approximately 30 seconds. Are you sure you wish to proceed? - + 重新启动过程可能需要大约30秒。您确定要继续吗? Remove server from application - + 移除本地服务器信息 Do you want to remove the server from application? - + 您想要从应用程序中移除服务器吗? + + + Remove server? + 移除本地服务器信息? All installed AmneziaVPN services will still remain on the server. - + 所有已安装的 AmneziaVPN 服务仍将保留在服务器上。 - Clear server from Amnezia software - + 清理Amnezia中服务器信息 - - - Do you want to clear server from Amnezia software? - + Clear server from Amnezia software? + 清理Amnezia中服务器信息 - All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. - - - - - Reset API config - - - - - Do you want to reset API config? - + 服务器上的所有容器都将被删除。配置文件、密钥和证书也将被删除。 @@ -1411,27 +1571,27 @@ Already installed containers were found on the server. All installed containers Server name - + 服务器名 Save - + 保存 Protocols - + 协议 Services - + 服务 Data - + 数据 @@ -1439,32 +1599,44 @@ Already installed containers were found on the server. All installed containers settings - + 配置 Remove - - - - - Remove %1 from server? - + 移除 All users with whom you shared a connection will no longer be able to connect to it. - + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 + + + from server? + 从服务器 + + + + Remove %1 from server? + 从服务器移除 %1 ? + + + All users with whom you shared a connection will no longer be able to connect to it + 与您共享连接的所有用户将无法再连接到此链接 Continue - + 继续 Cancel - + 取消 @@ -1472,103 +1644,127 @@ Already installed containers were found on the server. All installed containers Servers - + 服务器 PageSettingsSplitTunneling + + Only the addresses in the list must be opened via VPN + 仅列表中的地址须通过VPN访问 + + + Addresses from the list should never be opened via VPN + 勿通过VPN访问列表中的地址 + + + Split site tunneling + 网站级VPN分流 + Default server does not support split tunneling function - - Only the sites listed here will be accessed through the VPN - + Addresses from the list should be accessed via VPN + 仅使用VPN访问 Addresses from the list should not be accessed via VPN - + 不使用VPN访问 Split tunneling - + 隧道分离 Mode - + 规则 Remove - + 移除 Continue - + 继续 Cancel - + 取消 + + + Site or IP + 网站或IP地址 + + + Import/Export Sites + 导入/导出网站 + + + + Only the sites listed here will be accessed through the VPN + 只有这里列出的网站将通过VPN访问 website or IP - + 网站或IP Import / Export Sites - + 导入/导出网站 Import - + 导入 Save site list - + 保存网址 Save sites - + 保存网址 Sites files (*.json) - + Import a list of sites - + 导入网址列表 Replace site list - + 替换网址列表 Open sites file - + 打开网址文件 Add imported sites to existing ones - + 将导入的网址添加到现有网址中 @@ -1576,66 +1772,91 @@ Already installed containers were found on the server. All installed containers Server connection - + 服务器连接 Do not use connection code from public sources. It may have been created to intercept your data. It's okay as long as it's from someone you trust. - + 请勿使用公共来源的连接码。它可能是为了拦截您的数据而创建的。 +请确保连接码来源可信。 What do you have? - + 你用什么方式创建连接? File with connection settings or backup - + 包含连接配置或备份的文件 File with connection settings - + 包含连接配置的文件 Open config file - + 打开配置文件 QR-code - + 二维码 Key as text - + 授权码文本 PageSetupWizardCredentials + + Server connection + 连接服务器 + Configure your server - + 配置服务器 Server IP address [:port] - - - - - 255.255.255.255:22 - + 服务器IP [:端口] Login to connect via SSH + 用户 + + + Password / SSH private key + 密码 或 私钥 + + + + Continue + 继续 + + + + All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties + + + + All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties + 您输入的所有数据将严格保密 +不会向 Amnezia 或任何第三方分享或披露 + + + + 255.255.255.255:22 @@ -1643,35 +1864,25 @@ It's okay as long as it's from someone you trust. Password or SSH private key - - - Continue - - - - - All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties - - Ip address cannot be empty - + IP不能为空 Enter the address in the format 255.255.255.255:88 - + 按照这种格式输入 255.255.255.255:88 Login cannot be empty - + 账号不能为空 Password/private key cannot be empty - + 密码或私钥不能为空 @@ -1679,27 +1890,27 @@ It's okay as long as it's from someone you trust. What is the level of internet control in your region? - + 您所在地区的互联网管控力度如何? Set up a VPN yourself - + 自己架设VPN I want to choose a VPN protocol - + 我想选择VPN协议 Continue - + 继续 Set up later - + 稍后设置 @@ -1708,70 +1919,78 @@ It's okay as long as it's from someone you trust. Usually it takes no more than 5 minutes - + 通常不超过5分钟 The server has already been added to the application - + 服务器已添加到应用软件中 Amnezia has detected that your server is currently - + Amnezia 检测到您的服务器当前 busy installing other software. Amnezia installation - - - - - will pause until the server finishes installing other software - - - - - Installing - + 正安装其他软件。Amnezia安装 Cancel installation + + Amnesia has detected that your server is currently + Amnezia 检测到您的服务器当前 + + + busy installing other software. Amnesia installation + 正安装其他软件。Amnezia安装 + + + + will pause until the server finishes installing other software + 将暂停,直到其他软件安装完成。 + + + + Installing + 安装中 + PageSetupWizardProtocolSettings Installing %1 - + 正在安装 %1 More detailed - + 更多细节 Close - + 关闭 Network protocol - + 网络协议 Port - + 端口 Install - + 安装 @@ -1779,12 +1998,12 @@ It's okay as long as it's from someone you trust. VPN protocol - + VPN 协议 Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. - + 选择你认为优先级最高的一项。稍后,您可以安装其他协议和附加服务,例如 DNS 代理和 SFTP。 @@ -1792,7 +2011,7 @@ It's okay as long as it's from someone you trust. Point the camera at the QR code and hold for a couple of seconds. - + 将相机对准二维码并按住几秒钟 @@ -1800,27 +2019,27 @@ It's okay as long as it's from someone you trust. Settings restored from backup file - + 从备份文件还原配置 Free service for creating a personal VPN on your server. - + 在您的服务器上架设私人免费VPN服务。 Helps you access blocked content without revealing your privacy, even to VPN providers. - + 帮助您访问受限内容,保护您的隐私,即使是VPN提供商也无法获取。 I have the data to connect - + 我有连接配置 I have nothing - + 我没有 @@ -1833,83 +2052,68 @@ It's okay as long as it's from someone you trust. Connection key - + 连接授权码 A line that starts with vpn://... - + 以 vpn://... 开始的行 Key - + 授权码 Insert - + 插入 Continue - + 继续 PageSetupWizardViewConfig - + New connection - + 新连接 - + Do not use connection code from public sources. It could be created to intercept your data. - + 请勿使用公共来源的连接码。它可以被创建来拦截您的数据。 - + Collapse content - + 折叠内容 - + Show content - + 显示内容 - + Connect - + 连接 PageShare - - - Config revoked - - - - - Connection to - - - - - File with connection settings to - - Save OpenVPN config - + 保存OpenVPN配置 Save WireGuard config - + 保存WireGuard配置 @@ -1924,87 +2128,53 @@ It's okay as long as it's from someone you trust. For the AmneziaVPN app - + AmneziaVPN 应用 OpenVpn native format - + OpenVPN原生格式 WireGuard native format - + WireGuard原生格式 ShadowSocks native format - + ShadowSocks原生格式 Cloak native format - + Cloak原生格式 Share VPN Access - + 共享 VPN 访问 Share full access to the server and VPN - + 共享服务器和VPN的完全访问权限 Use for your own devices, or share with those you trust to manage the server. - - - - - - Share - - - - - Connection - + 用于您自己的设备,或与您信任的人共享以管理服务器 Users - + 用户 Share VPN access without the ability to manage the server - - - - - User name - - - - - - Server - - - - - - Protocol - - - - - - Connection format - + 共享 VPN 访问,无需管理服务器 @@ -2029,32 +2199,125 @@ It's okay as long as it's from someone you trust. Save - + 保存 Revoke - + 撤销 Revoke the config for a user - %1? - + 撤销用户的配置- %1? The user will no longer be able to connect to your server. - + 该用户将无法再连接到您的服务器 Continue - + 继续 Cancel - + 取消 + + + Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings. + 共享服务器管理访问权限。与您共享服务器全部访问权限的用户将可以添加和删除服务器上的任何协议和服务,以及更改设置。 + + + VPN Access + 访问VPN + + + + Connection + 连接 + + + Full access + 完全访问 + + + VPN access without the ability to manage the server + 访问VPN,但没有权限管理服务。 + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. + 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. + 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 + + + Full access to server + 获得服务器完整授权 + + + Servers + 服务器 + + + + + Server + 服务器 + + + Accessing + 访问 + + + File with accessing settings to + 访问配置文件的内容为: + + + + File with connection settings to + 连接配置文件的内容为: + + + Protocols + 协议 + + + + + Protocol + 协议 + + + + Connection to + 连接到 + + + + Config revoked + 配置已撤销 + + + + User name + 用户名 + + + + + Connection format + 连接格式 + + + + + Share + 共享 @@ -2062,7 +2325,7 @@ It's okay as long as it's from someone you trust. Full access to the server and VPN - + 对服务器和VPN的完全访问权限 @@ -2073,38 +2336,38 @@ It's okay as long as it's from someone you trust. If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. - + 如果您与其他人共享完全访问权限,他们可以从服务器中删除和添加协议和服务,这将导致VPN对所有用户的工作出现问题。 Server - + 服务器 Accessing - + 访问 File with accessing settings to - + 访问配置文件的内容为: Share - + 共享 Connection to - + 连接到 File with connection settings to - + 连接配置文件的内容为: @@ -2112,7 +2375,7 @@ It's okay as long as it's from someone you trust. Close - + 关闭 @@ -2120,38 +2383,38 @@ It's okay as long as it's from someone you trust. Password entry not found - + 未发现秘密 Could not decrypt data - + 数据无法加密 Unknown error - + 未知错误 Could not open wallet: %1; %2 - + 无法打开钱包: %1; %2 Password not found - + 未发现密码 Could not open keystore - + 无法打开密钥库 Could not remove private key from keystore - + 无法从密钥库中删除私钥 @@ -2159,12 +2422,12 @@ It's okay as long as it's from someone you trust. Unknown error - + 未知错误 Access to keychain denied - + 访问钥匙串被拒绝 @@ -2172,27 +2435,27 @@ It's okay as long as it's from someone you trust. Could not store data in settings: access error - + 无法在配置中存储数据:访问错误 Could not store data in settings: format error - + 无法在陪置中存储数据:格式错误 Could not delete data from settings: access error - + 无法在配置中删除数据:访问错误 Could not delete data from settings: format error - + 无法在配置中删除数据:格式错误 Entry not found - + 未找到条目 @@ -2200,80 +2463,80 @@ It's okay as long as it's from someone you trust. Password entry not found - + 未发现密码 Could not decrypt data - + 数据无法加密 D-Bus is not running - + D-Bus未运行 Unknown error - + 未知错误 No keychain service available - + 没有有效的钥匙串服务 Could not open wallet: %1; %2 - + 无法打开钱包: %1; %2 Access to keychain denied - + 访问钥匙串被拒绝 Could not determine data type: %1; %2 - + 无法确定数据类型: %1; %2 Entry not found - + 未找到记录 Unsupported entry type 'Map' - + 不支持的记录类型 'Map' Unknown kwallet entry type '%1' - + 未知钱包类型 '%1' Password not found - + 未发现密码 Could not open keystore - + 无法打开密钥库 Could not retrieve private key from keystore - + 无法从密钥存储库中检索私钥 Could not create decryption cipher - + 无法创建解密算法 @@ -2281,73 +2544,73 @@ It's okay as long as it's from someone you trust. Credential size exceeds maximum size of %1 - + 证书大小超过上限,最大为: %1 Credential key exceeds maximum size of %1 - + 凭证密钥大小超过上限,最大为: %1 Writing credentials failed: Win32 error code %1 - + 写入凭证失败,Win32错误码: %1 Encryption failed - + 加密失败 D-Bus is not running - + D-Bus未运行 Unknown error - + 未知错误 Could not open wallet: %1; %2 - + 无法打开钱包: %1; %2 Password not found - + 未发现密码 Could not open keystore - + 无法打开密钥库 Could not create private key generator - + 无法创建私钥生成器 Could not generate new private key - + 无法生成新的私钥 Could not retrieve private key from keystore - + 无法从密钥库检索私钥 Could not create encryption cipher - + 无法创建加密密码 Could not encrypt data - + 无法加密数据 @@ -2355,202 +2618,147 @@ It's okay as long as it's from someone you trust. Sftp service - + Sftp 服务 No error - + 没有错误 Unknown Error - + 未知错误 Function not implemented - + 功能未实现 Server check failed - + 服务器检测失败 Server port already used. Check for another software - + 检测服务器该端口是否被其他软件被占用 Server error: Docker container missing - + 服务器错误: Docker容器丢失 Server error: Docker failed - + 服务器错误: Docker失败 Installation canceled by user - + 用户取消安装 The user does not have permission to use sudo - + 用户没有root权限 Ssh request was denied - + ssh请求被拒绝 Ssh request was interrupted - + ssh请求中断 Ssh internal error - + ssh内部错误 Invalid private key or invalid passphrase entered - + 输入的私钥或密码无效 The selected private key format is not supported, use openssh ED25519 key types or PEM key types - + 不支持所选私钥格式,请使用 openssh ED25519 密钥类型或 PEM 密钥类型 Timeout connecting to server - + 连接服务器超时 Sftp error: End-of-file encountered - + Sftp错误: End-of-file encountered Sftp error: File does not exist - + Sftp错误: 文件不存在 Sftp error: Permission denied - + Sftp错误: 权限不足 Sftp error: Generic failure - + Sftp错误: 一般失败 Sftp error: Garbage received from server - + Sftp错误: 从服务器收到垃圾信息 Sftp error: No connection has been set up - + Sftp 错误: 未建立连接 Sftp error: There was a connection, but we lost it - + Sftp 错误: 已有连接丢失 Sftp error: Operation not supported by libssh yet - + Sftp error: libssh不支持该操作 Sftp error: Invalid file handle - + Sftp error: 无效的文件句柄 Sftp error: No such file or directory path exists - + Sftp 错误: 文件夹或文件不存在 Sftp error: An attempt to create an already existing file or directory has been made - + Sftp 错误: 文件或目录已存在 Sftp error: Write-protected filesystem - + Sftp 错误: 文件系统写保护 Sftp error: No media was in remote drive - - - - - OpenVPN config missing - - - - - OpenVPN management server error - - - - - OpenVPN executable missing - - - - - ShadowSocks (ss-local) executable missing - - - - - Cloak (ck-client) executable missing - - - - - Amnezia helper service error - - - - - OpenSSL failed - - - - - Can't connect: another VPN connection is active - - - - - Can't setup OpenVPN TAP network adapter - - - - - VPN pool error: no available addresses - - - - - The config does not contain any containers and credentials for connecting to the server - + Sftp 错误: 远程驱动器中没有媒介 @@ -2558,114 +2766,118 @@ It's okay as long as it's from someone you trust. - - Error when retrieving configuration from API - - - - - This config has already been added to the application - - - - - Internal error - - - - + ErrorCode: %1. + + Failed to save config to disk + 配置保存到磁盘失败 + + + + OpenVPN config missing + OpenVPN配置丢失 + + + + OpenVPN management server error + OpenVPN 管理服务器错误 + + + + OpenVPN executable missing + OpenVPN 可执行文件丢失 + + + + ShadowSocks (ss-local) executable missing + ShadowSocks (ss-local) 执行文件丢失 + + + + Cloak (ck-client) executable missing + Cloak (ck-client) 执行文件丢失 + + + + Amnezia helper service error + Amnezia 服务连接失败 + + + + OpenSSL failed + OpenSSL错误 + + + + Can't connect: another VPN connection is active + 无法连接:另一个VPN连接处于活跃状态 + + + + Can't setup OpenVPN TAP network adapter + 无法设置 OpenVPN TAP 网络适配器 + + + + VPN pool error: no available addresses + VPN 池错误:没有可用地址 + + + + The config does not contain any containers and credentials for connecting to the server + 配置不包含任何用于连接服务器的容器和凭据 + + + The config does not contain any containers and credentiaks for connecting to the server + 该配置不包含任何用于连接到服务器的容器和凭据。 + + + + Internal error + + IPsec - + Website in Tor network - + 在 Tor 网络中架设网站 Amnezia DNS - + Sftp file sharing service - + SFTP文件共享服务 OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. - + OpenVPN 是最流行的 VPN 协议,具有灵活的配置选项。它使用自己的安全协议与 SSL/TLS 进行密钥交换。 ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but it may be recognized by analysis systems in some highly censored regions. - + ShadowSocks - 掩盖VPN流量,使其类似于正常的网络流量,但在一些高度审查的地区可能会被分析系统识别 - OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. - - - - - WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. - - - - - AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. - - - - - IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. - - - - - Deploy a WordPress site on the Tor network in two clicks. - - - - - Replace the current DNS server with your own. This will increase your privacy level. - + OpenVPN over Cloak - OpenVPN with masquerading as web traffic and protection against active-probing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + OpenVPN over Cloak - OpenVPN与VPN结合,伪装成Web流量,并保护免受主动探测的侦测。非常适合在具有最高审查水平的地区绕过封锁 Create a file vault on your server to securely store and transfer files. - - - - - OpenVPN stands as one of the most popular and time-tested VPN protocols available. -It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. - -* Available in the AmneziaVPN across all platforms -* Normal power consumption on mobile devices -* Flexible customisation to suit user needs to work with different operating systems and devices -* Recognised by DPI analysis systems and therefore susceptible to blocking -* Can operate over both TCP and UDP network protocols. - - - - - Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. - -* Available in the AmneziaVPN only on desktop platforms -* Normal power consumption on mobile devices - -* Configurable encryption protocol -* Detectable by some DPI systems -* Works over TCP network protocol. - + 在您的服务器上创建一个文件保险库,用于安全存储和传输文件。 @@ -2700,7 +2912,147 @@ WireGuard is very susceptible to blocking due to its distinct packet signatures. * Minimum number of settings * Easily recognised by DPI analysis systems, susceptible to blocking * Works over UDP network protocol. - + 一个相对较新的流行VPN协议,具有简化的架构。 +WireGuard提供稳定的VPN连接,并在所有设备上具有高性能。它使用硬编码的加密设置。与OpenVPN相比,WireGuard具有较低的延迟和更好的数据传输吞吐量。 +WireGuard非常容易被阻挡,因为其独特的数据包签名。与一些其他VPN协议不同,这些协议使用混淆技术,WireGuard数据包的一致签名模式更容易被高级深度数据包检测(DPI)系统和其他网络监控工具识别和阻挡。 + + 在AmneziaVPN上适用于所有平台 + 低功耗 + 最少的设置 + 易于被DPI分析系统识别,容易被阻挡 + 通过UDP网络协议运行。 + + + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. + ShadowSocks - 混淆 VPN 流量,使其与正常的 Web 流量相似,但在一些审查力度高的地区可以被分析系统识别。 + + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + OpenVPN over Cloak - OpenVPN与VPN结合,伪装成Web流量,并保护免受主动探测的侦测。非常适合在具有最高审查水平的地区绕过封锁 + + + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + WireGuard - 新型流行的VPN协议,具有高性能、高速度和低功耗。建议用于审查力度较低的地区 + + + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. + AmneziaWG - Amnezia 的特殊协议,基于 WireGuard。它的速度像 WireGuard 一样快,但非常抗堵塞。推荐用于审查较严的地区。 + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. + IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 + + + + Deploy a WordPress site on the Tor network in two clicks. + 只需点击两次即可架设 WordPress 网站到 Tor 网络 + + + + Replace the current DNS server with your own. This will increase your privacy level. + 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私保护级别。 + + + Creates a file vault on your server to securely store and transfer files. + 在您的服务器上创建文件仓库,以便安全地存储和传输文件 + + + + OpenVPN stands as one of the most popular and time-tested VPN protocols available. +It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. + +* Available in the AmneziaVPN across all platforms +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices +* Recognised by DPI analysis systems and therefore susceptible to blocking +* Can operate over both TCP and UDP network protocols. + OpenVPN 是最流行且经过时间考验的 VPN 协议之一。 +它采用其独特的安全协议,利用 SSL/TLS 的优势进行加密和密钥交换。此外,OpenVPN 支持多种身份验证方法,使其具有多功能性和适应性,可适应各种设备和操作系统。由于其开源性质,OpenVPN 受益于全球社区的广泛审查,这不断增强了其安全性。凭借性能、安全性和兼容性的强大平衡,OpenVPN 仍然是注重隐私的个人和企业的首选。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 移动设备的正常功耗 +* 灵活定制,满足用户使用不同操作系统和设备的需求 +* 被DPI分析系统识别,因此容易被阻塞 +* 可以通过 TCP 和 UDP 网络协议运行 + + + + Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. + +* Available in the AmneziaVPN only on desktop platforms +* Normal power consumption on mobile devices + +* Configurable encryption protocol +* Detectable by some DPI systems +* Works over TCP network protocol. + Shadowsocks 受到 SOCKS5 协议的启发,使用 AEAD 密码保护连接。尽管 Shadowsocks 设计得谨慎且难以识别,但它与标准 HTTPS 连接并不相同。但是,某些流量分析系统可能仍会检测到 Shadowsocks 连接。由于Amnezia支持有限,建议使用AmneziaWG协议。 + +* 仅在桌面平台上的 AmneziaVPN 中可用 +* 移动设备的正常功耗 + +* 可配置的加密协议 +* 可以被某些 DPI 系统检测到 +* 通过 TCP 网络协议工作。 + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. + +OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works over TCP network protocol, 443 port. + + 这是 OpenVPN 协议和专门用于阻止保护的 Cloak 插件的组合。 + +OpenVPN 通过加密客户端和服务器之间的所有 Internet 流量来提供安全的 VPN 连接。 + +Cloak 可保护 OpenVPN 免遭检测和阻止。 + +Cloak 可以修改数据包元数据,以便将 VPN 流量完全屏蔽为正常 Web 流量,并且还可以保护 VPN 免受主动探测的检测。这使得它非常难以被发现 + +收到第一个数据包后,Cloak 立即对传入连接进行身份验证。如果身份验证失败,该插件会将服务器伪装成虚假网站,并且您的 VPN 对分析系统来说将变得不可见。 + +如果您所在地区的互联网审查非常严格,我们建议您在第一次连接时仅使用 OpenVPN over Cloak + +* 可在所有平台的 AmneziaVPN 中使用 +* 移动设备功耗高 +* 配置灵活 +* 不被 DPI 分析系统识别 +* 通过 TCP 网络协议、443 端口工作。 + + + A relatively new popular VPN protocol with a simplified architecture. +Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. + 一种相对较新的流行 VPN 协议,具有简化的架构。 +在所有设备上提供稳定的 VPN 连接和高性能。使用硬编码的加密设置。 WireGuard 与 OpenVPN 相比具有更低的延迟和更好的数据传输吞吐量。 +由于其独特的数据包签名,WireGuard 非常容易受到阻塞。与其他一些采用混淆技术的 VPN 协议不同,WireGuard 数据包的一致签名模式可以更容易地被高级深度数据包检测 (DPI) 系统和其他网络监控工具识别并阻止。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 低功耗 +* 配置简单 +* 容易被DPI分析系统识别,容易被阻塞 +* 通过 UDP 网络协议工作。 @@ -2713,7 +3065,15 @@ This means that AmneziaWG keeps the fast performance of the original while addin * Minimum number of settings * Not recognised by DPI analysis systems, resistant to blocking * Works over UDP network protocol. - + AmneziaWG 是流行 VPN 协议的现代迭代,它建立在 WireGuard 的基础上,保留了其简化的架构和跨设备的高性能功能。 +虽然 WireGuard 以其高效而闻名,但由于其独特的数据包签名,它存在容易被检测到的问题。 AmneziaWG 通过使用更好的混淆方法解决了这个问题,使其流量与常规互联网流量融合在一起。 +这意味着 AmneziaWG 保留了原始版本的快速性能,同时添加了额外的隐秘层,使其成为那些想要快速且谨慎的 VPN 连接的人的绝佳选择。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 低功耗 +* 配置简单 +* 不被DPI分析系统识别,抗阻塞 +* 通过 UDP 网络协议工作。 @@ -2726,77 +3086,105 @@ While it offers a blend of security, stability, and speed, it's essential t * Minimal configuration * Recognised by DPI analysis systems * Works over UDP network protocol, ports 500 and 4500. - + IKEv2 与 IPSec 加密层配合使用,是一种现代且稳定的 VPN 协议。 +其显着特征之一是能够在网络和设备之间快速切换,使其特别适应动态网络环境。 +虽然 IKEv2 兼具安全性、稳定性和速度,但必须注意的是,IKEv2 很容易被检测到,并且容易受到阻止。 + +* 仅在 Windows 上的 AmneziaVPN 中可用 +* 低功耗,在移动设备上 +* 最低配置 +* 获得DPI分析系统认可 +* 通过 UDP 网络协议、端口 500 和 4500 工作。 + + + OpenVPN container + OpenVPN容器 + + + Container with OpenVpn and ShadowSocks + 含 OpenVpn 和 ShadowSocks 的容器 + + + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin + 含 OpenVpn 和 ShadowSocks 协议的容器,通过 Cloak 插件配置混淆流量 + + + WireGuard container + WireGuard 容器 + + + IPsec container + IPsec 容器 DNS Service - + DNS 服务 Sftp file sharing service - is secure FTP service - + Sftp 文件共享服务 - 安全的 FTP 服务 Entry not found - + 未找到记录 Access to keychain denied - + 访问钥匙串被拒绝 No keyring daemon - + 没有密钥环守护进程 Already unlocked - + 已经解锁 No such keyring - + 没有这样的密钥环 Bad arguments - + 错误参数 I/O error - + I/O错误 Cancelled - + 已取消 Keyring already exists - + 密匙环已经存在 No match - + 不匹配 Unknown error - + 未知错误 error 0x%1: %2 - + 错误 0x%1: %2 @@ -2804,7 +3192,7 @@ While it offers a blend of security, stability, and speed, it's essential t Choose language - + 选择语言 @@ -2812,36 +3200,31 @@ While it offers a blend of security, stability, and speed, it's essential t Server #1 - + Server - + 服务器 SettingsController - - Software version - - - - + Backup file is corrupted - + 备份文件已损坏 - + All settings have been reset to default values - + 所配置恢复为默认值 - + Cached profiles cleared - + 缓存的配置文件已清除 @@ -2850,38 +3233,42 @@ While it offers a blend of security, stability, and speed, it's essential t Save AmneziaVPN config - + 保存配置 Share - + 共享 Copy - + 拷贝 Copied - + 已拷贝 Copy config string - + 复制配置字符串 Show connection settings - + 显示连接配置 + + + Show content + 展示内容 To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" - + 要应用二维码到 Amnezia,请底部工具栏点击“+”→“连接方式”→“二维码、授权码或配置文件” @@ -2889,42 +3276,42 @@ While it offers a blend of security, stability, and speed, it's essential t Hostname not look like ip adress or domain name - + 请输入有效的域名或IP地址 New site added: %1 - + 已经添加新网站: %1 Site removed: %1 - + 已移除网站: %1 Can't open file: %1 - + 无法打开文件: %1 Failed to parse JSON data from file: %1 - + JSON解析失败,文件: %1 The JSON data is not an array in file: %1 - + 文件中的JSON数据不是一个数组,文件: %1 Import completed - + 完成导入 Export completed - + 完成导出 @@ -2933,31 +3320,31 @@ While it offers a blend of security, stability, and speed, it's essential t Show - + 显示 Connect - + 连接 Disconnect - + 断开 Visit Website - + 官网 Quit - + 退出 @@ -2965,7 +3352,7 @@ While it offers a blend of security, stability, and speed, it's essential t The field can't be empty - + 输入不能为空 @@ -2973,7 +3360,7 @@ While it offers a blend of security, stability, and speed, it's essential t Mbps - + @@ -2981,42 +3368,42 @@ While it offers a blend of security, stability, and speed, it's essential t Unknown - + 未知 Disconnected - + 连接已断开 Preparing - + 准备中 Connecting... - + 连接中 Connected - + 已连接 Disconnecting... - + 断开中 Reconnecting... - + 重连中 Error - + 错误 @@ -3024,45 +3411,65 @@ While it offers a blend of security, stability, and speed, it's essential t Low - + Medium or High - + 中或高 Extreme - + 极度 I just want to increase the level of my privacy. - + 只是想提高隐私保护级别。 I want to bypass censorship. This option recommended in most cases. - + 想要绕过审查制度。大多数情况下推荐使用此选项。 Most VPN protocols are blocked. Recommended if other options are not working. - + 大多数 VPN 协议都被阻止。如果其他选项不起作用,推荐此选项。 + + + High + + + + Medium + + + + I just want to increase the level of privacy + 我只是想提高隐私保护级别 + + + Many foreign websites and VPN providers are blocked + 大多国外网站和VPN提供商被屏蔽 + + + Some foreign sites are blocked, but VPN providers are not blocked + 一些国外网站被屏蔽,但VPN提供商未被屏蔽 main2 - + Private key passphrase - + 私钥密码 - + Save - + 保存 diff --git a/client/ui/controllers/apiController.cpp b/client/ui/controllers/apiController.cpp index 43bea9fc3..0d8c75e3a 100644 --- a/client/ui/controllers/apiController.cpp +++ b/client/ui/controllers/apiController.cpp @@ -70,14 +70,17 @@ QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiCont void ApiController::updateServerConfigFromApi() { QtConcurrent::run([this]() { + if (m_isConfigUpdateStarted) { + emit updateFinished(false); + return; + } + auto serverConfig = m_serversModel->getDefaultServerConfig(); auto containerConfig = serverConfig.value(config_key::containers).toArray(); - bool isConfigUpdateStarted = false; - if (serverConfig.value(config_key::configVersion).toInt() && containerConfig.isEmpty()) { emit updateStarted(); - isConfigUpdateStarted = true; + m_isConfigUpdateStarted = true; QNetworkAccessManager manager; @@ -110,6 +113,12 @@ void ApiController::updateServerConfigFromApi() QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + if (ba.isEmpty()) { + emit errorOccurred(errorString(ApiConfigDownloadError)); + m_isConfigUpdateStarted = false; + return; + } + QByteArray ba_uncompressed = qUncompress(ba); if (!ba_uncompressed.isEmpty()) { ba = ba_uncompressed; @@ -127,17 +136,18 @@ void ApiController::updateServerConfigFromApi() auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString(); serverConfig.insert(config_key::defaultContainer, defaultContainer); - m_serversModel->editServer(serverConfig); - emit m_serversModel->defaultContainerChanged(ContainerProps::containerFromString(defaultContainer)); + m_serversModel->editServer(serverConfig, m_serversModel->getDefaultServerIndex()); } else { qDebug() << reply->error(); qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); emit errorOccurred(errorString(ApiConfigDownloadError)); + m_isConfigUpdateStarted = false; return; } } - emit updateFinished(isConfigUpdateStarted); + emit updateFinished(m_isConfigUpdateStarted); + m_isConfigUpdateStarted = false; return; }); } @@ -153,5 +163,5 @@ void ApiController::clearApiConfig() serverConfig.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); - m_serversModel->editServer(serverConfig); + m_serversModel->editServer(serverConfig, m_serversModel->getDefaultServerIndex()); } diff --git a/client/ui/controllers/apiController.h b/client/ui/controllers/apiController.h index c5d79ded2..2a1393c42 100644 --- a/client/ui/controllers/apiController.h +++ b/client/ui/controllers/apiController.h @@ -39,6 +39,8 @@ private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; + + bool m_isConfigUpdateStarted = false; }; #endif // APICONTROLLER_H diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index fbf53018f..f7a9cbe6e 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -52,7 +52,7 @@ void ConnectionController::openConnection() int serverIndex = m_serversModel->getDefaultServerIndex(); ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - DockerContainer container = m_serversModel->getDefaultContainer(serverIndex); + DockerContainer container = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); const QJsonObject &containerConfig = m_containersModel->getContainerConfig(container); if (container == DockerContainer::None) { diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 2b102e13f..0c3283260 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -45,7 +45,7 @@ void ExportController::generateFullAccessConfig() { clearPreviousConfig(); - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); QJsonObject config = m_settings->server(serverIndex); QJsonArray containers = config.value(config_key::containers).toArray(); @@ -99,7 +99,7 @@ void ExportController::generateConnectionConfig(const QString &clientName) { clearPreviousConfig(); - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); @@ -155,7 +155,7 @@ void ExportController::generateOpenVpnConfig(const QString &clientName) { clearPreviousConfig(); - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); @@ -193,7 +193,7 @@ void ExportController::generateWireGuardConfig(const QString &clientName) { clearPreviousConfig(); - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); @@ -232,7 +232,7 @@ void ExportController::generateShadowSocksConfig() { clearPreviousConfig(); - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); @@ -268,7 +268,7 @@ void ExportController::generateCloakConfig() { clearPreviousConfig(); - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); @@ -328,7 +328,7 @@ void ExportController::updateClientManagementModel(const DockerContainer contain void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials) { ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials, - m_serversModel->getCurrentlyProcessedServerIndex()); + m_serversModel->getProcessedServerIndex()); if (errorCode != ErrorCode::NoError) { emit exportErrorOccurred(errorString(errorCode)); } diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 117c8116a..25ce155f7 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -176,7 +176,7 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co void InstallController::installContainer(DockerContainer container, QJsonObject &config) { - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); ServerCredentials serverCredentials = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); @@ -238,7 +238,7 @@ bool InstallController::isServerAlreadyExists() void InstallController::scanServerForInstalledContainers() { - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); ServerCredentials serverCredentials = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); @@ -267,7 +267,7 @@ void InstallController::scanServerForInstalledContainers() void InstallController::updateContainer(QJsonObject config) { - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); ServerCredentials serverCredentials = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); @@ -283,8 +283,8 @@ void InstallController::updateContainer(QJsonObject config) m_serversModel->updateContainerConfig(container, config); m_protocolModel->updateModel(config); - if ((serverIndex == m_serversModel->getDefaultServerIndex()) - && (container == m_serversModel->getDefaultContainer(serverIndex))) { + auto defaultContainer = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); + if ((serverIndex == m_serversModel->getDefaultServerIndex()) && (container == defaultContainer)) { emit currentContainerUpdated(); } else { emit updateContainerFinished(tr("Settings updated successfully")); @@ -296,27 +296,27 @@ void InstallController::updateContainer(QJsonObject config) emit installationErrorOccurred(errorString(errorCode)); } -void InstallController::rebootCurrentlyProcessedServer() +void InstallController::rebootProcessedServer() { - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); m_serversModel->rebootServer(); - emit rebootCurrentlyProcessedServerFinished(tr("Server '%1' was rebooted").arg(serverName)); + emit rebootProcessedServerFinished(tr("Server '%1' was rebooted").arg(serverName)); } -void InstallController::removeCurrentlyProcessedServer() +void InstallController::removeProcessedServer() { - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); m_serversModel->removeServer(); - emit removeCurrentlyProcessedServerFinished(tr("Server '%1' was removed").arg(serverName)); + emit removeProcessedServerFinished(tr("Server '%1' was removed").arg(serverName)); } void InstallController::removeAllContainers() { - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); ErrorCode errorCode = m_serversModel->removeAllContainers(); @@ -329,7 +329,7 @@ void InstallController::removeAllContainers() void InstallController::removeCurrentlyProcessedContainer() { - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); int container = m_containersModel->getCurrentlyProcessedContainerIndex(); @@ -377,7 +377,7 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw QString mountPath; QString cmd; - int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + int serverIndex = m_serversModel->getProcessedServerIndex(); ServerCredentials serverCredentials = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); QString hostname = serverCredentials.hostName; diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index a67912cf9..6b5295dc1 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -30,8 +30,8 @@ public slots: void updateContainer(QJsonObject config); - void removeCurrentlyProcessedServer(); - void rebootCurrentlyProcessedServer(); + void removeProcessedServer(); + void rebootProcessedServer(); void removeAllContainers(); void removeCurrentlyProcessedContainer(); @@ -54,8 +54,8 @@ signals: void scanServerFinished(bool isInstalledContainerFound); - void rebootCurrentlyProcessedServerFinished(const QString &finishedMessage); - void removeCurrentlyProcessedServerFinished(const QString &finishedMessage); + void rebootProcessedServerFinished(const QString &finishedMessage); + void removeProcessedServerFinished(const QString &finishedMessage); void removeAllContainersFinished(const QString &finishedMessage); void removeCurrentlyProcessedContainerFinished(const QString &finishedMessage); diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 105f2115a..467da3ac9 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -118,36 +118,6 @@ void PageController::showOnStartup() } } -void PageController::updateDrawerRootPage(PageLoader::PageEnum page) -{ - m_drawerLayer = 0; - m_currentRootPage = page; -} - -void PageController::goToDrawerRootPage() -{ - - m_drawerLayer = 0; - - emit showTopCloseButton(false); - emit forceCloseDrawer(); -} - -void PageController::drawerOpen() -{ - m_drawerLayer = m_drawerLayer + 1; - emit showTopCloseButton(true); -} - -void PageController::drawerClose() -{ - m_drawerLayer = m_drawerLayer -1; - if (m_drawerLayer <= 0) { - emit showTopCloseButton(false); - m_drawerLayer = 0; - } -} - bool PageController::isTriggeredByConnectButton() { return m_isTriggeredByConnectButton; diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index f7e697fb6..5c0909fb1 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -82,11 +82,6 @@ public slots: void showOnStartup(); - void updateDrawerRootPage(PageLoader::PageEnum page); - void goToDrawerRootPage(); - void drawerOpen(); - void drawerClose(); - bool isTriggeredByConnectButton(); void setTriggeredBtConnectButton(bool trigger); @@ -118,17 +113,11 @@ signals: void showPassphraseRequestDrawer(); void passphraseRequestDrawerClosed(QString passphrase); - void showTopCloseButton(bool visible); - void forceCloseDrawer(); - private: QSharedPointer m_serversModel; std::shared_ptr m_settings; - PageLoader::PageEnum m_currentRootPage; - int m_drawerLayer; - bool m_isTriggeredByConnectButton; }; diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 99645cded..6ec553213 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -28,7 +28,7 @@ SettingsController::SettingsController(const QSharedPointer &serve m_sitesModel(sitesModel), m_settings(settings) { - m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_VERSION), __DATE__); + m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH); #ifdef Q_OS_ANDROID if (!m_settings->isScreenshotsEnabled()) { diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index c3552ea74..47e41708c 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -45,6 +45,7 @@ QString LanguageModel::getLocalLanguageName(const LanguageSettings::AvailableLan case LanguageSettings::AvailableLanguageEnum::Russian: strLanguage = "Русский"; break; case LanguageSettings::AvailableLanguageEnum::China_cn: strLanguage = "\347\256\200\344\275\223\344\270\255\346\226\207"; break; case LanguageSettings::AvailableLanguageEnum::Persian: strLanguage = "فارسی"; break; + case LanguageSettings::AvailableLanguageEnum::Arabic: strLanguage = "العربية"; break; default: break; } @@ -59,6 +60,7 @@ void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum case LanguageSettings::AvailableLanguageEnum::Russian: emit updateTranslations(QLocale::Russian); break; case LanguageSettings::AvailableLanguageEnum::China_cn: emit updateTranslations(QLocale::Chinese); break; case LanguageSettings::AvailableLanguageEnum::Persian: emit updateTranslations(QLocale::Persian); break; + case LanguageSettings::AvailableLanguageEnum::Arabic: emit updateTranslations(QLocale::Arabic); break; default: emit updateTranslations(QLocale::English); break; } } @@ -71,6 +73,7 @@ int LanguageModel::getCurrentLanguageIndex() case QLocale::Russian: return static_cast(LanguageSettings::AvailableLanguageEnum::Russian); break; case QLocale::Chinese: return static_cast(LanguageSettings::AvailableLanguageEnum::China_cn); break; case QLocale::Persian: return static_cast(LanguageSettings::AvailableLanguageEnum::Persian); break; + case QLocale::Arabic: return static_cast(LanguageSettings::AvailableLanguageEnum::Arabic); break; default: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; } } diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h index 8a55263c6..b07a5c78a 100644 --- a/client/ui/models/languageModel.h +++ b/client/ui/models/languageModel.h @@ -13,7 +13,8 @@ namespace LanguageSettings English, Russian, China_cn, - Persian + Persian, + Arabic }; Q_ENUM_NS(AvailableLanguageEnum) diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index af167f206..3c72ee498 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -5,19 +5,13 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { - m_servers = m_settings->serversArray(); - m_defaultServerIndex = m_settings->defaultServerIndex(); - m_currentlyProcessedServerIndex = m_defaultServerIndex; - connect(this, &ServersModel::defaultServerIndexChanged, this, &ServersModel::defaultServerNameChanged); - connect(this, &ServersModel::defaultContainerChanged, this, &ServersModel::defaultServerDescriptionChanged); + connect(this, &ServersModel::defaultServerIndexChanged, this, [this](const int serverIndex) { auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString()); - emit ServersModel::defaultContainerChanged(defaultContainer); - }); - connect(this, &ServersModel::currentlyProcessedServerIndexChanged, this, [this](const int serverIndex) { - auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString()); - emit ServersModel::defaultContainerChanged(defaultContainer); + emit ServersModel::defaultServerDefaultContainerChanged(defaultContainer); + emit ServersModel::defaultServerNameChanged(); + updateDefaultServerContainersModel(); }); } @@ -74,16 +68,14 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return name; } case ServerDescriptionRole: { - if (configVersion) { - return server.value(config_key::description).toString(); - } - return server.value(config_key::hostName).toString(); + auto description = getServerDescription(server, index.row()); + return configVersion ? description : description + server.value(config_key::hostName).toString(); } case HostNameRole: return server.value(config_key::hostName).toString(); case CredentialsRole: return QVariant::fromValue(serverCredentials(index.row())); case CredentialsLoginRole: return serverCredentials(index.row()).userName; case IsDefaultRole: return index.row() == m_defaultServerIndex; - case IsCurrentlyProcessedRole: return index.row() == m_currentlyProcessedServerIndex; + case IsCurrentlyProcessedRole: return index.row() == m_processedServerIndex; case HasWriteAccessRole: { auto credentials = serverCredentials(index.row()); return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); @@ -95,6 +87,13 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const case DefaultContainerRole: { return ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString()); } + case IsServerFromApiRole: { + return server.value(config_key::configVersion).toInt(); + } + case HasAmneziaDns: { + QString primaryDns = server.value(config_key::dns1).toString(); + return primaryDns == protocols::dns::amneziaDnsIp; + } } return QVariant(); @@ -111,8 +110,9 @@ void ServersModel::resetModel() beginResetModel(); m_servers = m_settings->serversArray(); m_defaultServerIndex = m_settings->defaultServerIndex(); - m_currentlyProcessedServerIndex = m_defaultServerIndex; + m_processedServerIndex = m_defaultServerIndex; endResetModel(); + emit defaultServerIndexChanged(m_defaultServerIndex); } void ServersModel::setDefaultServerIndex(const int index) @@ -132,12 +132,7 @@ const QString ServersModel::getDefaultServerName() return qvariant_cast(data(m_defaultServerIndex, NameRole)); } -const QString ServersModel::getDefaultServerHostName() -{ - return qvariant_cast(data(m_defaultServerIndex, HostNameRole)); -} - -QString ServersModel::getDefaultServerDescription(const QJsonObject &server) +QString ServersModel::getServerDescription(const QJsonObject &server, const int index) const { const auto configVersion = server.value(config_key::configVersion).toInt(); @@ -145,13 +140,12 @@ QString ServersModel::getDefaultServerDescription(const QJsonObject &server) if (configVersion) { return server.value(config_key::description).toString(); - } else if (isDefaultServerHasWriteAccess()) { - if (m_isAmneziaDnsEnabled - && isAmneziaDnsContainerInstalled(m_defaultServerIndex)) { + } else if (data(index, HasWriteAccessRole).toBool()) { + if (m_isAmneziaDnsEnabled && isAmneziaDnsContainerInstalled(index)) { description += "Amnezia DNS | "; } } else { - if (isDefaultServerConfigContainsAmneziaDns()) { + if (data(index, HasAmneziaDns).toBool()) { description += "Amnezia DNS | "; } } @@ -162,7 +156,7 @@ const QString ServersModel::getDefaultServerDescriptionCollapsed() { const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); const auto configVersion = server.value(config_key::configVersion).toInt(); - auto description = getDefaultServerDescription(server); + auto description = getServerDescription(server, m_defaultServerIndex); if (configVersion) { return description; } @@ -176,7 +170,7 @@ const QString ServersModel::getDefaultServerDescriptionExpanded() { const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); const auto configVersion = server.value(config_key::configVersion).toInt(); - auto description = getDefaultServerDescription(server); + auto description = getServerDescription(server, m_defaultServerIndex); if (configVersion) { return description; } @@ -199,26 +193,21 @@ bool ServersModel::hasServerWithWriteAccess() return false; } -void ServersModel::setCurrentlyProcessedServerIndex(const int index) +void ServersModel::setProcessedServerIndex(const int index) { - m_currentlyProcessedServerIndex = index; + m_processedServerIndex = index; updateContainersModel(); - emit currentlyProcessedServerIndexChanged(m_currentlyProcessedServerIndex); + emit processedServerIndexChanged(m_processedServerIndex); } -int ServersModel::getCurrentlyProcessedServerIndex() +int ServersModel::getProcessedServerIndex() { - return m_currentlyProcessedServerIndex; + return m_processedServerIndex; } -QString ServersModel::getCurrentlyProcessedServerHostName() +const ServerCredentials ServersModel::getProcessedServerCredentials() { - return qvariant_cast(data(m_currentlyProcessedServerIndex, HostNameRole)); -} - -const ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials() -{ - return serverCredentials(m_currentlyProcessedServerIndex); + return serverCredentials(m_processedServerIndex); } const ServerCredentials ServersModel::getServerCredentials(const int index) @@ -228,12 +217,17 @@ const ServerCredentials ServersModel::getServerCredentials(const int index) bool ServersModel::isDefaultServerCurrentlyProcessed() { - return m_defaultServerIndex == m_currentlyProcessedServerIndex; + return m_defaultServerIndex == m_processedServerIndex; } -bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() +bool ServersModel::isDefaultServerFromApi() { - return qvariant_cast(data(m_currentlyProcessedServerIndex, HasWriteAccessRole)); + return qvariant_cast(data(m_defaultServerIndex, IsServerFromApiRole)); +} + +bool ServersModel::isProcessedServerHasWriteAccess() +{ + return qvariant_cast(data(m_processedServerIndex, HasWriteAccessRole)); } bool ServersModel::isDefaultServerHasWriteAccess() @@ -249,40 +243,42 @@ void ServersModel::addServer(const QJsonObject &server) endResetModel(); } -void ServersModel::editServer(const QJsonObject &server) +void ServersModel::editServer(const QJsonObject &server, const int serverIndex) { - m_settings->editServer(m_currentlyProcessedServerIndex, server); - m_servers.replace(m_currentlyProcessedServerIndex, m_settings->serversArray().at(m_currentlyProcessedServerIndex)); - emit dataChanged(index(m_currentlyProcessedServerIndex, 0), index(m_currentlyProcessedServerIndex, 0)); + m_settings->editServer(serverIndex, server); + m_servers.replace(serverIndex, m_settings->serversArray().at(serverIndex)); + emit dataChanged(index(serverIndex, 0), index(serverIndex, 0)); + + if (serverIndex == m_defaultServerIndex) { + updateDefaultServerContainersModel(); + } updateContainersModel(); + + if (serverIndex == m_defaultServerIndex) { + auto defaultContainer = qvariant_cast(getDefaultServerData("defaultContainer")); + emit defaultServerDefaultContainerChanged(defaultContainer); + } } void ServersModel::removeServer() { beginResetModel(); - m_settings->removeServer(m_currentlyProcessedServerIndex); + m_settings->removeServer(m_processedServerIndex); m_servers = m_settings->serversArray(); - if (m_settings->defaultServerIndex() == m_currentlyProcessedServerIndex) { + if (m_settings->defaultServerIndex() == m_processedServerIndex) { setDefaultServerIndex(0); - } else if (m_settings->defaultServerIndex() > m_currentlyProcessedServerIndex) { + } else if (m_settings->defaultServerIndex() > m_processedServerIndex) { setDefaultServerIndex(m_settings->defaultServerIndex() - 1); } if (m_settings->serversCount() == 0) { setDefaultServerIndex(-1); } - setCurrentlyProcessedServerIndex(m_defaultServerIndex); + setProcessedServerIndex(m_defaultServerIndex); endResetModel(); } -bool ServersModel::isDefaultServerConfigContainsAmneziaDns() -{ - const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); - QString primaryDns = server.value(config_key::dns1).toString(); - return primaryDns == protocols::dns::amneziaDnsIp; -} - QHash ServersModel::roleNames() const { QHash roles; @@ -290,6 +286,8 @@ QHash ServersModel::roleNames() const roles[NameRole] = "serverName"; roles[NameRole] = "name"; roles[ServerDescriptionRole] = "serverDescription"; + roles[CollapsedServerDescriptionRole] = "collapsedServerDescription"; + roles[ExpandedServerDescriptionRole] = "expandedServerDescription"; roles[HostNameRole] = "hostName"; @@ -304,6 +302,8 @@ QHash ServersModel::roleNames() const roles[ContainsAmneziaDnsRole] = "containsAmneziaDns"; roles[DefaultContainerRole] = "defaultContainer"; + + roles[IsServerFromApiRole] = "isServerFromApi"; return roles; } @@ -322,28 +322,29 @@ ServerCredentials ServersModel::serverCredentials(int index) const void ServersModel::updateContainersModel() { - auto containers = m_servers.at(m_currentlyProcessedServerIndex).toObject().value(config_key::containers).toArray(); + auto containers = m_servers.at(m_processedServerIndex).toObject().value(config_key::containers).toArray(); emit containersUpdated(containers); } +void ServersModel::updateDefaultServerContainersModel() +{ + auto containers = m_servers.at(m_defaultServerIndex).toObject().value(config_key::containers).toArray(); + emit defaultServerContainersUpdated(containers); +} + QJsonObject ServersModel::getDefaultServerConfig() { return m_servers.at(m_defaultServerIndex).toObject(); } -QJsonObject ServersModel::getCurrentlyProcessedServerConfig() +void ServersModel::reloadDefaultServerContainerConfig() { - return m_servers.at(m_currentlyProcessedServerIndex).toObject(); -} - -void ServersModel::reloadContainerConfig() -{ - QJsonObject server = m_servers.at(m_currentlyProcessedServerIndex).toObject(); + QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); auto container = ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString()); auto containers = server.value(config_key::containers).toArray(); - auto config = m_settings->containerConfig(m_currentlyProcessedServerIndex, container); + auto config = m_settings->containerConfig(m_defaultServerIndex, container); for (auto i = 0; i < containers.size(); i++) { auto c = ContainerProps::containerFromString(containers.at(i).toObject().value(config_key::container).toString()); if (c == container) { @@ -353,13 +354,13 @@ void ServersModel::reloadContainerConfig() } server.insert(config_key::containers, containers); - editServer(server); + editServer(server, m_defaultServerIndex); } void ServersModel::updateContainerConfig(const int containerIndex, const QJsonObject config) { auto container = static_cast(containerIndex); - QJsonObject server = m_servers.at(m_currentlyProcessedServerIndex).toObject(); + QJsonObject server = m_servers.at(m_processedServerIndex).toObject(); auto containers = server.value(config_key::containers).toArray(); for (auto i = 0; i < containers.size(); i++) { @@ -377,30 +378,25 @@ void ServersModel::updateContainerConfig(const int containerIndex, const QJsonOb server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); } - editServer(server); + editServer(server, m_processedServerIndex); } void ServersModel::addContainerConfig(const int containerIndex, const QJsonObject config) { auto container = static_cast(containerIndex); - QJsonObject server = m_servers.at(m_currentlyProcessedServerIndex).toObject(); + QJsonObject server = m_servers.at(m_processedServerIndex).toObject(); auto containers = server.value(config_key::containers).toArray(); containers.push_back(config); server.insert(config_key::containers, containers); - bool isDefaultContainerChanged = false; auto defaultContainer = server.value(config_key::defaultContainer).toString(); if ((ContainerProps::containerFromString(defaultContainer) == DockerContainer::None || ContainerProps::containerService(container) != ServiceType::Other)) { server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - isDefaultContainerChanged = true; } - editServer(server); - if (isDefaultContainerChanged) { - emit defaultContainerChanged(container); - } + editServer(server, m_processedServerIndex); } void ServersModel::setDefaultContainer(const int serverIndex, const int containerIndex) @@ -408,18 +404,12 @@ void ServersModel::setDefaultContainer(const int serverIndex, const int containe auto container = static_cast(containerIndex); QJsonObject s = m_servers.at(serverIndex).toObject(); s.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - editServer(s); //check - emit defaultContainerChanged(container); + editServer(s, serverIndex); //check } -DockerContainer ServersModel::getDefaultContainer(const int serverIndex) +const QString ServersModel::getDefaultServerDefaultContainerName() { - return qvariant_cast(data(serverIndex, DefaultContainerRole)); -} - -const QString ServersModel::getDefaultContainerName() -{ - auto defaultContainer = getDefaultContainer(m_defaultServerIndex); + auto defaultContainer = qvariant_cast(getDefaultServerData("defaultContainer")); return ContainerProps::containerHumanNames().value(defaultContainer); } @@ -427,15 +417,14 @@ ErrorCode ServersModel::removeAllContainers() { ServerController serverController(m_settings); ErrorCode errorCode = - serverController.removeAllContainers(m_settings->serverCredentials(m_currentlyProcessedServerIndex)); + serverController.removeAllContainers(m_settings->serverCredentials(m_processedServerIndex)); if (errorCode == ErrorCode::NoError) { - QJsonObject s = m_servers.at(m_currentlyProcessedServerIndex).toObject(); + QJsonObject s = m_servers.at(m_processedServerIndex).toObject(); s.insert(config_key::containers, {}); s.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); - editServer(s); - emit defaultContainerChanged(DockerContainer::None); + editServer(s, m_processedServerIndex); } return errorCode; } @@ -443,7 +432,7 @@ ErrorCode ServersModel::removeAllContainers() ErrorCode ServersModel::rebootServer() { ServerController serverController(m_settings); - auto credentials = m_settings->serverCredentials(m_currentlyProcessedServerIndex); + auto credentials = m_settings->serverCredentials(m_processedServerIndex); ErrorCode errorCode = serverController.rebootServer(credentials); return errorCode; @@ -452,13 +441,13 @@ ErrorCode ServersModel::rebootServer() ErrorCode ServersModel::removeContainer(const int containerIndex) { ServerController serverController(m_settings); - auto credentials = m_settings->serverCredentials(m_currentlyProcessedServerIndex); + auto credentials = m_settings->serverCredentials(m_processedServerIndex); auto dockerContainer = static_cast(containerIndex); ErrorCode errorCode = serverController.removeContainer(credentials, dockerContainer); if (errorCode == ErrorCode::NoError) { - QJsonObject server = m_servers.at(m_currentlyProcessedServerIndex).toObject(); + QJsonObject server = m_servers.at(m_processedServerIndex).toObject(); auto containers = server.value(config_key::containers).toArray(); for (auto it = containers.begin(); it != containers.end(); it++) { @@ -480,32 +469,37 @@ ErrorCode ServersModel::removeContainer(const int containerIndex) server.insert(config_key::defaultContainer, ContainerProps::containerToString(defaultContainer)); } - editServer(server); - emit defaultContainerChanged(defaultContainer); + editServer(server, m_processedServerIndex); } return errorCode; } void ServersModel::clearCachedProfiles() { - const auto &containers = m_settings->containers(m_currentlyProcessedServerIndex); + const auto &containers = m_settings->containers(m_processedServerIndex); for (DockerContainer container : containers.keys()) { - m_settings->clearLastConnectionConfig(m_currentlyProcessedServerIndex, container); + m_settings->clearLastConnectionConfig(m_processedServerIndex, container); } - m_servers.replace(m_currentlyProcessedServerIndex, m_settings->server(m_currentlyProcessedServerIndex)); + m_servers.replace(m_processedServerIndex, m_settings->server(m_processedServerIndex)); + if (m_processedServerIndex == m_defaultServerIndex) { + updateDefaultServerContainersModel(); + } updateContainersModel(); } void ServersModel::clearCachedProfile(const DockerContainer container) { - m_settings->clearLastConnectionConfig(m_currentlyProcessedServerIndex, container); + m_settings->clearLastConnectionConfig(m_processedServerIndex, container); - m_servers.replace(m_currentlyProcessedServerIndex, m_settings->server(m_currentlyProcessedServerIndex)); + m_servers.replace(m_processedServerIndex, m_settings->server(m_processedServerIndex)); + if (m_processedServerIndex == m_defaultServerIndex) { + updateDefaultServerContainersModel(); + } updateContainersModel(); } -bool ServersModel::isAmneziaDnsContainerInstalled(const int serverIndex) +bool ServersModel::isAmneziaDnsContainerInstalled(const int serverIndex) const { QJsonObject server = m_servers.at(serverIndex).toObject(); auto containers = server.value(config_key::containers).toArray(); @@ -544,16 +538,6 @@ void ServersModel::toggleAmneziaDns(bool enabled) emit defaultServerDescriptionChanged(); } -bool ServersModel::isDefaultServerFromApi() -{ - return m_settings->server(m_defaultServerIndex).value(config_key::configVersion).toInt(); -} - -bool ServersModel::isCurrentlyProcessedServerFromApi() -{ - return m_settings->server(m_currentlyProcessedServerIndex).value(config_key::configVersion).toInt(); -} - bool ServersModel::isServerFromApiAlreadyExists(const quint16 crc) { for (const auto &server : qAsConst(m_servers)) { @@ -564,3 +548,52 @@ bool ServersModel::isServerFromApiAlreadyExists(const quint16 crc) return false; } +QVariant ServersModel::getDefaultServerData(const QString roleString) +{ + auto roles = roleNames(); + for (auto it = roles.begin(); it != roles.end(); it++) { + if (QString(it.value()) == roleString) { + return data(m_defaultServerIndex, it.key()); + } + } + + return {}; +} + +void ServersModel::setDefaultServerData(const QString roleString, const QVariant &value) +{ + +} + +QVariant ServersModel::getProcessedServerData(const QString roleString) +{ + auto roles = roleNames(); + for (auto it = roles.begin(); it != roles.end(); it++) { + if (QString(it.value()) == roleString) { + return data(m_processedServerIndex, it.key()); + } + } + + return {}; +} + +void ServersModel::setProcessedServerData(const QString roleString, const QVariant &value) +{ + +} + +bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling() +{ + auto server = m_servers.at(m_defaultServerIndex).toObject(); + auto defaultContainer = ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString()); + auto containerConfig = server.value(config_key::containers).toArray().at(defaultContainer).toObject(); + auto protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(defaultContainer)).toObject(); + + if (defaultContainer == DockerContainer::Awg || defaultContainer == DockerContainer::WireGuard) { + return !(protocolConfig.value(config_key::last_config).toString().contains("AllowedIPs = 0.0.0.0/0, ::/0")); + } else if (defaultContainer == DockerContainer::Cloak || defaultContainer == DockerContainer::OpenVpn || defaultContainer == DockerContainer::ShadowSocks) { + return !(protocolConfig.value(config_key::last_config).toString().contains("redirect-gateway")); + } + + return false; +} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index e9e1926c0..3e24e46c7 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -12,7 +12,8 @@ public: enum Roles { NameRole = Qt::UserRole + 1, ServerDescriptionRole, - + CollapsedServerDescriptionRole, + ExpandedServerDescriptionRole, HostNameRole, CredentialsRole, @@ -25,7 +26,11 @@ public: ContainsAmneziaDnsRole, - DefaultContainerRole + DefaultContainerRole, + + IsServerFromApiRole, + + HasAmneziaDns }; ServersModel(std::shared_ptr settings, QObject *parent = nullptr); @@ -40,47 +45,43 @@ public: Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged) Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerNameChanged) - Q_PROPERTY(QString defaultServerHostName READ getDefaultServerHostName NOTIFY defaultServerIndexChanged) - Q_PROPERTY(QString defaultContainerName READ getDefaultContainerName NOTIFY defaultContainerChanged) - Q_PROPERTY(QString defaultServerDescriptionCollapsed READ getDefaultServerDescriptionCollapsed NOTIFY defaultServerDescriptionChanged) - Q_PROPERTY(QString defaultServerDescriptionExpanded READ getDefaultServerDescriptionExpanded NOTIFY defaultServerDescriptionChanged) + Q_PROPERTY(QString defaultServerDefaultContainerName READ getDefaultServerDefaultContainerName NOTIFY defaultServerDefaultContainerChanged) + Q_PROPERTY(QString defaultServerDescriptionCollapsed READ getDefaultServerDescriptionCollapsed NOTIFY defaultServerDefaultContainerChanged) + Q_PROPERTY(QString defaultServerDescriptionExpanded READ getDefaultServerDescriptionExpanded NOTIFY defaultServerDefaultContainerChanged) + Q_PROPERTY(bool isDefaultServerDefaultContainerHasSplitTunneling READ isDefaultServerDefaultContainerHasSplitTunneling NOTIFY defaultServerDefaultContainerChanged) + Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged) - Q_PROPERTY(int currentlyProcessedIndex READ getCurrentlyProcessedServerIndex WRITE setCurrentlyProcessedServerIndex - NOTIFY currentlyProcessedServerIndexChanged) + Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged) public slots: void setDefaultServerIndex(const int index); const int getDefaultServerIndex(); const QString getDefaultServerName(); - const QString getDefaultServerHostName(); const QString getDefaultServerDescriptionCollapsed(); const QString getDefaultServerDescriptionExpanded(); + const QString getDefaultServerDefaultContainerName(); bool isDefaultServerCurrentlyProcessed(); + bool isDefaultServerFromApi(); - bool isCurrentlyProcessedServerHasWriteAccess(); + bool isProcessedServerHasWriteAccess(); bool isDefaultServerHasWriteAccess(); bool hasServerWithWriteAccess(); const int getServersCount(); - void setCurrentlyProcessedServerIndex(const int index); - int getCurrentlyProcessedServerIndex(); + void setProcessedServerIndex(const int index); + int getProcessedServerIndex(); - QString getCurrentlyProcessedServerHostName(); - const ServerCredentials getCurrentlyProcessedServerCredentials(); + const ServerCredentials getProcessedServerCredentials(); const ServerCredentials getServerCredentials(const int index); void addServer(const QJsonObject &server); - void editServer(const QJsonObject &server); + void editServer(const QJsonObject &server, const int serverIndex); void removeServer(); - bool isDefaultServerConfigContainsAmneziaDns(); - bool isAmneziaDnsContainerInstalled(const int serverIndex); - QJsonObject getDefaultServerConfig(); - QJsonObject getCurrentlyProcessedServerConfig(); - void reloadContainerConfig(); + void reloadDefaultServerContainerConfig(); void updateContainerConfig(const int containerIndex, const QJsonObject config); void addContainerConfig(const int containerIndex, const QJsonObject config); @@ -92,42 +93,49 @@ public slots: ErrorCode rebootServer(); void setDefaultContainer(const int serverIndex, const int containerIndex); - DockerContainer getDefaultContainer(const int serverIndex); - const QString getDefaultContainerName(); QStringList getAllInstalledServicesName(const int serverIndex); void toggleAmneziaDns(bool enabled); - bool isDefaultServerFromApi(); - bool isCurrentlyProcessedServerFromApi(); - bool isServerFromApiAlreadyExists(const quint16 crc); + QVariant getDefaultServerData(const QString roleString); + void setDefaultServerData(const QString roleString, const QVariant &value); + + QVariant getProcessedServerData(const QString roleString); + void setProcessedServerData(const QString roleString, const QVariant &value); + + bool isDefaultServerDefaultContainerHasSplitTunneling(); + protected: QHash roleNames() const override; signals: - void currentlyProcessedServerIndexChanged(const int index); + void processedServerIndexChanged(const int index); void defaultServerIndexChanged(const int index); void defaultServerNameChanged(); void defaultServerDescriptionChanged(); void containersUpdated(const QJsonArray &containers); - void defaultContainerChanged(const int containerIndex); + void defaultServerContainersUpdated(const QJsonArray &containers); + void defaultServerDefaultContainerChanged(const int containerIndex); private: ServerCredentials serverCredentials(int index) const; void updateContainersModel(); + void updateDefaultServerContainersModel(); - QString getDefaultServerDescription(const QJsonObject &server); + QString getServerDescription(const QJsonObject &server, const int index) const; + + bool isAmneziaDnsContainerInstalled(const int serverIndex) const; QJsonArray m_servers; std::shared_ptr m_settings; int m_defaultServerIndex; - int m_currentlyProcessedServerIndex; + int m_processedServerIndex; bool m_isAmneziaDnsEnabled = m_settings->useAmneziaDns(); }; diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp index f6cb9b136..96b6ca605 100644 --- a/client/ui/models/sites_model.cpp +++ b/client/ui/models/sites_model.cpp @@ -113,6 +113,7 @@ void SitesModel::toggleSplitTunneling(bool enabled) m_settings->setRouteMode(Settings::RouteMode::VpnAllSites); } m_isSplitTunnelingEnabled = enabled; + emit splitTunnelingToggled(); } QVector > SitesModel::getCurrentSites() diff --git a/client/ui/models/sites_model.h b/client/ui/models/sites_model.h index ad16b7a33..803b7fd16 100644 --- a/client/ui/models/sites_model.h +++ b/client/ui/models/sites_model.h @@ -22,6 +22,7 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Q_PROPERTY(int routeMode READ getRouteMode WRITE setRouteMode NOTIFY routeModeChanged) + Q_PROPERTY(bool isTunnelingEnabled READ isSplitTunnelingEnabled NOTIFY splitTunnelingToggled) public slots: bool addSite(const QString &hostname, const QString &ip); @@ -38,6 +39,7 @@ public slots: signals: void routeModeChanged(); + void splitTunnelingToggled(); protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index cb2b72dde..a915eb21f 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -138,9 +138,7 @@ Button { } onClicked: { - if (!ConnectionController.isConnectionInProgress) { - ServersModel.setCurrentlyProcessedServerIndex(ServersModel.defaultIndex) - ApiController.updateServerConfigFromApi() - } + ServersModel.setProcessedServerIndex(ServersModel.defaultIndex) + ApiController.updateServerConfigFromApi() } } diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 1f7b2f297..d9dc21f4c 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -8,18 +8,24 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -DrawerType { +DrawerType2 { id: root width: parent.width - height: parent.height * 0.4375 + height: parent.height + + expandedContent: ColumnLayout { + id: content - ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right spacing: 0 + Component.onCompleted: { + root.expandedHeight = content.implicitHeight + 32 + } + Header2Type { Layout.fillWidth: true Layout.topMargin: 24 @@ -40,7 +46,7 @@ DrawerType { clickedFunction: function() { PageController.goToPage(PageEnum.PageSetupWizardCredentials) - root.visible = false + root.close() } } @@ -54,7 +60,7 @@ DrawerType { clickedFunction: function() { PageController.goToPage(PageEnum.PageSetupWizardConfigSource) - root.visible = false + root.close() } } diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index cca17c6f7..c785af8b5 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -15,6 +15,7 @@ ListView { id: menuContent property var rootWidth + property var selectedText width: rootWidth height: menuContent.contentItem.height @@ -51,7 +52,7 @@ ListView { showImage: !isInstalled checkable: isInstalled && !ConnectionController.isConnected && isSupported - checked: proxyContainersModel.mapToSource(index) === ServersModel.getDefaultContainer(ServersModel.defaultIndex) + checked: proxyDefaultServerContainersModel.mapToSource(index) === ServersModel.getDefaultServerData("defaultContainer") onClicked: { if (ConnectionController.isConnected && isInstalled) { @@ -60,18 +61,18 @@ ListView { } if (checked) { - containersDropDown.menuVisible = false - ServersModel.setDefaultContainer(ServersModel.defaultIndex, proxyContainersModel.mapToSource(index)) + containersDropDown.close() + ServersModel.setDefaultContainer(ServersModel.defaultIndex, proxyDefaultServerContainersModel.mapToSource(index)) } else { if (!isSupported && isInstalled) { PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) return } - ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) + ContainersModel.setCurrentlyProcessedContainerIndex(proxyDefaultServerContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) - containersDropDown.menuVisible = false + containersDropDown.close() } } diff --git a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml new file mode 100644 index 000000000..bc1f1008b --- /dev/null +++ b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +DrawerType2 { + id: root + + anchors.fill: parent + expandedHeight: parent.height * 0.7 + + expandedContent: ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + spacing: 0 + + Header2Type { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + + headerText: qsTr("Split tunneling") + descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi") + + text: qsTr("Split tunneling on the server") + descriptionText: qsTr("Enabled \nCan't be disabled for current server") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { +// PageController.goToPage(PageEnum.PageSettingsSplitTunneling) +// root.close() + } + } + + DividerType { + visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + enabled: ! ServersModel.isDefaultServerDefaultContainerHasSplitTunneling || !ServersModel.getDefaultServerData("isServerFromApi") + + text: qsTr("Site-based split tunneling") + descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsSplitTunneling) + root.close() + } + } + + DividerType { + } + + LabelWithButtonType { + Layout.fillWidth: true + visible: false + + text: qsTr("App-based split tunneling") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { +// PageController.goToPage(PageEnum.PageSetupWizardConfigSource) + root.close() + } + } + + DividerType { + visible: false + } + } +} diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index 16cdcb393..c63c07b45 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -5,7 +5,7 @@ import QtQuick.Layouts import "../Controls2" import "../Controls2/TextTypes" -DrawerType { +DrawerType2 { id: root property string headerText @@ -16,23 +16,24 @@ DrawerType { property var yesButtonFunction property var noButtonFunction - width: parent.width - height: content.implicitHeight + 32 - - ColumnLayout { + expandedContent: ColumnLayout { id: content anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 16 - anchors.rightMargin: 16 - anchors.leftMargin: 16 spacing: 8 + onImplicitHeightChanged: { + root.expandedHeight = content.implicitHeight + 32 + } + Header2TextType { Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 text: headerText } @@ -40,6 +41,8 @@ DrawerType { ParagraphTextType { Layout.fillWidth: true Layout.topMargin: 8 + Layout.rightMargin: 16 + Layout.leftMargin: 16 text: descriptionText } @@ -47,10 +50,12 @@ DrawerType { BasicButtonType { Layout.fillWidth: true Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 text: yesButtonText - onClicked: { + clickedFunc: function() { if (yesButtonFunction && typeof yesButtonFunction === "function") { yesButtonFunction() } @@ -59,6 +64,8 @@ DrawerType { BasicButtonType { Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) @@ -69,7 +76,7 @@ DrawerType { text: noButtonText - onClicked: { + clickedFunc: function() { if (noButtonFunction && typeof noButtonFunction === "function") { noButtonFunction() } diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index d318aab89..260a3a98e 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -5,129 +5,136 @@ import QtQuick.Layouts import "../Controls2" import "../Controls2/TextTypes" -DrawerType { +DrawerType2 { id: root - width: parent.width - height: parent.height * 0.9 + expandedContent: Item { + id: container - ColumnLayout { - id: backButton + implicitHeight: root.height * 0.9 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 16 - - BackButtonType { - backButtonImage: "qrc:/images/controls/arrow-left.svg" - backButtonFunction: function() { - root.close() - } + Component.onCompleted: { + root.expandedHeight = container.implicitHeight } - } - - FlickableType { - anchors.top: backButton.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - contentHeight: content.implicitHeight ColumnLayout { - id: content + id: backButton - anchors.fill: parent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 - Header2Type { - id: header - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.rightMargin: 16 - Layout.leftMargin: 16 - - headerText: qsTr("Choose language") + BackButtonType { + backButtonImage: "qrc:/images/controls/arrow-left.svg" + backButtonFunction: function() { + root.close() + } } + } - ListView { - id: listView + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight - Layout.fillWidth: true - height: listView.contentItem.height + ColumnLayout { + id: content - clip: true - interactive: false + anchors.fill: parent - model: LanguageModel - currentIndex: LanguageModel.currentLanguageIndex + Header2Type { + id: header + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 - ButtonGroup { - id: buttonGroup + headerText: qsTr("Choose language") } - delegate: Item { - implicitWidth: root.width - implicitHeight: delegateContent.implicitHeight + ListView { + id: listView - ColumnLayout { - id: delegateContent + Layout.fillWidth: true + height: listView.contentItem.height - anchors.fill: parent + clip: true + interactive: false - RadioButton { - id: radioButton + model: LanguageModel + currentIndex: LanguageModel.currentLanguageIndex - implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight + ButtonGroup { + id: buttonGroup + } - hoverEnabled: true + delegate: Item { + implicitWidth: root.width + implicitHeight: delegateContent.implicitHeight - indicator: Rectangle { - anchors.fill: parent - color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + ColumnLayout { + id: delegateContent - Behavior on color { - PropertyAnimation { duration: 200 } - } - } + anchors.fill: parent - RowLayout { - id: radioButtonContent - anchors.fill: parent + RadioButton { + id: radioButton - anchors.rightMargin: 16 - anchors.leftMargin: 16 + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight - spacing: 0 + hoverEnabled: true - z: 1 + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" - ParagraphTextType { - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 - - text: languageName + Behavior on color { + PropertyAnimation { duration: 200 } + } } - Image { - source: "qrc:/images/controls/check.svg" - visible: radioButton.checked + RowLayout { + id: radioButtonContent + anchors.fill: parent - width: 24 - height: 24 + anchors.rightMargin: 16 + anchors.leftMargin: 16 - Layout.rightMargin: 8 + spacing: 0 + + z: 1 + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + + text: languageName + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } } - } - ButtonGroup.group: buttonGroup - checked: listView.currentIndex === index + ButtonGroup.group: buttonGroup + checked: listView.currentIndex === index - onClicked: { - listView.currentIndex = index - LanguageModel.changeLanguage(languageIndex) - root.close() + onClicked: { + listView.currentIndex = index + LanguageModel.changeLanguage(languageIndex) + root.close() + } } } } diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 45ef84a63..c209c1cea 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -16,19 +16,18 @@ import "../Controls2/TextTypes" import "../Config" import "../Components" -DrawerType { +DrawerType2 { id: root - property alias headerText: header.headerText - property alias configContentHeaderText: configContentHeader.headerText - property alias contentVisible: content.visible + property string headerText + property string configContentHeaderText + property string contentVisible property string configExtension: ".vpn" property string configCaption: qsTr("Save AmneziaVPN config") property string configFileName: "amnezia_config" - width: parent.width - height: parent.height * 0.9 + expandedHeight: parent.height * 0.9 onClosed: { configExtension = ".vpn" @@ -36,8 +35,8 @@ DrawerType { configFileName = "amnezia_config" } - Item { - anchors.fill: parent + expandedContent: Item { + implicitHeight: root.expandedHeight Header2Type { id: header @@ -47,6 +46,8 @@ DrawerType { anchors.topMargin: 20 anchors.leftMargin: 16 anchors.rightMargin: 16 + + headerText: root.headerText } FlickableType { @@ -64,6 +65,8 @@ DrawerType { anchors.leftMargin: 16 anchors.rightMargin: 16 + visible: root.contentVisible + BasicButtonType { Layout.fillWidth: true Layout.topMargin: 16 @@ -71,7 +74,7 @@ DrawerType { text: qsTr("Share") imageSource: "qrc:/images/controls/share-2.svg" - onClicked: { + clickedFunc: function() { var fileName = "" if (GC.isMobile()) { fileName = configFileName + configExtension @@ -91,6 +94,7 @@ DrawerType { } BasicButtonType { + id: copyConfigTextButton Layout.fillWidth: true Layout.topMargin: 8 @@ -104,7 +108,7 @@ DrawerType { text: qsTr("Copy") imageSource: "qrc:/images/controls/copy.svg" - onClicked: { + clickedFunc: function() { configText.selectAll() configText.copy() configText.select(0, 0) @@ -113,10 +117,11 @@ DrawerType { } BasicButtonType { + id: copyNativeConfigStringButton Layout.fillWidth: true Layout.topMargin: 8 - visible: nativeConfigString.text !== "" + visible: false defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) @@ -128,7 +133,7 @@ DrawerType { text: qsTr("Copy config string") imageSource: "qrc:/images/controls/copy.svg" - onClicked: { + clickedFunc: function() { nativeConfigString.selectAll() nativeConfigString.copy() nativeConfigString.select(0, 0) @@ -149,83 +154,117 @@ DrawerType { text: qsTr("Show connection settings") - onClicked: { - configContentDrawer.visible = true + clickedFunc: function() { + configContentDrawer.open() } } - DrawerType { + DrawerType2 { id: configContentDrawer - width: parent.width - height: parent.height * 0.9 + parent: root.parent - BackButtonType { - id: backButton + anchors.fill: parent + expandedHeight: parent.height * 0.9 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 16 + expandedContent: Item { + id: configContentContainer - backButtonFunction: function() { - configContentDrawer.visible = false + implicitHeight: configContentDrawer.expandedHeight + + Connections { + target: copyNativeConfigStringButton + function onClicked() { + nativeConfigString.selectAll() + nativeConfigString.copy() + nativeConfigString.select(0, 0) + PageController.showNotificationMessage(qsTr("Copied")) + } } - } - FlickableType { - anchors.top: backButton.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin - - ColumnLayout { - id: configContent - - anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - Header2Type { - id: configContentHeader - Layout.fillWidth: true - Layout.topMargin: 16 + Connections { + target: copyConfigTextButton + function onClicked() { + configText.selectAll() + configText.copy() + configText.select(0, 0) + PageController.showNotificationMessage(qsTr("Copied")) } + } - TextField { - id: nativeConfigString - visible: false - text: ExportController.nativeConfigString + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + configContentDrawer.open() } + } - TextArea { - id: configText + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 + ColumnLayout { + id: configContent - padding: 0 - leftPadding: 0 - height: 24 + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 - readOnly: true + Header2Type { + id: configContentHeader + Layout.fillWidth: true + Layout.topMargin: 16 - color: "#D7D8DB" - selectionColor: "#633303" - selectedTextColor: "#D7D8DB" + headerText: root.configContentHeaderText + } - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" + TextField { + id: nativeConfigString + visible: false + text: ExportController.nativeConfigString - text: ExportController.config + onTextChanged: { + copyNativeConfigStringButton.visible = nativeConfigString.text !== "" + } + } - wrapMode: Text.Wrap + TextArea { + id: configText - background: Rectangle { - color: "transparent" + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + padding: 0 + leftPadding: 0 + height: 24 + + readOnly: true + + color: "#D7D8DB" + selectionColor: "#633303" + selectedTextColor: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + text: ExportController.config + + wrapMode: Text.Wrap + + background: Rectangle { + color: "transparent" + } } } } diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index a5cde9519..257486d62 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -16,47 +16,39 @@ Button { property string textColor: "#0E0E11" property string borderColor: "#D7D8DB" + property string borderFocusedColor: "#D7D8DB" property int borderWidth: 0 + property int borderFocusedWidth: 1 property string imageSource + property string rightImageSource + property string leftImageColor: textColor property bool squareLeftSide: false + property var clickedFunc + implicitHeight: 56 hoverEnabled: true background: Rectangle { - id: background + id: background_border + + color: "transparent" + border.color: root.activeFocus ? root.borderFocusedColor : "transparent" + border.width: root.activeFocus ? root.borderFocusedWidth : "transparent" + anchors.fill: parent radius: 16 - color: { - if (root.enabled) { - if (root.pressed) { - return pressedColor - } - return root.hovered ? hoveredColor : defaultColor - } else { - return disabledColor - } - } - border.color: borderColor - border.width: borderWidth - - Behavior on color { - PropertyAnimation { duration: 200 } - } Rectangle { - visible: root.squareLeftSide + id: background - z: 1 + anchors.fill: background_border + anchors.margins: root.activeFocus ? 2: 0 - width: parent.radius - height: parent.radius - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left + radius: 16 color: { if (root.enabled) { if (root.pressed) { @@ -67,24 +59,53 @@ Button { return disabledColor } } + border.color: root.activeFocus ? "transparent" : borderColor + border.width: root.activeFocus ? 0 : borderWidth Behavior on color { PropertyAnimation { duration: 200 } } + + Rectangle { + visible: root.squareLeftSide + + z: 1 + + width: parent.radius + height: parent.radius + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + color: { + if (root.enabled) { + if (root.pressed) { + return pressedColor + } + return root.hovered ? hoveredColor : defaultColor + } else { + return disabledColor + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } } } MouseArea { - anchors.fill: background + anchors.fill: background_border enabled: false cursorShape: Qt.PointingHandCursor } contentItem: Item { - anchors.fill: background + anchors.fill: background_border implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight + RowLayout { id: content anchors.centerIn: parent @@ -99,7 +120,7 @@ Button { layer { enabled: true effect: ColorOverlay { - color: textColor + color: leftImageColor } } } @@ -112,6 +133,39 @@ Button { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter } + + Image { + Layout.preferredHeight: 20 + Layout.preferredWidth: 20 + + source: root.rightImageSource + visible: root.rightImageSource === "" ? false : true + + layer { + enabled: true + effect: ColorOverlay { + color: textColor + } + } + } + } + } + + Keys.onEnterPressed: { + if (root.clickedFunc && typeof root.clickedFunc === "function") { + root.clickedFunc() + } + } + + Keys.onReturnPressed: { + if (root.clickedFunc && typeof root.clickedFunc === "function") { + root.clickedFunc() + } + } + + onClicked: { + if (root.clickedFunc && typeof root.clickedFunc === "function") { + root.clickedFunc() } } } diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml deleted file mode 100644 index 830f59f9a..000000000 --- a/client/ui/qml/Controls2/DrawerType.qml +++ /dev/null @@ -1,84 +0,0 @@ -import QtQuick -import QtQuick.Controls - -import "../Config" - -Drawer { - id: drawer - property bool needCloseButton: true - - Connections { - target: PageController - - function onForceCloseDrawer() { - visible = false - } - } - - edge: Qt.BottomEdge - - clip: true - modal: true - dragMargin: -10 - - enter: Transition { - SmoothedAnimation { - velocity: 4 - } - } - - exit: Transition { - SmoothedAnimation { - velocity: 4 - } - } - - background: Rectangle { - anchors.fill: parent - anchors.bottomMargin: -radius - radius: 16 - color: "#1C1D21" - - border.color: "#2C2D30" - border.width: 1 - - Rectangle { - visible: GC.isMobile() - - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 10 - - width: 20 - height: 2 - color: "#2C2D30" - } - } - - Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) - } - - onAboutToShow: { - if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(0xFF1C1D21) - } - } - - onOpened: { - if (needCloseButton) { - PageController.drawerOpen() - } - } - - onClosed: { - if (needCloseButton) { - PageController.drawerClose() - } - - var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() - if (initialPageNavigationBarColor !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(initialPageNavigationBarColor) - } - } -} diff --git a/client/ui/qml/Controls2/DrawerType2.qml b/client/ui/qml/Controls2/DrawerType2.qml new file mode 100644 index 000000000..5e6f785cc --- /dev/null +++ b/client/ui/qml/Controls2/DrawerType2.qml @@ -0,0 +1,242 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +Item { + id: root + + readonly property string drawerExpanded: "expanded" + readonly property string drawerCollapsed: "collapsed" + + readonly property bool isOpened: drawerContent.state === root.drawerExpanded || (drawerContent.state === root.drawerCollapsed && dragArea.drag.active === true) + readonly property bool isClosed: drawerContent.state === root.drawerCollapsed && dragArea.drag.active === false + + readonly property bool isExpanded: drawerContent.state === root.drawerExpanded + readonly property bool isCollapsed: drawerContent.state === root.drawerCollapsed + + property Component collapsedContent + property Component expandedContent + + property string defaultColor: "#1C1D21" + property string borderColor: "#2C2D30" + + property real expandedHeight + property real collapsedHeight: 0 + + signal entered + signal exited + signal pressed(bool pressed, bool entered) + + signal aboutToHide + signal aboutToShow + signal close + signal open + signal closed + signal opened + + Connections { + target: root + + function onClose() { + if (isCollapsed) { + return + } + + aboutToHide() + + drawerContent.state = root.drawerCollapsed + closed() + } + + function onOpen() { + if (isExpanded) { + return + } + + aboutToShow() + + drawerContent.state = root.drawerExpanded + opened() + } + } + + /** Set once based on first implicit height change once all children are layed out */ + Component.onCompleted: { + if (root.isCollapsed && root.collapsedHeight == 0) { + root.collapsedHeight = drawerContent.implicitHeight + } + } + + Rectangle { + id: background + + anchors.fill: parent + color: root.isCollapsed ? "transparent" : Qt.rgba(14/255, 14/255, 17/255, 0.8) + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + MouseArea { + id: emptyArea + anchors.fill: parent + enabled: root.isExpanded + visible: enabled + onClicked: { + root.close() + } + } + + MouseArea { + id: dragArea + + anchors.fill: drawerContentBackground + cursorShape: root.isCollapsed ? Qt.PointingHandCursor : Qt.ArrowCursor + hoverEnabled: true + + enabled: drawerContent.implicitHeight > 0 + + drag.target: drawerContent + drag.axis: Drag.YAxis + drag.maximumY: root.height - root.collapsedHeight + drag.minimumY: root.height - root.expandedHeight + + /** If drag area is released at any point other than min or max y, transition to the other state */ + onReleased: { + if (root.isCollapsed && drawerContent.y < dragArea.drag.maximumY) { + root.open() + return + } + if (root.isExpanded && drawerContent.y > dragArea.drag.minimumY) { + root.close() + return + } + } + + onEntered: { + root.entered() + } + onExited: { + root.exited() + } + onPressedChanged: { + root.pressed(pressed, entered) + } + + onClicked: { + if (root.isCollapsed) { + root.open() + } + } + } + + Rectangle { + id: drawerContentBackground + + anchors { left: drawerContent.left; right: drawerContent.right; top: drawerContent.top } + height: root.height + radius: 16 + color: root.defaultColor + border.color: root.borderColor + border.width: 1 + + Rectangle { + width: parent.radius + height: parent.radius + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + color: parent.color + } + } + + Item { + id: drawerContent + + Drag.active: dragArea.drag.active + anchors.right: root.right + anchors.left: root.left + y: root.height - drawerContent.height + state: root.drawerCollapsed + + implicitHeight: root.isCollapsed ? collapsedLoader.implicitHeight : expandedLoader.implicitHeight + + onStateChanged: { + if (root.isCollapsed) { + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + return + } + if (root.isExpanded) { + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + return + } + } + + states: [ + State { + name: root.drawerCollapsed + PropertyChanges { + target: drawerContent + y: root.height - root.collapsedHeight + } + }, + State { + name: root.drawerExpanded + PropertyChanges { + target: drawerContent + y: dragArea.drag.minimumY + + } + } + ] + + transitions: [ + Transition { + from: root.drawerCollapsed + to: root.drawerExpanded + PropertyAnimation { + target: drawerContent + properties: "y" + duration: 200 + } + }, + Transition { + from: root.drawerExpanded + to: root.drawerCollapsed + PropertyAnimation { + target: drawerContent + properties: "y" + duration: 200 + } + } + ] + + Loader { + id: collapsedLoader + + visible: root.isCollapsed + sourceComponent: root.collapsedContent + + anchors.right: parent.right + anchors.left: parent.left + } + + Loader { + id: expandedLoader + + visible: root.isExpanded + sourceComponent: root.expandedContent + + anchors.right: parent.right + anchors.left: parent.left + } + } +} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index b91f0b7a7..5b876d79f 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -36,19 +36,23 @@ Item { property int rootButtonTextBottomMargin: 16 property real drawerHeight: 0.9 + property Item drawerParent property Component listView - property alias menuVisible: menu.visible + signal open + signal close implicitWidth: rootButtonContent.implicitWidth implicitHeight: rootButtonContent.implicitHeight - onMenuVisibleChanged: { - if (menuVisible) { - rootButtonBackground.border.color = rootButtonPressedBorderColor - } else { - rootButtonBackground.border.color = rootButtonDefaultBorderColor - } + onOpen: { + menu.open() + rootButtonBackground.border.color = rootButtonPressedBorderColor + } + + onClose: { + menu.close() + rootButtonBackground.border.color = rootButtonDefaultBorderColor } onEnabledChanged: { @@ -133,21 +137,21 @@ Item { hoverEnabled: root.enabled ? true : false onEntered: { - if (menu.visible === false) { + if (menu.isClosed) { rootButtonBackground.border.color = rootButtonHoveredBorderColor rootButtonBackground.color = rootButtonBackgroundHoveredColor } } onExited: { - if (menu.visible === false) { + if (menu.isClosed) { rootButtonBackground.border.color = rootButtonDefaultBorderColor rootButtonBackground.color = rootButtonBackgroundColor } } onPressed: { - if (menu.visible === false) { + if (menu.isClosed) { rootButtonBackground.color = pressed ? rootButtonBackgroundPressedColor : entered ? rootButtonHoveredBorderColor : rootButtonDefaultBorderColor } } @@ -156,60 +160,68 @@ Item { if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") { rootButtonClickedFunction() } else { - menu.visible = true + menu.open() } } } - DrawerType { + DrawerType2 { id: menu - width: parent.width - height: parent.height * drawerHeight + parent: drawerParent - ColumnLayout { - id: header + anchors.fill: parent + expandedHeight: drawerParent.height * drawerHeight - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 16 + expandedContent: Item { + id: container - BackButtonType { - backButtonImage: root.headerBackButtonImage - backButtonFunction: function() { - root.menuVisible = false - } - } - } + implicitHeight: menu.expandedHeight - FlickableType { - anchors.top: header.bottom - anchors.topMargin: 16 - contentHeight: col.implicitHeight + ColumnLayout { + id: header - Column { - id: col anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right + anchors.topMargin: 16 - spacing: 16 + BackButtonType { + backButtonImage: root.headerBackButtonImage + backButtonFunction: function() { + menu.close() + } + } + } - Header2Type { + FlickableType { + anchors.top: header.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight + + Column { + id: col + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - headerText: root.headerText + spacing: 16 - width: parent.width - } + Header2Type { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 - Loader { - id: listViewLoader - sourceComponent: root.listView + headerText: root.headerText + + width: parent.width + } + + Loader { + id: listViewLoader + sourceComponent: root.listView + } } } } diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 2c176b404..706a32e12 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -7,6 +7,8 @@ Item { property StackView stackView: StackView.view + property var defaultActiveFocusItem: null + // MouseArea { // id: globalMouseArea // z: 99 @@ -19,4 +21,17 @@ Item { // mouse.accepted = false // } // } + + // Set a timer to set focus after a short delay + Timer { + id: timer + interval: 100 // Milliseconds + onTriggered: { + if (defaultActiveFocusItem) { + defaultActiveFocusItem.forceActiveFocus() + } + } + repeat: false // Stop the timer after one trigger + running: true // Start the timer + } } diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index e4d2a4494..c85997dc7 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -66,7 +66,7 @@ Popup { borderWidth: 0 text: qsTr("Close") - onClicked: { + clickedFunc: function() { root.close() } } diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index ac0473cf7..606aa83ef 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -69,10 +69,13 @@ Item { TextField { id: textField + activeFocusOnTab: false enabled: root.textFieldEditable color: root.enabled ? root.textFieldTextColor : root.textFieldTextDisabledColor + inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText + placeholderText: root.textFieldPlaceholderText placeholderTextColor: "#494B50" @@ -142,7 +145,7 @@ Item { Layout.preferredWidth: content.implicitHeight squareLeftSide: true - onClicked: { + clickedFunc: function() { if (root.clickedFunc && typeof root.clickedFunc === "function") { root.clickedFunc() } @@ -186,4 +189,12 @@ Item { function getBackgroundBorderColor(noneFocusedColor) { return textField.focus ? root.borderFocusedColor : noneFocusedColor } + + Keys.onEnterPressed: { + KeyNavigation.tab.forceActiveFocus(); + } + + Keys.onReturnPressed: { + KeyNavigation.tab.forceActiveFocus(); + } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 79b9b572e..e81359686 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -18,498 +18,396 @@ import "../Components" PageType { id: root - property string defaultColor: "#1C1D21" - - property string borderColor: "#2C2D30" - Connections { target: PageController function onRestorePageHomeState(isContainerInstalled) { - buttonContent.state = "expanded" + drawer.open() if (isContainerInstalled) { containersDropDown.rootButtonClickedFunction() } } - - function onForceCloseDrawer() { - buttonContent.state = "collapsed" - } - } - - MouseArea { - anchors.fill: parent - enabled: buttonContent.state === "expanded" - onClicked: { - buttonContent.state = "collapsed" - } } Item { anchors.fill: parent - anchors.bottomMargin: buttonContent.collapsedHeight + anchors.bottomMargin: drawer.collapsedHeight ConnectButton { + id: connectButton anchors.centerIn: parent } - } - MouseArea { - id: dragArea - - anchors.fill: buttonBackground - cursorShape: buttonContent.state === "collapsed" ? Qt.PointingHandCursor : Qt.ArrowCursor - hoverEnabled: true - - drag.target: buttonContent - drag.axis: Drag.YAxis - drag.maximumY: root.height - buttonContent.collapsedHeight - drag.minimumY: root.height - root.height * 0.9 - - /** If drag area is released at any point other than min or max y, transition to the other state */ - onReleased: { - if (buttonContent.state === "collapsed" && buttonContent.y < dragArea.drag.maximumY) { - buttonContent.state = "expanded" - return - } - if (buttonContent.state === "expanded" && buttonContent.y > dragArea.drag.minimumY) { - buttonContent.state = "collapsed" - return - } - } - - onEntered: { - collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor - collapsedButtonHeader.opacity = 0.8 - } - onExited: { - collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor - collapsedButtonHeader.opacity = 1 - } - onPressedChanged: { - collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor - collapsedButtonHeader.opacity = 0.7 - } - - - onClicked: { - if (buttonContent.state === "collapsed") { - buttonContent.state = "expanded" - } - } - } - - Rectangle { - id: buttonBackground - - anchors { left: buttonContent.left; right: buttonContent.right; top: buttonContent.top } - height: root.height - radius: 16 - color: root.defaultColor - border.color: root.borderColor - border.width: 1 - - Rectangle { - width: parent.radius - height: parent.radius + BasicButtonType { + anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - color: parent.color + anchors.bottomMargin: 34 + leftPadding: 16 + rightPadding: 16 + + implicitHeight: 36 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#878B91" + leftImageColor: "transparent" + borderWidth: 0 + + property bool isSplitTunnelingEnabled: SitesModel.isTunnelingEnabled || + (ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi")) + + text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled") + + imageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : "" + rightImageSource: "qrc:/images/controls/chevron-down.svg" + + onClicked: { + homeSplitTunnelingDrawer.open() + } + + HomeSplitTunnelingDrawer { + id: homeSplitTunnelingDrawer + + parent: root + } } } - ColumnLayout { - id: buttonContent - /** Initial height of button content */ - property int collapsedHeight: 0 - /** True when expanded objects should be visible */ - property bool expandedVisibility: buttonContent.state === "expanded" || (buttonContent.state === "collapsed" && dragArea.drag.active === true) - /** True when collapsed objects should be visible */ - property bool collapsedVisibility: buttonContent.state === "collapsed" && dragArea.drag.active === false + DrawerType2 { + id: drawer + anchors.fill: parent - Drag.active: dragArea.drag.active - anchors.right: root.right - anchors.left: root.left - y: root.height - buttonContent.height - - Component.onCompleted: { - buttonContent.state = "collapsed" - } - - /** Set once based on first implicit height change once all children are layed out */ - onImplicitHeightChanged: { - if (buttonContent.state === "collapsed" && collapsedHeight == 0) { - collapsedHeight = implicitHeight - } - } - - onStateChanged: { - if (buttonContent.state === "collapsed") { - var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() - if (initialPageNavigationBarColor !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(initialPageNavigationBarColor) - } - PageController.drawerClose() - return - } - if (buttonContent.state === "expanded") { - if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(0xFF1C1D21) - } - PageController.drawerOpen() - return - } - } - - /** Two states of buttonContent, great place to add any future animations for the drawer */ - states: [ - State { - name: "collapsed" - PropertyChanges { - target: buttonContent - y: root.height - collapsedHeight - } - }, - State { - name: "expanded" - PropertyChanges { - target: buttonContent - y: dragArea.drag.minimumY - - } - } - ] - - transitions: [ - Transition { - from: "collapsed" - to: "expanded" - PropertyAnimation { - target: buttonContent - properties: "y" - duration: 200 - } - }, - Transition { - from: "expanded" - to: "collapsed" - PropertyAnimation { - target: buttonContent - properties: "y" - duration: 200 - } - } - ] - - DividerType { - Layout.topMargin: 10 - Layout.fillWidth: false - Layout.preferredWidth: 20 - Layout.preferredHeight: 2 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - - visible: (buttonContent.collapsedVisibility || buttonContent.expandedVisibility) - } - - RowLayout { - Layout.topMargin: 14 - Layout.leftMargin: 24 - Layout.rightMargin: 24 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - visible: buttonContent.collapsedVisibility - - spacing: 0 - - Header1TextType { - id: collapsedButtonHeader - Layout.maximumWidth: buttonContent.width - 48 - 18 - 12 // todo - - maximumLineCount: 2 - elide: Qt.ElideRight - - text: ServersModel.defaultServerName - horizontalAlignment: Qt.AlignHCenter - - Behavior on opacity { - PropertyAnimation { duration: 200 } - } - } - - ImageButtonType { - id: collapsedButtonChevron - - Layout.leftMargin: 8 - - hoverEnabled: false - image: "qrc:/images/controls/chevron-down.svg" - imageColor: "#d7d8db" - - icon.width: 18 - icon.height: 18 - backgroundRadius: 16 - horizontalPadding: 4 - topPadding: 4 - bottomPadding: 3 - - onClicked: { - if (buttonContent.state === "collapsed") { - buttonContent.state = "expanded" - } - } - } - } - - LabelTextType { - id: collapsedServerMenuDescription - Layout.bottomMargin: 10 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - visible: buttonContent.collapsedVisibility - text: ServersModel.defaultServerDescriptionCollapsed - } - - GraphViewType { - id: graph1 - Layout.preferredHeight: 50 - Layout.fillWidth: true - visible: buttonContent.collapsedVisibility - } - - ColumnLayout { - id: serversMenuHeader - - Layout.alignment: Qt.AlignTop | Qt.AlignHCenter - Layout.fillWidth: true - visible: buttonContent.expandedVisibility - - Header1TextType { - Layout.fillWidth: true - Layout.topMargin: 14 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: ServersModel.defaultServerName - horizontalAlignment: Qt.AlignHCenter - maximumLineCount: 2 - elide: Qt.ElideRight - } - - LabelTextType { - id: expandedServersMenuDescription - Layout.bottomMargin: 24 - Layout.fillWidth: true - horizontalAlignment: Qt.AlignHCenter - verticalAlignment: Qt.AlignVCenter - text: ServersModel.defaultServerDescriptionExpanded - } - - GraphViewType { - id: graph2 - Layout.preferredHeight: 50 - Layout.fillWidth: true - visible: buttonContent.expandedVisibility + collapsedContent: ColumnLayout { + DividerType { + Layout.topMargin: 10 + Layout.fillWidth: false + Layout.preferredWidth: 20 + Layout.preferredHeight: 2 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter } RowLayout { + Layout.topMargin: 14 + Layout.leftMargin: 24 + Layout.rightMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - spacing: 8 - DropDownType { - id: containersDropDown + spacing: 0 - rootButtonImageColor: "#0E0E11" - rootButtonBackgroundColor: "#D7D8DB" - rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8) - rootButtonBackgroundPressedColor: Qt.rgba(215, 216, 219, 0.65) - rootButtonHoveredBorderColor: "transparent" - rootButtonDefaultBorderColor: "transparent" - rootButtonTextTopMargin: 8 - rootButtonTextBottomMargin: 8 - - text: ServersModel.defaultContainerName - textColor: "#0E0E11" - headerText: qsTr("VPN protocol") - headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" - - rootButtonClickedFunction: function() { - ServersModel.currentlyProcessedIndex = serversMenuContent.currentIndex - containersDropDown.menuVisible = true + Connections { + target: drawer + function onEntered() { + collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor + collapsedButtonHeader.opacity = 0.8 } - listView: HomeContainersListView { - rootWidth: root.width + function onExited() { + collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor + collapsedButtonHeader.opacity = 1 + } + + function onPressed(pressed, entered) { + collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor + collapsedButtonHeader.opacity = 0.7 + } + } + + Header1TextType { + id: collapsedButtonHeader + Layout.maximumWidth: drawer.width - 48 - 18 - 12 // todo + + maximumLineCount: 2 + elide: Qt.ElideRight + + text: ServersModel.defaultServerName + horizontalAlignment: Qt.AlignHCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } + } + + ImageButtonType { + id: collapsedButtonChevron + + Layout.leftMargin: 8 + + hoverEnabled: false + image: "qrc:/images/controls/chevron-down.svg" + imageColor: "#d7d8db" + + icon.width: 18 + icon.height: 18 + backgroundRadius: 16 + horizontalPadding: 4 + topPadding: 4 + bottomPadding: 3 + + onClicked: { + if (drawer.isCollapsed) { + drawer.open() + } + } + } + } + + LabelTextType { + id: collapsedServerMenuDescription + Layout.bottomMargin: 10 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + text: ServersModel.defaultServerDescriptionCollapsed + } + + + GraphViewType { + id: graph1 + Layout.preferredHeight: 50 + Layout.fillWidth: true + } + + } + expandedContent: Item { + id: serverMenuContainer + + implicitHeight: root.height * 0.9 + + Component.onCompleted: { + drawer.expandedHeight = serverMenuContainer.implicitHeight + } + + ColumnLayout { + id: serversMenuHeader + + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + + + Header1TextType { + Layout.fillWidth: true + Layout.topMargin: 14 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: ServersModel.defaultServerName + horizontalAlignment: Qt.AlignHCenter + maximumLineCount: 2 + elide: Qt.ElideRight + } + + LabelTextType { + id: expandedServersMenuDescription + Layout.bottomMargin: ServersModel.isDefaultServerFromApi ? 69 : 24 + Layout.fillWidth: true + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + text: ServersModel.defaultServerDescriptionExpanded + } + + GraphViewType { + id: graph2 + Layout.preferredHeight: 50 + Layout.fillWidth: true + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + spacing: 8 + + visible: !ServersModel.isDefaultServerFromApi + onVisibleChanged: expandedServersMenuDescription.Layout + + DropDownType { + id: containersDropDown + + rootButtonImageColor: "#0E0E11" + rootButtonBackgroundColor: "#D7D8DB" + rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8) + rootButtonBackgroundPressedColor: Qt.rgba(215, 216, 219, 0.65) + rootButtonHoveredBorderColor: "transparent" + rootButtonDefaultBorderColor: "transparent" + rootButtonTextTopMargin: 8 + rootButtonTextBottomMargin: 8 + + text: ServersModel.defaultServerDefaultContainerName + textColor: "#0E0E11" + headerText: qsTr("VPN protocol") + headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" + + rootButtonClickedFunction: function() { + containersDropDown.open() + } + + drawerParent: root + + listView: HomeContainersListView { + rootWidth: root.width + + Connections { + target: ServersModel + + function onDefaultServerIndexChanged() { + updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isDefaultServerHasWriteAccess()) { + proxyDefaultServerContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() + } else { + proxyDefaultServerContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() + } + } + + model: SortFilterProxyModel { + id: proxyDefaultServerContainersModel + sourceModel: DefaultServerContainersModel + + sorters: [ + RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder } + ] + } + + Component.onCompleted: updateContainersModelFilters() + } + } + } + + Header2Type { + Layout.fillWidth: true + Layout.topMargin: 48 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Servers") + } + } + + Flickable { + id: serversContainer + + anchors.top: serversMenuHeader.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.topMargin: 16 + + contentHeight: col.height + col.anchors.bottomMargin + implicitHeight: parent.height - serversMenuHeader.implicitHeight + clip: true + + ScrollBar.vertical: ScrollBar { + id: scrollBar + policy: serversContainer.height >= serversContainer.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn + } + + Keys.onUpPressed: scrollBar.decrease() + Keys.onDownPressed: scrollBar.increase() + + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottomMargin: 32 + + spacing: 16 + + ButtonGroup { + id: serversRadioButtonGroup + } + + ListView { + id: serversMenuContent + width: parent.width + height: serversMenuContent.contentItem.height + + model: ServersModel + currentIndex: ServersModel.defaultIndex Connections { target: ServersModel - - function onCurrentlyProcessedServerIndexChanged() { - updateContainersModelFilters() + function onDefaultServerIndexChanged(serverIndex) { + serversMenuContent.currentIndex = serverIndex } } - function updateContainersModelFilters() { - if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { - proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() - } else { - proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() - } - } + clip: true + interactive: false - model: SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - } + delegate: Item { + id: menuContentDelegate - Component.onCompleted: updateContainersModelFilters() - } - } - } + property variant delegateData: model - Header2Type { - Layout.fillWidth: true - Layout.topMargin: 48 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - visible: buttonContent.expandedVisibility + implicitWidth: serversMenuContent.width + implicitHeight: serverRadioButtonContent.implicitHeight - headerText: qsTr("Servers") - } - } + ColumnLayout { + id: serverRadioButtonContent - Flickable { - id: serversContainer - Layout.alignment: Qt.AlignTop | Qt.AlignHCenter - Layout.fillWidth: true - Layout.topMargin: 16 - contentHeight: col.implicitHeight - implicitHeight: root.height - (root.height * 0.1) - serversMenuHeader.implicitHeight - 52 //todo 52 is tabbar height - visible: buttonContent.expandedVisibility - clip: true + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 - ScrollBar.vertical: ScrollBar { - id: scrollBar - policy: serversContainer.height >= serversContainer.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn - } + spacing: 0 - Keys.onUpPressed: scrollBar.decrease() - Keys.onDownPressed: scrollBar.increase() + RowLayout { + VerticalRadioButton { + id: serverRadioButton - Column { - id: col - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + Layout.fillWidth: true - spacing: 16 + text: name + descriptionText: serverDescription - ButtonGroup { - id: serversRadioButtonGroup - } + checked: index === serversMenuContent.currentIndex + checkable: !ConnectionController.isConnected - ListView { - id: serversMenuContent - width: parent.width - height: serversMenuContent.contentItem.height + ButtonGroup.group: serversRadioButtonGroup - model: ServersModel - currentIndex: ServersModel.defaultIndex + onClicked: { + if (ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection")) + return + } - Connections { - target: ServersModel - function onDefaultServerIndexChanged(serverIndex) { - serversMenuContent.currentIndex = serverIndex - } - } + serversMenuContent.currentIndex = index - clip: true - interactive: false + ServersModel.defaultIndex = index + } - delegate: Item { - id: menuContentDelegate + MouseArea { + anchors.fill: serverRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } - property variant delegateData: model + ImageButtonType { + image: "qrc:/images/controls/settings.svg" + imageColor: "#D7D8DB" - implicitWidth: serversMenuContent.width - implicitHeight: serverRadioButtonContent.implicitHeight + implicitWidth: 56 + implicitHeight: 56 - ColumnLayout { - id: serverRadioButtonContent + z: 1 - anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - spacing: 0 - - RowLayout { - VerticalRadioButton { - id: serverRadioButton + onClicked: function() { + ServersModel.processedIndex = index + PageController.goToPage(PageEnum.PageSettingsServerInfo) + drawer.close() + } + } + } + DividerType { Layout.fillWidth: true - - text: name - descriptionText: { - var fullDescription = "" - if (hasWriteAccess) { - if (SettingsController.isAmneziaDnsEnabled() - && ServersModel.isAmneziaDnsContainerInstalled(index)) { - fullDescription += "Amnezia DNS | " - } - } else { - if (containsAmneziaDns) { - fullDescription += "Amnezia DNS | " - } - } - - return fullDescription += serverDescription - } - - checked: index === serversMenuContent.currentIndex - checkable: !ConnectionController.isConnected - - ButtonGroup.group: serversRadioButtonGroup - - onClicked: { - if (ConnectionController.isConnected) { - PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection")) - return - } - - serversMenuContent.currentIndex = index - - ServersModel.currentlyProcessedIndex = index - ServersModel.defaultIndex = index - } - - MouseArea { - anchors.fill: serverRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false - } + Layout.leftMargin: 0 + Layout.rightMargin: 0 } - - ImageButtonType { - image: "qrc:/images/controls/settings.svg" - imageColor: "#D7D8DB" - - implicitWidth: 56 - implicitHeight: 56 - - z: 1 - - onClicked: function() { - ServersModel.currentlyProcessedIndex = index - PageController.goToPage(PageEnum.PageSettingsServerInfo) - buttonContent.state = "collapsed" - } - } - } - - DividerType { - Layout.fillWidth: true - Layout.leftMargin: 0 - Layout.rightMargin: 0 } } } diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index a4f5abe35..df9f0b9f6 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -12,9 +12,12 @@ import "../Controls2/TextTypes" import "../Config" import "../Components" + PageType { id: root + defaultActiveFocusItem: listview.currentItem.portTextField.textField + ColumnLayout { id: backButton @@ -41,9 +44,11 @@ PageType { anchors.left: parent.left anchors.right: parent.right - enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + enabled: ServersModel.isProcessedServerHasWriteAccess() ListView { + + id: listview width: parent.width @@ -55,9 +60,13 @@ PageType { model: AwgConfigModel delegate: Item { + id: _delegate + implicitWidth: listview.width implicitHeight: col.implicitHeight + property alias portTextField:portTextField + ColumnLayout { id: col @@ -93,6 +102,8 @@ PageType { } checkEmptyText: true + + KeyNavigation.tab: junkPacketCountTextField.textField } TextFieldWithHeaderType { @@ -116,6 +127,8 @@ PageType { } checkEmptyText: true + + KeyNavigation.tab: junkPacketMinSizeTextField.textField } TextFieldWithHeaderType { @@ -134,6 +147,8 @@ PageType { } checkEmptyText: true + + KeyNavigation.tab: junkPacketMaxSizeTextField.textField } TextFieldWithHeaderType { @@ -152,6 +167,8 @@ PageType { } checkEmptyText: true + + KeyNavigation.tab: initPacketJunkSizeTextField.textField } TextFieldWithHeaderType { @@ -170,6 +187,8 @@ PageType { } checkEmptyText: true + + KeyNavigation.tab: responsePacketJunkSizeTextField.textField } TextFieldWithHeaderType { @@ -188,6 +207,8 @@ PageType { } checkEmptyText: true + + KeyNavigation.tab: initPacketMagicHeaderTextField.textField } TextFieldWithHeaderType { @@ -206,6 +227,8 @@ PageType { } checkEmptyText: true + + KeyNavigation.tab: responsePacketMagicHeaderTextField.textField } TextFieldWithHeaderType { @@ -224,6 +247,8 @@ PageType { } checkEmptyText: true + + KeyNavigation.tab: transportPacketMagicHeaderTextField.textField } TextFieldWithHeaderType { @@ -242,6 +267,8 @@ PageType { } checkEmptyText: true + + KeyNavigation.tab: underloadPacketMagicHeaderTextField.textField } TextFieldWithHeaderType { @@ -260,6 +287,8 @@ PageType { } checkEmptyText: true + + KeyNavigation.tab: saveRestartButton } BasicButtonType { @@ -275,24 +304,24 @@ PageType { text: qsTr("Remove AmneziaWG") onClicked: { - questionDrawer.headerText = qsTr("Remove AmneziaWG from server?") - questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Remove AmneziaWG from server?") + var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } BasicButtonType { + id: saveRestartButton + Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 @@ -310,7 +339,7 @@ PageType { text: qsTr("Save and Restart Amnezia") - onClicked: { + clickedFunc: function() { forceActiveFocus() PageController.goToPage(PageEnum.PageSetupWizardInstalling); InstallController.updateContainer(AwgConfigModel.getConfig()) @@ -318,11 +347,8 @@ PageType { } } } - } - } - QuestionDrawer { - id: questionDrawer + } } } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 98e9c28fd..b86397ba8 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -15,6 +15,8 @@ import "../Components" PageType { id: root + defaultActiveFocusItem: listview.currentItem.trafficFromField.textField + ColumnLayout { id: backButton @@ -41,7 +43,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + enabled: ServersModel.isProcessedServerHasWriteAccess() ListView { id: listview @@ -58,6 +60,8 @@ PageType { implicitWidth: listview.width implicitHeight: col.implicitHeight + property alias trafficFromField: trafficFromField + ColumnLayout { id: col @@ -77,6 +81,8 @@ PageType { } TextFieldWithHeaderType { + id: trafficFromField + Layout.fillWidth: true Layout.topMargin: 32 @@ -96,9 +102,13 @@ PageType { } } } + + KeyNavigation.tab: portTextField.textField } TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true Layout.topMargin: 16 @@ -112,6 +122,8 @@ PageType { port = textFieldText } } + + KeyNavigation.tab: saveRestartButton } DropDownType { @@ -122,6 +134,8 @@ PageType { descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") + drawerParent: root + listView: ListViewWithRadioButtonType { id: cipherListView @@ -138,7 +152,7 @@ PageType { clickedFunction: function() { cipherDropDown.text = selectedText cipher = cipherDropDown.text - cipherDropDown.menuVisible = false + cipherDropDown.close() } Component.onCompleted: { @@ -154,13 +168,15 @@ PageType { } BasicButtonType { + id: saveRestartButton + Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 text: qsTr("Save and Restart Amnezia") - onClicked: { + clickedFunc: function() { forceActiveFocus() PageController.goToPage(PageEnum.PageSetupWizardInstalling); InstallController.updateContainer(CloakConfigModel.getConfig()) diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 571b8eaa3..7fb4634ca 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -16,6 +16,8 @@ import "../Components" PageType { id: root + defaultActiveFocusItem: listview.currentItem.vpnAddressSubnetTextField.textField + ColumnLayout { id: backButton @@ -42,7 +44,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + enabled: ServersModel.isProcessedServerHasWriteAccess() ListView { id: listview @@ -53,12 +55,14 @@ PageType { clip: true interactive: false - model: OpenVpnConfigModel + model: OpenVpnConfigModel delegate: Item { implicitWidth: listview.width implicitHeight: col.implicitHeight + property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField + ColumnLayout { id: col @@ -78,6 +82,8 @@ PageType { } TextFieldWithHeaderType { + id: vpnAddressSubnetTextField + Layout.fillWidth: true Layout.topMargin: 32 @@ -89,6 +95,8 @@ PageType { subnetAddress = textFieldText } } + + KeyNavigation.tab: portTextField.enabled ? portTextField.textField : saveRestartButton } ParagraphTextType { @@ -119,6 +127,9 @@ PageType { } TextFieldWithHeaderType { + id: portTextField + + Layout.fillWidth: true Layout.topMargin: 40 @@ -134,6 +145,8 @@ PageType { port = textFieldText } } + + KeyNavigation.tab: saveRestartButton } SwitcherType { @@ -162,6 +175,8 @@ PageType { descriptionText: qsTr("Hash") headerText: qsTr("Hash") + drawerParent: root + listView: ListViewWithRadioButtonType { id: hashListView @@ -183,7 +198,7 @@ PageType { clickedFunction: function() { hashDropDown.text = selectedText hash = hashDropDown.text - hashDropDown.menuVisible = false + hashDropDown.close() } Component.onCompleted: { @@ -208,6 +223,8 @@ PageType { descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") + drawerParent: root + listView: ListViewWithRadioButtonType { id: cipherListView @@ -229,7 +246,7 @@ PageType { clickedFunction: function() { cipherDropDown.text = selectedText cipher = cipherDropDown.text - cipherDropDown.menuVisible = false + cipherDropDown.close() } Component.onCompleted: { @@ -363,32 +380,33 @@ PageType { text: qsTr("Remove OpenVPN") - onClicked: { - questionDrawer.headerText = qsTr("Remove OpenVpn from server?") - questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + clickedFunc: function() { + var headerText = qsTr("Remove OpenVpn from server?") + var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } BasicButtonType { + id: saveRestartButton + Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 text: qsTr("Save and Restart Amnezia") - onClicked: { + clickedFunc: function() { forceActiveFocus() PageController.goToPage(PageEnum.PageSetupWizardInstalling); InstallController.updateContainer(OpenVpnConfigModel.getConfig()) @@ -398,9 +416,5 @@ PageType { } } } - - QuestionDrawer { - id: questionDrawer - } } } diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 967b605bd..12d1722b8 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -90,71 +90,77 @@ PageType { DividerType {} - DrawerType { + DrawerType2 { id: configContentDrawer - width: parent.width - height: parent.height * 0.9 + expandedHeight: root.height * 0.9 - BackButtonType { - id: backButton + parent: root + anchors.fill: parent - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 16 + expandedContent: Item { + implicitHeight: configContentDrawer.expandedHeight - backButtonFunction: function() { - configContentDrawer.visible = false - } - } + BackButtonType { + id: backButton - FlickableType { - anchors.top: backButton.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 - ColumnLayout { - id: configContent - - anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - Header2Type { - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("Connection options %1").arg(protocolName) + backButtonFunction: function() { + configContentDrawer.close() } + } - TextArea { - id: configText + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 + ColumnLayout { + id: configContent - padding: 0 - leftPadding: 0 - height: 24 + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 - color: "#D7D8DB" - selectionColor: "#633303" - selectedTextColor: "#D7D8DB" + Header2Type { + Layout.fillWidth: true + Layout.topMargin: 16 - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" + headerText: qsTr("Connection options %1").arg(protocolName) + } - text: rawConfig + TextArea { + id: configText - wrapMode: Text.Wrap + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 - background: Rectangle { - color: "transparent" + padding: 0 + leftPadding: 0 + height: 24 + + color: "#D7D8DB" + selectionColor: "#633303" + selectedTextColor: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + text: rawConfig + + wrapMode: Text.Wrap + + background: Rectangle { + color: "transparent" + } } } } @@ -169,26 +175,25 @@ PageType { width: parent.width - visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + visible: ServersModel.isProcessedServerHasWriteAccess() text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) - questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) + var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } MouseArea { @@ -200,9 +205,5 @@ PageType { DividerType {} } - - QuestionDrawer { - id: questionDrawer - } } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 573aca060..5284f807b 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -15,6 +15,8 @@ import "../Components" PageType { id: root + defaultActiveFocusItem: listview.currentItem.portTextField.textField + ColumnLayout { id: backButton @@ -41,7 +43,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + enabled: ServersModel.isProcessedServerHasWriteAccess() ListView { id: listview @@ -58,6 +60,8 @@ PageType { implicitWidth: listview.width implicitHeight: col.implicitHeight + property alias portTextField: portTextField + ColumnLayout { id: col @@ -77,6 +81,8 @@ PageType { } TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true Layout.topMargin: 40 @@ -90,6 +96,8 @@ PageType { port = textFieldText } } + + KeyNavigation.tab: saveRestartButton } DropDownType { @@ -100,6 +108,8 @@ PageType { descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") + drawerParent: root + listView: ListViewWithRadioButtonType { id: cipherListView @@ -116,7 +126,7 @@ PageType { clickedFunction: function() { cipherDropDown.text = selectedText cipher = cipherDropDown.text - cipherDropDown.menuVisible = false + cipherDropDown.close() } Component.onCompleted: { @@ -132,13 +142,15 @@ PageType { } BasicButtonType { + id: saveRestartButton + Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 text: qsTr("Save and Restart Amnezia") - onClicked: { + clickedFunc: function() { forceActiveFocus() PageController.goToPage(PageEnum.PageSetupWizardInstalling); InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index 10fe6f569..c24bdd422 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -63,19 +63,18 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } MouseArea { @@ -86,10 +85,6 @@ PageType { } DividerType {} - - QuestionDrawer { - id: questionDrawer - } } } } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index b12302dd7..9a0e5ff1c 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -49,7 +49,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + enabled: ServersModel.isProcessedServerHasWriteAccess() ListView { id: listview @@ -88,7 +88,7 @@ PageType { Layout.topMargin: 32 text: qsTr("Host") - descriptionText: ServersModel.getCurrentlyProcessedServerHostName() + descriptionText: ServersModel.getProcessedServerData("HostName") descriptionOnTop: true @@ -170,7 +170,7 @@ PageType { text: qsTr("Mount folder on device") - onClicked: { + clickedFunc: function() { PageController.showBusyIndicator(true) InstallController.mountSftpDrive(port, password, username) PageController.showBusyIndicator(false) @@ -229,7 +229,7 @@ PageType { text: qsTr("Detailed instructions") - onClicked: { + clickedFunc: function() { // Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") } } @@ -247,29 +247,24 @@ PageType { text: qsTr("Remove SFTP and all data stored there") - onClicked: { - questionDrawer.headerText = qsTr("Remove SFTP and all data stored there?") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + clickedFunc: function() { + var headerText = qsTr("Remove SFTP and all data stored there?") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } } } } } - - QuestionDrawer { - id: questionDrawer - } } } diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index eef174c37..002e03109 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -125,26 +125,21 @@ PageType { text: qsTr("Remove website") - onClicked: { - questionDrawer.headerText = qsTr("The site with all data will be removed from the tor network.") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + clickedFunc: function() { + var headerText = qsTr("The site with all data will be removed from the tor network.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } } - - QuestionDrawer { - id: questionDrawer - } } } diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index f81285073..b912e2cdd 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -81,7 +81,7 @@ PageType { text: qsTr("Card on Patreon") - onClicked: function() { + clickedFunc: function() { Qt.openUrlExternally(qsTr("https://www.patreon.com/amneziavpn")) } } @@ -101,7 +101,9 @@ PageType { text: qsTr("Show other methods on Github") - onClicked: Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client#donate")) + clickedFunc: function() { + Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client#donate")) + } } ParagraphTextType { @@ -173,7 +175,7 @@ PageType { horizontalAlignment: Text.AlignHCenter - text: SettingsController.getAppVersion() + text: qsTr("Software version: %1").arg(SettingsController.getAppVersion()) color: "#878B91" } @@ -191,7 +193,7 @@ PageType { text: qsTr("Check for updates") - onClicked: { + clickedFunc: function() { Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 05e468f03..af029b7bf 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -84,6 +84,27 @@ PageType { visible: !GC.isMobile() } + SwitcherType { + visible: !GC.isMobile() + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Auto connect") + descriptionText: qsTr("Connect to VPN on app start") + + checked: SettingsController.isAutoConnectEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isAutoConnectEnabled()) { + SettingsController.toggleAutoConnect(checked) + } + } + } + + DividerType { + visible: !GC.isMobile() + } + SwitcherType { visible: !GC.isMobile() @@ -117,10 +138,6 @@ PageType { } } - SelectLanguageDrawer { - id: selectLanguageDrawer - } - DividerType {} @@ -143,30 +160,33 @@ PageType { text: qsTr("Reset settings and remove all data from the application") rightImageSource: "qrc:/images/controls/chevron-right.svg" + textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Reset settings and remove all data from the application?") - questionDrawer.descriptionText = qsTr("All settings will be reset to default. All installed AmneziaVPN services will still remain on the server.") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Reset settings and remove all data from the application?") + var descriptionText = qsTr("All settings will be reset to default. All installed AmneziaVPN services will still remain on the server.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { SettingsController.clearSettings() PageController.replaceStartPage() } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } DividerType {} - - QuestionDrawer { - id: questionDrawer - } } } + + SelectLanguageDrawer { + id: selectLanguageDrawer + + width: root.width + height: root.height + } } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 81be04650..6b3b3fb5f 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -88,7 +88,7 @@ PageType { text: qsTr("Make a backup") - onClicked: { + clickedFunc: function() { var fileName = "" if (GC.isMobile()) { fileName = "AmneziaVPN.backup" @@ -121,7 +121,7 @@ PageType { text: qsTr("Restore from backup") - onClicked: { + clickedFunc: function() { var filePath = SystemController.getFileName(qsTr("Open backup file"), qsTr("Backup files (*.backup)")) if (filePath !== "") { @@ -133,24 +133,19 @@ PageType { } function restoreBackup(filePath) { - questionDrawer.headerText = qsTr("Import settings from a backup file?") - questionDrawer.descriptionText = qsTr("All current settings will be reset"); - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Import settings from a backup file?") + var descriptionText = qsTr("All current settings will be reset"); + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.showBusyIndicator(true) SettingsController.restoreAppConfig(filePath) PageController.showBusyIndicator(false) } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true - } - QuestionDrawer { - id: questionDrawer + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 3a0c5c3cb..4d88b397b 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -41,27 +41,6 @@ PageType { headerText: qsTr("Connection") } - SwitcherType { - visible: !GC.isMobile() - - Layout.fillWidth: true - Layout.margins: 16 - - text: qsTr("Auto connect") - descriptionText: qsTr("Connect to VPN on app start") - - checked: SettingsController.isAutoConnectEnabled() - onCheckedChanged: { - if (checked !== SettingsController.isAutoConnectEnabled()) { - SettingsController.toggleAutoConnect(checked) - } - } - } - - DividerType { - visible: !GC.isMobile() - } - SwitcherType { Layout.fillWidth: true Layout.margins: 16 diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 1970da522..be2bd290a 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -13,6 +13,8 @@ import "../Components" PageType { id: root + defaultActiveFocusItem: primaryDns.textField + BackButtonType { id: backButton @@ -28,10 +30,12 @@ PageType { anchors.bottom: parent.bottom contentHeight: content.height - enabled: !ServersModel.isDefaultServerFromApi() + property var isServerFromApi: ServersModel.getDefaultServerData("isServerFromApi") + + enabled: !isServerFromApi Component.onCompleted: { - if (ServersModel.isDefaultServerFromApi()) { + if (isServerFromApi) { PageController.showNotificationMessage(qsTr("Default server does not support custom dns")) } } @@ -68,6 +72,8 @@ PageType { textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressRegExp() } + + KeyNavigation.tab: secondaryDns.textField } TextFieldWithHeaderType { @@ -80,6 +86,8 @@ PageType { textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressRegExp() } + + KeyNavigation.tab: saveButton } BasicButtonType { @@ -94,32 +102,33 @@ PageType { text: qsTr("Restore default") - onClicked: function() { - questionDrawer.headerText = qsTr("Restore default DNS settings?") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + clickedFunc: function() { + var headerText = qsTr("Restore default DNS settings?") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { SettingsController.primaryDns = "1.1.1.1" primaryDns.textFieldText = SettingsController.primaryDns SettingsController.secondaryDns = "1.0.0.1" secondaryDns.textFieldText = SettingsController.secondaryDns PageController.showNotificationMessage(qsTr("Settings have been reset")) } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } BasicButtonType { + id: saveButton + Layout.fillWidth: true text: qsTr("Save") - onClicked: function() { + clickedFunc: function() { if (primaryDns.textFieldText !== SettingsController.primaryDns) { SettingsController.primaryDns = primaryDns.textFieldText } @@ -130,8 +139,6 @@ PageType { } } } - QuestionDrawer { - id: questionDrawer - } } + } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index b302bffcb..b05ffd2bf 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -143,21 +143,20 @@ PageType { image: "qrc:/images/controls/delete.svg" onClicked: function() { - questionDrawer.headerText = qsTr("Clear logs?") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Clear logs?") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.showBusyIndicator(true) SettingsController.clearLogs() PageController.showBusyIndicator(false) PageController.showNotificationMessage(qsTr("Logs have been cleaned up")) } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } @@ -170,10 +169,6 @@ PageType { } } } - - QuestionDrawer { - id: questionDrawer - } } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 99d8a87af..969452133 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -28,7 +28,7 @@ PageType { PageController.showErrorMessage(message) } - function onRemoveCurrentlyProcessedServerFinished(finishedMessage) { + function onRemoveProcessedServerFinished(finishedMessage) { if (!ServersModel.getServersCount()) { PageController.replaceStartPage() } else { @@ -38,7 +38,7 @@ PageType { PageController.showNotificationMessage(finishedMessage) } - function onRebootCurrentlyProcessedServerFinished(finishedMessage) { + function onRebootProcessedServerFinished(finishedMessage) { PageController.showNotificationMessage(finishedMessage) } @@ -64,8 +64,8 @@ PageType { Connections { target: ServersModel - function onCurrentlyProcessedServerIndexChanged() { - content.isServerWithWriteAccess = ServersModel.isCurrentlyProcessedServerHasWriteAccess() + function onProcessedServerIndexChanged() { + content.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess() } } @@ -82,7 +82,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - property bool isServerWithWriteAccess: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess() LabelWithButtonType { visible: content.isServerWithWriteAccess @@ -92,21 +92,20 @@ PageType { descriptionText: qsTr("May be needed when changing other settings") clickedFunction: function() { - questionDrawer.headerText = qsTr("Clear cached profiles?") - questionDrawer.descriptionText = qsTr("") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Clear cached profiles?") + var descriptionText = qsTr("") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.showBusyIndicator(true) SettingsController.clearCachedProfiles() PageController.showBusyIndicator(false) } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } @@ -140,24 +139,23 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Do you want to reboot the server?") - questionDrawer.descriptionText = qsTr("The reboot process may take approximately 30 seconds. Are you sure you wish to proceed?") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Do you want to reboot the server?") + var descriptionText = qsTr("The reboot process may take approximately 30 seconds. Are you sure you wish to proceed?") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.showBusyIndicator(true) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() } - InstallController.rebootCurrentlyProcessedServer() + InstallController.rebootProcessedServer() PageController.showBusyIndicator(false) } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } @@ -172,24 +170,23 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Do you want to remove the server from application?") - questionDrawer.descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Do you want to remove the server from application?") + var descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.showBusyIndicator(true) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() } - InstallController.removeCurrentlyProcessedServer() + InstallController.removeProcessedServer() PageController.showBusyIndicator(false) } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } @@ -203,23 +200,22 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Do you want to clear server from Amnezia software?") - questionDrawer.descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Do you want to clear server from Amnezia software?") + var descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() } InstallController.removeAllContainers() } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } @@ -228,37 +224,32 @@ PageType { } LabelWithButtonType { - visible: ServersModel.isCurrentlyProcessedServerFromApi() + visible: ServersModel.getProcessedServerData("isServerFromApi") Layout.fillWidth: true text: qsTr("Reset API config") textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Do you want to reset API config?") - questionDrawer.descriptionText = "" - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Do you want to reset API config?") + var descriptionText = "" + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.showBusyIndicator(true) ApiController.clearApiConfig() PageController.showBusyIndicator(false) } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } DividerType { - visible: ServersModel.isCurrentlyProcessedServerFromApi() - } - - QuestionDrawer { - id: questionDrawer + visible: ServersModel.getProcessedServerData("isServerFromApi") } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index df740ff98..01db56950 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -63,7 +63,7 @@ PageType { headerText: name descriptionText: { - if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + if (ServersModel.isProcessedServerHasWriteAccess()) { return credentialsLogin + " · " + hostName } else { return hostName @@ -71,30 +71,33 @@ PageType { } actionButtonFunction: function() { - serverNameEditDrawer.visible = true + serverNameEditDrawer.open() } } - DrawerType { + DrawerType2 { id: serverNameEditDrawer - width: root.width - height: root.height * 0.35 + parent: root - onVisibleChanged: { - if (serverNameEditDrawer.visible) { - serverName.textField.forceActiveFocus() - } - } + anchors.fill: parent + expandedHeight: root.height * 0.35 - ColumnLayout { + expandedContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 16 + anchors.topMargin: 32 anchors.leftMargin: 16 anchors.rightMargin: 16 + Connections { + target: serverNameEditDrawer + function onOpened() { + serverName.textField.forceActiveFocus() + } + } + TextFieldWithHeaderType { id: serverName @@ -103,14 +106,18 @@ PageType { textFieldText: name textField.maximumLength: 30 checkEmptyText: true + + KeyNavigation.tab: saveButton } BasicButtonType { + id: saveButton + Layout.fillWidth: true text: qsTr("Save") - onClicked: { + clickedFunc: function() { if (serverName.textFieldText === "") { return } @@ -118,7 +125,13 @@ PageType { if (serverName.textFieldText !== name) { name = serverName.textFieldText } - serverNameEditDrawer.visible = false + serverNameEditDrawer.close() + } + } + + Component.onCompleted: { + if (header.itemAt(0)) { + defaultActiveFocusItem = serverName.textField } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index a961cf56a..b75e73429 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -107,26 +107,25 @@ PageType { width: parent.width - visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + visible: ServersModel.isProcessedServerHasWriteAccess() text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) - questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) + var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } MouseArea { @@ -138,9 +137,9 @@ PageType { DividerType {} } + } - QuestionDrawer { - id: questionDrawer - } + QuestionDrawer { + id: questionDrawer } } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml index 9fc2abde2..1dfd2886c 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -38,13 +38,13 @@ PageType { Connections { target: ServersModel - function onCurrentlyProcessedServerIndexChanged() { + function onProcessedServerIndexChanged() { settingsContainersListView.updateContainersModelFilters() } } function updateContainersModelFilters() { - if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + if (ServersModel.isProcessedServerHasWriteAccess()) { proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() } else { proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml index 4f8326511..8795bd23e 100644 --- a/client/ui/qml/Pages2/PageSettingsServerServices.qml +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -38,13 +38,13 @@ PageType { Connections { target: ServersModel - function onCurrentlyProcessedServerIndexChanged() { + function onProcessedServerIndexChanged() { settingsContainersListView.updateContainersModelFilters() } } function updateContainersModelFilters() { - if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + if (ServersModel.isProcessedServerHasWriteAccess()) { proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters() } else { proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters() @@ -55,6 +55,9 @@ PageType { model: SortFilterProxyModel { id: proxyContainersModel sourceModel: ContainersModel + sorters: [ + RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder } + ] } Component.onCompleted: updateContainersModelFilters() diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index dca904aee..eb07eaf4b 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -87,7 +87,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - ServersModel.currentlyProcessedIndex = index + ServersModel.processedIndex = index PageController.goToPage(PageEnum.PageSettingsServerInfo) } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 3aa5742ac..1ce3cd646 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -20,13 +20,23 @@ import "../Components" PageType { id: root + property var isServerFromApi: ServersModel.getDefaultServerData("isServerFromApi") + + defaultActiveFocusItem: website_ip_field.textField + property bool pageEnabled: { - return !ConnectionController.isConnected && !ServersModel.isDefaultServerFromApi() + return !ConnectionController.isConnected && !isServerFromApi } Component.onCompleted: { - if (ServersModel.isDefaultServerFromApi()) { + if (ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot change split tunneling settings during active connection")) + root.pageEnabled = false + } else if (ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && isServerFromApi) { PageController.showNotificationMessage(qsTr("Default server does not support split tunneling function")) + root.pageEnabled = false + } else { + root.pageEnabled = true } } @@ -104,7 +114,7 @@ PageType { Layout.fillWidth: true Layout.rightMargin: 16 - checked: SitesModel.isSplitTunnelingEnabled() + checked: SitesModel.isTunnelingEnabled onToggled: { SitesModel.toggleSplitTunneling(checked) selector.text = root.routeModesModel[getRouteModesModelIndex()].name @@ -121,6 +131,7 @@ PageType { Layout.rightMargin: 16 drawerHeight: 0.4375 + drawerParent: root enabled: root.pageEnabled @@ -135,7 +146,7 @@ PageType { clickedFunction: function() { selector.text = selectedText - selector.menuVisible = false + selector.close() if (SitesModel.routeMode !== root.routeModesModel[currentIndex].type) { SitesModel.routeMode = root.routeModesModel[currentIndex].type } @@ -202,26 +213,21 @@ PageType { rightImageColor: "#D7D8DB" clickedFunction: function() { - questionDrawer.headerText = qsTr("Remove ") + url + "?" - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + var headerText = qsTr("Remove ") + url + "?" + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + var yesButtonFunction = function() { SitesController.removeSite(index) } - questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + var noButtonFunction = function() { } - questionDrawer.visible = true + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } DividerType {} - - QuestionDrawer { - id: questionDrawer - } } } } @@ -249,6 +255,8 @@ PageType { anchors.bottomMargin: 24 TextFieldWithHeaderType { + id: website_ip_field + Layout.fillWidth: true textFieldPlaceholderText: qsTr("website or IP") @@ -275,151 +283,155 @@ PageType { } } - DrawerType { + DrawerType2 { id: moreActionsDrawer - width: parent.width - height: parent.height * 0.4375 + anchors.fill: parent + expandedHeight: parent.height * 0.4375 - FlickableType { - anchors.fill: parent - contentHeight: moreActionsDrawerContent.height - ColumnLayout { - id: moreActionsDrawerContent - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - Header2Type { - Layout.fillWidth: true - Layout.margins: 16 - - headerText: qsTr("Import / Export Sites") - } - - LabelWithButtonType { - Layout.fillWidth: true - - text: qsTr("Import") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - - clickedFunction: function() { - importSitesDrawer.open() - } - } - - DividerType {} - - LabelWithButtonType { - Layout.fillWidth: true - text: qsTr("Save site list") - - clickedFunction: function() { - var fileName = "" - if (GC.isMobile()) { - fileName = "amnezia_sites.json" - } else { - fileName = SystemController.getFileName(qsTr("Save sites"), - qsTr("Sites files (*.json)"), - StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/amnezia_sites", - true, - ".json") - } - if (fileName !== "") { - PageController.showBusyIndicator(true) - SitesController.exportSites(fileName) - moreActionsDrawer.close() - PageController.showBusyIndicator(false) - } - } - } - - DividerType {} - } - } - } - - DrawerType { - id: importSitesDrawer - - width: parent.width - height: parent.height * 0.4375 - - BackButtonType { - id: importSitesDrawerBackButton + expandedContent: ColumnLayout { + id: moreActionsDrawerContent anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 16 - backButtonFunction: function() { - importSitesDrawer.close() + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import / Export Sites") } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Import") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + importSitesDrawer.open() + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + text: qsTr("Save site list") + + clickedFunction: function() { + var fileName = "" + if (GC.isMobile()) { + fileName = "amnezia_sites.json" + } else { + fileName = SystemController.getFileName(qsTr("Save sites"), + qsTr("Sites files (*.json)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/amnezia_sites", + true, + ".json") + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + SitesController.exportSites(fileName) + moreActionsDrawer.close() + PageController.showBusyIndicator(false) + } + } + } + + DividerType {} } + } - FlickableType { - anchors.top: importSitesDrawerBackButton.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom + DrawerType2 { + id: importSitesDrawer - contentHeight: importSitesDrawerContent.height + anchors.fill: parent + expandedHeight: parent.height * 0.4375 - ColumnLayout { - id: importSitesDrawerContent + expandedContent: Item { + implicitHeight: importSitesDrawer.expandedHeight + + BackButtonType { + id: importSitesDrawerBackButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right + anchors.topMargin: 16 - Header2Type { - Layout.fillWidth: true - Layout.margins: 16 - - headerText: qsTr("Import a list of sites") - } - - LabelWithButtonType { - Layout.fillWidth: true - - text: qsTr("Replace site list") - - clickedFunction: function() { - var fileName = SystemController.getFileName(qsTr("Open sites file"), - qsTr("Sites files (*.json)")) - if (fileName !== "") { - importSitesDrawerContent.importSites(fileName, true) - } - } - } - - DividerType {} - - LabelWithButtonType { - Layout.fillWidth: true - text: qsTr("Add imported sites to existing ones") - - clickedFunction: function() { - var fileName = SystemController.getFileName(qsTr("Open sites file"), - qsTr("Sites files (*.json)")) - if (fileName !== "") { - importSitesDrawerContent.importSites(fileName, false) - } - } - } - - function importSites(fileName, replaceExistingSites) { - PageController.showBusyIndicator(true) - SitesController.importSites(fileName, replaceExistingSites) - PageController.showBusyIndicator(false) + backButtonFunction: function() { importSitesDrawer.close() - moreActionsDrawer.close() } + } - DividerType {} + FlickableType { + anchors.top: importSitesDrawerBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + contentHeight: importSitesDrawerContent.height + + ColumnLayout { + id: importSitesDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import a list of sites") + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Replace site list") + + clickedFunction: function() { + var fileName = SystemController.getFileName(qsTr("Open sites file"), + qsTr("Sites files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, true) + } + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + text: qsTr("Add imported sites to existing ones") + + clickedFunction: function() { + var fileName = SystemController.getFileName(qsTr("Open sites file"), + qsTr("Sites files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, false) + } + } + } + + function importSites(fileName, replaceExistingSites) { + PageController.showBusyIndicator(true) + SitesController.importSites(fileName, replaceExistingSites) + PageController.showBusyIndicator(false) + importSitesDrawer.close() + moreActionsDrawer.close() + } + + DividerType {} + } } } } + + QuestionDrawer { + id: questionDrawer + } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 60ff9baa9..c256f3a46 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -12,6 +12,8 @@ import "../Controls2/TextTypes" PageType { id: root + defaultActiveFocusItem: hostname.textField + BackButtonType { id: backButton @@ -57,6 +59,8 @@ PageType { onFocusChanged: { textField.text = textField.text.replace(/^\s+|\s+$/g, ''); } + + KeyNavigation.tab: username.textField } TextFieldWithHeaderType { @@ -65,6 +69,8 @@ PageType { Layout.fillWidth: true headerText: qsTr("Login to connect via SSH") textFieldPlaceholderText: "root" + + KeyNavigation.tab: secretData.textField } TextFieldWithHeaderType { @@ -85,15 +91,19 @@ PageType { onFocusChanged: { textField.text = textField.text.replace(/^\s+|\s+$/g, ''); } + + KeyNavigation.tab: continueButton } BasicButtonType { + id: continueButton + Layout.fillWidth: true Layout.topMargin: 24 text: qsTr("Continue") - onClicked: function() { + clickedFunc: function() { forceActiveFocus() if (!isCredentialsFilled()) { return diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 95951507d..69109a85f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -158,7 +158,7 @@ PageType { text: qsTr("Continue") - onClicked: function() { + clickedFunc: function() { if (root.isEasySetup) { ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) PageController.goToPage(PageEnum.PageSetupWizardInstalling) @@ -192,13 +192,12 @@ PageType { return ContainersModel.isAnyContainerInstalled() } - return true } text: qsTr("Set up later") - onClicked: function() { + clickedFunc: function() { PageController.goToPage(PageEnum.PageSetupWizardInstalling) InstallController.addEmptyServer() } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 1ad3d95d9..2a5cafd87 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -26,7 +26,7 @@ PageType { function onInstallContainerFinished(finishedMessage, isServiceInstall) { if (!ConnectionController.isConnected && !isServiceInstall) { - ServersModel.setDefaultContainer(ServersModel.currentlyProcessedIndex, ContainersModel.getCurrentlyProcessedContainerIndex()) + ServersModel.setDefaultContainer(ServersModel.processedIndex, ContainersModel.getCurrentlyProcessedContainerIndex()) } PageController.closePage() // close installing page @@ -42,7 +42,7 @@ PageType { function onInstallServerFinished(finishedMessage) { if (!ConnectionController.isConnected) { ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); - ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex + ServersModel.processedIndex = ServersModel.defaultIndex } PageController.goToStartPage() @@ -55,7 +55,7 @@ PageType { function onServerAlreadyExists(serverIndex) { PageController.goToStartPage() - ServersModel.currentlyProcessedIndex = serverIndex + ServersModel.processedIndex = serverIndex PageController.goToPage(PageEnum.PageSettingsServerInfo, false) PageController.showErrorMessage(qsTr("The server has already been added to the application")) @@ -165,7 +165,7 @@ PageType { text: qsTr("Cancel installation") - onClicked: { + clickedFunc: function() { InstallController.cancelInstallation() PageController.showBusyIndicator(true) } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 7698c7550..02e4ee6cd 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -52,6 +52,8 @@ PageType { implicitWidth: processedContainerListView.width implicitHeight: (delegateContent.implicitHeight > root.height) ? delegateContent.implicitHeight : root.height + property alias port:port + ColumnLayout { id: delegateContent @@ -92,81 +94,85 @@ PageType { text: qsTr("More detailed") - onClicked: { + clickedFunc: function() { showDetailsDrawer.open() } } - DrawerType { + DrawerType2 { id: showDetailsDrawer + parent: root - width: parent.width - height: parent.height * 0.9 + anchors.fill: parent + expandedHeight: parent.height * 0.9 + expandedContent: Item { + implicitHeight: showDetailsDrawer.expandedHeight - BackButtonType { - id: showDetailsBackButton - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 16 - - backButtonFunction: function() { - showDetailsDrawer.close() - } - } - - FlickableType { - anchors.top: showDetailsBackButton.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - contentHeight: { - var emptySpaceHeight = parent.height - showDetailsBackButton.implicitHeight - showDetailsBackButton.anchors.topMargin - return (showDetailsDrawerContent.height > emptySpaceHeight) ? - showDetailsDrawerContent.height : emptySpaceHeight - } - - ColumnLayout { - id: showDetailsDrawerContent + BackButtonType { + id: showDetailsBackButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 + anchors.topMargin: 16 - Header2Type { - id: showDetailsDrawerHeader - Layout.fillWidth: true - Layout.topMargin: 16 + backButtonFunction: function() { + showDetailsDrawer.close() + } + } - headerText: name + FlickableType { + anchors.top: showDetailsBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: { + var emptySpaceHeight = parent.height - showDetailsBackButton.implicitHeight - showDetailsBackButton.anchors.topMargin + return (showDetailsDrawerContent.height > emptySpaceHeight) ? + showDetailsDrawerContent.height : emptySpaceHeight } - ParagraphTextType { - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 + ColumnLayout { + id: showDetailsDrawerContent - text: detailedDescription - textFormat: Text.MarkdownText - } + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2Type { + id: showDetailsDrawerHeader + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: name + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + text: detailedDescription + textFormat: Text.MarkdownText + } - Rectangle { - Layout.fillHeight: true - color: "transparent" - } + Rectangle { + Layout.fillHeight: true + color: "transparent" + } - BasicButtonType { - Layout.fillWidth: true - Layout.bottomMargin: 32 + BasicButtonType { + Layout.fillWidth: true + Layout.bottomMargin: 32 - text: qsTr("Close") + text: qsTr("Close") - onClicked: function() { - showDetailsDrawer.close() + clickedFunc: function() { + showDetailsDrawer.close() + } } } } @@ -197,6 +203,8 @@ PageType { headerText: qsTr("Port") textField.maximumLength: 5 textField.validator: IntValidator { bottom: 1; top: 65535 } + + KeyNavigation.tab: installButton } Rectangle { @@ -212,7 +220,7 @@ PageType { text: qsTr("Install") - onClicked: function() { + clickedFunc: function() { PageController.goToPage(PageEnum.PageSetupWizardInstalling); InstallController.install(dockerContainer, port.textFieldText, transportProtoSelector.currentIndex) } @@ -232,6 +240,8 @@ PageType { var protocolSelectorVisible = ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) transportProtoSelector.visible = protocolSelectorVisible transportProtoHeader.visible = protocolSelectorVisible + + defaultActiveFocusItem = port.textField } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index a820ac715..161b85daa 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -115,8 +115,8 @@ PageType { text: qsTr("I have the data to connect") - onClicked: { - connectionTypeSelection.visible = true + clickedFunc: function() { + connectionTypeSelection.open() } } @@ -135,13 +135,15 @@ PageType { text: qsTr("I have nothing") - onClicked: Qt.openUrlExternally(qsTr("https://amnezia.org/instructions/0_starter-guide")) + clickedFunc: function() { + Qt.openUrlExternally(qsTr("https://amnezia.org/instructions/0_starter-guide")) + } } } + } - ConnectionTypeSelectionDrawer { - id: connectionTypeSelection - } + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection } BusyIndicatorType { diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 4cdfc4447..4a3220424 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -12,6 +12,8 @@ import "../Config" PageType { id: root + defaultActiveFocusItem: textKey.textField + FlickableType { id: fl anchors.top: parent.top @@ -56,11 +58,15 @@ PageType { textField.text = "" textField.paste() } + + KeyNavigation.tab: continueButton } } } BasicButtonType { + id: continueButton + anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right @@ -70,7 +76,7 @@ PageType { text: qsTr("Continue") - onClicked: function() { + clickedFunc: function() { ImportController.extractConfigFromCode(textKey.textFieldText) PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 79b51bdb5..c755d88d6 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -30,7 +30,7 @@ PageType { function onImportFinished() { if (!ConnectionController.isConnected) { ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); - ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex + ServersModel.processedIndex = ServersModel.defaultIndex } PageController.goToStartPage() @@ -109,7 +109,7 @@ PageType { text: showContent ? qsTr("Collapse content") : qsTr("Show content") - onClicked: { + clickedFunc: function() { showContent = !showContent } } @@ -151,7 +151,7 @@ PageType { Layout.bottomMargin: 32 text: qsTr("Connect") - onClicked: { + clickedFunc: function() { ImportController.importConfig() } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index c7777feab..cd012e273 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -16,6 +16,8 @@ import "../Components" PageType { id: root + defaultActiveFocusItem: clientNameTextField.textField + enum ConfigType { AmneziaConnection, OpenVpn, @@ -29,7 +31,7 @@ PageType { PageController.showBusyIndicator(true) ExportController.revokeConfig(index, ContainersModel.getCurrentlyProcessedContainerIndex(), - ServersModel.getCurrentlyProcessedServerCredentials()) + ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) PageController.showNotificationMessage(qsTr("Config revoked")) } @@ -41,8 +43,6 @@ PageType { shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - shareConnectionDrawer.needCloseButton = false - shareConnectionDrawer.open() shareConnectionDrawer.contentVisible = false PageController.showBusyIndicator(true) @@ -80,11 +80,6 @@ PageType { } PageController.showBusyIndicator(false) - - shareConnectionDrawer.needCloseButton = true - PageController.showTopCloseButton(true) - - shareConnectionDrawer.contentVisible = true } function onExportErrorOccurred(errorMessage) { @@ -129,7 +124,7 @@ PageType { FlickableType { anchors.top: parent.top anchors.bottom: parent.bottom - contentHeight: content.height + contentHeight: content.height + 10 ColumnLayout { id: content @@ -154,14 +149,15 @@ PageType { shareFullAccessDrawer.open() } - DrawerType { + DrawerType2 { id: shareFullAccessDrawer - width: root.width - height: root.height * 0.45 + parent: root + anchors.fill: parent + expandedHeight: root.height * 0.45 - ColumnLayout { + expandedContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -234,7 +230,7 @@ PageType { accessTypeSelector.currentIndex = 1 PageController.showBusyIndicator(true) ExportController.updateClientManagementModel(ContainersModel.getCurrentlyProcessedContainerIndex(), - ServersModel.getCurrentlyProcessedServerCredentials()) + ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) } } @@ -264,6 +260,8 @@ PageType { textField.maximumLength: 20 checkEmptyText: true + + KeyNavigation.tab: shareButton } DropDownType { @@ -276,6 +274,7 @@ PageType { Layout.topMargin: 16 drawerHeight: 0.4375 + drawerParent: root descriptionText: qsTr("Server") headerText: qsTr("Server") @@ -305,7 +304,7 @@ PageType { serverSelector.severSelectorIndexChanged() } - serverSelector.menuVisible = false + serverSelector.close() } Component.onCompleted: { @@ -316,7 +315,7 @@ PageType { function handler() { serverSelector.text = selectedText - ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex) + ServersModel.processedIndex = proxyServersModel.mapToSource(currentIndex) } } } @@ -328,6 +327,7 @@ PageType { Layout.topMargin: 16 drawerHeight: 0.5 + drawerParent: root descriptionText: qsTr("Protocol") headerText: qsTr("Protocol") @@ -358,14 +358,14 @@ PageType { clickedFunction: function() { handler() - protocolSelector.menuVisible = false + protocolSelector.close() } Connections { target: serverSelector function onSeverSelectorIndexChanged() { - var defaultContainer = proxyContainersModel.mapFromSource(ServersModel.getDefaultContainer(ServersModel.currentlyProcessedIndex)) + var defaultContainer = proxyContainersModel.mapFromSource(ServersModel.getProcessedServerData("defaultContainer")) protocolSelectorListView.currentIndex = defaultContainer protocolSelectorListView.triggerCurrentItem() } @@ -388,7 +388,7 @@ PageType { if (accessTypeSelector.currentIndex === 1) { PageController.showBusyIndicator(true) ExportController.updateClientManagementModel(ContainersModel.getCurrentlyProcessedContainerIndex(), - ServersModel.getCurrentlyProcessedServerCredentials()) + ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) } } @@ -423,6 +423,7 @@ PageType { Layout.topMargin: 16 drawerHeight: 0.4375 + drawerParent: root visible: accessTypeSelector.currentIndex === 0 enabled: root.connectionTypesModel.length > 1 @@ -446,7 +447,7 @@ PageType { clickedFunction: function() { exportTypeSelector.text = selectedText exportTypeSelector.currentIndex = currentIndex - exportTypeSelector.menuVisible = false + exportTypeSelector.close() } Component.onCompleted: { @@ -456,11 +457,9 @@ PageType { } } - ShareConnectionDrawer { - id: shareConnectionDrawer - } - BasicButtonType { + id: shareButton + Layout.fillWidth: true Layout.topMargin: 40 @@ -470,7 +469,7 @@ PageType { text: qsTr("Share") imageSource: "qrc:/images/controls/share-2.svg" - onClicked: { + clickedFunc: function(){ if (clientNameTextField.textFieldText !== "") { ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type) } @@ -561,13 +560,15 @@ PageType { DividerType {} - DrawerType { + DrawerType2 { id: clientInfoDrawer - width: root.width - height: root.height * 0.5 + parent: root - ColumnLayout { + anchors.fill: parent + expandedHeight: root.height * 0.5 + + expandedContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -598,30 +599,33 @@ PageType { text: qsTr("Rename") - onClicked: function() { + clickedFunc: function() { clientNameEditDrawer.open() } - DrawerType { + DrawerType2 { id: clientNameEditDrawer - width: root.width - height: root.height * 0.35 + parent: root - onVisibleChanged: { - if (clientNameEditDrawer.visible) { - clientNameEditor.textField.forceActiveFocus() - } - } + anchors.fill: parent + expandedHeight: root.height * 0.35 - ColumnLayout { + expandedContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 16 + anchors.topMargin: 32 anchors.leftMargin: 16 anchors.rightMargin: 16 + Connections { + target: clientNameEditDrawer + function onOpened() { + clientNameEditor.textField.forceActiveFocus() + } + } + TextFieldWithHeaderType { id: clientNameEditor Layout.fillWidth: true @@ -629,14 +633,18 @@ PageType { textFieldText: clientName textField.maximumLength: 20 checkEmptyText: true + + KeyNavigation.tab: saveButton } BasicButtonType { + id: saveButton + Layout.fillWidth: true text: qsTr("Save") - onClicked: { + clickedFunc: function() { if (clientNameEditor.textFieldText === "") { return } @@ -646,7 +654,7 @@ PageType { ExportController.renameClient(index, clientNameEditor.textFieldText, ContainersModel.getCurrentlyProcessedContainerIndex(), - ServersModel.getCurrentlyProcessedServerCredentials()) + ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) clientNameEditDrawer.close() } @@ -668,21 +676,20 @@ PageType { text: qsTr("Revoke") - onClicked: function() { - questionDrawer.headerText = qsTr("Revoke the config for a user - %1?").arg(clientName) - questionDrawer.descriptionText = qsTr("The user will no longer be able to connect to your server.") - questionDrawer.yesButtonText = qsTr("Continue") - questionDrawer.noButtonText = qsTr("Cancel") + clickedFunc: function() { + var headerText = qsTr("Revoke the config for a user - %1?").arg(clientName) + var descriptionText = qsTr("The user will no longer be able to connect to your server.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + var yesButtonFunction = function() { clientInfoDrawer.close() root.revokeConfig(index) } - questionDrawer.noButtonFunction = function() { - questionDrawer.close() + var noButtonFunction = function() { } - questionDrawer.open() + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } } @@ -690,12 +697,15 @@ PageType { } } } - - QuestionDrawer { - id: questionDrawer - } } } + + ShareConnectionDrawer { + id: shareConnectionDrawer + + anchors.fill: parent + } + MouseArea { anchors.fill: parent onPressed: function(mouse) { diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index df0e2a008..8acb42c2b 100644 --- a/client/ui/qml/Pages2/PageShareFullAccess.qml +++ b/client/ui/qml/Pages2/PageShareFullAccess.qml @@ -69,6 +69,7 @@ PageType { Layout.topMargin: 16 drawerHeight: 0.4375 + drawerParent: root descriptionText: qsTr("Server") headerText: qsTr("Server") @@ -99,7 +100,7 @@ PageType { shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text - serverSelector.menuVisible = false + serverSelector.close() } Component.onCompleted: { @@ -110,7 +111,7 @@ PageType { function handler() { serverSelector.text = selectedText - ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex) + ServersModel.processedIndex = proxyServersModel.mapToSource(currentIndex) } } } @@ -122,12 +123,10 @@ PageType { text: qsTr("Share") imageSource: "qrc:/images/controls/share-2.svg" - onClicked: function() { + clickedFunc: function() { shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - shareConnectionDrawer.needCloseButton = false - shareConnectionDrawer.open() shareConnectionDrawer.contentVisible = false PageController.showBusyIndicator(true) @@ -140,16 +139,15 @@ PageType { PageController.showBusyIndicator(false) - shareConnectionDrawer.needCloseButton = true - PageController.showTopCloseButton(true) - shareConnectionDrawer.contentVisible = true } } - - ShareConnectionDrawer { - id: shareConnectionDrawer - } } } + + ShareConnectionDrawer { + id: shareConnectionDrawer + + anchors.fill: parent + } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 3ade9af8b..dac9db937 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -20,22 +20,16 @@ PageType { function onGoToPageHome() { tabBar.setCurrentIndex(0) tabBarStackView.goToTabBarPage(PageEnum.PageHome) - - PageController.updateDrawerRootPage(PageEnum.PageHome) } function onGoToPageSettings() { tabBar.setCurrentIndex(2) tabBarStackView.goToTabBarPage(PageEnum.PageSettings) - - PageController.updateDrawerRootPage(PageEnum.PageSettings) } function onGoToPageViewConfig() { var pagePath = PageController.getPagePath(PageEnum.PageSetupWizardViewConfig) tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) - - PageController.updateDrawerRootPage(PageEnum.PageSetupWizardViewConfig) } function onShowBusyIndicator(visible) { @@ -44,15 +38,14 @@ PageType { tabBar.enabled = !visible } -// function onShowTopCloseButton(visible) { -// topCloseButton.visible = visible -// } - function onEnableTabBar(enabled) { tabBar.enabled = enabled } function onClosePage() { + tabBar.isServerInfoShow = tabBarStackView.currentItem.objectName !== PageController.getPagePath(PageEnum.PageSettingsServerInfo) + && tabBarStackView.currentItem.objectName !== PageController.getPagePath(PageEnum.PageSettingsSplitTunneling) + if (tabBarStackView.depth <= 1) { return } @@ -61,13 +54,14 @@ PageType { function onGoToPage(page, slide) { var pagePath = PageController.getPagePath(page) + if (slide) { tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) } else { tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) } - - PageController.updateDrawerRootPage(page) + + tabBar.isServerInfoShow = page === PageEnum.PageSettingsServerInfo || PageEnum.PageSettingsSplitTunneling || tabBar.isServerInfoShow } function onGoToStartPage() { @@ -115,20 +109,12 @@ PageType { function onNoInstalledContainers() { PageController.setTriggeredBtConnectButton(true) - ServersModel.currentlyProcessedIndex = ServersModel.getDefaultServerIndex() + ServersModel.processedIndex = ServersModel.getDefaultServerIndex() InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardEasy) } } - Connections { - target: ApiController - - function onErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - } - StackViewType { id: tabBarStackView @@ -146,26 +132,21 @@ PageType { var pagePath = PageController.getPagePath(page) tabBarStackView.clear(StackView.Immediate) tabBarStackView.replace(pagePath, { "objectName" : pagePath }, StackView.Immediate) - - PageController.updateDrawerRootPage(page) + tabBar.isServerInfoShow = false } Component.onCompleted: { var pagePath = PageController.getPagePath(PageEnum.PageHome) - ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex + ServersModel.processedIndex = ServersModel.defaultIndex tabBarStackView.push(pagePath, { "objectName" : pagePath }) } - -// onWidthChanged: { -// topCloseButton.x = tabBarStackView.x + tabBarStackView.width - -// topCloseButton.buttonWidth - topCloseButton.rightPadding -// } } TabBar { id: tabBar property int previousIndex: 0 + property bool isServerInfoShow: false anchors.right: parent.right anchors.left: parent.left @@ -196,11 +177,11 @@ PageType { } TabImageButtonType { - isSelected: tabBar.currentIndex === 0 + isSelected: tabBar.isServerInfoShow ? false : tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" onClicked: { tabBarStackView.goToTabBarPage(PageEnum.PageHome) - ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex + ServersModel.processedIndex = ServersModel.defaultIndex tabBar.previousIndex = 0 } } @@ -230,7 +211,7 @@ PageType { } TabImageButtonType { - isSelected: tabBar.currentIndex === 2 + isSelected: tabBar.isServerInfoShow ? true : tabBar.currentIndex === 2 image: "qrc:/images/controls/settings-2.svg" onClicked: { tabBarStackView.goToTabBarPage(PageEnum.PageSettings) @@ -253,13 +234,6 @@ PageType { z: 1 } -// TopCloseButtonType { -// id: topCloseButton - -// x: tabBarStackView.width - topCloseButton.buttonWidth - topCloseButton.rightPadding -// z: 1 -// } - ConnectionTypeSelectionDrawer { id: connectionTypeSelection diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 14a6c926d..47608709f 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -8,6 +8,7 @@ import PageEnum 1.0 import "Config" import "Controls2" +import "Components" Window { id: root @@ -134,32 +135,15 @@ Window { } Item { - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom + anchors.fill: parent - implicitHeight: popupErrorMessage.height - - DrawerType { + DrawerType2 { id: privateKeyPassphraseDrawer - width: root.width - height: root.height * 0.35 + anchors.fill: parent + expandedHeight: root.height * 0.35 - onVisibleChanged: { - if (privateKeyPassphraseDrawer.visible) { - passphrase.textFieldText = "" - passphrase.textField.forceActiveFocus() - } - } - onAboutToHide: { - PageController.showBusyIndicator(true) - } - onAboutToShow: { - PageController.showBusyIndicator(false) - } - - ColumnLayout { + expandedContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -167,6 +151,24 @@ Window { anchors.leftMargin: 16 anchors.rightMargin: 16 + Connections { + target: privateKeyPassphraseDrawer + function onOpened() { + passphrase.textFieldText = "" + passphrase.textField.forceActiveFocus() + } + + function onAboutToHide() { + if (passphrase.textFieldText !== "") { + PageController.showBusyIndicator(true) + } + } + + function onAboutToShow() { + PageController.showBusyIndicator(false) + } + } + TextFieldWithHeaderType { id: passphrase @@ -180,9 +182,13 @@ Window { clickedFunc: function() { hidePassword = !hidePassword } + + KeyNavigation.tab: saveButton } BasicButtonType { + id: saveButton + Layout.fillWidth: true defaultColor: "transparent" @@ -194,7 +200,7 @@ Window { text: qsTr("Save") - onClicked: { + clickedFunc: function() { privateKeyPassphraseDrawer.close() PageController.passphraseRequestDrawerClosed(passphrase.textFieldText) } @@ -203,6 +209,37 @@ Window { } } + Item { + anchors.fill: parent + + QuestionDrawer { + id: questionDrawer + + anchors.fill: parent + } + } + + function showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) { + questionDrawer.headerText = headerText + questionDrawer.descriptionText = descriptionText + questionDrawer.yesButtonText = yesButtonText + questionDrawer.noButtonText = noButtonText + + questionDrawer.yesButtonFunction = function() { + questionDrawer.close() + if (yesButtonFunction && typeof yesButtonFunction === "function") { + yesButtonFunction() + } + } + questionDrawer.noButtonFunction = function() { + questionDrawer.close() + if (noButtonFunction && typeof noButtonFunction === "function") { + noButtonFunction() + } + } + questionDrawer.open() + } + FileDialog { id: mainFileDialog diff --git a/deploy/build_macos.sh b/deploy/build_macos.sh index 0d64a9ec8..ffdbd20bf 100755 --- a/deploy/build_macos.sh +++ b/deploy/build_macos.sh @@ -146,7 +146,8 @@ if [ "${MAC_CERT_PW+x}" ]; then fi echo "Building DMG installer..." -hdiutil create -size 120mb -volname AmneziaVPN -srcfolder $BUILD_DIR/installer/$APP_NAME.app -ov -format UDZO $DMG_FILENAME +# Allow Terminal to make changes in Privacy & Security > App Management +hdiutil create -size 256mb -volname AmneziaVPN -srcfolder $BUILD_DIR/installer/$APP_NAME.app -ov -format UDZO $DMG_FILENAME if [ "${MAC_CERT_PW+x}" ]; then echo "Signing DMG installer..."