feature: added page for export api native configs

This commit is contained in:
vladimir.kuznetsov 2025-02-07 22:22:14 +07:00
parent 389c1f5327
commit 42d3d9b98a
30 changed files with 461 additions and 129 deletions

View file

@ -57,7 +57,9 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
endif() endif()
qt_standard_project_setup() 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)) 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) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)

View file

@ -2,6 +2,8 @@
#include <QClipboard> #include <QClipboard>
#include <QFontDatabase> #include <QFontDatabase>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMimeData> #include <QMimeData>
#include <QQuickItem> #include <QQuickItem>
#include <QQuickStyle> #include <QQuickStyle>
@ -10,8 +12,6 @@
#include <QTextDocument> #include <QTextDocument>
#include <QTimer> #include <QTimer>
#include <QTranslator> #include <QTranslator>
#include <QLocalSocket>
#include <QLocalServer>
#include "logger.h" #include "logger.h"
#include "ui/models/installedAppsModel.h" #include "ui/models/installedAppsModel.h"
@ -282,16 +282,17 @@ bool AmneziaApplication::parseCommands()
} }
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void AmneziaApplication::startLocalServer() { void AmneziaApplication::startLocalServer()
{
const QString serverName("AmneziaVPNInstance"); const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName); QLocalServer::removeServer(serverName);
QLocalServer* server = new QLocalServer(this); QLocalServer *server = new QLocalServer(this);
server->listen(serverName); server->listen(serverName);
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() { QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
if (server) { if (server) {
QLocalSocket* clientConnection = server->nextPendingConnection(); QLocalSocket *clientConnection = server->nextPendingConnection();
clientConnection->deleteLater(); clientConnection->deleteLater();
} }
emit m_pageController->raiseMainWindow(); emit m_pageController->raiseMainWindow();
@ -467,6 +468,9 @@ void AmneziaApplication::initControllers()
m_systemController.reset(new SystemController(m_settings)); m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); 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_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
} }

View file

@ -25,7 +25,7 @@
#include "ui/controllers/sitesController.h" #include "ui/controllers/sitesController.h"
#include "ui/controllers/systemController.h" #include "ui/controllers/systemController.h"
#include "ui/controllers/appSplitTunnelingController.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/controllers/api/apiSettingsController.h"
#include "ui/models/containers_model.h" #include "ui/models/containers_model.h"
#include "ui/models/languageModel.h" #include "ui/models/languageModel.h"
@ -139,6 +139,7 @@ private:
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController; QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
QScopedPointer<ApiSettingsController> m_apiSettingsController; QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController;
QNetworkAccessManager *m_nam; QNetworkAccessManager *m_nam;

View file

@ -23,7 +23,6 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/networkUtilities.h ${CLIENT_ROOT_DIR}/core/networkUtilities.h
${CLIENT_ROOT_DIR}/core/serialization/serialization.h ${CLIENT_ROOT_DIR}/core/serialization/serialization.h
${CLIENT_ROOT_DIR}/core/serialization/transfer.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}/../common/logger/logger.h
${CLIENT_ROOT_DIR}/utils/qmlUtils.h ${CLIENT_ROOT_DIR}/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/api/apiUtils.h ${CLIENT_ROOT_DIR}/core/api/apiUtils.h

32
client/core/api/apiDefs.h Normal file
View file

@ -0,0 +1,32 @@
#ifndef APIDEFS_H
#define APIDEFS_H
#include <QString>
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

View file

@ -1,10 +1,46 @@
#include "apiUtils.h" #include "apiUtils.h"
#include <QDateTime> #include <QDateTime>
#include <QJsonObject>
bool ApiUtils::isSubscriptionExpired(const QString &subscriptionEndDate) bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
{ {
QDateTime now = QDateTime::currentDateTime(); QDateTime now = QDateTime::currentDateTime();
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs); QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
return endDate < now; 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;
}
};
}

View file

