mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-07-03 14:07:39 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f2898062b | |||
| ff1dea2fdd |
+2
-2
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
|||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.8.4.3
|
project(${PROJECT} VERSION 4.8.4.1
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 2080)
|
set(APP_ANDROID_VERSION_CODE 2078)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTemporaryDir>
|
#include <QTemporaryDir>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
@@ -20,17 +19,13 @@
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings,
|
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
const QSharedPointer<ServerController> &serverController, bool isAwg,
|
bool isAwg, QObject *parent)
|
||||||
QObject *parent)
|
|
||||||
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
||||||
{
|
{
|
||||||
m_serverConfigPath =
|
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||||
m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
||||||
m_serverPublicKeyPath =
|
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
||||||
m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
|
||||||
m_serverPskKeyPath =
|
|
||||||
m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
|
||||||
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
||||||
|
|
||||||
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
||||||
@@ -68,31 +63,9 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QHostAddress> WireguardConfigurator::getIpsFromConf(const QString &input)
|
|
||||||
{
|
|
||||||
QRegularExpression regex("AllowedIPs = (\\d+\\.\\d+\\.\\d+\\.\\d+)");
|
|
||||||
QRegularExpressionMatchIterator matchIterator = regex.globalMatch(input);
|
|
||||||
|
|
||||||
QList<QHostAddress> ips;
|
|
||||||
|
|
||||||
while (matchIterator.hasNext()) {
|
|
||||||
QRegularExpressionMatch match = matchIterator.next();
|
|
||||||
const QString address_string { match.captured(1) };
|
|
||||||
const QHostAddress address { address_string };
|
|
||||||
if (address.isNull()) {
|
|
||||||
qWarning() << "Couldn't recognize the ip address: " << address_string;
|
|
||||||
} else {
|
|
||||||
ips << address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips;
|
|
||||||
}
|
|
||||||
|
|
||||||
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
||||||
DockerContainer container,
|
DockerContainer container,
|
||||||
const QJsonObject &containerConfig,
|
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||||
ErrorCode &errorCode)
|
|
||||||
{
|
{
|
||||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||||
connData.host = credentials.hostName;
|
connData.host = credentials.hostName;
|
||||||
@@ -103,45 +76,65 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
// Get list of already created clients (only IP addresses)
|
||||||
QString stdOut;
|
QString nextIpNumber;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
{
|
||||||
stdOut += data + "\n";
|
QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
||||||
return ErrorCode::NoError;
|
QString stdOut;
|
||||||
};
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
|
stdOut += data + "\n";
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
};
|
||||||
|
|
||||||
errorCode = m_serverController->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
|
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
auto ips = getIpsFromConf(stdOut);
|
|
||||||
|
|
||||||
QHostAddress nextIp = [&] {
|
stdOut.replace("AllowedIPs = ", "");
|
||||||
QHostAddress result;
|
stdOut.replace("/32", "");
|
||||||
QHostAddress lastIp;
|
QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts);
|
||||||
if (ips.empty()) {
|
|
||||||
lastIp.setAddress(containerConfig.value(m_protocolName)
|
// remove extra IPs from each line for case when user manually edited the wg0.conf
|
||||||
.toObject()
|
// and added there more IPs for route his itnernal networks, like:
|
||||||
.value(config_key::subnet_address)
|
// ...
|
||||||
.toString(protocols::wireguard::defaultSubnetAddress));
|
// AllowedIPs = 10.8.1.6/32, 192.168.1.0/24, 192.168.2.0/24, ...
|
||||||
|
// ...
|
||||||
|
// without this code - next IP would be 1 if last item in 'ips' has format above
|
||||||
|
QStringList vpnIps;
|
||||||
|
for (const auto &ip : ips) {
|
||||||
|
vpnIps.append(ip.split(",", Qt::SkipEmptyParts).first().trimmed());
|
||||||
|
}
|
||||||
|
ips = vpnIps;
|
||||||
|
|
||||||
|
// Calc next IP address
|
||||||
|
if (ips.isEmpty()) {
|
||||||
|
nextIpNumber = "2";
|
||||||
} else {
|
} else {
|
||||||
lastIp = ips.last();
|
int next = ips.last().split(".").last().toInt() + 1;
|
||||||
|
if (next > 254) {
|
||||||
|
errorCode = ErrorCode::AddressPoolError;
|
||||||
|
return connData;
|
||||||
|
}
|
||||||
|
nextIpNumber = QString::number(next);
|
||||||
}
|
}
|
||||||
quint8 lastOctet = static_cast<quint8>(lastIp.toIPv4Address());
|
}
|
||||||
switch (lastOctet) {
|
|
||||||
case 254: result.setAddress(lastIp.toIPv4Address() + 3); break;
|
QString subnetIp = containerConfig.value(m_protocolName).toObject().value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||||
case 255: result.setAddress(lastIp.toIPv4Address() + 2); break;
|
{
|
||||||
default: result.setAddress(lastIp.toIPv4Address() + 1); break;
|
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
||||||
|
if (l.isEmpty()) {
|
||||||
|
errorCode = ErrorCode::AddressPoolError;
|
||||||
|
return connData;
|
||||||
}
|
}
|
||||||
|
l.removeLast();
|
||||||
|
l.append(nextIpNumber);
|
||||||
|
|
||||||
return result;
|
connData.clientIP = l.join(".");
|
||||||
}();
|
}
|
||||||
|
|
||||||
connData.clientIP = nextIp.toString();
|
|
||||||
|
|
||||||
// Get keys
|
// Get keys
|
||||||
connData.serverPubKey =
|
connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
||||||
m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
|
||||||
connData.serverPubKey.replace("\n", "");
|
connData.serverPubKey.replace("\n", "");
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return connData;
|
return connData;
|
||||||
@@ -168,12 +161,10 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
|
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'").arg(m_serverConfigPath);
|
||||||
.arg(m_serverConfigPath);
|
|
||||||
|
|
||||||
errorCode = m_serverController->runScript(
|
errorCode = m_serverController->runScript(
|
||||||
credentials,
|
credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
||||||
m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
|
||||||
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
@@ -182,8 +173,8 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
|||||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||||
QString config = m_serverController->replaceVars(
|
QString config =
|
||||||
scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
@@ -217,16 +208,16 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
|||||||
return QJsonDocument(jConfig).toJson();
|
return QJsonDocument(jConfig).toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns,
|
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
const bool isApiConfig, QString &protocolConfigString)
|
QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
return protocolConfigString;
|
return protocolConfigString;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns,
|
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
const bool isApiConfig, QString &protocolConfigString)
|
QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#ifndef WIREGUARD_CONFIGURATOR_H
|
#ifndef WIREGUARD_CONFIGURATOR_H
|
||||||
#define WIREGUARD_CONFIGURATOR_H
|
#define WIREGUARD_CONFIGURATOR_H
|
||||||
|
|
||||||
#include <QHostAddress>
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
|
|
||||||
@@ -13,8 +12,8 @@ class WireguardConfigurator : public ConfiguratorBase
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg,
|
||||||
bool isAwg, QObject *parent = nullptr);
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
struct ConnectionData
|
struct ConnectionData
|
||||||
{
|
{
|
||||||
@@ -27,18 +26,15 @@ public:
|
|||||||
QString port;
|
QString port;
|
||||||
};
|
};
|
||||||
|
|
||||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
ErrorCode &errorCode);
|
||||||
|
|
||||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||||
QString &protocolConfigString);
|
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||||
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
|
||||||
QString &protocolConfigString);
|
|
||||||
|
|
||||||
static ConnectionData genClientKeys();
|
static ConnectionData genClientKeys();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<QHostAddress> getIpsFromConf(const QString &input);
|
|
||||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
|
|
||||||
|
|||||||
@@ -93,9 +93,6 @@ void CoreController::initModels()
|
|||||||
|
|
||||||
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
|
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
|
m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
|
||||||
|
|
||||||
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
|
||||||
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initControllers()
|
void CoreController::initControllers()
|
||||||
@@ -135,8 +132,7 @@ void CoreController::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(
|
m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_settings));
|
||||||
new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, 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_apiServicesModel, m_settings));
|
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
||||||
|
|||||||
@@ -25,9 +25,8 @@
|
|||||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||||
#endif
|
#endif
|
||||||
#include "ui/models/api/apiAccountInfoModel.h"
|
#include "ui/models/api/apiAccountInfoModel.h"
|
||||||
#include "ui/models/api/apiCountryModel.h"
|
|
||||||
#include "ui/models/api/apiDevicesModel.h"
|
|
||||||
#include "ui/models/api/apiServicesModel.h"
|
#include "ui/models/api/apiServicesModel.h"
|
||||||
|
#include "ui/models/api/apiCountryModel.h"
|
||||||
#include "ui/models/appSplitTunnelingModel.h"
|
#include "ui/models/appSplitTunnelingModel.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
#include "ui/models/protocols/awgConfigModel.h"
|
#include "ui/models/protocols/awgConfigModel.h"
|
||||||
@@ -118,7 +117,6 @@ private:
|
|||||||
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||||
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
|
||||||
|
|
||||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ namespace
|
|||||||
constexpr char apiPayload[] = "api_payload";
|
constexpr char apiPayload[] = "api_payload";
|
||||||
constexpr char keyPayload[] = "key_payload";
|
constexpr char keyPayload[] = "key_payload";
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
|
|
||||||
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
|
|
||||||
constexpr QLatin1String errorResponsePattern3("Account not found.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent)
|
GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent)
|
||||||
@@ -152,7 +148,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
|
|
||||||
QByteArray encryptedResponseBody = reply->readAll();
|
QByteArray encryptedResponseBody = reply->readAll();
|
||||||
|
|
||||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, false)) {
|
||||||
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
||||||
request.setUrl(url);
|
request.setUrl(url);
|
||||||
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||||
@@ -161,12 +157,12 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
||||||
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
||||||
encryptedResponseBody = nestedReply->readAll();
|
encryptedResponseBody = nestedReply->readAll();
|
||||||
reply = nestedReply;
|
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
|
||||||
sslErrors = nestedSslErrors;
|
sslErrors = nestedSslErrors;
|
||||||
return false;
|
reply = nestedReply;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||||
@@ -216,45 +212,45 @@ QStringList GatewayController::getProxyUrls()
|
|||||||
wait.exec();
|
wait.exec();
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||||
auto encryptedResponseBody = reply->readAll();
|
break;
|
||||||
reply->deleteLater();
|
|
||||||
|
|
||||||
EVP_PKEY *privateKey = nullptr;
|
|
||||||
QByteArray responseBody;
|
|
||||||
try {
|
|
||||||
if (!m_isDevEnvironment) {
|
|
||||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
|
||||||
hash.addData(key);
|
|
||||||
QByteArray hashResult = hash.result().toHex();
|
|
||||||
|
|
||||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
|
||||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
|
||||||
|
|
||||||
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
|
||||||
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
|
||||||
} else {
|
|
||||||
responseBody = encryptedResponseBody;
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
Utils::logException();
|
|
||||||
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
|
||||||
|
|
||||||
QStringList endpoints;
|
|
||||||
for (const auto &endpoint : endpointsArray) {
|
|
||||||
endpoints.push_back(endpoint.toString());
|
|
||||||
}
|
|
||||||
return endpoints;
|
|
||||||
} else {
|
|
||||||
reply->deleteLater();
|
|
||||||
}
|
}
|
||||||
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
return {};
|
|
||||||
|
auto encryptedResponseBody = reply->readAll();
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
EVP_PKEY *privateKey = nullptr;
|
||||||
|
QByteArray responseBody;
|
||||||
|
try {
|
||||||
|
if (!m_isDevEnvironment) {
|
||||||
|
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||||
|
hash.addData(key);
|
||||||
|
QByteArray hashResult = hash.result().toHex();
|
||||||
|
|
||||||
|
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||||
|
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||||
|
|
||||||
|
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
||||||
|
|
||||||
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||||
|
} else {
|
||||||
|
responseBody = encryptedResponseBody;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||||
|
|
||||||
|
QStringList endpoints;
|
||||||
|
for (const auto &endpoint : endpointsArray) {
|
||||||
|
endpoints.push_back(endpoint.toString());
|
||||||
|
}
|
||||||
|
return endpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
||||||
@@ -266,15 +262,6 @@ bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray
|
|||||||
} else if (responseBody.contains("html")) {
|
} else if (responseBody.contains("html")) {
|
||||||
qDebug() << "The response contains an html tag";
|
qDebug() << "The response contains an html tag";
|
||||||
return true;
|
return true;
|
||||||
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
|
|
||||||
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
|
||||||
|| responseBody.contains(errorResponsePattern3)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
|
||||||
return true;
|
|
||||||
} else if (checkEncryption) {
|
} else if (checkEncryption) {
|
||||||
try {
|
try {
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
@@ -309,7 +296,7 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl
|
|||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
wait.exec();
|
wait.exec();
|
||||||
|
|
||||||
if (replyProcessingFunction(reply, sslErrors)) {
|
if (!replyProcessingFunction(reply, sslErrors)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -709,7 +709,7 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
||||||
|
|
||||||
// TODO reimplement with netstat
|
// TODO reimplement with netstat
|
||||||
QString script = QString("sudo ls0f -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
||||||
for (auto &port : fixedPorts) {
|
for (auto &port : fixedPorts) {
|
||||||
script = script.append("|:%1").arg(port);
|
script = script.append("|:%1").arg(port);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M20 3H4C2.89543 3 2 3.89543 2 5V15C2 16.1046 2.89543 17 4 17H20C21.1046 17 22 16.1046 22 15V5C22 3.89543 21.1046 3 20 3Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M8 21H16" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M12 17V21" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 522 B |
@@ -14,15 +14,10 @@ extension UIApplication {
|
|||||||
var keyWindows: [UIWindow] {
|
var keyWindows: [UIWindow] {
|
||||||
connectedScenes
|
connectedScenes
|
||||||
.compactMap {
|
.compactMap {
|
||||||
guard let windowScene = $0 as? UIWindowScene else { return nil }
|
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
guard let keywindow = windowScene.keyWindow else {
|
($0 as? UIWindowScene)?.keyWindow
|
||||||
windowScene.windows.first?.makeKey()
|
|
||||||
return windowScene.windows.first
|
|
||||||
}
|
|
||||||
return keywindow
|
|
||||||
} else {
|
} else {
|
||||||
return windowScene.windows.first { $0.isKeyWindow }
|
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,8 +229,6 @@
|
|||||||
<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>
|
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
|
|
||||||
<file>images/controls/monitor.svg</file>
|
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/countriesFlags">
|
<qresource prefix="/countriesFlags">
|
||||||
<file>images/flagKit/ZW.svg</file>
|
<file>images/flagKit/ZW.svg</file>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
#include "apiConfigsController.h"
|
#include "apiConfigsController.h"
|
||||||
|
|
||||||
#include <QClipboard>
|
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
|
#include <QClipboard>
|
||||||
|
|
||||||
#include "amnezia_application.h"
|
#include "amnezia_application.h"
|
||||||
#include "configurators/wireguard_configurator.h"
|
#include "configurators/wireguard_configurator.h"
|
||||||
@@ -251,7 +251,6 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
|||||||
|
|
||||||
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
||||||
newServerConfig.insert(configKey::authData, authData);
|
newServerConfig.insert(configKey::authData, authData);
|
||||||
// newServerConfig.insert(
|
|
||||||
|
|
||||||
m_serversModel->editServer(newServerConfig, serverIndex);
|
m_serversModel->editServer(newServerConfig, serverIndex);
|
||||||
if (reloadServiceConfig) {
|
if (reloadServiceConfig) {
|
||||||
@@ -355,43 +354,6 @@ bool ApiConfigsController::deactivateDevice()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode)
|
|
||||||
{
|
|
||||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
|
||||||
|
|
||||||
auto serverIndex = m_serversModel->getProcessedServerIndex();
|
|
||||||
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
|
||||||
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
|
||||||
|
|
||||||
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString();
|
|
||||||
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
|
||||||
|
|
||||||
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
|
||||||
apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode);
|
|
||||||
apiPayload[configKey::serverCountryCode] = serverCountryCode;
|
|
||||||
apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType);
|
|
||||||
apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData);
|
|
||||||
apiPayload[configKey::uuid] = uuid;
|
|
||||||
|
|
||||||
QByteArray responseBody;
|
|
||||||
ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody);
|
|
||||||
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
|
|
||||||
emit errorOccurred(errorCode);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uuid == m_settings->getInstallationUuid(true)) {
|
|
||||||
serverConfigObject.remove(config_key::containers);
|
|
||||||
m_serversModel->editServer(serverConfigObject, serverIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ApiConfigsController::isConfigValid()
|
bool ApiConfigsController::isConfigValid()
|
||||||
{
|
{
|
||||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||||
@@ -407,7 +369,7 @@ bool ApiConfigsController::isConfigValid()
|
|||||||
return updateServiceFromGateway(serverIndex, "", "");
|
return updateServiceFromGateway(serverIndex, "", "");
|
||||||
} else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) {
|
} else if (configSource && 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 (configSource == apiDefs::ConfigSource::AmneziaGateway) {
|
if (configSource == apiDefs::ConfigSource::Telegram) {
|
||||||
return updateServiceFromGateway(serverIndex, "", "");
|
return updateServiceFromGateway(serverIndex, "", "");
|
||||||
} else {
|
} else {
|
||||||
m_serversModel->removeApiConfig(serverIndex);
|
m_serversModel->removeApiConfig(serverIndex);
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ public slots:
|
|||||||
bool reloadServiceConfig = false);
|
bool reloadServiceConfig = false);
|
||||||
bool updateServiceFromTelegram(const int serverIndex);
|
bool updateServiceFromTelegram(const int serverIndex);
|
||||||
bool deactivateDevice();
|
bool deactivateDevice();
|
||||||
bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
|
|
||||||
|
|
||||||
bool isConfigValid();
|
bool isConfigValid();
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,11 @@ namespace
|
|||||||
ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel,
|
ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel,
|
||||||
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
||||||
const QSharedPointer<ApiCountryModel> &apiCountryModel,
|
const QSharedPointer<ApiCountryModel> &apiCountryModel,
|
||||||
const QSharedPointer<ApiDevicesModel> &apiDevicesModel,
|
|
||||||
const std::shared_ptr<Settings> &settings, QObject *parent)
|
const std::shared_ptr<Settings> &settings, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
m_serversModel(serversModel),
|
m_serversModel(serversModel),
|
||||||
m_apiAccountInfoModel(apiAccountInfoModel),
|
m_apiAccountInfoModel(apiAccountInfoModel),
|
||||||
m_apiCountryModel(apiCountryModel),
|
m_apiCountryModel(apiCountryModel),
|
||||||
m_apiDevicesModel(apiDevicesModel),
|
|
||||||
m_settings(settings)
|
m_settings(settings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -75,7 +73,6 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
|||||||
|
|
||||||
if (reload) {
|
if (reload) {
|
||||||
updateApiCountryModel();
|
updateApiCountryModel();
|
||||||
updateApiDevicesModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -86,8 +83,3 @@ void ApiSettingsController::updateApiCountryModel()
|
|||||||
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
|
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
|
||||||
m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo());
|
m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiSettingsController::updateApiDevicesModel()
|
|
||||||
{
|
|
||||||
m_apiDevicesModel->updateModel(m_apiAccountInfoModel->getIssuedConfigsInfo());
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include "ui/models/api/apiAccountInfoModel.h"
|
#include "ui/models/api/apiAccountInfoModel.h"
|
||||||
#include "ui/models/api/apiCountryModel.h"
|
#include "ui/models/api/apiCountryModel.h"
|
||||||
#include "ui/models/api/apiDevicesModel.h"
|
|
||||||
#include "ui/models/servers_model.h"
|
#include "ui/models/servers_model.h"
|
||||||
|
|
||||||
class ApiSettingsController : public QObject
|
class ApiSettingsController : public QObject
|
||||||
@@ -13,14 +12,13 @@ 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 QSharedPointer<ApiCountryModel> &apiCountryModel, const QSharedPointer<ApiDevicesModel> &apiDevicesModel,
|
const QSharedPointer<ApiCountryModel> &apiCountryModel, const std::shared_ptr<Settings> &settings,
|
||||||
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
|
QObject *parent = nullptr);
|
||||||
~ApiSettingsController();
|
~ApiSettingsController();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
bool getAccountInfo(bool reload);
|
bool getAccountInfo(bool reload);
|
||||||
void updateApiCountryModel();
|
void updateApiCountryModel();
|
||||||
void updateApiDevicesModel();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(ErrorCode errorCode);
|
void errorOccurred(ErrorCode errorCode);
|
||||||
@@ -29,7 +27,6 @@ private:
|
|||||||
QSharedPointer<ServersModel> m_serversModel;
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
|
||||||
|
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ namespace
|
|||||||
ConfigTypes checkConfigFormat(const QString &config)
|
ConfigTypes checkConfigFormat(const QString &config)
|
||||||
{
|
{
|
||||||
const QString openVpnConfigPatternCli = "client";
|
const QString openVpnConfigPatternCli = "client";
|
||||||
|
const QString openVpnConfigPatternProto1 = "proto tcp";
|
||||||
|
const QString openVpnConfigPatternProto2 = "proto udp";
|
||||||
const QString openVpnConfigPatternDriver1 = "dev tun";
|
const QString openVpnConfigPatternDriver1 = "dev tun";
|
||||||
const QString openVpnConfigPatternDriver2 = "dev tap";
|
const QString openVpnConfigPatternDriver2 = "dev tap";
|
||||||
|
|
||||||
@@ -51,13 +53,14 @@ namespace
|
|||||||
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
|
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
|
||||||
&& config.contains(amneziaConfigPatternPassword))) {
|
&& config.contains(amneziaConfigPatternPassword))) {
|
||||||
return ConfigTypes::Amnezia;
|
return ConfigTypes::Amnezia;
|
||||||
|
} else if (config.contains(openVpnConfigPatternCli)
|
||||||
|
&& (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2))
|
||||||
|
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
|
||||||
|
return ConfigTypes::OpenVpn;
|
||||||
} else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) {
|
} else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) {
|
||||||
return ConfigTypes::WireGuard;
|
return ConfigTypes::WireGuard;
|
||||||
} else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) {
|
} else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) {
|
||||||
return ConfigTypes::Xray;
|
return ConfigTypes::Xray;
|
||||||
} else if (config.contains(openVpnConfigPatternCli)
|
|
||||||
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
|
|
||||||
return ConfigTypes::OpenVpn;
|
|
||||||
}
|
}
|
||||||
return ConfigTypes::Invalid;
|
return ConfigTypes::Invalid;
|
||||||
}
|
}
|
||||||
@@ -342,7 +345,7 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
|
|||||||
arr.push_back(containers);
|
arr.push_back(containers);
|
||||||
|
|
||||||
QString hostName;
|
QString hostName;
|
||||||
const static QRegularExpression hostNameRegExp("remote\\s+([^\\s]+)");
|
const static QRegularExpression hostNameRegExp("remote (.*) [0-9]*");
|
||||||
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
|
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
|
||||||
if (hostNameMatch.hasMatch()) {
|
if (hostNameMatch.hasMatch()) {
|
||||||
hostName = hostNameMatch.captured(1);
|
hostName = hostNameMatch.captured(1);
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ namespace PageLoader
|
|||||||
PageSettingsApiSupport,
|
PageSettingsApiSupport,
|
||||||
PageSettingsApiInstructions,
|
PageSettingsApiInstructions,
|
||||||
PageSettingsApiNativeConfigs,
|
PageSettingsApiNativeConfigs,
|
||||||
PageSettingsApiDevices,
|
|
||||||
|
|
||||||
PageServiceSftpSettings,
|
PageServiceSftpSettings,
|
||||||
PageServiceTorWebsiteSettings,
|
PageServiceTorWebsiteSettings,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ void SitesController::addSite(QString hostname)
|
|||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
|
||||||
Q_ARG(QStringList, QStringList() << hostname));
|
Q_ARG(QStringList, QStringList() << hostname));
|
||||||
}
|
}
|
||||||
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) {
|
const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) {
|
||||||
@@ -74,6 +75,7 @@ void SitesController::removeSite(int index)
|
|||||||
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection,
|
||||||
Q_ARG(QStringList, QStringList() << hostname));
|
Q_ARG(QStringList, QStringList() << hostname));
|
||||||
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
|
||||||
|
|
||||||
emit finished(tr("Site removed: %1").arg(hostname));
|
emit finished(tr("Site removed: %1").arg(hostname));
|
||||||
}
|
}
|
||||||
@@ -122,6 +124,7 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting)
|
|||||||
m_sitesModel->addSites(sites, replaceExisting);
|
m_sitesModel->addSites(sites, replaceExisting);
|
||||||
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips));
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips));
|
||||||
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
|
||||||
|
|
||||||
emit finished(tr("Import completed"));
|
emit finished(tr("Import completed"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
case ServiceDescriptionRole: {
|
case ServiceDescriptionRole: {
|
||||||
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) {
|
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||||
return tr("Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. "
|
return tr("Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to 200 "
|
||||||
"Speeds up to 200 Mbps");
|
"Mb/s");
|
||||||
} else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
} 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 "
|
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.");
|
"more. YouTube is not included in the free plan.");
|
||||||
@@ -58,19 +58,6 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
case IsComponentVisibleRole: {
|
case IsComponentVisibleRole: {
|
||||||
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2;
|
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2;
|
||||||
}
|
}
|
||||||
case HasExpiredWorkerRole: {
|
|
||||||
for (int i = 0; i < m_issuedConfigsInfo.size(); i++) {
|
|
||||||
QJsonObject issuedConfigObject = m_issuedConfigsInfo.at(i).toObject();
|
|
||||||
|
|
||||||
auto lastDownloaded = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::lastDownloaded).toString());
|
|
||||||
auto workerLastUpdated = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString());
|
|
||||||
|
|
||||||
if (lastDownloaded < workerLastUpdated) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -137,7 +124,6 @@ QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
|
|||||||
roles[ConnectedDevicesRole] = "connectedDevices";
|
roles[ConnectedDevicesRole] = "connectedDevices";
|
||||||
roles[ServiceDescriptionRole] = "serviceDescription";
|
roles[ServiceDescriptionRole] = "serviceDescription";
|
||||||
roles[IsComponentVisibleRole] = "isComponentVisible";
|
roles[IsComponentVisibleRole] = "isComponentVisible";
|
||||||
roles[HasExpiredWorkerRole] = "hasExpiredWorker";
|
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ public:
|
|||||||
ConnectedDevicesRole,
|
ConnectedDevicesRole,
|
||||||
ServiceDescriptionRole,
|
ServiceDescriptionRole,
|
||||||
EndDateRole,
|
EndDateRole,
|
||||||
IsComponentVisibleRole,
|
IsComponentVisibleRole
|
||||||
HasExpiredWorkerRole
|
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ApiAccountInfoModel(QObject *parent = nullptr);
|
explicit ApiAccountInfoModel(QObject *parent = nullptr);
|
||||||
|
|||||||
@@ -44,9 +44,6 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
|
|||||||
case IsIssuedRole: {
|
case IsIssuedRole: {
|
||||||
return isIssued;
|
return isIssued;
|
||||||
}
|
}
|
||||||
case IsWorkerExpiredRole: {
|
|
||||||
return issuedConfigInfo.lastDownloaded < issuedConfigInfo.workerLastUpdated;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -117,6 +114,5 @@ QHash<int, QByteArray> ApiCountryModel::roleNames() const
|
|||||||
roles[CountryCodeRole] = "countryCode";
|
roles[CountryCodeRole] = "countryCode";
|
||||||
roles[CountryImageCodeRole] = "countryImageCode";
|
roles[CountryImageCodeRole] = "countryImageCode";
|
||||||
roles[IsIssuedRole] = "isIssued";
|
roles[IsIssuedRole] = "isIssued";
|
||||||
roles[IsWorkerExpiredRole] = "isWorkerExpired";
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ public:
|
|||||||
CountryNameRole = Qt::UserRole + 1,
|
CountryNameRole = Qt::UserRole + 1,
|
||||||
CountryCodeRole,
|
CountryCodeRole,
|
||||||
CountryImageCodeRole,
|
CountryImageCodeRole,
|
||||||
IsIssuedRole,
|
IsIssuedRole
|
||||||
IsWorkerExpiredRole
|
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ApiCountryModel(QObject *parent = nullptr);
|
explicit ApiCountryModel(QObject *parent = nullptr);
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
#include "apiDevicesModel.h"
|
|
||||||
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
#include "core/api/apiDefs.h"
|
|
||||||
#include "logger.h"
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
Logger logger("ApiDevicesModel");
|
|
||||||
|
|
||||||
constexpr QLatin1String gatewayAccount("gateway_account");
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiDevicesModel::ApiDevicesModel(std::shared_ptr<Settings> settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
int ApiDevicesModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(parent)
|
|
||||||
return m_issuedConfigs.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ApiDevicesModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
|
|
||||||
return QVariant();
|
|
||||||
|
|
||||||
IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.at(index.row());
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case OsVersionRole: {
|
|
||||||
return issuedConfigInfo.osVersion;
|
|
||||||
}
|
|
||||||
case SupportTagRole: {
|
|
||||||
return issuedConfigInfo.installationUuid;
|
|
||||||
}
|
|
||||||
case CountryCodeRole: {
|
|
||||||
return issuedConfigInfo.countryCode;
|
|
||||||
}
|
|
||||||
case LastUpdateRole: {
|
|
||||||
return QDateTime::fromString(issuedConfigInfo.lastDownloaded, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
|
|
||||||
}
|
|
||||||
case IsCurrentDeviceRole: {
|
|
||||||
return issuedConfigInfo.installationUuid == m_settings->getInstallationUuid(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiDevicesModel::updateModel(const QJsonArray &issuedConfigs)
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
|
|
||||||
m_issuedConfigs.clear();
|
|
||||||
for (int i = 0; i < issuedConfigs.size(); i++) {
|
|
||||||
IssuedConfigInfo issuedConfigInfo;
|
|
||||||
QJsonObject issuedConfigObject = issuedConfigs.at(i).toObject();
|
|
||||||
|
|
||||||
if (issuedConfigObject.value(apiDefs::key::sourceType).toString() != gatewayAccount) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
issuedConfigInfo.installationUuid = issuedConfigObject.value(apiDefs::key::installationUuid).toString();
|
|
||||||
issuedConfigInfo.workerLastUpdated = issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString();
|
|
||||||
issuedConfigInfo.lastDownloaded = issuedConfigObject.value(apiDefs::key::lastDownloaded).toString();
|
|
||||||
issuedConfigInfo.sourceType = issuedConfigObject.value(apiDefs::key::sourceType).toString();
|
|
||||||
issuedConfigInfo.osVersion = issuedConfigObject.value(apiDefs::key::osVersion).toString();
|
|
||||||
|
|
||||||
issuedConfigInfo.countryName = issuedConfigObject.value(apiDefs::key::serverCountryName).toString();
|
|
||||||
issuedConfigInfo.countryCode = issuedConfigObject.value(apiDefs::key::serverCountryCode).toString();
|
|
||||||
|
|
||||||
m_issuedConfigs.push_back(issuedConfigInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> ApiDevicesModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[OsVersionRole] = "osVersion";
|
|
||||||
roles[SupportTagRole] = "supportTag";
|
|
||||||
roles[CountryCodeRole] = "countryCode";
|
|
||||||
roles[LastUpdateRole] = "lastUpdate";
|
|
||||||
roles[IsCurrentDeviceRole] = "isCurrentDevice";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#ifndef APIDEVICESMODEL_H
|
|
||||||
#define APIDEVICESMODEL_H
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
#include "settings.h"
|
|
||||||
|
|
||||||
class ApiDevicesModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum Roles {
|
|
||||||
OsVersionRole = Qt::UserRole + 1,
|
|
||||||
SupportTagRole,
|
|
||||||
CountryCodeRole,
|
|
||||||
LastUpdateRole,
|
|
||||||
IsCurrentDeviceRole
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit ApiDevicesModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void updateModel(const QJsonArray &issuedConfigs);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct IssuedConfigInfo
|
|
||||||
{
|
|
||||||
QString installationUuid;
|
|
||||||
QString workerLastUpdated;
|
|
||||||
QString lastDownloaded;
|
|
||||||
QString sourceType;
|
|
||||||
QString osVersion;
|
|
||||||
|
|
||||||
QString countryName;
|
|
||||||
QString countryCode;
|
|
||||||
};
|
|
||||||
|
|
||||||
QVector<IssuedConfigInfo> m_issuedConfigs;
|
|
||||||
|
|
||||||
std::shared_ptr<Settings> m_settings;
|
|
||||||
};
|
|
||||||
#endif // APIDEVICESMODEL_H
|
|
||||||
@@ -65,8 +65,8 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
case CardDescriptionRole: {
|
case CardDescriptionRole: {
|
||||||
auto speed = apiServiceData.serviceInfo.speed;
|
auto speed = apiServiceData.serviceInfo.speed;
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium) {
|
||||||
return tr("Amnezia Premium is classic VPN for seamless work, downloading large files, and watching videos. "
|
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. "
|
||||||
"Access all websites and online resources. Speeds up to %1 Mbps.")
|
"Works for any sites with no restrictions. Speed up to %1 MBit/s. Unlimited traffic.")
|
||||||
.arg(speed);
|
.arg(speed);
|
||||||
} else if (serviceType == serviceType::amneziaFree) {
|
} else if (serviceType == serviceType::amneziaFree) {
|
||||||
QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
||||||
@@ -79,8 +79,8 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
case ServiceDescriptionRole: {
|
case ServiceDescriptionRole: {
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium) {
|
||||||
return tr("Amnezia Premium is classic VPN for for seamless work, downloading large files, and watching videos. "
|
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. "
|
||||||
"Access all websites and online resources.");
|
"Works for any sites with no restrictions.");
|
||||||
} else {
|
} else {
|
||||||
return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Rectangle {
|
|||||||
Layout.rightMargin: 10
|
Layout.rightMargin: 10
|
||||||
Layout.leftMargin: 10
|
Layout.leftMargin: 10
|
||||||
|
|
||||||
text: qsTr("Amnezia Premium - for access to all websites and online resources")
|
text: qsTr("Amnezia Premium - for access to any website")
|
||||||
color: AmneziaStyle.color.pearlGray
|
color: AmneziaStyle.color.pearlGray
|
||||||
|
|
||||||
lineHeight: 18
|
lineHeight: 18
|
||||||
|
|||||||
@@ -27,6 +27,5 @@ QtObject {
|
|||||||
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
|
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
|
||||||
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
||||||
readonly property color pearlGray: '#EAEAEC'
|
readonly property color pearlGray: '#EAEAEC'
|
||||||
readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ PageType {
|
|||||||
actionButtonImage: "qrc:/images/controls/settings.svg"
|
actionButtonImage: "qrc:/images/controls/settings.svg"
|
||||||
|
|
||||||
headerText: root.processedServer.name
|
headerText: root.processedServer.name
|
||||||
descriptionText: qsTr("Location for connection")
|
descriptionText: qsTr("Locations for connection")
|
||||||
|
|
||||||
actionButtonFunction: function() {
|
actionButtonFunction: function() {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import QtQuick.Dialogs
|
|
||||||
|
|
||||||
import QtCore
|
|
||||||
|
|
||||||
import SortFilterProxyModel 0.2
|
|
||||||
|
|
||||||
import PageEnum 1.0
|
|
||||||
import Style 1.0
|
|
||||||
|
|
||||||
import "./"
|
|
||||||
import "../Controls2"
|
|
||||||
import "../Controls2/TextTypes"
|
|
||||||
import "../Config"
|
|
||||||
import "../Components"
|
|
||||||
|
|
||||||
PageType {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
ListViewType {
|
|
||||||
id: listView
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.topMargin: 20
|
|
||||||
anchors.bottomMargin: 24
|
|
||||||
|
|
||||||
model: ApiDevicesModel
|
|
||||||
|
|
||||||
header: ColumnLayout {
|
|
||||||
width: listView.width
|
|
||||||
|
|
||||||
BackButtonType {
|
|
||||||
id: backButton
|
|
||||||
}
|
|
||||||
|
|
||||||
HeaderType {
|
|
||||||
id: header
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
Layout.leftMargin: 16
|
|
||||||
|
|
||||||
headerText: qsTr("Active Devices")
|
|
||||||
descriptionText: qsTr("Manage currently connected devices")
|
|
||||||
}
|
|
||||||
|
|
||||||
WarningType {
|
|
||||||
Layout.topMargin: 16
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
textString: qsTr("You can find the identifier on the Support tab or, for older versions of the app, "
|
|
||||||
+ "by tapping '+' and then the three dots at the top of the page.")
|
|
||||||
|
|
||||||
iconPath: "qrc:/images/controls/alert-circle.svg"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
|
||||||
width: listView.width
|
|
||||||
|
|
||||||
LabelWithButtonType {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 6
|
|
||||||
|
|
||||||
text: osVersion + (isCurrentDevice ? qsTr(" (current device)") : "")
|
|
||||||
descriptionText: qsTr("Support tag: ") + "\n" + supportTag + "\n" + qsTr("Last updated: ") + lastUpdate
|
|
||||||
rightImageSource: "qrc:/images/controls/trash.svg"
|
|
||||||
|
|
||||||
clickedFunction: function() {
|
|
||||||
if (isCurrentDevice && ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
|
||||||
PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var headerText = qsTr("Are you sure you want to unlink this device?")
|
|
||||||
var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.")
|
|
||||||
var yesButtonText = qsTr("Continue")
|
|
||||||
var noButtonText = qsTr("Cancel")
|
|
||||||
|
|
||||||
var yesButtonFunction = function() {
|
|
||||||
Qt.callLater(deactivateExternalDevice, supportTag, countryCode)
|
|
||||||
}
|
|
||||||
var noButtonFunction = function() {
|
|
||||||
}
|
|
||||||
|
|
||||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DividerType {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function deactivateExternalDevice(supportTag, countryCode) {
|
|
||||||
PageController.showBusyIndicator(true)
|
|
||||||
if (ApiConfigsController.deactivateExternalDevice(supportTag, countryCode)) {
|
|
||||||
ApiSettingsController.getAccountInfo(true)
|
|
||||||
}
|
|
||||||
PageController.showBusyIndicator(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -99,7 +99,7 @@ PageType {
|
|||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("How to connect on another device")
|
headerText: qsTr("How to connect on another device")
|
||||||
descriptionText: qsTr("Setup guides on the Amnezia website")
|
descriptionText: qsTr("Instructions on the Amnezia website")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +107,7 @@ PageType {
|
|||||||
width: listView.width
|
width: listView.width
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
|
id: telegramButton
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 6
|
Layout.topMargin: 6
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Configuration Files")
|
headerText: qsTr("Configuration files")
|
||||||
descriptionText: qsTr("For router setup or the AmneziaWG app")
|
descriptionText: qsTr("To connect a router or AmneziaWG application")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,9 +58,6 @@ PageType {
|
|||||||
Layout.topMargin: 6
|
Layout.topMargin: 6
|
||||||
|
|
||||||
text: countryName
|
text: countryName
|
||||||
descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : ""
|
|
||||||
descriptionColor: AmneziaStyle.color.vibrantRed
|
|
||||||
|
|
||||||
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
||||||
rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg"
|
rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg"
|
||||||
|
|
||||||
@@ -123,13 +120,13 @@ PageType {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: 16
|
Layout.margins: 16
|
||||||
|
|
||||||
headerText: moreOptionsDrawer.countryName + qsTr(" configuration file")
|
headerText: qsTr("Configuration file ") + moreOptionsDrawer.countryName
|
||||||
}
|
}
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
text: qsTr("Generate a new configuration file")
|
text: qsTr("Create a new")
|
||||||
descriptionText: qsTr("The previously created one will stop working")
|
descriptionText: qsTr("The previously created one will stop working")
|
||||||
|
|
||||||
clickedFunction: function() {
|
clickedFunction: function() {
|
||||||
@@ -193,15 +190,9 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showQuestion(isConfigIssue, countryCode, countryName) {
|
function showQuestion(isConfigIssue, countryCode, countryName) {
|
||||||
var headerText
|
var headerText = qsTr("Revoke the actual %1 configuration file?").arg(countryName)
|
||||||
if (isConfigIssue) {
|
var descriptionText = qsTr("The previously created file will no longer be valid. It will not be possible to connect using it.")
|
||||||
headerText = qsTr("Generate a new %1 configuration file?").arg(countryName)
|
var yesButtonText = qsTr("Continue")
|
||||||
} else {
|
|
||||||
headerText = qsTr("Revoke the current %1 configuration file?").arg(countryName)
|
|
||||||
}
|
|
||||||
|
|
||||||
var descriptionText = qsTr("Your previous configuration file will no longer work, and it will not be possible to connect using it")
|
|
||||||
var yesButtonText = isConfigIssue ? qsTr("Download") : qsTr("Continue")
|
|
||||||
var noButtonText = qsTr("Cancel")
|
var noButtonText = qsTr("Cancel")
|
||||||
|
|
||||||
var yesButtonFunction = function() {
|
var yesButtonFunction = function() {
|
||||||
|
|||||||
@@ -26,15 +26,15 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: statusObject
|
id: statusObject
|
||||||
|
|
||||||
readonly property string title: qsTr("Subscription Status")
|
readonly property string title: qsTr("Subscription status")
|
||||||
readonly property string contentKey: "subscriptionStatus"
|
readonly property string contentKey: "subscriptionStatus"
|
||||||
readonly property string objectImageSource: "qrc:/images/controls/info.svg"
|
readonly property string objectImageSource: "qrc:/images/controls/map-pin.svg"
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: endDateObject
|
id: endDateObject
|
||||||
|
|
||||||
readonly property string title: qsTr("Valid Until")
|
readonly property string title: qsTr("Valid until")
|
||||||
readonly property string contentKey: "endDate"
|
readonly property string contentKey: "endDate"
|
||||||
readonly property string objectImageSource: "qrc:/images/controls/history.svg"
|
readonly property string objectImageSource: "qrc:/images/controls/history.svg"
|
||||||
}
|
}
|
||||||
@@ -42,9 +42,9 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: deviceCountObject
|
id: deviceCountObject
|
||||||
|
|
||||||
readonly property string title: qsTr("Active Connections")
|
readonly property string title: qsTr("Connected devices")
|
||||||
readonly property string contentKey: "connectedDevices"
|
readonly property string contentKey: "connectedDevices"
|
||||||
readonly property string objectImageSource: "qrc:/images/controls/monitor.svg"
|
readonly property string objectImageSource: "qrc:/images/controls/gauge.svg"
|
||||||
}
|
}
|
||||||
|
|
||||||
property var processedServer
|
property var processedServer
|
||||||
@@ -158,32 +158,15 @@ PageType {
|
|||||||
|
|
||||||
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
|
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
|
||||||
|
|
||||||
WarningType {
|
|
||||||
id: warning
|
|
||||||
|
|
||||||
Layout.topMargin: 32
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
backGroundColor: AmneziaStyle.color.translucentRichBrown
|
|
||||||
|
|
||||||
textString: qsTr("Configurations have been updated for some countries. Download and install the updated configuration files")
|
|
||||||
|
|
||||||
iconPath: "qrc:/images/controls/alert-circle.svg"
|
|
||||||
|
|
||||||
visible: ApiAccountInfoModel.data("hasExpiredWorker")
|
|
||||||
}
|
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
id: vpnKey
|
id: vpnKey
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: warning.visible ? 16 : 32
|
Layout.topMargin: 32
|
||||||
|
|
||||||
visible: false //footer.isVisibleForAmneziaFree
|
visible: false //footer.isVisibleForAmneziaFree
|
||||||
|
|
||||||
text: qsTr("Subscription Key")
|
text: qsTr("Subscription key")
|
||||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
clickedFunction: function() {
|
clickedFunction: function() {
|
||||||
@@ -191,7 +174,7 @@ PageType {
|
|||||||
|
|
||||||
shareConnectionDrawer.openTriggered()
|
shareConnectionDrawer.openTriggered()
|
||||||
shareConnectionDrawer.isSelfHostedConfig = false;
|
shareConnectionDrawer.isSelfHostedConfig = false;
|
||||||
shareConnectionDrawer.shareButtonText = qsTr("Save VPN key as a file")
|
shareConnectionDrawer.shareButtonText = qsTr("Save VPN key to file")
|
||||||
shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key")
|
shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key")
|
||||||
|
|
||||||
|
|
||||||
@@ -209,13 +192,13 @@ PageType {
|
|||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: warning.visible ? 16 : 32
|
Layout.topMargin: 32
|
||||||
|
|
||||||
visible: footer.isVisibleForAmneziaFree
|
visible: footer.isVisibleForAmneziaFree
|
||||||
|
|
||||||
text: qsTr("Configuration Files")
|
text: qsTr("Configuration files")
|
||||||
|
|
||||||
descriptionText: qsTr("Manage configuration files")
|
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() {
|
||||||
@@ -228,26 +211,6 @@ PageType {
|
|||||||
visible: footer.isVisibleForAmneziaFree
|
visible: footer.isVisibleForAmneziaFree
|
||||||
}
|
}
|
||||||
|
|
||||||
LabelWithButtonType {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
visible: footer.isVisibleForAmneziaFree
|
|
||||||
|
|
||||||
text: qsTr("Active Devices")
|
|
||||||
|
|
||||||
descriptionText: qsTr("Manage currently connected devices")
|
|
||||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
|
||||||
|
|
||||||
clickedFunction: function() {
|
|
||||||
ApiSettingsController.updateApiDevicesModel()
|
|
||||||
PageController.goToPage(PageEnum.PageSettingsApiDevices)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DividerType {
|
|
||||||
visible: footer.isVisibleForAmneziaFree
|
|
||||||
}
|
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32
|
Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32
|
||||||
@@ -265,8 +228,6 @@ PageType {
|
|||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
visible: footer.isVisibleForAmneziaFree
|
|
||||||
|
|
||||||
text: qsTr("How to connect on another device")
|
text: qsTr("How to connect on another device")
|
||||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
@@ -275,9 +236,7 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DividerType {
|
DividerType {}
|
||||||
visible: footer.isVisibleForAmneziaFree
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: resetButton
|
id: resetButton
|
||||||
@@ -329,17 +288,16 @@ PageType {
|
|||||||
pressedColor: AmneziaStyle.color.sheerWhite
|
pressedColor: AmneziaStyle.color.sheerWhite
|
||||||
textColor: AmneziaStyle.color.vibrantRed
|
textColor: AmneziaStyle.color.vibrantRed
|
||||||
|
|
||||||
text: qsTr("Unlink this device")
|
text: qsTr("Deactivate the subscription on this device")
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
var headerText = qsTr("Are you sure you want to unlink this device?")
|
var headerText = qsTr("Deactivate the subscription on this device?")
|
||||||
var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.")
|
|
||||||
var yesButtonText = qsTr("Continue")
|
var yesButtonText = qsTr("Continue")
|
||||||
var noButtonText = qsTr("Cancel")
|
var noButtonText = qsTr("Cancel")
|
||||||
|
|
||||||
var yesButtonFunction = function() {
|
var yesButtonFunction = function() {
|
||||||
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||||
PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection"))
|
PageController.showNotificationMessage(qsTr("The next time the “Connect” button is pressed, the device will be activated again"))
|
||||||
} else {
|
} else {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
if (ApiConfigsController.deactivateDevice()) {
|
if (ApiConfigsController.deactivateDevice()) {
|
||||||
@@ -351,7 +309,7 @@ PageType {
|
|||||||
var noButtonFunction = function() {
|
var noButtonFunction = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,110 +16,92 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
QtObject {
|
ColumnLayout {
|
||||||
id: telegram
|
id: backButtonLayout
|
||||||
|
|
||||||
readonly property string title: qsTr("Telegram")
|
anchors.top: parent.top
|
||||||
readonly property string description: "@" + ApiAccountInfoModel.getTelegramBotLink()
|
anchors.left: parent.left
|
||||||
readonly property string link: "https://t.me/" + ApiAccountInfoModel.getTelegramBotLink()
|
anchors.right: parent.right
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: techSupport
|
|
||||||
|
|
||||||
readonly property string title: qsTr("Email")
|
|
||||||
readonly property string description: qsTr("support@amnezia.org")
|
|
||||||
readonly property string link: "mailto:support@amnezia.org"
|
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: paymentSupport
|
|
||||||
|
|
||||||
readonly property string title: qsTr("Email Billing & Orders")
|
|
||||||
readonly property string description: qsTr("help@vpnpay.io")
|
|
||||||
readonly property string link: "mailto:help@vpnpay.io"
|
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: site
|
|
||||||
|
|
||||||
readonly property string title: qsTr("Website")
|
|
||||||
readonly property string description: qsTr("amnezia.org")
|
|
||||||
readonly property string link: LanguageModel.getCurrentSiteUrl()
|
|
||||||
}
|
|
||||||
|
|
||||||
property list<QtObject> supportModel: [
|
|
||||||
telegram,
|
|
||||||
techSupport,
|
|
||||||
paymentSupport,
|
|
||||||
site
|
|
||||||
]
|
|
||||||
|
|
||||||
ListViewType {
|
|
||||||
id: listView
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20
|
||||||
anchors.bottomMargin: 24
|
|
||||||
|
|
||||||
model: supportModel
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
header: ColumnLayout {
|
|
||||||
width: listView.width
|
|
||||||
|
|
||||||
BackButtonType {
|
|
||||||
id: backButton
|
|
||||||
}
|
|
||||||
|
|
||||||
HeaderType {
|
|
||||||
id: header
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
Layout.leftMargin: 16
|
|
||||||
|
|
||||||
headerText: qsTr("Support")
|
|
||||||
descriptionText: qsTr("Our technical support specialists are available to assist you at any time")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
|
||||||
width: listView.width
|
|
||||||
|
|
||||||
LabelWithButtonType {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: title
|
|
||||||
descriptionText: description
|
|
||||||
rightImageSource: "qrc:/images/controls/external-link.svg"
|
|
||||||
clickedFunction: function() {
|
|
||||||
Qt.openUrlExternally(link)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DividerType {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
footer: ColumnLayout {
|
HeaderType {
|
||||||
width: listView.width
|
id: header
|
||||||
|
|
||||||
LabelWithButtonType {
|
Layout.fillWidth: true
|
||||||
id: supportUuid
|
Layout.rightMargin: 16
|
||||||
Layout.fillWidth: true
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
text: qsTr("Support tag")
|
headerText: qsTr("Support")
|
||||||
descriptionText: SettingsController.getInstallationUuid()
|
descriptionText: qsTr("Our technical support specialists are ready to help you at any time")
|
||||||
|
}
|
||||||
|
|
||||||
descriptionOnTop: true
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
rightImageSource: "qrc:/images/controls/copy.svg"
|
readonly property string telegramBotLink: ApiAccountInfoModel.getTelegramBotLink()
|
||||||
rightImageColor: AmneziaStyle.color.paleGray
|
|
||||||
|
|
||||||
clickedFunction: function() {
|
text: qsTr("Telegram")
|
||||||
GC.copyToClipBoard(descriptionText)
|
descriptionText: "@" + telegramBotLink
|
||||||
PageController.showNotificationMessage(qsTr("Copied"))
|
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||||
if (!GC.isMobile()) {
|
|
||||||
this.rightButton.forceActiveFocus()
|
clickedFunction: function() {
|
||||||
}
|
Qt.openUrlExternally("https://t.me/" + telegramBotLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Mail")
|
||||||
|
descriptionText: qsTr("support@amnezia.org")
|
||||||
|
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
Qt.openUrlExternally(qsTr("mailto:support@amnezia.org"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Site")
|
||||||
|
descriptionText: qsTr("amnezia.org")
|
||||||
|
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
id: supportUuid
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Support tag")
|
||||||
|
descriptionText: SettingsController.getInstallationUuid()
|
||||||
|
|
||||||
|
descriptionOnTop: true
|
||||||
|
|
||||||
|
rightImageSource: "qrc:/images/controls/copy.svg"
|
||||||
|
rightImageColor: AmneziaStyle.color.paleGray
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
GC.copyToClipBoard(descriptionText)
|
||||||
|
PageController.showNotificationMessage(qsTr("Copied"))
|
||||||
|
if (!GC.isMobile()) {
|
||||||
|
this.rightButton.forceActiveFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ PageType {
|
|||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!checkable) {
|
if (!checkable) {
|
||||||
PageController.showNotificationMessage(qsTr("Cannot change KillSwitch settings during active connection"))
|
PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user