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()
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)

View file

@ -2,6 +2,8 @@
#include <QClipboard>
#include <QFontDatabase>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMimeData>
#include <QQuickItem>
#include <QQuickStyle>
@ -10,8 +12,6 @@
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QLocalSocket>
#include <QLocalServer>
#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());
}

View file

@ -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<AppSplitTunnelingController> m_appSplitTunnelingController;
QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController;
QNetworkAccessManager *m_nam;

View file

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

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 <QDateTime>
#include <QJsonObject>
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;
}
};
}

View file

@ -3,9 +3,15 @@
#include <QObject>
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

View file

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

View file

@ -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);

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/Pages2/PageSettingsApiSupport.qml</file>
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
</qresource>
<qresource prefix="/countriesFlags">
<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,
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel, const std::shared_ptr<Settings> &settings,
QObject *parent)
: QObject(parent), m_serversModel(serversModel), m_apiAccountInfoModel(apiAccountInfoModel), m_settings(settings)
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
const QSharedPointer<ApiCountryModel> &apiCountryModel,
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;
}
void ApiSettingsController::updateApiCountryModel()
{
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
}

View file

@ -4,6 +4,7 @@
#include <QObject>
#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> &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();
public slots:
bool getAccountInfo();
void updateApiCountryModel();
private:
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
QSharedPointer<ApiCountryModel> m_apiCountryModel;
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 "core/controllers/vpnConfigurationController.h"
#include "core/enums/apiEnums.h"
#include "core/api/apiDefs.h"
#include "version.h"
ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &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();

View file

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

View file

@ -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<int, QByteArray> ApiAccountInfoModel::roleNames() const
{
QHash<int, QByteArray> roles;

View file

@ -5,6 +5,8 @@
#include <QJsonArray>
#include <QJsonObject>
#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<int, QByteArray> roleNames() const override;
private:
struct CountryInfo
{
QString serverCountryCode;
QString serverCountryName;
QString lastUpdated;
};
struct AccountInfoData
{
QString subscriptionEndDate;
int activeDeviceCount;
int maxDeviceCount;
QString vpnKey;
QVector<CountryInfo> AvailableCountries;
apiDefs::ConfigType configType;
};
AccountInfoData m_accountInfoData;
QJsonArray m_availableCountries;
};
#endif // APIACCOUNTINFOMODEL_H

View file

@ -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;

View file

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

View file

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

View file

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

View file

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

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"
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)
}
}

View file

@ -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")
}

View file

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