@ -3,9 +3,15 @@
#include <QObject> #include <QObject>
namespace ApiUtils #include "apiDefs.h"
namespace apiUtils
{ {
bool isServerFromApi(const QJsonObject &serverConfigObject);
bool isSubscriptionExpired(const QString &subscriptionEndDate); bool isSubscriptionExpired(const QString &subscriptionEndDate);
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
} }
#endif // APIUTILS_H #endif // APIUTILS_H

View file

@ -7,7 +7,7 @@
#include "amnezia_application.h" #include "amnezia_application.h"
#include "configurators/wireguard_configurator.h" #include "configurators/wireguard_configurator.h"
#include "core/enums/apiEnums.h" #include "core/api/apiDefs.h"
#include "gatewayController.h" #include "gatewayController.h"
#include "version.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::containers] = newServerConfig.value(config_key::containers);
serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); 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::configVersion] = newServerConfig.value(config_key::configVersion);
serverConfig[config_key::description] = newServerConfig.value(config_key::description); serverConfig[config_key::description] = newServerConfig.value(config_key::description);
serverConfig[config_key::name] = newServerConfig.value(config_key::name); 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()); map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap());
auto apiConfig = QJsonObject::fromVariantMap(map); 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()); 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; 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;
}

View file

@ -25,8 +25,6 @@ public slots:
ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData,
QJsonObject &serverConfig); QJsonObject &serverConfig);
ErrorCode getNativeConfig(const QString &userCountryCode, const QString &serviceType, const QString &protocol,
const QString &serverCountryCode, const QJsonObject &authData, QString &nativeConfig);
signals: signals:
void errorOccurred(ErrorCode errorCode); void errorOccurred(ErrorCode errorCode);

View file

@ -1,9 +0,0 @@
#ifndef APIENUMS_H
#define APIENUMS_H
enum ApiConfigSources {
Telegram = 1,
AmneziaGateway
};
#endif // APIENUMS_H

View file

@ -228,6 +228,7 @@
<file>ui/qml/Controls2/ListViewType.qml</file> <file>ui/qml/Controls2/ListViewType.qml</file>
<file>ui/qml/Pages2/PageSettingsApiSupport.qml</file> <file>ui/qml/Pages2/PageSettingsApiSupport.qml</file>
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file> <file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
</qresource> </qresource>
<qresource prefix="/countriesFlags"> <qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file> <file>images/flagKit/ZW.svg</file>

View file

@ -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> &serversModel, const std::shared_ptr<Settings> &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;
}

View file

@ -0,0 +1,35 @@
#ifndef APICONFIGSCONTROLLER_H
#define APICONFIGSCONTROLLER_H
#include <QObject>
#include "configurators/openvpn_configurator.h"
#include "ui/models/servers_model.h"
class ApiConfigsController : public QObject
{
Q_OBJECT
public:
ApiConfigsController(const QSharedPointer<ServersModel> &serversModel, const std::shared_ptr<Settings> &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<ServersModel> m_serversModel;
std::shared_ptr<Settings> m_settings;
};
#endif // APICONFIGSCONTROLLER_H

View file

@ -19,9 +19,14 @@ namespace
} }
ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel, const std::shared_ptr<Settings> &settings, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
QObject *parent) const QSharedPointer<ApiCountryModel> &apiCountryModel,
: QObject(parent), m_serversModel(serversModel), m_apiAccountInfoModel(apiAccountInfoModel), m_settings(settings) const std::shared_ptr<Settings> &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; return true;
} }
void ApiSettingsController::updateApiCountryModel()
{
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
}

View file

@ -4,6 +4,7 @@
#include <QObject> #include <QObject>
#include "ui/models/api/apiAccountInfoModel.h" #include "ui/models/api/apiAccountInfoModel.h"
#include "ui/models/apiCountryModel.h"
#include "ui/models/servers_model.h" #include "ui/models/servers_model.h"
class ApiSettingsController : public QObject class ApiSettingsController : public QObject
@ -11,15 +12,18 @@ class ApiSettingsController : public QObject
Q_OBJECT Q_OBJECT
public: public:
ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel, ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr); const QSharedPointer<ApiCountryModel> &apiCountryModel, const std::shared_ptr<Settings> &settings,
QObject *parent = nullptr);
~ApiSettingsController(); ~ApiSettingsController();
public slots: public slots:
bool getAccountInfo(); bool getAccountInfo();
void updateApiCountryModel();
private: private:
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel; QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
QSharedPointer<ApiCountryModel> m_apiCountryModel;
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
}; };

