From 728b48044c30f855375378bc485f506ac9f59d6b Mon Sep 17 00:00:00 2001 From: Nethius Date: Fri, 28 Feb 2025 18:17:43 +0300 Subject: [PATCH 1/2] Merge pull request #1440 from amnezia-vpn/feature/subscription-settings-page feature/subscription settings page --- CMakeLists.txt | 4 +- client/core/controllers/coreController.cpp | 6 +- client/core/controllers/coreController.h | 4 +- client/core/controllers/gatewayController.cpp | 2 +- client/images/controls/monitor.svg | 5 + client/resources.qrc | 2 + .../controllers/api/apiConfigsController.cpp | 40 ++++- .../ui/controllers/api/apiConfigsController.h | 1 + .../controllers/api/apiSettingsController.cpp | 8 + .../controllers/api/apiSettingsController.h | 7 +- client/ui/controllers/pageController.h | 1 + client/ui/models/api/apiAccountInfoModel.cpp | 14 ++ client/ui/models/api/apiAccountInfoModel.h | 3 +- client/ui/models/api/apiCountryModel.cpp | 4 + client/ui/models/api/apiCountryModel.h | 3 +- client/ui/models/api/apiDevicesModel.cpp | 90 +++++++++++ client/ui/models/api/apiDevicesModel.h | 52 +++++++ client/ui/qml/Modules/Style/AmneziaStyle.qml | 1 + .../ui/qml/Pages2/PageSettingsApiDevices.qml | 100 ++++++++++++ .../Pages2/PageSettingsApiInstructions.qml | 1 - .../Pages2/PageSettingsApiNativeConfigs.qml | 3 + .../qml/Pages2/PageSettingsApiServerInfo.qml | 50 +++++- .../ui/qml/Pages2/PageSettingsApiSupport.qml | 146 ++++++++++-------- 23 files changed, 466 insertions(+), 81 deletions(-) create mode 100644 client/images/controls/monitor.svg create mode 100644 client/ui/models/api/apiDevicesModel.cpp create mode 100644 client/ui/models/api/apiDevicesModel.h create mode 100644 client/ui/qml/Pages2/PageSettingsApiDevices.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 3989f3b1..7c602249 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.4.1 +project(${PROJECT} VERSION 4.8.4.2 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -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 2078) +set(APP_ANDROID_VERSION_CODE 2079) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/client/core/controllers/coreController.cpp b/client/core/controllers/coreController.cpp index 8d63974c..82232c99 100644 --- a/client/core/controllers/coreController.cpp +++ b/client/core/controllers/coreController.cpp @@ -93,6 +93,9 @@ void CoreController::initModels() m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this)); m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get()); + + m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get()); } void CoreController::initControllers() @@ -132,7 +135,8 @@ void CoreController::initControllers() m_systemController.reset(new SystemController(m_settings)); m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); - m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_settings)); + m_apiSettingsController.reset( + new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings)); m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get()); m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings)); diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h index 029044c4..700504af 100644 --- a/client/core/controllers/coreController.h +++ b/client/core/controllers/coreController.h @@ -25,8 +25,9 @@ #include "ui/models/protocols/ikev2ConfigModel.h" #endif #include "ui/models/api/apiAccountInfoModel.h" -#include "ui/models/api/apiServicesModel.h" #include "ui/models/api/apiCountryModel.h" +#include "ui/models/api/apiDevicesModel.h" +#include "ui/models/api/apiServicesModel.h" #include "ui/models/appSplitTunnelingModel.h" #include "ui/models/clientManagementModel.h" #include "ui/models/protocols/awgConfigModel.h" @@ -117,6 +118,7 @@ private: QSharedPointer m_apiServicesModel; QSharedPointer m_apiCountryModel; QSharedPointer m_apiAccountInfoModel; + QSharedPointer m_apiDevicesModel; QScopedPointer m_openVpnConfigModel; QScopedPointer m_shadowSocksConfigModel; diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index 61c3d594..15776328 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -148,7 +148,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api QByteArray encryptedResponseBody = reply->readAll(); - if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, false)) { + if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) { auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) { request.setUrl(url); return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson()); diff --git a/client/images/controls/monitor.svg b/client/images/controls/monitor.svg new file mode 100644 index 00000000..1cdf57c2 --- /dev/null +++ b/client/images/controls/monitor.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index cd44ca60..16071da0 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -229,6 +229,8 @@ ui/qml/Pages2/PageSettingsApiSupport.qml ui/qml/Pages2/PageSettingsApiInstructions.qml ui/qml/Pages2/PageSettingsApiNativeConfigs.qml + ui/qml/Pages2/PageSettingsApiDevices.qml + images/controls/monitor.svg images/flagKit/ZW.svg diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 27813f9c..d3c8747d 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -1,7 +1,7 @@ #include "apiConfigsController.h" -#include #include +#include #include "amnezia_application.h" #include "configurators/wireguard_configurator.h" @@ -251,6 +251,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const newServerConfig.insert(configKey::apiConfig, newApiConfig); newServerConfig.insert(configKey::authData, authData); + // newServerConfig.insert( m_serversModel->editServer(newServerConfig, serverIndex); if (reloadServiceConfig) { @@ -354,6 +355,43 @@ bool ApiConfigsController::deactivateDevice() return true; } +bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode) +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + + auto serverIndex = m_serversModel->getProcessedServerIndex(); + auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); + auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + + if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) { + return true; + } + + QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); + ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + + QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); + apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); + apiPayload[configKey::serverCountryCode] = serverCountryCode; + apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); + apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); + apiPayload[configKey::uuid] = uuid; + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { + emit errorOccurred(errorCode); + return false; + } + + if (uuid == m_settings->getInstallationUuid(true)) { + serverConfigObject.remove(config_key::containers); + m_serversModel->editServer(serverConfigObject, serverIndex); + } + + return true; +} + bool ApiConfigsController::isConfigValid() { int serverIndex = m_serversModel->getDefaultServerIndex(); diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h index 26b02978..2fe981e4 100644 --- a/client/ui/controllers/api/apiConfigsController.h +++ b/client/ui/controllers/api/apiConfigsController.h @@ -31,6 +31,7 @@ public slots: bool reloadServiceConfig = false); bool updateServiceFromTelegram(const int serverIndex); bool deactivateDevice(); + bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode); bool isConfigValid(); diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp index 53b6e5a8..737bfd1a 100644 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -25,11 +25,13 @@ namespace ApiSettingsController::ApiSettingsController(const QSharedPointer &serversModel, const QSharedPointer &apiAccountInfoModel, const QSharedPointer &apiCountryModel, + const QSharedPointer &apiDevicesModel, const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_apiAccountInfoModel(apiAccountInfoModel), m_apiCountryModel(apiCountryModel), + m_apiDevicesModel(apiDevicesModel), m_settings(settings) { } @@ -73,6 +75,7 @@ bool ApiSettingsController::getAccountInfo(bool reload) if (reload) { updateApiCountryModel(); + updateApiDevicesModel(); } return true; @@ -83,3 +86,8 @@ void ApiSettingsController::updateApiCountryModel() m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), ""); m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo()); } + +void ApiSettingsController::updateApiDevicesModel() +{ + m_apiDevicesModel->updateModel(m_apiAccountInfoModel->getIssuedConfigsInfo()); +} diff --git a/client/ui/controllers/api/apiSettingsController.h b/client/ui/controllers/api/apiSettingsController.h index e12e232a..afe9a570 100644 --- a/client/ui/controllers/api/apiSettingsController.h +++ b/client/ui/controllers/api/apiSettingsController.h @@ -5,6 +5,7 @@ #include "ui/models/api/apiAccountInfoModel.h" #include "ui/models/api/apiCountryModel.h" +#include "ui/models/api/apiDevicesModel.h" #include "ui/models/servers_model.h" class ApiSettingsController : public QObject @@ -12,13 +13,14 @@ class ApiSettingsController : public QObject Q_OBJECT public: ApiSettingsController(const QSharedPointer &serversModel, const QSharedPointer &apiAccountInfoModel, - const QSharedPointer &apiCountryModel, const std::shared_ptr &settings, - QObject *parent = nullptr); + const QSharedPointer &apiCountryModel, const QSharedPointer &apiDevicesModel, + const std::shared_ptr &settings, QObject *parent = nullptr); ~ApiSettingsController(); public slots: bool getAccountInfo(bool reload); void updateApiCountryModel(); + void updateApiDevicesModel(); signals: void errorOccurred(ErrorCode errorCode); @@ -27,6 +29,7 @@ private: QSharedPointer m_serversModel; QSharedPointer m_apiAccountInfoModel; QSharedPointer m_apiCountryModel; + QSharedPointer m_apiDevicesModel; std::shared_ptr m_settings; }; diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 9ec93cc0..60621414 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -36,6 +36,7 @@ namespace PageLoader PageSettingsApiSupport, PageSettingsApiInstructions, PageSettingsApiNativeConfigs, + PageSettingsApiDevices, PageServiceSftpSettings, PageServiceTorWebsiteSettings, diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index 055c2347..cdb9dfe8 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -58,6 +58,19 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const case IsComponentVisibleRole: { return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2; } + case HasExpiredWorkerRole: { + for (int i = 0; i < m_issuedConfigsInfo.size(); i++) { + QJsonObject issuedConfigObject = m_issuedConfigsInfo.at(i).toObject(); + + auto lastDownloaded = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::lastDownloaded).toString()); + auto workerLastUpdated = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString()); + + if (lastDownloaded < workerLastUpdated) { + return true; + } + } + return false; + } } return QVariant(); @@ -124,6 +137,7 @@ QHash ApiAccountInfoModel::roleNames() const roles[ConnectedDevicesRole] = "connectedDevices"; roles[ServiceDescriptionRole] = "serviceDescription"; roles[IsComponentVisibleRole] = "isComponentVisible"; + roles[HasExpiredWorkerRole] = "hasExpiredWorker"; return roles; } diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h index e50ea5f5..44eb7ee6 100644 --- a/client/ui/models/api/apiAccountInfoModel.h +++ b/client/ui/models/api/apiAccountInfoModel.h @@ -17,7 +17,8 @@ public: ConnectedDevicesRole, ServiceDescriptionRole, EndDateRole, - IsComponentVisibleRole + IsComponentVisibleRole, + HasExpiredWorkerRole }; explicit ApiAccountInfoModel(QObject *parent = nullptr); diff --git a/client/ui/models/api/apiCountryModel.cpp b/client/ui/models/api/apiCountryModel.cpp index 4ded6fed..12f4658e 100644 --- a/client/ui/models/api/apiCountryModel.cpp +++ b/client/ui/models/api/apiCountryModel.cpp @@ -44,6 +44,9 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const case IsIssuedRole: { return isIssued; } + case IsWorkerExpiredRole: { + return issuedConfigInfo.lastDownloaded < issuedConfigInfo.workerLastUpdated; + } } return QVariant(); @@ -114,5 +117,6 @@ QHash ApiCountryModel::roleNames() const roles[CountryCodeRole] = "countryCode"; roles[CountryImageCodeRole] = "countryImageCode"; roles[IsIssuedRole] = "isIssued"; + roles[IsWorkerExpiredRole] = "isWorkerExpired"; return roles; } diff --git a/client/ui/models/api/apiCountryModel.h b/client/ui/models/api/apiCountryModel.h index e57ec0dd..08ac3685 100644 --- a/client/ui/models/api/apiCountryModel.h +++ b/client/ui/models/api/apiCountryModel.h @@ -14,7 +14,8 @@ public: CountryNameRole = Qt::UserRole + 1, CountryCodeRole, CountryImageCodeRole, - IsIssuedRole + IsIssuedRole, + IsWorkerExpiredRole }; explicit ApiCountryModel(QObject *parent = nullptr); diff --git a/client/ui/models/api/apiDevicesModel.cpp b/client/ui/models/api/apiDevicesModel.cpp new file mode 100644 index 00000000..6c0d60d0 --- /dev/null +++ b/client/ui/models/api/apiDevicesModel.cpp @@ -0,0 +1,90 @@ +#include "apiDevicesModel.h" + +#include + +#include "core/api/apiDefs.h" +#include "logger.h" + +namespace +{ + Logger logger("ApiDevicesModel"); + + constexpr QLatin1String gatewayAccount("gateway_account"); +} + +ApiDevicesModel::ApiDevicesModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) +{ +} + +int ApiDevicesModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_issuedConfigs.size(); +} + +QVariant ApiDevicesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) + return QVariant(); + + IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.at(index.row()); + + switch (role) { + case OsVersionRole: { + return issuedConfigInfo.osVersion; + } + case SupportTagRole: { + return issuedConfigInfo.installationUuid; + } + case CountryCodeRole: { + return issuedConfigInfo.countryCode; + } + case LastUpdateRole: { + return QDateTime::fromString(issuedConfigInfo.lastDownloaded, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); + } + case IsCurrentDeviceRole: { + return issuedConfigInfo.installationUuid == m_settings->getInstallationUuid(false); + } + } + + return QVariant(); +} + +void ApiDevicesModel::updateModel(const QJsonArray &issuedConfigs) +{ + beginResetModel(); + + m_issuedConfigs.clear(); + for (int i = 0; i < issuedConfigs.size(); i++) { + IssuedConfigInfo issuedConfigInfo; + QJsonObject issuedConfigObject = issuedConfigs.at(i).toObject(); + + if (issuedConfigObject.value(apiDefs::key::sourceType).toString() != gatewayAccount) { + continue; + } + + issuedConfigInfo.installationUuid = issuedConfigObject.value(apiDefs::key::installationUuid).toString(); + issuedConfigInfo.workerLastUpdated = issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString(); + issuedConfigInfo.lastDownloaded = issuedConfigObject.value(apiDefs::key::lastDownloaded).toString(); + issuedConfigInfo.sourceType = issuedConfigObject.value(apiDefs::key::sourceType).toString(); + issuedConfigInfo.osVersion = issuedConfigObject.value(apiDefs::key::osVersion).toString(); + + issuedConfigInfo.countryName = issuedConfigObject.value(apiDefs::key::serverCountryName).toString(); + issuedConfigInfo.countryCode = issuedConfigObject.value(apiDefs::key::serverCountryCode).toString(); + + m_issuedConfigs.push_back(issuedConfigInfo); + } + + endResetModel(); +} + +QHash ApiDevicesModel::roleNames() const +{ + QHash roles; + roles[OsVersionRole] = "osVersion"; + roles[SupportTagRole] = "supportTag"; + roles[CountryCodeRole] = "countryCode"; + roles[LastUpdateRole] = "lastUpdate"; + roles[IsCurrentDeviceRole] = "isCurrentDevice"; + return roles; +} diff --git a/client/ui/models/api/apiDevicesModel.h b/client/ui/models/api/apiDevicesModel.h new file mode 100644 index 00000000..e6a59dba --- /dev/null +++ b/client/ui/models/api/apiDevicesModel.h @@ -0,0 +1,52 @@ +#ifndef APIDEVICESMODEL_H +#define APIDEVICESMODEL_H + +#include +#include +#include + +#include "settings.h" + +class ApiDevicesModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + OsVersionRole = Qt::UserRole + 1, + SupportTagRole, + CountryCodeRole, + LastUpdateRole, + IsCurrentDeviceRole + }; + + explicit ApiDevicesModel(std::shared_ptr settings, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonArray &issuedConfigs); + +protected: + QHash roleNames() const override; + +private: + struct IssuedConfigInfo + { + QString installationUuid; + QString workerLastUpdated; + QString lastDownloaded; + QString sourceType; + QString osVersion; + + QString countryName; + QString countryCode; + }; + + QVector m_issuedConfigs; + + std::shared_ptr m_settings; +}; +#endif // APIDEVICESMODEL_H diff --git a/client/ui/qml/Modules/Style/AmneziaStyle.qml b/client/ui/qml/Modules/Style/AmneziaStyle.qml index f54fefce..4e2e80f0 100644 --- a/client/ui/qml/Modules/Style/AmneziaStyle.qml +++ b/client/ui/qml/Modules/Style/AmneziaStyle.qml @@ -27,5 +27,6 @@ QtObject { readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8) readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65) readonly property color pearlGray: '#EAEAEC' + readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiDevices.qml b/client/ui/qml/Pages2/PageSettingsApiDevices.qml new file mode 100644 index 00000000..5cc21d07 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApiDevices.qml @@ -0,0 +1,100 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ListViewType { + id: listView + + anchors.fill: parent + anchors.topMargin: 20 + anchors.bottomMargin: 24 + + model: ApiDevicesModel + + header: ColumnLayout { + width: listView.width + + BackButtonType { + id: backButton + } + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Connected devices") + descriptionText: qsTr("To manage connected devices") + } + + WarningType { + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.fillWidth: true + + textString: qsTr("You can find the identifier on the Support tab or, for older versions of the app, " + + "by tapping '+' and then the three dots at the top of the page.") + + iconPath: "qrc:/images/controls/alert-circle.svg" + } + } + + delegate: ColumnLayout { + width: listView.width + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 6 + + text: osVersion + (isCurrentDevice ? qsTr(" (current device)") : "") + descriptionText: qsTr("Support tag: ") + "\n" + supportTag + "\n" + qsTr("Last updated: ") + lastUpdate + rightImageSource: "qrc:/images/controls/trash.svg" + + clickedFunction: function() { + var headerText = qsTr("Deactivate the subscription on selected device") + var descriptionText = qsTr("The next time the “Connect” button is pressed, the device will be activated again") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + Qt.callLater(deactivateExternalDevice, supportTag, countryCode) + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + DividerType {} + } + } + + function deactivateExternalDevice(supportTag, countryCode) { + PageController.showBusyIndicator(true) + if (ApiConfigsController.deactivateExternalDevice(supportTag, countryCode)) { + ApiSettingsController.getAccountInfo(true) + } + PageController.showBusyIndicator(false) + } +} diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml index 70a0df74..3651407b 100644 --- a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -107,7 +107,6 @@ PageType { width: listView.width LabelWithButtonType { - id: telegramButton Layout.fillWidth: true Layout.topMargin: 6 diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml index 7fa2b8f4..a1cc1fb8 100644 --- a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -58,6 +58,9 @@ PageType { Layout.topMargin: 6 text: countryName + descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : "" + descriptionColor: AmneziaStyle.color.vibrantRed + leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg" diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 44ed6f4b..7d089639 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -28,7 +28,7 @@ PageType { readonly property string title: qsTr("Subscription status") readonly property string contentKey: "subscriptionStatus" - readonly property string objectImageSource: "qrc:/images/controls/map-pin.svg" + readonly property string objectImageSource: "qrc:/images/controls/info.svg" } QtObject { @@ -44,7 +44,7 @@ PageType { readonly property string title: qsTr("Connected devices") readonly property string contentKey: "connectedDevices" - readonly property string objectImageSource: "qrc:/images/controls/gauge.svg" + readonly property string objectImageSource: "qrc:/images/controls/monitor.svg" } property var processedServer @@ -158,11 +158,28 @@ PageType { readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible") + WarningType { + id: warning + + Layout.topMargin: 32 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.fillWidth: true + + backGroundColor: AmneziaStyle.color.translucentRichBrown + + textString: qsTr("Configurations have been updated for some countries. Download and install the updated configuration files") + + iconPath: "qrc:/images/controls/alert-circle.svg" + + visible: ApiAccountInfoModel.data("hasExpiredWorker") + } + LabelWithButtonType { id: vpnKey Layout.fillWidth: true - Layout.topMargin: 32 + Layout.topMargin: warning.visible ? 16 : 32 visible: false //footer.isVisibleForAmneziaFree @@ -192,7 +209,7 @@ PageType { LabelWithButtonType { Layout.fillWidth: true - Layout.topMargin: 32 + Layout.topMargin: warning.visible ? 16 : 32 visible: footer.isVisibleForAmneziaFree @@ -211,6 +228,26 @@ PageType { visible: footer.isVisibleForAmneziaFree } + LabelWithButtonType { + Layout.fillWidth: true + + visible: footer.isVisibleForAmneziaFree + + text: qsTr("Connected devices") + + descriptionText: qsTr("To manage connected devices") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + ApiSettingsController.updateApiDevicesModel() + PageController.goToPage(PageEnum.PageSettingsApiDevices) + } + } + + DividerType { + visible: footer.isVisibleForAmneziaFree + } + LabelWithButtonType { Layout.fillWidth: true Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32 @@ -292,12 +329,13 @@ PageType { clickedFunc: function() { var headerText = qsTr("Deactivate the subscription on this device?") + var descriptionText = qsTr("The next time the “Connect” button is pressed, the device will be activated again") var yesButtonText = qsTr("Continue") var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - PageController.showNotificationMessage(qsTr("The next time the “Connect” button is pressed, the device will be activated again")) + PageController.showNotificationMessage(qsTr("Cannot deactivate subscription during active connection")) } else { PageController.showBusyIndicator(true) if (ApiConfigsController.deactivateDevice()) { @@ -309,7 +347,7 @@ PageType { var noButtonFunction = function() { } - showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml index 64921a61..424e10c5 100644 --- a/client/ui/qml/Pages2/PageSettingsApiSupport.qml +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -16,92 +16,110 @@ import "../Components" PageType { id: root - ColumnLayout { - id: backButtonLayout + QtObject { + id: telegram - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + readonly property string title: qsTr("Telegram") + readonly property string description: "@" + ApiAccountInfoModel.getTelegramBotLink() + readonly property string link: "https://t.me/" + ApiAccountInfoModel.getTelegramBotLink() + } + QtObject { + id: techSupport + + readonly property string title: qsTr("For technical support") + readonly property string description: qsTr("support@amnezia.org") + readonly property string link: "mailto:support@amnezia.org" + } + + QtObject { + id: paymentSupport + + readonly property string title: qsTr("For payment issues") + readonly property string description: qsTr("help@vpnpay.io") + readonly property string link: "mailto:help@vpnpay.io" + } + + QtObject { + id: site + + readonly property string title: qsTr("Site") + readonly property string description: qsTr("amnezia.org") + readonly property string link: LanguageModel.getCurrentSiteUrl() + } + + property list supportModel: [ + telegram, + techSupport, + paymentSupport, + site + ] + + ListViewType { + id: listView + + anchors.fill: parent anchors.topMargin: 20 + anchors.bottomMargin: 24 - BackButtonType { - id: backButton - } + model: supportModel + header: ColumnLayout { + width: listView.width - HeaderType { - id: header + BackButtonType { + id: backButton + } - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 + HeaderType { + id: header - headerText: qsTr("Support") - descriptionText: qsTr("Our technical support specialists are ready to help you at any time") - } + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 - LabelWithButtonType { - Layout.fillWidth: true - - readonly property string telegramBotLink: ApiAccountInfoModel.getTelegramBotLink() - - text: qsTr("Telegram") - descriptionText: "@" + telegramBotLink - rightImageSource: "qrc:/images/controls/external-link.svg" - - clickedFunction: function() { - Qt.openUrlExternally("https://t.me/" + telegramBotLink) + headerText: qsTr("Support") + descriptionText: qsTr("Our technical support specialists are ready to help you at any time") } } - DividerType {} + delegate: ColumnLayout { + width: listView.width - LabelWithButtonType { - Layout.fillWidth: true - - text: qsTr("Mail") - descriptionText: qsTr("support@amnezia.org") - rightImageSource: "qrc:/images/controls/external-link.svg" - - clickedFunction: function() { - Qt.openUrlExternally(qsTr("mailto:support@amnezia.org")) + LabelWithButtonType { + Layout.fillWidth: true + text: title + descriptionText: description + rightImageSource: "qrc:/images/controls/external-link.svg" + clickedFunction: function() { + Qt.openUrlExternally(link) + } } + DividerType {} } - DividerType {} - LabelWithButtonType { - Layout.fillWidth: true + footer: ColumnLayout { + width: listView.width - text: qsTr("Site") - descriptionText: qsTr("amnezia.org") - rightImageSource: "qrc:/images/controls/external-link.svg" + LabelWithButtonType { + id: supportUuid + Layout.fillWidth: true - clickedFunction: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } - } + text: qsTr("Support tag") + descriptionText: SettingsController.getInstallationUuid() - DividerType {} + descriptionOnTop: true - LabelWithButtonType { - id: supportUuid - Layout.fillWidth: true + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray - text: qsTr("Support tag") - descriptionText: SettingsController.getInstallationUuid() - - descriptionOnTop: true - - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray - - clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + if (!GC.isMobile()) { + this.rightButton.forceActiveFocus() + } } } } From 678bfffe492ed89911a9dcacfb125e82aa1ce1be Mon Sep 17 00:00:00 2001 From: Nethius Date: Tue, 4 Mar 2025 13:33:35 +0700 Subject: [PATCH 2/2] chore: minor ui fixes (#1446) * chore: minor ui fixes * chore: update ru translation file * bugfix: fixed config update by ttl for gateway configs * bugfix: fixed proxy bypassing * chore: minor ui fixes * chore: update ru translation file * chore: bump version --- CMakeLists.txt | 4 +- client/core/controllers/gatewayController.cpp | 74 +- client/translations/amneziavpn_ru_RU.ts | 1215 ++++++++++++----- .../controllers/api/apiConfigsController.cpp | 2 +- client/ui/models/api/apiAccountInfoModel.cpp | 4 +- .../PageSettingsApiAvailableCountries.qml | 2 +- .../ui/qml/Pages2/PageSettingsApiDevices.qml | 13 +- .../Pages2/PageSettingsApiInstructions.qml | 2 +- .../Pages2/PageSettingsApiNativeConfigs.qml | 18 +- .../qml/Pages2/PageSettingsApiServerInfo.qml | 22 +- .../ui/qml/Pages2/PageSettingsApiSupport.qml | 8 +- 11 files changed, 963 insertions(+), 401 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c602249..4692f28f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.4.2 +project(${PROJECT} VERSION 4.8.4.3 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -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 2079) +set(APP_ANDROID_VERSION_CODE 2080) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index 15776328..ba4a8d36 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -157,12 +157,12 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt, this](QNetworkReply *nestedReply, const QList &nestedSslErrors) { encryptedResponseBody = nestedReply->readAll(); - if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) { + reply = nestedReply; + if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) { sslErrors = nestedSslErrors; - reply = nestedReply; - return true; + return false; } - return false; + return true; }; bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction); @@ -212,45 +212,45 @@ QStringList GatewayController::getProxyUrls() wait.exec(); if (reply->error() == QNetworkReply::NetworkError::NoError) { - break; - } - reply->deleteLater(); - } + auto encryptedResponseBody = reply->readAll(); + reply->deleteLater(); - auto encryptedResponseBody = reply->readAll(); - reply->deleteLater(); + EVP_PKEY *privateKey = nullptr; + QByteArray responseBody; + try { + if (!m_isDevEnvironment) { + QCryptographicHash hash(QCryptographicHash::Sha512); + hash.addData(key); + QByteArray hashResult = hash.result().toHex(); - EVP_PKEY *privateKey = nullptr; - QByteArray responseBody; - try { - if (!m_isDevEnvironment) { - QCryptographicHash hash(QCryptographicHash::Sha512); - hash.addData(key); - QByteArray hashResult = hash.result().toHex(); + QByteArray key = QByteArray::fromHex(hashResult.left(64)); + QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32)); - QByteArray key = QByteArray::fromHex(hashResult.left(64)); - QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32)); + QByteArray ba = QByteArray::fromBase64(encryptedResponseBody); - QByteArray ba = QByteArray::fromBase64(encryptedResponseBody); + QSimpleCrypto::QBlockCipher blockCipher; + responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv); + } else { + responseBody = encryptedResponseBody; + } + } catch (...) { + Utils::logException(); + qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody; + continue; + } - QSimpleCrypto::QBlockCipher blockCipher; - responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv); + auto endpointsArray = QJsonDocument::fromJson(responseBody).array(); + + QStringList endpoints; + for (const auto &endpoint : endpointsArray) { + endpoints.push_back(endpoint.toString()); + } + return endpoints; } else { - responseBody = encryptedResponseBody; + reply->deleteLater(); } - } catch (...) { - Utils::logException(); - qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody; - return {}; } - - auto endpointsArray = QJsonDocument::fromJson(responseBody).array(); - - QStringList endpoints; - for (const auto &endpoint : endpointsArray) { - endpoints.push_back(endpoint.toString()); - } - return endpoints; + return {}; } bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key, @@ -262,7 +262,7 @@ bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray } else if (responseBody.contains("html")) { qDebug() << "The response contains an html tag"; return true; - } else if (checkEncryption) { + } else if (reply->error() == QNetworkReply::NetworkError::NoError && checkEncryption) { try { QSimpleCrypto::QBlockCipher blockCipher; static_cast(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt)); @@ -296,7 +296,7 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); wait.exec(); - if (!replyProcessingFunction(reply, sslErrors)) { + if (replyProcessingFunction(reply, sslErrors)) { break; } } diff --git a/client/translations/amneziavpn_ru_RU.ts b/client/translations/amneziavpn_ru_RU.ts index 1b4d441f..1aa62f89 100644 --- a/client/translations/amneziavpn_ru_RU.ts +++ b/client/translations/amneziavpn_ru_RU.ts @@ -4,60 +4,129 @@ AdLabel - + Amnezia Premium - for access to any website Amnezia Premium - для доступа к любым сайтам + + ApiAccountInfoModel + + + + Active + + + + + Inactive + + + + + %1 out of %2 + + + + + Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. Speeds up to 200 Mbps + + + + + Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and more. YouTube is not included in the free plan. + + + + + amnezia_free_support_bot + + + + + amnezia_premium_support_bot + + + + + ApiConfigsController + + + %1 installed successfully. + %1 успешно установлен. + + + + API config reloaded + Конфигурация API перезагружена + + + + Successfully changed the country of connection to %1 + Изменение страны подключения на %1 + + ApiServicesModel - Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to %1 MBit/s - Классический VPN для комфортной работы, загрузки больших файлов и просмотра видео. Работает для любых сайтов. Скорость до %1 Мбит/с + Классический VPN для комфортной работы, загрузки больших файлов и просмотра видео. Работает для любых сайтов. Скорость до %1 Мбит/с - VPN to access blocked sites in regions with high levels of Internet censorship. - VPN для доступа к заблокированным сайтам в регионах с высоким уровнем интернет-цензуры. + VPN для доступа к заблокированным сайтам в регионах с высоким уровнем интернет-цензуры. - + <p><a style="color: #EB5757;">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a> <p><a style="color: #EB5757;">Недоступно в вашем регионе. Если у вас включен VPN, отключите его, вернитесь на предыдущий экран и попробуйте снова.</a> - Amnezia Premium - A classic VPN for comfortable work, downloading large files, and watching videos in high resolution. It works for all websites, even in countries with the highest level of internet censorship. - Amnezia Premium — классический VPN для комфортной работы, загрузки больших файлов и просмотра видео в высоком разрешении. Работает на всех сайтах, даже в странах с самым высоким уровнем интернет-цензуры. + Amnezia Premium — классический VPN для комфортной работы, загрузки больших файлов и просмотра видео в высоком разрешении. Работает на всех сайтах, даже в странах с самым высоким уровнем интернет-цензуры. - Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorship - Amnezia Free - это бесплатный VPN для обхода блокировок в странах с высоким уровнем интернет-цензуры + Amnezia Free - это бесплатный VPN для обхода блокировок в странах с высоким уровнем интернет-цензуры - + + Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. Works for any sites with no restrictions. Speed up to %1 MBit/s. Unlimited traffic. + + + + + + AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan. + + + + + Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. Works for any sites with no restrictions. + + + + %1 MBit/s - + %1 days %1 дней - + VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. Other sites will be opened from your real IP address, <a href="%1/free" style="color: #FBB26A;">more details on the website.</a> Через VPN будут открываться только популярные сайты, заблокированные в вашем регионе, такие как Instagram, Facebook, Twitter и другие. Остальные сайты будут открываться с вашего реального IP-адреса, <a href="%1/free" style="color: #FBB26A;">подробности на сайте.</a> - + Free Бесплатно - + %1 $/month %1 $/месяц @@ -96,62 +165,59 @@ ConnectionController - VPN Protocols is not installed. Please install VPN container at first - VPN-протоколы не установлены. + VPN-протоколы не установлены. Пожалуйста, установите протокол - + Connecting... Подключение... - + Connected Подключено - + Preparing... Подготовка... - + Settings updated successfully, reconnnection... Настройки успешно обновлены, переподключение... - + Settings updated successfully Настройки успешно обновлены - The selected protocol is not supported on the current platform - Выбранный протокол не поддерживается на данном устройстве + Выбранный протокол не поддерживается на данном устройстве - unable to create configuration - не удалось создать конфигурацию + не удалось создать конфигурацию - + Reconnecting... Переподключение... - - - - + + + + Connect Подключиться - + Disconnecting... Отключение... @@ -274,12 +340,12 @@ Can't be disabled for current server Неверный файл конфигурации - + Scanned %1 of %2. Отсканировано %1 из %2. - + In the imported configuration, potentially dangerous lines were found: В импортированной конфигурации были обнаружены потенциально опасные строки: @@ -287,88 +353,85 @@ Can't be disabled for current server InstallController - + %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' - + Api config removed Конфигурация API удалена - + %1 cached profile cleared %1 закэшированный профиль очищен - + Please login as the user Пожалуйста, войдите в систему от имени пользователя - + Server added successfully Сервер успешно добавлен - %1 installed successfully. - %1 успешно установлен. + %1 успешно установлен. - API config reloaded - Конфигурация API перезагружена + Конфигурация API перезагружена - Successfully changed the country of connection to %1 - Изменение страны подключения на %1 + Изменение страны подключения на %1 @@ -480,12 +543,12 @@ Already installed containers were found on the server. All installed containers Раздельное туннелирование выключено - + VPN protocol VPN-протокол - + Servers Серверы @@ -1386,22 +1449,22 @@ Already installed containers were found on the server. All installed containers Приложение - + Backup Резервное копирование - + About AmneziaVPN Об AmneziaVPN - + Dev console - + Close application Закрыть приложение @@ -1409,7 +1472,7 @@ Already installed containers were found on the server. All installed containers PageSettingsAbout - + Support Amnezia Поддержите Amnezia @@ -1418,12 +1481,12 @@ Already installed containers were found on the server. All installed containers Показать другие способы на GitHub - + Amnezia is a free and open-source application. You can support the developers if you like it. Amnezia — это бесплатное приложение с открытым исходным кодом. Поддержите разработчиков, если оно вам нравится. - + Contacts Контакты @@ -1457,32 +1520,36 @@ Already installed containers were found on the server. All installed containers Для отзывов и сообщений об ошибках - Copied - Скопировано + Скопировано - + + mailto:support@amnezia.org + + + + GitHub GitHub - + Discover the source code - + https://github.com/amnezia-vpn/amnezia-client https://github.com/amnezia-vpn/amnezia-client - + Website Веб-сайт - + Visit official website @@ -1491,109 +1558,480 @@ Already installed containers were found on the server. All installed containers https://amnezia.org - + Software version: %1 Версия ПО: %1 - + Check for updates Проверить обновления - + Privacy Policy Политика конфиденциальности + + PageSettingsApiAvailableCountries + + + Location for connection + + + + + Unable change server location while there is an active connection + Невозможно изменить локацию во время активного соединения + + + + PageSettingsApiDevices + + + Active devices + + + + + Manage currently connected devices + + + + + You can find the identifier on the Support tab or, for older versions of the app, by tapping '+' and then the three dots at the top of the page. + + + + + (current device) + + + + + Support tag: + + + + + Last updated: + + + + + Cannot unlink device during active connection + + + + + Are you sure you want to unlink this device? + + + + + This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect. + + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + PageSettingsApiInstructions + + + Windows + + + + + https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#windows + + + + + macOS + + + + + https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#macos + + + + + Android + + + + + https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#android + + + + + AndroidTV + + + + + https://docs.amnezia.org/ru/documentation/instructions/android_tv_connect/ + + + + + iOS + + + + + https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#ios + + + + + Linux + + + + + https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#linux + + + + + Routers + + + + + https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#routers + + + + + How to connect on another device + + + + + Setup guides on the Amnezia website + + + PageSettingsApiLanguageList - Unable change server location while there is an active connection - Невозможно изменить локацию во время активного соединения + Невозможно изменить локацию во время активного соединения + + + + PageSettingsApiNativeConfigs + + + Save AmneziaVPN config + Сохранить конфигурацию AmneziaVPN + + + + Configuration files + + + + + For router setup or the AmneziaWG app + + + + + The configuration needs to be reissued + + + + + configuration file + + + + + Generate a new configuration file + + + + + The previously created one will stop working + + + + + Revoke the current configuration file + + + + + Config file saved + + + + + The config has been revoked + + + + + Generate a new %1 configuration file? + + + + + Revoke the current %1 configuration file? + + + + + Your previous configuration file will no longer work, and it will not be possible to connect using it + + + + + Download + + + + + Continue + Продолжить + + + + Cancel + Отменить PageSettingsApiServerInfo - For the region - Для региона + Для региона - Price - Цена + Цена - Work period - Период работы + Период работы - + Valid until - Speed - Скорость + Скорость - - Support tag + Copied + Скопировано + + + + Subscription status - - Copied - Скопировано + + Active connections + - + + Configurations have been updated for some countries. Download and install the updated configuration files + + + + + Subscription key + + + + + Amnezia Premium subscription key + + + + + Save VPN key to file + + + + + Copy VPN key + + + + + Configuration files + + + + + Manage configuration files + + + + + Active devices + + + + + Manage currently connected devices + + + + + Support + + + + + How to connect on another device + + + + Reload API config Перезагрузить конфигурацию API - + Reload API config? Перезагрузить конфигурацию API? - - + + + Continue Продолжить - - + + + Cancel Отменить - + Cannot reload API config during active connection Невозможно перзагрузить API конфигурацию при активном соединении - + + Unlink this device + + + + + Are you sure you want to unlink this device? + + + + + This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect. + + + + + Cannot unlink device during active connection + + + + Remove from application Удалить из приложения - + Remove from application? Удалить из приложения? - + Cannot remove server during active connection Невозможно удалить сервер во время активного соединения + + PageSettingsApiSupport + + + Telegram + + + + + Email Support + + + + + support@amnezia.org + + + + + Email Billing & Orders + + + + + help@vpnpay.io + + + + + Website + Веб-сайт + + + + amnezia.org + + + + + Support + + + + + Our technical support specialists are available to assist you at any time + + + + + Support tag + + + + + Copied + Скопировано + + PageSettingsAppSplitTunneling @@ -2009,20 +2447,20 @@ Already installed containers were found on the server. All installed containers Открыть папку с логами - - + + Save Сохранить - - + + Logs files (*.log) Файлы логов (*.log) - - + + Logs file saved Файл с логами сохранен @@ -2056,32 +2494,32 @@ Already installed containers were found on the server. All installed containers Логи очищены - + Client logs - + AmneziaVPN logs - + Open logs folder Открыть папку с логами - + Export logs Сохранить логи - + Service logs - + AmneziaVPN-service logs @@ -2121,93 +2559,93 @@ 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 секунд. Вы уверены, что хотите продолжить? - + Cannot reboot server during active connection Невозможно перезагрузить сервер во время активного соединения - + Do you want to remove the server from application? Вы уверены, что хотите удалить сервер из приложения? - + Cannot remove server during active connection Невозможно удалить сервер во время активного соединения - + Do you want to clear server from Amnezia software? Вы хотите очистить сервер от всех сервисов Amnezia? - + All users whom you shared a connection with will no longer be able to connect to it. Все пользователи, с которыми вы поделились конфигурацией вашего VPN, больше не смогут к нему подключаться. - + Cannot clear server from Amnezia software during active connection Невозможно очистить сервер от сервисов Amnezia во время активного соединения - + Reset API config Сбросить конфигурацию API - + Do you want to reset API config? Вы хотите сбросить конфигурацию API? - + Cannot reset API config during active connection Невозможно сбросить конфигурацию API во время активного соединения - + Remove server from application Удалить сервер из приложения @@ -2216,12 +2654,12 @@ Already installed containers were found on the server. All installed containers Удалить сервер? - + All installed AmneziaVPN services will still remain on the server. Все установленные сервисы и протоколы Amnezia останутся на сервере. - + Clear server from Amnezia software Очистить сервер от протоколов и сервисов Amnezia @@ -2237,32 +2675,29 @@ Already installed containers were found on the server. All installed containers PageSettingsServerInfo - Subscription is valid until - Подписка заканчивается через + Подписка заканчивается через - Server name - Имя сервера + Имя сервера - Save - Сохранить + Сохранить - + Protocols Протоколы - + Services Сервисы - + Management Управление @@ -2394,17 +2829,17 @@ Already installed containers were found on the server. All installed containers Режим - + Remove Удалить - + Continue Продолжить - + Cancel Отменить @@ -2413,7 +2848,7 @@ Already installed containers were found on the server. All installed containers Сайт или IP - + Import / Export Sites Импорт/экспорт сайтов @@ -2428,12 +2863,12 @@ Already installed containers were found on the server. All installed containers Невозможно изменить настройки раздельного туннелирования во время активного соединения - + website or IP веб-сайт или IP - + Import Импорт @@ -2449,29 +2884,29 @@ Already installed containers were found on the server. All installed containers - - + + Sites files (*.json) Файлы сайтов (*.json) - + Import a list of sites Импортировать список с сайтами - + Replace site list Заменить список с сайтами - - + + Open sites file Открыть список с сайтами - + Add imported sites to existing ones Добавить импортированные сайты к существующим @@ -2545,7 +2980,7 @@ It's okay as long as it's from someone you trust. Что у вас есть? - + File with connection settings Файл с настройками подключения @@ -2604,80 +3039,65 @@ It's okay as long as it's from someone you trust. Другие варианты подключения - + Site Amnezia Сайт Amnezia - + VPN by Amnezia VPN от Amnezia - + Connect to classic paid and free VPN services from Amnezia Подключайтесь к классическим платным и бесплатным VPN-сервисам от Amnezia - + Self-hosted VPN Self-hosted VPN - + Configure Amnezia VPN on your own server Настроить VPN на собственном сервере - + Restore from backup Восстановить из резервной копии - + - + Open backup file Открыть резервную копию - + Backup files (*.backup) Файлы резервных копий (*.backup) - - - - - - + Open config file Открыть файл с конфигурацией - + QR code QR-код - - - - - - + I have nothing У меня ничего нет - - - - - Key as text Ключ в виде текста @@ -2690,7 +3110,7 @@ It's okay as long as it's from someone you trust. Подключение к серверу - + Server IP address [:port] IP-адрес[:порт] сервера @@ -2703,7 +3123,7 @@ It's okay as long as it's from someone you trust. Password / SSH private key - + Continue Продолжить @@ -2713,7 +3133,7 @@ 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 @@ -2727,47 +3147,47 @@ and will not be shared or disclosed to the Amnezia or any third parties Настроить ваш сервер - + 255.255.255.255:22 255.255.255.255:22 - + SSH Username Имя пользователя SSH - + Password or SSH private key Пароль или закрытый ключ SSH - + All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties Все данные, которые вы вводите, останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или каким-либо третьим лицам - + How to run your VPN server Как создать VPN на собственном сервере - + Where to get connection data, step-by-step instructions for buying a VPS Где взять данные для подключения, пошаговые инструкции по покупке VPS - + Ip address cannot be empty Поле с IP-адресом не может быть пустым - + Login cannot be empty Поле с логином не может быть пустым - + Password/private key cannot be empty Поле с паролем/закрытым ключом не может быть пустым @@ -2775,17 +3195,26 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardEasy - What is the level of internet control in your region? - Какой уровень контроля над интернетом в вашем регионе? + Какой уровень контроля над интернетом в вашем регионе? + + + + Choose Installation Type + + Manual + + + + Choose a VPN protocol Выберите VPN-протокол - + Skip setup Пропустить настройку @@ -2798,7 +3227,7 @@ and will not be shared or disclosed to the Amnezia or any third parties Выбрать VPN-протокол - + Continue Продолжить @@ -3005,12 +3434,12 @@ and will not be shared or disclosed to the Amnezia or any third parties PageShare - + OpenVPN native format Оригинальный формат OpenVPN - + WireGuard native format Оригинальный формат WireGuard @@ -3019,7 +3448,7 @@ and will not be shared or disclosed to the Amnezia or any third parties VPN-Доступ - + Connection Соединение @@ -3032,8 +3461,8 @@ and will not be shared or disclosed to the Amnezia or any third parties Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки. + - Server Сервер @@ -3057,113 +3486,113 @@ and will not be shared or disclosed to the Amnezia or any third parties Файл с настройками подключения к - + Save OpenVPN config Сохранить конфигурацию OpenVPN - + Save WireGuard config Сохранить конфигурацию WireGuard - + Save AmneziaWG config Сохранить конфигурацию AmneziaWG - + Save Shadowsocks config Сохранить конфигурацию Shadowsocks - + Save Cloak config Сохранить конфигурацию Cloak - + Save XRay config Сохранить конфигурацию XRay - + For the AmneziaVPN app Для приложения AmneziaVPN - + AmneziaWG native format Оригинальный формат AmneziaWG - + Shadowsocks native format Оригинальный формат Shadowsocks - + Cloak native format Оригинальный формат Cloak - + XRay native format Оригинальный формат XRay - + 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 Пользователи - + User name Имя пользователя - + Search Поиск - + Creation date: %1 Дата создания: %1 - + Latest handshake: %1 Последнее рукопожатие: %1 - + Data received: %1 Получено данных: %1 - + Data sent: %1 Отправлено данных: %1 - + Allowed IPs: %1 @@ -3172,42 +3601,42 @@ and will not be shared or disclosed to the Amnezia or any third parties Дата создания: - + 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 Отменить @@ -3216,25 +3645,25 @@ and will not be shared or disclosed to the Amnezia or any third parties Полный доступ - + Share VPN access without the ability to manage the server Поделиться доступом к VPN без возможности управления сервером + - Protocol Протокол + - Connection format Формат подключения - - + + Share Поделиться @@ -3298,17 +3727,17 @@ and will not be shared or disclosed to the Amnezia or any third parties PageStart - + Logging was disabled after 14 days, log files were deleted Логирование было отключено по прошествии 14 дней, файлы логов были удалены. - + Settings restored from backup file Настройки восстановлены из бэкап файла - + Logging is enabled. Note that logs will be automaticallydisabled after 14 days, and all log files will be deleted. Логирование включено. Обратите внимание, что через 14 дней оно будет автоматически отключено, а все файлы логов будут удалены. @@ -3578,72 +4007,77 @@ and will not be shared or disclosed to the Amnezia or any third parties Фоновая служба не запущена - + + The selected protocol is not supported on the current platform + Выбранный протокол не поддерживается на данном устройстве + + + 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 - + Server error: Packet manager error Ошибка сервера: ошибка менеджера пакетов - + 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 Тайм-аут подключения к серверу - + SCP error: Generic failure Ошибка SCP: общий сбой @@ -3700,22 +4134,23 @@ and will not be shared or disclosed to the Amnezia or any third parties Sftp error: No media was in remote drive - + The config does not contain any containers and credentials for connecting to the server Конфигурация не содержит каких-либо контейнеров и учетных данных для подключения к серверу - + + Error when retrieving configuration from API Ошибка при получении конфигурации из API - + This config has already been added to the application Данная конфигурация уже была добавлена в приложение - + ErrorCode: %1. Код ошибки: %1. @@ -3724,127 +4159,139 @@ and will not be shared or disclosed to the Amnezia or any third parties 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: нет доступных адресов - + Unable to open config file Не удалось открыть файл конфигурации - + + VPN Protocols is not installed. + Please install VPN container at first + VPN-протоколы не установлены. + Пожалуйста, установите протокол + + + VPN connection error Ошибка VPN-соединения - + In the response from the server, an empty config was received В ответе от сервера была получена пустая конфигурация - + SSL error occurred Произошла ошибка SSL - + Server response timeout on api request Тайм-аут ответа сервера на запрос API - + Missing AGW public key - + Failed to decrypt response payload - + Missing list of available services + The limit of allowed configurations per subscription has been exceeded + + + + QFile error: The file could not be opened Ошибка QFile: не удалось открыть файл - + QFile error: An error occurred when reading from the file Ошибка QFile: произошла ошибка при чтении из файла - + QFile error: The file could not be accessed Ошибка QFile: не удалось получить доступ к файлу - + QFile error: An unspecified error occurred Ошибка QFile: произошла неизвестная ошибка - + QFile error: A fatal error occurred Ошибка QFile: произошла фатальная ошибка - + QFile error: The operation was aborted Ошибка QFile: операция была прервана - + Internal error Внутренняя ошибка @@ -3854,27 +4301,24 @@ and will not be shared or disclosed to the Amnezia or any third parties 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. - Shadowsocks маскирует VPN-трафик под обычный веб-трафик, но распознается системами анализа в некоторых регионах с высоким уровнем цензуры. + 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-трафика под обычный веб-трафик и защитой от обнаружения активным зондированием. Подходит для регионов с самым высоким уровнем цензуры. + OpenVPN over Cloak — это OpenVPN с маскировкой VPN-трафика под обычный веб-трафик и защитой от обнаружения активным зондированием. Подходит для регионов с самым высоким уровнем цензуры. - + IKEv2/IPsec - 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/IPsec — современный стабильный протокол, немного быстрее других, восстанавливает соединение после потери сигнала. Он имеет встроенную поддержку в последних версиях Android и iOS. - + 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. @@ -3893,7 +4337,7 @@ 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 и плагина Cloak, разработанная специально для защиты от блокировки. OpenVPN обеспечивает безопасное VPN-соединение, шифруя весь интернет-трафик между клиентом и сервером. @@ -3912,7 +4356,6 @@ Cloak изменяет метаданные пакетов таким образ * Работает по сетевому протоколу TCP, использует порт 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. @@ -3922,7 +4365,7 @@ 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-протокол с простой архитектурой. + Относительно новый и популярный VPN-протокол с простой архитектурой. WireGuard обеспечивает стабильное VPN-соединение и высокую производительность на всех устройствах. Он использует строго заданные настройки шифрования. WireGuard по сравнению с OpenVPN имеет меньшую задержку и лучшую пропускную способность при передаче данных. WireGuard очень уязвим для блокировки из-за характерных сигнатур пакетов. В отличие от некоторых других VPN-протоколов, использующих методы обфускации, последовательные сигнатуры пакетов WireGuard легче идентифицируются и, следовательно, могут блокироваться современными Deep Packet Inspection (DPI) системами и другими инструментами для сетевого мониторинга. @@ -3933,18 +4376,17 @@ WireGuard очень уязвим для блокировки из-за хара * Работает по сетевому протоколу UDP - The REALITY protocol, a pioneering development by the creators of XRay, is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion. It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, thus presenting an authentic TLS certificate and data. This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, legitimate sites without the need for specific configurations. Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, REALITY's innovative "friend or foe" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship. - Протокол REALITY, новаторская разработка создателей XRay, специально спроектирован для противодействия самой строгой цензуре с помощью нового способа обхода блокировок. + Протокол REALITY, новаторская разработка создателей XRay, специально спроектирован для противодействия самой строгой цензуре с помощью нового способа обхода блокировок. Он уникальным образом идентифицирует цензоров на этапе TLS-рукопожатия, беспрепятственно работая в качестве прокси для реальных клиентов и перенаправляя цензоров на реальные сайты, такие как google.com, тем самым предъявляя подлинный TLS-сертификат и данные. REALITY отличается от аналогичных технологий благодаря способности без специальной настройки маскировать веб-трафик так, как будто он поступает со случайных легитимных сайтов. В отличие от более старых протоколов, таких как VMess, VLESS и транспорт XTLS-Vision, технология распознавания "друг или враг" на этапе TLS-рукопожатия повышает безопасность и обходит обнаружение сложными системами DPI-анализа, которые используют методы активного зондирования. Это делает REALITY эффективным решением для поддержания свободы интернета в регионах с жесткой цензурой. - + 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. @@ -3965,7 +4407,7 @@ While it offers a blend of security, stability, and speed, it's essential t * Работает по сетевому протоколу UDP, использует порты 500 и 4500 - + DNS Service Сервис DNS @@ -3976,7 +4418,7 @@ While it offers a blend of security, stability, and speed, it's essential t - + Website in Tor network Веб-сайт в сети Tor @@ -3991,36 +4433,123 @@ While it offers a blend of security, stability, and speed, it's essential t OpenVPN — самый популярный VPN-протокол с гибкой настройкой. Имеет собственный протокол безопасности с SSL/TLS для обмена ключами. - + + Shadowsocks masks VPN traffic, making it resemble normal web traffic, but it may still be detected by certain analysis systems. + + + + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probing detection. It is very resistant to detection, but offers low speed. + + + + + WireGuard - popular VPN protocol with high performance, high speed and low power consumption. + + + + + AmneziaWG is a special protocol from Amnezia based on WireGuard. It provides high connection speed and ensures stable operation even in the most challenging network conditions. + + + + + XRay with REALITY masks VPN traffic as web traffic and protects against active probing. It is highly resistant to detection and offers high speed. + + + + + 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 systems and therefore susceptible to blocking +* Can operate over both TCP and UDP network protocols. + + + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for protecting against detection. + +OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client and the server. + +Cloak protects OpenVPN from detection. + +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. + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by detection systems +* Works over TCP network protocol, 443 port. + + + + + + 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 detection and 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. + + + + + 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 traffic analysis systems +* Works over UDP network protocol. + + + + + The REALITY protocol, a pioneering development by the creators of XRay, is designed to provide the highest level of protection against detection through its innovative approach to security and privacy. +It uniquely identifies attackers during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting attackers to genuine websites, thus presenting an authentic TLS certificate and data. +This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, legitimate sites without the need for specific configurations. +Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, REALITY's innovative "friend or foe" recognition at the TLS handshake enhances security. This makes REALITY a robust solution for maintaining internet freedom. + + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. - WireGuard — новый популярный VPN-протокол с высокой производительностью, высокой скоростью и низким энергопотреблением. Рекомендуется для регионов с низким уровнем цензуры. + 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, но очень устойчив к блокировкам. Рекомендуется для регионов с высоким уровнем цензуры. + AmneziaWG — специальный протокол от Amnezia, основанный на протоколе WireGuard. Он такой же быстрый, как WireGuard, но очень устойчив к блокировкам. Рекомендуется для регионов с высоким уровнем цензуры. - XRay with REALITY - Suitable for countries with the highest level of internet censorship. Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods. - XRay with REALITY подойдет для стран с самым высоким уровнем цензуры. Маскировка трафика под веб-трафик на уровне TLS и защита от обнаружения методами активного зондирования. + XRay with REALITY подойдет для стран с самым высоким уровнем цензуры. Маскировка трафика под веб-трафик на уровне TLS и защита от обнаружения методами активного зондирования. IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after signal loss. IKEv2/IPsec — современный стабильный протокол, немного быстрее других, восстанавливает соединение после потери сигнала. - + 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. @@ -4029,7 +4558,7 @@ It employs its unique security protocol, leveraging the strength of SSL/TLS for * 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-протоколов. + OpenVPN — один из самых популярных и проверенных временем VPN-протоколов. В нем используется уникальный протокол безопасности, опирающийся на SSL/TLS для шифрования и обмена ключами. Кроме того, OpenVPN поддерживает множество методов аутентификации, что делает его универсальным и адаптируемым к широкому спектру устройств и операционных систем. Благодаря открытому исходному коду OpenVPN подвергается тщательному анализу со стороны мирового сообщества, что постоянно повышает его безопасность. Оптимальное соотношение производительности, безопасности и совместимости делает OpenVPN лучшим выбором как для частных лиц, так и для компаний, заботящихся о конфиденциальности. * Доступен в AmneziaVPN на всех платформах @@ -4039,7 +4568,7 @@ It employs its unique security protocol, leveraging the strength of SSL/TLS for * Может работать по сетевым протоколам 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 @@ -4064,7 +4593,7 @@ It employs its unique security protocol, leveraging the strength of SSL/TLS for В отличие от более старых протоколов, таких как VMess, VLESS и XTLS-Vision, технология распознавания "друг или враг" на этапе TLS-рукопожатия повышает безопасность и обходит обнаружение сложными системами DPI-анализа, которые используют методы активного зондирования. Это делает REALITY эффективным решением для поддержания свободы интернета в регионах с жесткой цензурой. - + After installation, Amnezia will create a file storage on your server. You will be able to access it using @@ -4141,7 +4670,6 @@ WireGuard очень восприимчив к блокированию из-з * Работает по сетевому протоколу 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. @@ -4151,7 +4679,7 @@ 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. AmneziaWG опирается на фундамент, заложенный WireGuard, сохраняя упрощенную архитектуру и высокую производительность на различных устройствах. + AmneziaWG — усовершенствованная версия популярного VPN-протокола WireGuard. AmneziaWG опирается на фундамент, заложенный WireGuard, сохраняя упрощенную архитектуру и высокую производительность на различных устройствах. Хотя WireGuard известен своей эффективностью, у него были проблемы с обнаружением из-за характерных сигнатур пакетов. AmneziaWG решает эту проблему за счет использования более совершенных методов обфускации, благодаря чему его трафик сливается с обычным интернет-трафиком. Таким образом, AmneziaWG сохраняет высокую производительность оригинального протокола, добавляя при этом дополнительный уровень скрытности, что делает его отличным выбором для тех, кому нужно быстрое и незаметное VPN-соединение. @@ -4242,7 +4770,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin - + SOCKS5 proxy server Прокси-сервер SOCKS5 @@ -4378,6 +4906,19 @@ This means that AmneziaWG keeps the fast performance of the original while addin Невозможно найти разделитель-двоеточие между именем хоста и портом + + RenameServerDrawer + + + Server name + Имя сервера + + + + Save + Сохранить + + SelectLanguageDrawer @@ -4428,24 +4969,24 @@ This means that AmneziaWG keeps the fast performance of the original while addin ShareConnectionDrawer - - + + Save AmneziaVPN config Сохранить конфигурацию AmneziaVPN - + Share Поделиться - + Copy Скопировать - - + + Copied Скопировано @@ -4455,12 +4996,12 @@ This means that AmneziaWG keeps the fast performance of the original while addin Скопировать строку конфигурации - + 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-код" @@ -4544,7 +5085,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin TextFieldWithHeaderType - + The field can't be empty Поле не может быть пустым @@ -4552,7 +5093,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin VpnConnection - + Mbps Мбит/с @@ -4603,28 +5144,24 @@ This means that AmneziaWG keeps the fast performance of the original while addin amnezia::ContainerProps - Low - Низкий + Низкий - 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. @@ -4646,6 +5183,16 @@ This means that AmneziaWG keeps the fast performance of the original while addin I just want to increase the level of privacy Хочу просто повысить уровень приватности + + + Automatic + + + + + AmneziaWG protocol will be installed. It provides high connection speed and ensures stable operation even in the most challenging network conditions. + + main2 diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index d3c8747d..b8696201 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -407,7 +407,7 @@ bool ApiConfigsController::isConfigValid() return updateServiceFromGateway(serverIndex, "", ""); } else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) { qDebug() << "attempt to update api config by expires_at event"; - if (configSource == apiDefs::ConfigSource::Telegram) { + if (configSource == apiDefs::ConfigSource::AmneziaGateway) { return updateServiceFromGateway(serverIndex, "", ""); } else { m_serversModel->removeApiConfig(serverIndex); diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index cdb9dfe8..191582a5 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -48,8 +48,8 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const } case ServiceDescriptionRole: { if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) { - return tr("Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to 200 " - "Mb/s"); + return tr("Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. " + "Speeds up to 200 Mbps"); } else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { return tr("Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and " "more. YouTube is not included in the free plan."); diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index 43fbb160..6e67ef1f 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -81,7 +81,7 @@ PageType { actionButtonImage: "qrc:/images/controls/settings.svg" headerText: root.processedServer.name - descriptionText: qsTr("Locations for connection") + descriptionText: qsTr("Location for connection") actionButtonFunction: function() { PageController.showBusyIndicator(true) diff --git a/client/ui/qml/Pages2/PageSettingsApiDevices.qml b/client/ui/qml/Pages2/PageSettingsApiDevices.qml index 5cc21d07..07df07a7 100644 --- a/client/ui/qml/Pages2/PageSettingsApiDevices.qml +++ b/client/ui/qml/Pages2/PageSettingsApiDevices.qml @@ -42,8 +42,8 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - headerText: qsTr("Connected devices") - descriptionText: qsTr("To manage connected devices") + headerText: qsTr("Active devices") + descriptionText: qsTr("Manage currently connected devices") } WarningType { @@ -71,8 +71,13 @@ PageType { rightImageSource: "qrc:/images/controls/trash.svg" clickedFunction: function() { - var headerText = qsTr("Deactivate the subscription on selected device") - var descriptionText = qsTr("The next time the “Connect” button is pressed, the device will be activated again") + if (isCurrentDevice && ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection")) + return + } + + var headerText = qsTr("Are you sure you want to unlink this device?") + var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.") var yesButtonText = qsTr("Continue") var noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml index 3651407b..7961594b 100644 --- a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -99,7 +99,7 @@ PageType { Layout.leftMargin: 16 headerText: qsTr("How to connect on another device") - descriptionText: qsTr("Instructions on the Amnezia website") + descriptionText: qsTr("Setup guides on the Amnezia website") } } diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml index a1cc1fb8..f7acdf76 100644 --- a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -46,7 +46,7 @@ PageType { Layout.leftMargin: 16 headerText: qsTr("Configuration files") - descriptionText: qsTr("To connect a router or AmneziaWG application") + descriptionText: qsTr("For router setup or the AmneziaWG app") } } @@ -123,13 +123,13 @@ PageType { Layout.fillWidth: true Layout.margins: 16 - headerText: qsTr("Configuration file ") + moreOptionsDrawer.countryName + headerText: moreOptionsDrawer.countryName + qsTr(" configuration file") } LabelWithButtonType { Layout.fillWidth: true - text: qsTr("Create a new") + text: qsTr("Generate a new configuration file") descriptionText: qsTr("The previously created one will stop working") clickedFunction: function() { @@ -193,9 +193,15 @@ PageType { } function showQuestion(isConfigIssue, countryCode, countryName) { - var headerText = qsTr("Revoke the actual %1 configuration file?").arg(countryName) - var descriptionText = qsTr("The previously created file will no longer be valid. It will not be possible to connect using it.") - var yesButtonText = qsTr("Continue") + var headerText + if (isConfigIssue) { + headerText = qsTr("Generate a new %1 configuration file?").arg(countryName) + } else { + headerText = qsTr("Revoke the current %1 configuration file?").arg(countryName) + } + + var descriptionText = qsTr("Your previous configuration file will no longer work, and it will not be possible to connect using it") + var yesButtonText = isConfigIssue ? qsTr("Download") : qsTr("Continue") var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 7d089639..c7650862 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -42,7 +42,7 @@ PageType { QtObject { id: deviceCountObject - readonly property string title: qsTr("Connected devices") + readonly property string title: qsTr("Active connections") readonly property string contentKey: "connectedDevices" readonly property string objectImageSource: "qrc:/images/controls/monitor.svg" } @@ -215,7 +215,7 @@ PageType { text: qsTr("Configuration files") - descriptionText: qsTr("To connect a router or AmneziaWG application") + descriptionText: qsTr("Manage configuration files") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -233,9 +233,9 @@ PageType { visible: footer.isVisibleForAmneziaFree - text: qsTr("Connected devices") + text: qsTr("Active devices") - descriptionText: qsTr("To manage connected devices") + descriptionText: qsTr("Manage currently connected devices") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -265,6 +265,8 @@ PageType { LabelWithButtonType { Layout.fillWidth: true + visible: footer.isVisibleForAmneziaFree + text: qsTr("How to connect on another device") rightImageSource: "qrc:/images/controls/chevron-right.svg" @@ -273,7 +275,9 @@ PageType { } } - DividerType {} + DividerType { + visible: footer.isVisibleForAmneziaFree + } BasicButtonType { id: resetButton @@ -325,17 +329,17 @@ PageType { pressedColor: AmneziaStyle.color.sheerWhite textColor: AmneziaStyle.color.vibrantRed - text: qsTr("Deactivate the subscription on this device") + text: qsTr("Unlink this device") clickedFunc: function() { - var headerText = qsTr("Deactivate the subscription on this device?") - var descriptionText = qsTr("The next time the “Connect” button is pressed, the device will be activated again") + var headerText = qsTr("Are you sure you want to unlink this device?") + var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.") var yesButtonText = qsTr("Continue") var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - PageController.showNotificationMessage(qsTr("Cannot deactivate subscription during active connection")) + PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection")) } else { PageController.showBusyIndicator(true) if (ApiConfigsController.deactivateDevice()) { diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml index 424e10c5..3e4f0149 100644 --- a/client/ui/qml/Pages2/PageSettingsApiSupport.qml +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -27,7 +27,7 @@ PageType { QtObject { id: techSupport - readonly property string title: qsTr("For technical support") + readonly property string title: qsTr("Email Support") readonly property string description: qsTr("support@amnezia.org") readonly property string link: "mailto:support@amnezia.org" } @@ -35,7 +35,7 @@ PageType { QtObject { id: paymentSupport - readonly property string title: qsTr("For payment issues") + readonly property string title: qsTr("Email Billing & Orders") readonly property string description: qsTr("help@vpnpay.io") readonly property string link: "mailto:help@vpnpay.io" } @@ -43,7 +43,7 @@ PageType { QtObject { id: site - readonly property string title: qsTr("Site") + readonly property string title: qsTr("Website") readonly property string description: qsTr("amnezia.org") readonly property string link: LanguageModel.getCurrentSiteUrl() } @@ -79,7 +79,7 @@ PageType { Layout.leftMargin: 16 headerText: qsTr("Support") - descriptionText: qsTr("Our technical support specialists are ready to help you at any time") + descriptionText: qsTr("Our technical support specialists are available to assist you at any time") } }