diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 294b9646..1ed3df08 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -57,7 +57,9 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) endif() qt_standard_project_setup() -qt_add_executable(${PROJECT} MANUAL_FINALIZATION) +qt_add_executable(${PROJECT} MANUAL_FINALIZATION + core/api/apiDefs.h +) if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 2bd0b991..7e72defe 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -10,8 +12,6 @@ #include #include #include -#include -#include #include "logger.h" #include "ui/models/installedAppsModel.h" @@ -282,16 +282,17 @@ bool AmneziaApplication::parseCommands() } #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) -void AmneziaApplication::startLocalServer() { +void AmneziaApplication::startLocalServer() +{ const QString serverName("AmneziaVPNInstance"); QLocalServer::removeServer(serverName); - QLocalServer* server = new QLocalServer(this); + QLocalServer *server = new QLocalServer(this); server->listen(serverName); QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() { if (server) { - QLocalSocket* clientConnection = server->nextPendingConnection(); + QLocalSocket *clientConnection = server->nextPendingConnection(); clientConnection->deleteLater(); } emit m_pageController->raiseMainWindow(); @@ -467,6 +468,9 @@ void AmneziaApplication::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_settings)); + m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_settings)); m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get()); + + m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_settings)); + m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get()); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index dcadb77b..3b9d06dd 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -25,7 +25,7 @@ #include "ui/controllers/sitesController.h" #include "ui/controllers/systemController.h" #include "ui/controllers/appSplitTunnelingController.h" -// #include "ui/controllers/api/importController.h" +#include "ui/controllers/api/apiConfigsController.h" #include "ui/controllers/api/apiSettingsController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" @@ -139,6 +139,7 @@ private: QScopedPointer m_appSplitTunnelingController; QScopedPointer m_apiSettingsController; + QScopedPointer m_apiConfigsController; QNetworkAccessManager *m_nam; diff --git a/client/cmake/sources.cmake b/client/cmake/sources.cmake index ace83685..1d7c4080 100644 --- a/client/cmake/sources.cmake +++ b/client/cmake/sources.cmake @@ -23,7 +23,6 @@ set(HEADERS ${HEADERS} ${CLIENT_ROOT_DIR}/core/networkUtilities.h ${CLIENT_ROOT_DIR}/core/serialization/serialization.h ${CLIENT_ROOT_DIR}/core/serialization/transfer.h - ${CLIENT_ROOT_DIR}/core/enums/apiEnums.h ${CLIENT_ROOT_DIR}/../common/logger/logger.h ${CLIENT_ROOT_DIR}/utils/qmlUtils.h ${CLIENT_ROOT_DIR}/core/api/apiUtils.h diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h new file mode 100644 index 00000000..0431839c --- /dev/null +++ b/client/core/api/apiDefs.h @@ -0,0 +1,32 @@ +#ifndef APIDEFS_H +#define APIDEFS_H + +#include + +namespace apiDefs +{ + enum ConfigType { + AmneziaFreeV2 = 1, + AmneziaFreeV3, + AmneziaPremiumV1, + AmneziaPremiumV2, + SelfHosted + }; + + enum ConfigSource { + Telegram = 1, + AmneziaGateway + }; + + namespace key + { + constexpr QLatin1String configVersion("config_version"); + + constexpr QLatin1String apiConfig("api_config"); + constexpr QLatin1String stackType("stack_type"); + } + + const int requestTimeoutMsecs = 12 * 1000; // 12 secs +} + +#endif // APIDEFS_H diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp index eed34f65..d90ac1dc 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/api/apiUtils.cpp @@ -1,10 +1,46 @@ #include "apiUtils.h" #include +#include -bool ApiUtils::isSubscriptionExpired(const QString &subscriptionEndDate) +bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate) { QDateTime now = QDateTime::currentDateTime(); QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs); return endDate < now; } + +bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject) +{ + auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt(); + switch (configVersion) { + case apiDefs::ConfigSource::Telegram: return true; + case apiDefs::ConfigSource::AmneziaGateway: return true; + default: return false; + } +} + +apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject) +{ + auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt(); + switch (configVersion) { + case apiDefs::ConfigSource::Telegram: { + }; + case apiDefs::ConfigSource::AmneziaGateway: { + constexpr QLatin1String premium("prem"); + constexpr QLatin1String free("free"); + + auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject(); + auto stackType = apiConfigObject.value(apiDefs::key::stackType).toString(); + + if (stackType == premium) { + return apiDefs::ConfigType::AmneziaPremiumV2; + } else if (stackType == free) { + return apiDefs::ConfigType::AmneziaFreeV3; + } + } + default: { + return apiDefs::ConfigType::SelfHosted; + } + }; +} diff --git a/client/core/api/apiUtils.h b/client/core/api/apiUtils.h index dc863c22..2c9496bd 100644 --- a/client/core/api/apiUtils.h +++ b/client/core/api/apiUtils.h @@ -3,9 +3,15 @@ #include -namespace ApiUtils +#include "apiDefs.h" + +namespace apiUtils { + bool isServerFromApi(const QJsonObject &serverConfigObject); + bool isSubscriptionExpired(const QString &subscriptionEndDate); + + apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject); } #endif // APIUTILS_H diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index 4e43f148..34fb30a0 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -7,7 +7,7 @@ #include "amnezia_application.h" #include "configurators/wireguard_configurator.h" -#include "core/enums/apiEnums.h" +#include "core/api/apiDefs.h" #include "gatewayController.h" #include "version.h" @@ -106,7 +106,7 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle serverConfig[config_key::containers] = newServerConfig.value(config_key::containers); serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); - if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) { + if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion); serverConfig[config_key::description] = newServerConfig.value(config_key::description); serverConfig[config_key::name] = newServerConfig.value(config_key::name); @@ -119,7 +119,7 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap()); auto apiConfig = QJsonObject::fromVariantMap(map); - if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) { + if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject()); } @@ -274,23 +274,4 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co return errorCode; } -ErrorCode ApiController::getNativeConfig(const QString &userCountryCode, const QString &serviceType, const QString &protocol, - const QString &serverCountryCode, const QJsonObject &authData, QString &nativeConfig) -{ - GatewayController gatewayController(m_gatewayEndpoint, m_isDevEnvironment, requestTimeoutMsecs); - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); - - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = userCountryCode; - apiPayload[configKey::serverCountryCode] = serverCountryCode; - apiPayload[configKey::serviceType] = serviceType; - apiPayload[configKey::authData] = authData; - - QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/country_config"), apiPayload, responseBody); - - nativeConfig = responseBody; - - return errorCode; -} diff --git a/client/core/controllers/apiController.h b/client/core/controllers/apiController.h index 9fb1f49d..ab34bde4 100644 --- a/client/core/controllers/apiController.h +++ b/client/core/controllers/apiController.h @@ -25,8 +25,6 @@ public slots: ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, QJsonObject &serverConfig); - ErrorCode getNativeConfig(const QString &userCountryCode, const QString &serviceType, const QString &protocol, - const QString &serverCountryCode, const QJsonObject &authData, QString &nativeConfig); signals: void errorOccurred(ErrorCode errorCode); diff --git a/client/core/enums/apiEnums.h b/client/core/enums/apiEnums.h deleted file mode 100644 index 1f050007..00000000 --- a/client/core/enums/apiEnums.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef APIENUMS_H -#define APIENUMS_H - -enum ApiConfigSources { - Telegram = 1, - AmneziaGateway -}; - -#endif // APIENUMS_H diff --git a/client/resources.qrc b/client/resources.qrc index 308accda..cd44ca60 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -228,6 +228,7 @@ ui/qml/Controls2/ListViewType.qml ui/qml/Pages2/PageSettingsApiSupport.qml ui/qml/Pages2/PageSettingsApiInstructions.qml + ui/qml/Pages2/PageSettingsApiNativeConfigs.qml images/flagKit/ZW.svg diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp new file mode 100644 index 00000000..62ccebb8 --- /dev/null +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -0,0 +1,106 @@ +#include "ApiConfigsController.h" + +#include "configurators/wireguard_configurator.h" +#include "core/api/apiDefs.h" +#include "core/controllers/gatewayController.h" +#include "version.h" +#include "ui/controllers/systemController.h" + +namespace +{ + namespace configKey + { + constexpr char cloak[] = "cloak"; + constexpr char awg[] = "awg"; + + constexpr char apiEdnpoint[] = "api_endpoint"; + constexpr char accessToken[] = "api_key"; + constexpr char certificate[] = "certificate"; + constexpr char publicKey[] = "public_key"; + + constexpr char uuid[] = "installation_uuid"; + constexpr char osVersion[] = "os_version"; + constexpr char appVersion[] = "app_version"; + + constexpr char userCountryCode[] = "user_country_code"; + constexpr char serverCountryCode[] = "server_country_code"; + constexpr char serviceType[] = "service_type"; + constexpr char serviceInfo[] = "service_info"; + constexpr char serviceProtocol[] = "service_protocol"; + + constexpr char aesKey[] = "aes_key"; + constexpr char aesIv[] = "aes_iv"; + constexpr char aesSalt[] = "aes_salt"; + + constexpr char apiPayload[] = "api_payload"; + constexpr char keyPayload[] = "key_payload"; + + constexpr char apiConfig[] = "api_config"; + constexpr char authData[] = "auth_data"; + + constexpr char config[] = "config"; + } +} + +ApiConfigsController::ApiConfigsController(const QSharedPointer &serversModel, const std::shared_ptr &settings, + QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_settings(settings) +{ +} + +void ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName) +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + + auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); + auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + + 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] = apiConfigObject.value(configKey::serverCountryCode); + apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); + apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); + apiPayload[configKey::uuid] = m_settings->getInstallationUuid(true); + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/native_config"), apiPayload, responseBody); + // // if (errorCode != ErrorCode::NoError) { + + // // } + + QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object(); + QString nativeConfig = jsonConfig.value(configKey::config).toString(); + + SystemController::saveFile(fileName, nativeConfig); +} + +ApiConfigsController::ApiPayloadData ApiConfigsController::generateApiPayloadData(const QString &protocol) +{ + ApiConfigsController::ApiPayloadData apiPayload; + if (protocol == configKey::cloak) { + apiPayload.certRequest = OpenVpnConfigurator::createCertRequest(); + } else if (protocol == configKey::awg) { + auto connData = WireguardConfigurator::genClientKeys(); + apiPayload.wireGuardClientPubKey = connData.clientPubKey; + apiPayload.wireGuardClientPrivKey = connData.clientPrivKey; + } + return apiPayload; +} + +QJsonObject ApiConfigsController::fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData) +{ + QJsonObject obj; + if (protocol == configKey::cloak) { + obj[configKey::certificate] = apiPayloadData.certRequest.request; + } else if (protocol == configKey::awg) { + obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey; + } + + obj[configKey::osVersion] = QSysInfo::productType(); + obj[configKey::appVersion] = QString(APP_VERSION); + + return obj; +} diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h new file mode 100644 index 00000000..8f3249a7 --- /dev/null +++ b/client/ui/controllers/api/apiConfigsController.h @@ -0,0 +1,35 @@ +#ifndef APICONFIGSCONTROLLER_H +#define APICONFIGSCONTROLLER_H + +#include + +#include "configurators/openvpn_configurator.h" +#include "ui/models/servers_model.h" + +class ApiConfigsController : public QObject +{ + Q_OBJECT +public: + ApiConfigsController(const QSharedPointer &serversModel, const std::shared_ptr &settings, + QObject *parent = nullptr); + +public slots: + void exportNativeConfig(const QString &serverCountryCode, const QString &fileName); + +private: + struct ApiPayloadData + { + OpenVpnConfigurator::ConnectionData certRequest; + + QString wireGuardClientPrivKey; + QString wireGuardClientPubKey; + }; + + ApiPayloadData generateApiPayloadData(const QString &protocol); + QJsonObject fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData); + + QSharedPointer m_serversModel; + std::shared_ptr m_settings; +}; + +#endif // APICONFIGSCONTROLLER_H diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp index d1025432..00620d07 100644 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -19,9 +19,14 @@ namespace } ApiSettingsController::ApiSettingsController(const QSharedPointer &serversModel, - const QSharedPointer &apiAccountInfoModel, const std::shared_ptr &settings, - QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_apiAccountInfoModel(apiAccountInfoModel), m_settings(settings) + const QSharedPointer &apiAccountInfoModel, + const QSharedPointer &apiCountryModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), + m_serversModel(serversModel), + m_apiAccountInfoModel(apiAccountInfoModel), + m_apiCountryModel(apiCountryModel), + m_settings(settings) { } @@ -55,3 +60,8 @@ bool ApiSettingsController::getAccountInfo() return true; } + +void ApiSettingsController::updateApiCountryModel() +{ + m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), ""); +} diff --git a/client/ui/controllers/api/apiSettingsController.h b/client/ui/controllers/api/apiSettingsController.h index cad0e9a1..32aa1673 100644 --- a/client/ui/controllers/api/apiSettingsController.h +++ b/client/ui/controllers/api/apiSettingsController.h @@ -4,6 +4,7 @@ #include #include "ui/models/api/apiAccountInfoModel.h" +#include "ui/models/apiCountryModel.h" #include "ui/models/servers_model.h" class ApiSettingsController : public QObject @@ -11,15 +12,18 @@ class ApiSettingsController : public QObject Q_OBJECT public: ApiSettingsController(const QSharedPointer &serversModel, const QSharedPointer &apiAccountInfoModel, - const std::shared_ptr &settings, QObject *parent = nullptr); + const QSharedPointer &apiCountryModel, const std::shared_ptr &settings, + QObject *parent = nullptr); ~ApiSettingsController(); public slots: bool getAccountInfo(); + void updateApiCountryModel(); private: QSharedPointer m_serversModel; QSharedPointer m_apiAccountInfoModel; + QSharedPointer m_apiCountryModel; std::shared_ptr m_settings; }; diff --git a/client/ui/controllers/api/importController.cpp b/client/ui/controllers/api/importController.cpp deleted file mode 100644 index e352326f..00000000 --- a/client/ui/controllers/api/importController.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "importController.h" - diff --git a/client/ui/controllers/api/importController.h b/client/ui/controllers/api/importController.h deleted file mode 100644 index f4580360..00000000 --- a/client/ui/controllers/api/importController.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef IMPORTCONTROLLER_H -#define IMPORTCONTROLLER_H - -#include - -#include "ui/models/api/apiAccountInfoModel.h" -#include "ui/models/servers_model.h" - -// namespace api -// { -// class ImportController : public QObject -// { -// Q_OBJECT -// public: -// ImportController(const QSharedPointer &serversModel, QSharedPointer &accountInfoModel); -// ~ImportController(); - -// private: -// QSharedPointer m_serversModel; -// QSharedPointer m_accountInfoModel; -// }; -// } - -#endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index f9491d4e..9cd386c5 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -8,7 +8,7 @@ #include #include "core/controllers/vpnConfigurationController.h" -#include "core/enums/apiEnums.h" +#include "core/api/apiDefs.h" #include "version.h" ConnectionController::ConnectionController(const QSharedPointer &serversModel, @@ -48,15 +48,15 @@ void ConnectionController::openConnection() emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing); - if (configVersion == ApiConfigSources::Telegram + if (configVersion == apiDefs::ConfigSource::Telegram && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { emit updateApiConfigFromTelegram(); - } else if (configVersion == ApiConfigSources::AmneziaGateway + } else if (configVersion == apiDefs::ConfigSource::AmneziaGateway && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { emit updateApiConfigFromGateway(); } else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) { qDebug() << "attempt to update api config by expires_at event"; - if (configVersion == ApiConfigSources::Telegram) { + if (configVersion == apiDefs::ConfigSource::Telegram) { emit updateApiConfigFromTelegram(); } else { emit updateApiConfigFromGateway(); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 6e1efda5..9ec93cc0 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -35,6 +35,7 @@ namespace PageLoader PageSettingsApiAvailableCountries, PageSettingsApiSupport, PageSettingsApiInstructions, + PageSettingsApiNativeConfigs, PageServiceSftpSettings, PageServiceTorWebsiteSettings, diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index 7679f0ff..36923491 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -12,9 +12,9 @@ namespace namespace configKey { constexpr char availableCountries[] = "available_countries"; - constexpr char serverCountryCode[] = "server_country_code"; - constexpr char serverCountryName[] = "server_country_name"; - constexpr char lastUpdated[] = "last_updated"; + // constexpr char serverCountryCode[] = "server_country_code"; + // constexpr char serverCountryName[] = "server_country_name"; + // constexpr char lastUpdated[] = "last_updated"; constexpr char activeDeviceCount[] = "active_device_count"; constexpr char maxDeviceCount[] = "max_device_count"; constexpr char subscriptionEndDate[] = "subscription_end_date"; @@ -38,7 +38,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const switch (role) { case SubscriptionStatusRole: { - return ApiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active"); + return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active"); } case EndDateRole: { return QDateTime::fromString(m_accountInfoData.subscriptionEndDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); @@ -46,9 +46,15 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const case ConnectedDevicesRole: { return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount); } - // case ServiceDescriptionRole: { - // return apiServiceData.serviceInfo.name; - // } + 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"); + } 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."); + } + } } return QVariant(); @@ -60,21 +66,23 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons AccountInfoData accountInfoData; - auto availableCountries = accountInfoObject.value(configKey::availableCountries).toArray(); - for (const auto &country : availableCountries) { - auto countryObject = country.toObject(); - CountryInfo countryInfo; - countryInfo.serverCountryCode = countryObject.value(configKey::serverCountryCode).toString(); - countryInfo.serverCountryName = countryObject.value(configKey::serverCountryName).toString(); - countryInfo.lastUpdated = countryObject.value(configKey::lastUpdated).toString(); + m_availableCountries = accountInfoObject.value(configKey::availableCountries).toArray(); + // for (const auto &country : availableCountries) { + // auto countryObject = country.toObject(); + // CountryInfo countryInfo; + // countryInfo.serverCountryCode = countryObject.value(configKey::serverCountryCode).toString(); + // countryInfo.serverCountryName = countryObject.value(configKey::serverCountryName).toString(); + // countryInfo.lastUpdated = countryObject.value(configKey::lastUpdated).toString(); - accountInfoData.AvailableCountries.push_back(countryInfo); - } + // accountInfoData.AvailableCountries.push_back(countryInfo); + // } accountInfoData.activeDeviceCount = accountInfoObject.value(configKey::activeDeviceCount).toInt(); accountInfoData.maxDeviceCount = accountInfoObject.value(configKey::maxDeviceCount).toInt(); accountInfoData.subscriptionEndDate = accountInfoObject.value(configKey::subscriptionEndDate).toString(); + accountInfoData.configType = apiUtils::getConfigType(serverConfig); + m_accountInfoData = accountInfoData; endResetModel(); @@ -93,6 +101,11 @@ QVariant ApiAccountInfoModel::data(const QString &roleString) return {}; } +QJsonArray ApiAccountInfoModel::getAvailableCountries() +{ + return m_availableCountries; +} + QHash ApiAccountInfoModel::roleNames() const { QHash roles; diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h index f7cabb69..66d283da 100644 --- a/client/ui/models/api/apiAccountInfoModel.h +++ b/client/ui/models/api/apiAccountInfoModel.h @@ -5,6 +5,8 @@ #include #include +#include "core/api/apiDefs.h" + class ApiAccountInfoModel : public QAbstractListModel { Q_OBJECT @@ -27,29 +29,23 @@ public slots: void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig); QVariant data(const QString &roleString); + QJsonArray getAvailableCountries(); + protected: QHash roleNames() const override; private: - struct CountryInfo - { - QString serverCountryCode; - QString serverCountryName; - QString lastUpdated; - }; - struct AccountInfoData { QString subscriptionEndDate; int activeDeviceCount; int maxDeviceCount; - QString vpnKey; - - QVector AvailableCountries; + apiDefs::ConfigType configType; }; AccountInfoData m_accountInfoData; + QJsonArray m_availableCountries; }; #endif // APIACCOUNTINFOMODEL_H diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index b72b10c3..f77eb6fc 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -1,7 +1,7 @@ #include "servers_model.h" +#include "core/api/apiDefs.h" #include "core/controllers/serverController.h" -#include "core/enums/apiEnums.h" #include "core/networkUtilities.h" #ifdef Q_OS_IOS @@ -132,10 +132,10 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return serverHasInstalledContainers(index.row()); } case IsServerFromTelegramApiRole: { - return server.value(config_key::configVersion).toInt() == ApiConfigSources::Telegram; + return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::Telegram; } case IsServerFromGatewayApiRole: { - return server.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway; + return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway; } case ApiConfigRole: { return apiConfig; diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 870b83b7..845586ba 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -112,11 +112,13 @@ ListView { ServersModel.processedIndex = index if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { - ApiSettingsController.getAccountInfo() - if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) } else { + PageController.showBusyIndicator(true) + ApiSettingsController.getAccountInfo() + PageController.showBusyIndicator(false) + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } } else { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 38a87e72..8b97efd1 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -299,11 +299,13 @@ PageType { ServersModel.processedIndex = ServersModel.defaultIndex if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { - ApiSettingsController.getAccountInfo() - if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) } else { + PageController.showBusyIndicator(true) + ApiSettingsController.getAccountInfo() + PageController.showBusyIndicator(false) + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } } else { diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index 853a44a3..9f00f9f4 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -92,6 +92,10 @@ PageType { descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") actionButtonFunction: function() { + PageController.showBusyIndicator(true) + ApiSettingsController.getAccountInfo() + PageController.showBusyIndicator(false) + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml index 9106808a..d1c8231e 100644 --- a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -20,31 +20,67 @@ PageType { id: windows readonly property string title: qsTr("Windows") - readonly property string imageSource: "qrc:/images/controls/external-link.svg" - readonly property var handler: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } + readonly property string link: qsTr("") + } + + QtObject { + id: macos + + readonly property string title: qsTr("macOS") + readonly property string link: qsTr("") + } + + QtObject { + id: android + + readonly property string title: qsTr("Android") + readonly property string link: qsTr("") + } + + QtObject { + id: androidTv + + readonly property string title: qsTr("AndroidTV") + readonly property string link: qsTr("") + } + + QtObject { + id: ios + + readonly property string title: qsTr("iOS") + readonly property string link: qsTr("") } QtObject { id: linux - readonly property string title: qsTr("Windows") - readonly property string imageSource: "qrc:/images/controls/external-link.svg" - readonly property var handler: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } + readonly property string title: qsTr("Linux") + readonly property string link: qsTr("") + } + + QtObject { + id: routers + + readonly property string title: qsTr("Routers") + readonly property string link: qsTr("") } property list instructionsModel: [ windows, - linux + macos, + android, + androidTv, + ios, + linux, + routers ] ListViewType { id: listView anchors.fill: parent + anchors.topMargin: 20 + anchors.bottomMargin: 24 model: instructionsModel @@ -62,8 +98,8 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - headerText: "Support" - descriptionText: qsTr("Our technical support specialists are ready to help you at any time") + headerText: qsTr("How to connect on another device") + descriptionText: qsTr("Instructions on the Amnezia website") } } @@ -76,9 +112,11 @@ PageType { Layout.topMargin: 6 text: title - leftImageSource: imageSource + rightImageSource: "qrc:/images/controls/external-link.svg" - clickedFunction: handler + clickedFunction: function() { + Qt.openUrlExternally(link) + } } DividerType {} diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml new file mode 100644 index 00000000..0cac82b8 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -0,0 +1,87 @@ +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 + + property string configExtension: ".conf" + property string configCaption: qsTr("Save AmneziaVPN config") + + ListViewType { + id: listView + + anchors.fill: parent + anchors.topMargin: 20 + anchors.bottomMargin: 24 + + model: ApiCountryModel + + header: ColumnLayout { + width: listView.width + + BackButtonType { + id: backButton + } + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Configuration files") + descriptionText: qsTr("To connect a router or AmneziaWG application") + } + } + + delegate: ColumnLayout { + width: listView.width + + LabelWithButtonType { + id: telegramButton + Layout.fillWidth: true + Layout.topMargin: 6 + + text: countryName + leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" + rightImageSource: "qrc:/images/controls/download.svg" + + clickedFunction: function() { + var fileName = "" + if (GC.isMobile()) { + fileName = countryCode + configExtension + } else { + fileName = SystemController.getFileName(configCaption, + qsTr("Config files (*" + configExtension + ")"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + countryCode, + true, + configExtension) + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + ApiConfigsController.exportNativeConfig(countryCode, fileName) + PageController.showBusyIndicator(false) + } + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 350f2ea2..0e3e7261 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -105,7 +105,7 @@ PageType { actionButtonImage: "qrc:/images/controls/edit-3.svg" headerText: root.processedServer.name - descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") + descriptionText: ApiAccountInfoModel.data("serviceDescription") actionButtonFunction: function() { serverNameEditDrawer.openTriggered() @@ -145,6 +145,8 @@ PageType { spacing: 0 LabelWithButtonType { + id: vpnKey + Layout.fillWidth: true Layout.topMargin: 32 @@ -157,17 +159,22 @@ PageType { } } - DividerType {} + DividerType { + visible: false + } LabelWithButtonType { Layout.fillWidth: true + Layout.topMargin: vpnKey.visible ? 0 : 32 text: qsTr("Configuration files") - descriptionText: qsTr("To connect the router") + descriptionText: qsTr("To connect a router or AmneziaWG application") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { + ApiSettingsController.updateApiCountryModel() + PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml index fafd1c02..e547359e 100644 --- a/client/ui/qml/Pages2/PageSettingsApiSupport.qml +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -37,7 +37,7 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - headerText: "Support" + headerText: qsTr("Support") descriptionText: qsTr("Our technical support specialists are ready to help you at any time") } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 31d9f72a..bdd2366f 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -95,7 +95,9 @@ PageType { ServersModel.processedIndex = index if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { + PageController.showBusyIndicator(true) ApiSettingsController.getAccountInfo() + PageController.showBusyIndicator(false) PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } else {