View file

@ -1,2 +0,0 @@
#include "importController.h"

View file

@ -1,24 +0,0 @@
#ifndef IMPORTCONTROLLER_H
#define IMPORTCONTROLLER_H
#include <QObject>
#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> &serversModel, QSharedPointer<AccountInfoModel> &accountInfoModel);
// ~ImportController();
// private:
// QSharedPointer<ServersModel> m_serversModel;
// QSharedPointer<AccountInfoModel> m_accountInfoModel;
// };
// }
#endif // IMPORTCONTROLLER_H

View file

@ -8,7 +8,7 @@
#include <QtConcurrent> #include <QtConcurrent>
#include "core/controllers/vpnConfigurationController.h" #include "core/controllers/vpnConfigurationController.h"
#include "core/enums/apiEnums.h" #include "core/api/apiDefs.h"
#include "version.h" #include "version.h"
ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &serversModel, ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &serversModel,
@ -48,15 +48,15 @@ void ConnectionController::openConnection()
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing); emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
if (configVersion == ApiConfigSources::Telegram if (configVersion == apiDefs::ConfigSource::Telegram
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit updateApiConfigFromTelegram(); emit updateApiConfigFromTelegram();
} else if (configVersion == ApiConfigSources::AmneziaGateway } else if (configVersion == apiDefs::ConfigSource::AmneziaGateway
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit updateApiConfigFromGateway(); emit updateApiConfigFromGateway();
} else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) { } else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) {
qDebug() << "attempt to update api config by expires_at event"; qDebug() << "attempt to update api config by expires_at event";
if (configVersion == ApiConfigSources::Telegram) { if (configVersion == apiDefs::ConfigSource::Telegram) {
emit updateApiConfigFromTelegram(); emit updateApiConfigFromTelegram();
} else { } else {
emit updateApiConfigFromGateway(); emit updateApiConfigFromGateway();

View file

@ -35,6 +35,7 @@ namespace PageLoader
PageSettingsApiAvailableCountries, PageSettingsApiAvailableCountries,
PageSettingsApiSupport, PageSettingsApiSupport,
PageSettingsApiInstructions, PageSettingsApiInstructions,
PageSettingsApiNativeConfigs,
PageServiceSftpSettings, PageServiceSftpSettings,
PageServiceTorWebsiteSettings, PageServiceTorWebsiteSettings,

View file

@ -12,9 +12,9 @@ namespace
namespace configKey namespace configKey
{ {
constexpr char availableCountries[] = "available_countries"; constexpr char availableCountries[] = "available_countries";
constexpr char serverCountryCode[] = "server_country_code"; // constexpr char serverCountryCode[] = "server_country_code";
constexpr char serverCountryName[] = "server_country_name"; // constexpr char serverCountryName[] = "server_country_name";
constexpr char lastUpdated[] = "last_updated"; // constexpr char lastUpdated[] = "last_updated";
constexpr char activeDeviceCount[] = "active_device_count"; constexpr char activeDeviceCount[] = "active_device_count";
constexpr char maxDeviceCount[] = "max_device_count"; constexpr char maxDeviceCount[] = "max_device_count";
constexpr char subscriptionEndDate[] = "subscription_end_date"; constexpr char subscriptionEndDate[] = "subscription_end_date";
@ -38,7 +38,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
switch (role) { switch (role) {
case SubscriptionStatusRole: { case SubscriptionStatusRole: {
return ApiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active"); return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active");
} }
case EndDateRole: { case EndDateRole: {
return QDateTime::fromString(m_accountInfoData.subscriptionEndDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); 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: { case ConnectedDevicesRole: {
return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount); return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount);
} }
// case ServiceDescriptionRole: { case ServiceDescriptionRole: {
// return apiServiceData.serviceInfo.name; 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(); return QVariant();
@ -60,21 +66,23 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons
AccountInfoData accountInfoData; AccountInfoData accountInfoData;
auto availableCountries = accountInfoObject.value(configKey::availableCountries).toArray(); m_availableCountries = accountInfoObject.value(configKey::availableCountries).toArray();
for (const auto &country : availableCountries) { // for (const auto &country : availableCountries) {
auto countryObject = country.toObject(); // auto countryObject = country.toObject();
CountryInfo countryInfo; // CountryInfo countryInfo;
countryInfo.serverCountryCode = countryObject.value(configKey::serverCountryCode).toString(); // countryInfo.serverCountryCode = countryObject.value(configKey::serverCountryCode).toString();
countryInfo.serverCountryName = countryObject.value(configKey::serverCountryName).toString(); // countryInfo.serverCountryName = countryObject.value(configKey::serverCountryName).toString();
countryInfo.lastUpdated = countryObject.value(configKey::lastUpdated).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.activeDeviceCount = accountInfoObject.value(configKey::activeDeviceCount).toInt();
accountInfoData.maxDeviceCount = accountInfoObject.value(configKey::maxDeviceCount).toInt(); accountInfoData.maxDeviceCount = accountInfoObject.value(configKey::maxDeviceCount).toInt();
accountInfoData.subscriptionEndDate = accountInfoObject.value(configKey::subscriptionEndDate).toString(); accountInfoData.subscriptionEndDate = accountInfoObject.value(configKey::subscriptionEndDate).toString();
accountInfoData.configType = apiUtils::getConfigType(serverConfig);
m_accountInfoData = accountInfoData; m_accountInfoData = accountInfoData;
endResetModel(); endResetModel();
@ -93,6 +101,11 @@ QVariant ApiAccountInfoModel::data(const QString &roleString)
return {}; return {};
} }
QJsonArray ApiAccountInfoModel::getAvailableCountries()
{
return m_availableCountries;
}
QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;

View file

@ -5,6 +5,8 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include "core/api/apiDefs.h"
class ApiAccountInfoModel : public QAbstractListModel class ApiAccountInfoModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
@ -27,29 +29,23 @@ public slots:
void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig); void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig);
QVariant data(const QString &roleString); QVariant data(const QString &roleString);
QJsonArray getAvailableCountries();
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
private: private:
struct CountryInfo
{
QString serverCountryCode;
QString serverCountryName;
QString lastUpdated;
};
struct AccountInfoData struct AccountInfoData
{ {
QString subscriptionEndDate; QString subscriptionEndDate;
int activeDeviceCount; int activeDeviceCount;
int maxDeviceCount; int maxDeviceCount;
QString vpnKey; apiDefs::ConfigType configType;
QVector<CountryInfo> AvailableCountries;
}; };
AccountInfoData m_accountInfoData; AccountInfoData m_accountInfoData;
QJsonArray m_availableCountries;
}; };
#endif // APIACCOUNTINFOMODEL_H #endif // APIACCOUNTINFOMODEL_H

View file

@ -1,7 +1,7 @@
#include "servers_model.h" #include "servers_model.h"
#include "core/api/apiDefs.h"
#include "core/controllers/serverController.h" #include "core/controllers/serverController.h"
#include "core/enums/apiEnums.h"
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
@ -132,10 +132,10 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
return serverHasInstalledContainers(index.row()); return serverHasInstalledContainers(index.row());
} }
case IsServerFromTelegramApiRole: { case IsServerFromTelegramApiRole: {
return server.value(config_key::configVersion).toInt() == ApiConfigSources::Telegram; return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::Telegram;
} }
case IsServerFromGatewayApiRole: { case IsServerFromGatewayApiRole: {
return server.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway; return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway;
} }
case ApiConfigRole: { case ApiConfigRole: {
return apiConfig; return apiConfig;

View file

@ -112,11 +112,13 @@ ListView {
ServersModel.processedIndex = index ServersModel.processedIndex = index
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
ApiSettingsController.getAccountInfo()
if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
} else { } else {
PageController.showBusyIndicator(true)
ApiSettingsController.getAccountInfo()
PageController.showBusyIndicator(false)
PageController.goToPage(PageEnum.PageSettingsApiServerInfo) PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
} }
} else { } else {

View file

@ -299,11 +299,13 @@ PageType {
ServersModel.processedIndex = ServersModel.defaultIndex ServersModel.processedIndex = ServersModel.defaultIndex
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
ApiSettingsController.getAccountInfo()
if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
} else { } else {
PageController.showBusyIndicator(true)
ApiSettingsController.getAccountInfo()
PageController.showBusyIndicator(false)
PageController.goToPage(PageEnum.PageSettingsApiServerInfo) PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
} }
} else { } else {

View file

@ -92,6 +92,10 @@ PageType {
descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription")
actionButtonFunction: function() { actionButtonFunction: function() {
PageController.showBusyIndicator(true)
ApiSettingsController.getAccountInfo()
PageController.showBusyIndicator(false)
PageController.goToPage(PageEnum.PageSettingsApiServerInfo) PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
} }
} }

View file

@ -20,31 +20,67 @@ PageType {
id: windows id: windows
readonly property string title: qsTr("Windows") readonly property string title: qsTr("Windows")
readonly property string imageSource: "qrc:/images/controls/external-link.svg" readonly property string link: qsTr("")
readonly property var handler: function() { }
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
} 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 { QtObject {
id: linux id: linux
readonly property string title: qsTr("Windows") readonly property string title: qsTr("Linux")
readonly property string imageSource: "qrc:/images/controls/external-link.svg" readonly property string link: qsTr("")
readonly property var handler: function() { }
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
} QtObject {
id: routers
readonly property string title: qsTr("Routers")
readonly property string link: qsTr("")
} }
property list<QtObject> instructionsModel: [ property list<QtObject> instructionsModel: [
windows, windows,
linux macos,
android,
androidTv,
ios,
linux,
routers
] ]
ListViewType { ListViewType {
id: listView id: listView
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 20
anchors.bottomMargin: 24
model: instructionsModel model: instructionsModel
@ -62,8 +98,8 @@ PageType {
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: "Support" headerText: qsTr("How to connect on another device")
descriptionText: qsTr("Our technical support specialists are ready to help you at any time") descriptionText: qsTr("Instructions on the Amnezia website")
} }
} }
@ -76,9 +112,11 @@ PageType {
Layout.topMargin: 6 Layout.topMargin: 6
text: title text: title
leftImageSource: imageSource rightImageSource: "qrc:/images/controls/external-link.svg"
clickedFunction: handler clickedFunction: function() {
Qt.openUrlExternally(link)
}
} }
DividerType {} DividerType {}

View file

@ -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 {}
}
}
}

View file

@ -105,7 +105,7 @@ PageType {
actionButtonImage: "qrc:/images/controls/edit-3.svg" actionButtonImage: "qrc:/images/controls/edit-3.svg"
headerText: root.processedServer.name headerText: root.processedServer.name
descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") descriptionText: ApiAccountInfoModel.data("serviceDescription")
actionButtonFunction: function() { actionButtonFunction: function() {
serverNameEditDrawer.openTriggered() serverNameEditDrawer.openTriggered()
@ -145,6 +145,8 @@ PageType {
spacing: 0 spacing: 0
LabelWithButtonType { LabelWithButtonType {
id: vpnKey
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 32 Layout.topMargin: 32
@ -157,17 +159,22 @@ PageType {
} }
} }
DividerType {} DividerType {
visible: false
}
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: vpnKey.visible ? 0 : 32
text: qsTr("Configuration files") 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" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
ApiSettingsController.updateApiCountryModel()
PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs)
} }
} }

View file

@ -37,7 +37,7 @@ PageType {
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: "Support" headerText: qsTr("Support")
descriptionText: qsTr("Our technical support specialists are ready to help you at any time") descriptionText: qsTr("Our technical support specialists are ready to help you at any time")
} }

View file

@ -95,7 +95,9 @@ PageType {
ServersModel.processedIndex = index ServersModel.processedIndex = index
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
PageController.showBusyIndicator(true)
ApiSettingsController.getAccountInfo() ApiSettingsController.getAccountInfo()
PageController.showBusyIndicator(false)
PageController.goToPage(PageEnum.PageSettingsApiServerInfo) PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
} else { } else {