mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6e7d3ccf1 | |||
| a754a11913 | |||
| 4d25e3b6f6 | |||
| 1fac280497 | |||
| c886c5e6a7 | |||
| cd7f78b9ca | |||
| a587d3230f | |||
| 93e7b45136 | |||
| e024f71ce1 | |||
| 50d1be7b4a | |||
| 3ec6d8973b | |||
| 3ea47d31a9 | |||
| 30c8cc4548 | |||
| 98586d2dd9 | |||
| c66d8ecca0 | |||
| db535f7e7d | |||
| 89f30d8c31 | |||
| 8bce432824 | |||
| f3539b2632 | |||
| 7a96c212f3 | |||
| 2d5dc54e0f | |||
| cef4c262e9 | |||
| 34309261a8 | |||
| 657eeb40c7 | |||
| b4938c2cc9 | |||
| 524fefc5cb | |||
| 73f13404bb | |||
| 5fc68cca83 | |||
| fcb7b8fa8d | |||
| a81e32ff95 | |||
| c897052107 | |||
| 4d0efc7ea5 | |||
| a77842c9e3 | |||
| 0ded9db780 | |||
| 58d480fcb5 | |||
| 7154428d26 | |||
| 02a52d0169 | |||
| ec60764072 | |||
| 17d2fa5532 | |||
| 3ca8b534e8 | |||
| e88f7c5e46 | |||
| 3ac5d7bd1f | |||
| 19cad00a00 | |||
| 1ea716a163 | |||
| 4551659c2a | |||
| c568bf8c24 | |||
| a412d91105 | |||
| ad01f23bbe | |||
| 656070b132 | |||
| c907f5ca36 | |||
| 94a13b2b54 | |||
| 169f11d9c7 | |||
| 816dc3af95 | |||
| b802863de5 | |||
| 8dc2a4b76c | |||
| beb1c6dbf2 | |||
| 3eb06916c7 | |||
| 30d0f84a4f | |||
| 251f2aa5db | |||
| 16d92ddb7c |
@@ -9,6 +9,7 @@ deploy/build_32/*
|
|||||||
deploy/build_64/*
|
deploy/build_64/*
|
||||||
winbuild*.bat
|
winbuild*.bat
|
||||||
.cache/
|
.cache/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
|
||||||
# Qt-es
|
# Qt-es
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
set(AMNEZIAVPN_VERSION 4.8.9.2)
|
set(AMNEZIAVPN_VERSION 4.8.11.0)
|
||||||
|
|
||||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
@@ -12,7 +12,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 2092)
|
set(APP_ANDROID_VERSION_CODE 2095)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
@@ -25,7 +25,9 @@
|
|||||||
#include <QtQuick/QQuickWindow> // for QQuickWindow
|
#include <QtQuick/QQuickWindow> // for QQuickWindow
|
||||||
#include <QWindow> // for qobject_cast<QWindow*>
|
#include <QWindow> // for qobject_cast<QWindow*>
|
||||||
|
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
|
||||||
|
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
|
||||||
|
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs"))
|
||||||
{
|
{
|
||||||
setQuitOnLastWindowClosed(false);
|
setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
@@ -51,18 +53,8 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
|
|||||||
|
|
||||||
AmneziaApplication::~AmneziaApplication()
|
AmneziaApplication::~AmneziaApplication()
|
||||||
{
|
{
|
||||||
if (m_vpnConnection) {
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteLater", Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_vpnConnectionThread.quit();
|
m_vpnConnectionThread.quit();
|
||||||
|
|
||||||
if (!m_vpnConnectionThread.wait(5000)) {
|
|
||||||
m_vpnConnectionThread.terminate();
|
|
||||||
m_vpnConnectionThread.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_engine) {
|
if (m_engine) {
|
||||||
QObject::disconnect(m_engine, 0, 0, 0);
|
QObject::disconnect(m_engine, 0, 0, 0);
|
||||||
delete m_engine;
|
delete m_engine;
|
||||||
@@ -119,7 +111,7 @@ void AmneziaApplication::init()
|
|||||||
Logger::setServiceLogsEnabled(enabled);
|
Logger::setServiceLogsEnabled(enabled);
|
||||||
|
|
||||||
#ifdef Q_OS_WIN //TODO
|
#ifdef Q_OS_WIN //TODO
|
||||||
if (m_parser.isSet("a"))
|
if (m_parser.isSet(m_optAutostart))
|
||||||
m_coreController->pageController()->showOnStartup();
|
m_coreController->pageController()->showOnStartup();
|
||||||
else
|
else
|
||||||
emit m_coreController->pageController()->raiseMainWindow();
|
emit m_coreController->pageController()->raiseMainWindow();
|
||||||
@@ -187,15 +179,12 @@ bool AmneziaApplication::parseCommands()
|
|||||||
m_parser.addHelpOption();
|
m_parser.addHelpOption();
|
||||||
m_parser.addVersionOption();
|
m_parser.addVersionOption();
|
||||||
|
|
||||||
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
|
m_parser.addOption(m_optAutostart);
|
||||||
m_parser.addOption(c_autostart);
|
m_parser.addOption(m_optCleanup);
|
||||||
|
|
||||||
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
|
|
||||||
m_parser.addOption(c_cleanup);
|
|
||||||
|
|
||||||
m_parser.process(*this);
|
m_parser.process(*this);
|
||||||
|
|
||||||
if (m_parser.isSet(c_cleanup)) {
|
if (m_parser.isSet(m_optCleanup)) {
|
||||||
Logger::cleanUp();
|
Logger::cleanUp();
|
||||||
QTimer::singleShot(100, this, [this] { quit(); });
|
QTimer::singleShot(100, this, [this] { quit(); });
|
||||||
exec();
|
exec();
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ private:
|
|||||||
|
|
||||||
QCommandLineParser m_parser;
|
QCommandLineParser m_parser;
|
||||||
|
|
||||||
|
QCommandLineOption m_optAutostart;
|
||||||
|
QCommandLineOption m_optCleanup;
|
||||||
|
|
||||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
QThread m_vpnConnectionThread;
|
QThread m_vpnConnectionThread;
|
||||||
|
|
||||||
|
|||||||
+16
-4
@@ -46,6 +46,7 @@ set(SOURCES ${SOURCES}
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -135,10 +136,21 @@ set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
|||||||
add_subdirectory(ios/networkextension)
|
add_subdirectory(ios/networkextension)
|
||||||
add_dependencies(${PROJECT} networkextension)
|
add_dependencies(${PROJECT} networkextension)
|
||||||
|
|
||||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
set(OPENVPN_FRAMEWORK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios")
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
|
set(OPENVPN_EMBEDDED_FRAMEWORKS
|
||||||
|
"${OPENVPN_FRAMEWORK_DIR}/OpenVPNAdapter.framework"
|
||||||
|
"${OPENVPN_FRAMEWORK_DIR}/OpenVPNClient.framework"
|
||||||
|
"${OPENVPN_FRAMEWORK_DIR}/mbedTLS.framework"
|
||||||
|
"${OPENVPN_FRAMEWORK_DIR}/LZ4.framework"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
|
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS "${OPENVPN_EMBEDDED_FRAMEWORKS}")
|
||||||
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
|
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "$(inherited) ${OPENVPN_FRAMEWORK_DIR}")
|
||||||
|
|
||||||
|
foreach(_framework ${OPENVPN_EMBEDDED_FRAMEWORKS})
|
||||||
|
target_link_libraries(networkextension PRIVATE "${_framework}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set_property(TARGET networkextension PROPERTY XCODE_EMBED_FRAMEWORKS "${OPENVPN_EMBEDDED_FRAMEWORKS}")
|
||||||
|
set_property(TARGET networkextension PROPERTY XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON)
|
||||||
|
set_property(TARGET networkextension PROPERTY XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "$(inherited) ${OPENVPN_FRAMEWORK_DIR}")
|
||||||
|
|||||||
@@ -83,12 +83,30 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto sanitizeStaticKey = [](const QString &key) {
|
||||||
|
QStringList lines = key.split('\n');
|
||||||
|
QStringList filtered;
|
||||||
|
filtered.reserve(lines.size());
|
||||||
|
for (const QString &line : lines) {
|
||||||
|
const QString trimmed = line.trimmed();
|
||||||
|
if (trimmed.startsWith('#')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
filtered.append(line);
|
||||||
|
}
|
||||||
|
QString result = filtered.join('\n');
|
||||||
|
if (!result.endsWith('\n')) {
|
||||||
|
result.append('\n');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
config.replace("$OPENVPN_CA_CERT", connData.caCert);
|
config.replace("$OPENVPN_CA_CERT", connData.caCert);
|
||||||
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
|
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
|
||||||
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
|
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
|
||||||
|
|
||||||
if (config.contains("$OPENVPN_TA_KEY")) {
|
if (config.contains("$OPENVPN_TA_KEY")) {
|
||||||
config.replace("$OPENVPN_TA_KEY", connData.taKey);
|
config.replace("$OPENVPN_TA_KEY", sanitizeStaticKey(connData.taKey));
|
||||||
} else {
|
} else {
|
||||||
config.replace("<tls-auth>", "");
|
config.replace("<tls-auth>", "");
|
||||||
config.replace("</tls-auth>", "");
|
config.replace("</tls-auth>", "");
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ namespace apiDefs
|
|||||||
constexpr QLatin1String id("id");
|
constexpr QLatin1String id("id");
|
||||||
constexpr QLatin1String orderId("order_id");
|
constexpr QLatin1String orderId("order_id");
|
||||||
constexpr QLatin1String migrationCode("migration_code");
|
constexpr QLatin1String migrationCode("migration_code");
|
||||||
|
|
||||||
|
constexpr QLatin1String transactionId("transaction_id");
|
||||||
|
|
||||||
|
constexpr QLatin1String userCountryCode("user_country_code");
|
||||||
}
|
}
|
||||||
|
|
||||||
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace
|
|||||||
|
|
||||||
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
||||||
{
|
{
|
||||||
QDateTime now = QDateTime::currentDateTime();
|
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||||
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
|
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
|
||||||
return endDate < now;
|
return endDate < now;
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,9 @@ apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigO
|
|||||||
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
|
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
||||||
|
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
||||||
|
const QByteArray &responseBody)
|
||||||
{
|
{
|
||||||
const int httpStatusCodeConflict = 409;
|
const int httpStatusCodeConflict = 409;
|
||||||
const int httpStatusCodeNotFound = 404;
|
const int httpStatusCodeNotFound = 404;
|
||||||
@@ -90,21 +92,19 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
|||||||
if (!sslErrors.empty()) {
|
if (!sslErrors.empty()) {
|
||||||
qDebug().noquote() << sslErrors;
|
qDebug().noquote() << sslErrors;
|
||||||
return amnezia::ErrorCode::ApiConfigSslError;
|
return amnezia::ErrorCode::ApiConfigSslError;
|
||||||
} else if (reply->error() == QNetworkReply::NoError) {
|
} else if (replyError == QNetworkReply::NoError) {
|
||||||
return amnezia::ErrorCode::NoError;
|
return amnezia::ErrorCode::NoError;
|
||||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
qDebug() << reply->error();
|
qDebug() << replyError;
|
||||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||||
qDebug() << reply->error();
|
qDebug() << replyError;
|
||||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||||
} else {
|
} else {
|
||||||
QString err = reply->errorString();
|
qDebug() << QString::fromUtf8(responseBody);
|
||||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
qDebug() << replyError;
|
||||||
qDebug() << QString::fromUtf8(reply->readAll());
|
qDebug() << replyErrorString;
|
||||||
qDebug() << reply->error();
|
|
||||||
qDebug() << err;
|
|
||||||
qDebug() << httpStatusCode;
|
qDebug() << httpStatusCode;
|
||||||
if (httpStatusCode == httpStatusCodeConflict) {
|
if (httpStatusCode == httpStatusCodeConflict) {
|
||||||
return amnezia::ErrorCode::ApiConfigLimitError;
|
return amnezia::ErrorCode::ApiConfigLimitError;
|
||||||
@@ -162,3 +162,51 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
|||||||
|
|
||||||
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString vpnKeyText = "";
|
||||||
|
|
||||||
|
auto apiConfig = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||||
|
auto authData = serverConfigObject.value(QLatin1String("auth_data")).toObject();
|
||||||
|
|
||||||
|
const QString name = serverConfigObject.value(apiDefs::key::name).toString();
|
||||||
|
const QString description = serverConfigObject.value(apiDefs::key::description).toString();
|
||||||
|
const double configVersion = serverConfigObject.value(apiDefs::key::configVersion).toDouble();
|
||||||
|
|
||||||
|
const QString serviceType = apiConfig.value(apiDefs::key::serviceType).toString();
|
||||||
|
const QString serviceProtocol = apiConfig.value(QLatin1String("service_protocol")).toString();
|
||||||
|
const QString userCountryCode = apiConfig.value(QLatin1String("user_country_code")).toString();
|
||||||
|
|
||||||
|
const QString apiKey = authData.value(apiDefs::key::apiKey).toString();
|
||||||
|
|
||||||
|
QString vpnKeyStr = "{";
|
||||||
|
vpnKeyStr += "\"" + QString(apiDefs::key::name) + "\": \"" + name + "\", ";
|
||||||
|
vpnKeyStr += "\"" + QString(apiDefs::key::description) + "\": \"" + description + "\", ";
|
||||||
|
vpnKeyStr += "\"" + QString(apiDefs::key::configVersion) + "\": " + QString::number(static_cast<int>(configVersion)) + ", ";
|
||||||
|
|
||||||
|
vpnKeyStr += "\"" + QString(apiDefs::key::apiConfig) + "\": {";
|
||||||
|
vpnKeyStr += "\"" + QString(apiDefs::key::serviceType) + "\": \"" + serviceType + "\", ";
|
||||||
|
vpnKeyStr += "\"service_protocol\": \"" + serviceProtocol + "\", ";
|
||||||
|
vpnKeyStr += "\"user_country_code\": \"" + userCountryCode + "\"";
|
||||||
|
vpnKeyStr += "}, ";
|
||||||
|
|
||||||
|
vpnKeyStr += "\"auth_data\": {";
|
||||||
|
vpnKeyStr += "\"" + QString(apiDefs::key::apiKey) + "\": \"" + apiKey + "\"";
|
||||||
|
vpnKeyStr += "}";
|
||||||
|
|
||||||
|
vpnKeyStr += "}";
|
||||||
|
|
||||||
|
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
|
||||||
|
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
|
||||||
|
vpnKeyCompressed = vpnKeyCompressed.mid(4);
|
||||||
|
|
||||||
|
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
|
||||||
|
vpnKeyText = QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
||||||
|
|
||||||
|
return vpnKeyText;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,9 +18,12 @@ namespace apiUtils
|
|||||||
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
||||||
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
||||||
|
|
||||||
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
|
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
||||||
|
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
||||||
|
const QByteArray &responseBody);
|
||||||
|
|
||||||
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
||||||
|
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // APIUTILS_H
|
#endif // APIUTILS_H
|
||||||
|
|||||||
@@ -26,9 +26,8 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
|||||||
|
|
||||||
initNotificationHandler();
|
initNotificationHandler();
|
||||||
|
|
||||||
auto locale = m_settings->getAppLanguage();
|
|
||||||
m_translator.reset(new QTranslator());
|
m_translator.reset(new QTranslator());
|
||||||
updateTranslator(locale);
|
updateTranslator(m_settings->getAppLanguage());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initModels()
|
void CoreController::initModels()
|
||||||
@@ -100,6 +99,9 @@ void CoreController::initModels()
|
|||||||
|
|
||||||
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
||||||
|
|
||||||
|
m_newsModel.reset(new NewsModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initControllers()
|
void CoreController::initControllers()
|
||||||
@@ -154,6 +156,9 @@ void CoreController::initControllers()
|
|||||||
|
|
||||||
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
||||||
|
|
||||||
|
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initAndroidController()
|
void CoreController::initAndroidController()
|
||||||
@@ -233,7 +238,7 @@ void CoreController::initSignalHandlers()
|
|||||||
|
|
||||||
void CoreController::initNotificationHandler()
|
void CoreController::initNotificationHandler()
|
||||||
{
|
{
|
||||||
#ifndef Q_OS_ANDROID
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||||
|
|
||||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||||
@@ -317,6 +322,11 @@ void CoreController::initContainerModelUpdateHandler()
|
|||||||
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||||
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||||
&ContainersModel::updateModel);
|
&ContainersModel::updateModel);
|
||||||
|
connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() {
|
||||||
|
if (m_serversModel->hasServersFromGatewayApi()) {
|
||||||
|
m_apiNewsController->fetchNews();
|
||||||
|
}
|
||||||
|
});
|
||||||
m_serversModel->resetModel();
|
m_serversModel->resetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
#include "ui/systemtray_notificationhandler.h"
|
#include "ui/systemtray_notificationhandler.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "ui/controllers/api/apiConfigsController.h"
|
#include "ui/controllers/api/apiConfigsController.h"
|
||||||
#include "ui/controllers/api/apiSettingsController.h"
|
#include "ui/controllers/api/apiSettingsController.h"
|
||||||
#include "ui/controllers/api/apiPremV1MigrationController.h"
|
#include "ui/controllers/api/apiPremV1MigrationController.h"
|
||||||
|
#include "ui/controllers/api/apiNewsController.h"
|
||||||
#include "ui/controllers/appSplitTunnelingController.h"
|
#include "ui/controllers/appSplitTunnelingController.h"
|
||||||
#include "ui/controllers/allowedDnsController.h"
|
#include "ui/controllers/allowedDnsController.h"
|
||||||
#include "ui/controllers/connectionController.h"
|
#include "ui/controllers/connectionController.h"
|
||||||
@@ -47,8 +48,9 @@
|
|||||||
#include "ui/models/services/sftpConfigModel.h"
|
#include "ui/models/services/sftpConfigModel.h"
|
||||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||||
#include "ui/models/sites_model.h"
|
#include "ui/models/sites_model.h"
|
||||||
|
#include "ui/models/newsModel.h"
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
#include "ui/notificationhandler.h"
|
#include "ui/notificationhandler.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ private:
|
|||||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
QSharedPointer<QTranslator> m_translator;
|
QSharedPointer<QTranslator> m_translator;
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -118,6 +120,7 @@ private:
|
|||||||
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||||
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||||
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
||||||
|
QScopedPointer<ApiNewsController> m_apiNewsController;
|
||||||
|
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
QSharedPointer<ContainersModel> m_containersModel;
|
||||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||||
@@ -125,6 +128,7 @@ private:
|
|||||||
QSharedPointer<LanguageModel> m_languageModel;
|
QSharedPointer<LanguageModel> m_languageModel;
|
||||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
|
QSharedPointer<NewsModel> m_newsModel;
|
||||||
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
||||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
|||||||
@@ -50,68 +50,6 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_IOS
|
|
||||||
IosController::Instance()->requestInetAccess();
|
|
||||||
QThread::msleep(10);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QNetworkRequest request;
|
|
||||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
|
|
||||||
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
|
|
||||||
|
|
||||||
// bypass killSwitch exceptions for API-gateway
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
if (m_isStrictKillSwitchEnabled) {
|
|
||||||
QString host = QUrl(request.url()).host();
|
|
||||||
QString ip = NetworkUtilities::getIPAddress(host);
|
|
||||||
if (!ip.isEmpty()) {
|
|
||||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QNetworkReply *reply;
|
|
||||||
reply = amnApp->networkManager()->get(request);
|
|
||||||
|
|
||||||
QEventLoop wait;
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
|
||||||
|
|
||||||
QList<QSslError> sslErrors;
|
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
|
||||||
wait.exec();
|
|
||||||
|
|
||||||
responseBody = reply->readAll();
|
|
||||||
|
|
||||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
|
|
||||||
auto requestFunction = [&request, &responseBody](const QString &url) {
|
|
||||||
request.setUrl(url);
|
|
||||||
return amnApp->networkManager()->get(request);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
|
|
||||||
const QList<QSslError> &nestedSslErrors) {
|
|
||||||
responseBody = nestedReply->readAll();
|
|
||||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
|
|
||||||
sslErrors = nestedSslErrors;
|
|
||||||
reply = nestedReply;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
|
||||||
reply->deleteLater();
|
|
||||||
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
@@ -122,8 +60,9 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||||
|
|
||||||
request.setUrl(endpoint.arg(m_gatewayEndpoint));
|
request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||||
|
|
||||||
// bypass killSwitch exceptions for API-gateway
|
// bypass killSwitch exceptions for API-gateway
|
||||||
#ifdef AMNEZIA_DESKTOP
|
#ifdef AMNEZIA_DESKTOP
|
||||||
@@ -186,29 +125,37 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
wait.exec();
|
wait.exec();
|
||||||
|
|
||||||
QByteArray encryptedResponseBody = reply->readAll();
|
QByteArray encryptedResponseBody = reply->readAll();
|
||||||
|
QString replyErrorString = reply->errorString();
|
||||||
|
auto replyError = reply->error();
|
||||||
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
reply->deleteLater();
|
||||||
|
|
||||||
|
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
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());
|
||||||
};
|
};
|
||||||
|
|
||||||
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &key, &iv,
|
||||||
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
&salt, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
||||||
encryptedResponseBody = nestedReply->readAll();
|
encryptedResponseBody = reply->readAll();
|
||||||
reply = nestedReply;
|
replyErrorString = reply->errorString();
|
||||||
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
replyError = reply->error();
|
||||||
|
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
if (!sslErrors.isEmpty() || shouldBypassProxy(replyError, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
sslErrors = nestedSslErrors;
|
sslErrors = nestedSslErrors;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
|
||||||
|
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
|
||||||
|
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody);
|
||||||
reply->deleteLater();
|
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
return errorCode;
|
return errorCode;
|
||||||
}
|
}
|
||||||
@@ -223,7 +170,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList GatewayController::getProxyUrls()
|
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
|
||||||
{
|
{
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
@@ -233,15 +180,26 @@ QStringList GatewayController::getProxyUrls()
|
|||||||
QList<QSslError> sslErrors;
|
QList<QSslError> sslErrors;
|
||||||
QNetworkReply *reply;
|
QNetworkReply *reply;
|
||||||
|
|
||||||
QStringList proxyStorageUrls;
|
QStringList baseUrls;
|
||||||
if (m_isDevEnvironment) {
|
if (m_isDevEnvironment) {
|
||||||
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||||
} else {
|
} else {
|
||||||
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
|
||||||
|
QStringList proxyStorageUrls;
|
||||||
|
if (!serviceType.isEmpty()) {
|
||||||
|
for (const auto &baseUrl : baseUrls) {
|
||||||
|
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
|
||||||
|
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &baseUrl : baseUrls) {
|
||||||
|
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||||
request.setUrl(proxyStorageUrl);
|
request.setUrl(proxyStorageUrl);
|
||||||
reply = amnApp->networkManager()->get(request);
|
reply = amnApp->networkManager()->get(request);
|
||||||
@@ -286,7 +244,10 @@ QStringList GatewayController::getProxyUrls()
|
|||||||
}
|
}
|
||||||
return endpoints;
|
return endpoints;
|
||||||
} else {
|
} else {
|
||||||
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
auto replyError = reply->error();
|
||||||
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
qDebug() << replyError;
|
||||||
|
qDebug() << httpStatusCode;
|
||||||
qDebug() << "go to the next storage endpoint";
|
qDebug() << "go to the next storage endpoint";
|
||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
@@ -295,33 +256,33 @@ QStringList GatewayController::getProxyUrls()
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody,
|
||||||
const QByteArray &iv, const QByteArray &salt)
|
bool checkEncryption, const QByteArray &key, const QByteArray &iv, const QByteArray &salt)
|
||||||
{
|
{
|
||||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
qDebug() << "timeout occurred";
|
qDebug() << "timeout occurred";
|
||||||
qDebug() << reply->error();
|
qDebug() << replyError;
|
||||||
return true;
|
return true;
|
||||||
} 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) {
|
} else if (replyError == QNetworkReply::NetworkError::ContentNotFoundError) {
|
||||||
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||||
|| responseBody.contains(errorResponsePattern3)) {
|
|| responseBody.contains(errorResponsePattern3)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << reply->error();
|
qDebug() << replyError;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||||
if (responseBody.contains(updateRequestResponsePattern)) {
|
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << reply->error();
|
qDebug() << replyError;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
} else if (replyError != QNetworkReply::NetworkError::NoError) {
|
||||||
qDebug() << reply->error();
|
qDebug() << replyError;
|
||||||
return true;
|
return true;
|
||||||
} else if (checkEncryption) {
|
} else if (checkEncryption) {
|
||||||
try {
|
try {
|
||||||
@@ -335,29 +296,74 @@ bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
|
void GatewayController::bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
||||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
||||||
{
|
{
|
||||||
QStringList proxyUrls = getProxyUrls();
|
QStringList proxyUrls = getProxyUrls(serviceType, userCountryCode);
|
||||||
std::random_device randomDevice;
|
std::random_device randomDevice;
|
||||||
std::mt19937 generator(randomDevice());
|
std::mt19937 generator(randomDevice());
|
||||||
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
||||||
|
|
||||||
QEventLoop wait;
|
|
||||||
QList<QSslError> sslErrors;
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
|
|
||||||
for (const QString &proxyUrl : proxyUrls) {
|
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl,
|
||||||
|
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
|
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
|
||||||
|
QEventLoop wait;
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
|
||||||
qDebug() << "go to the next proxy endpoint";
|
qDebug() << "go to the next proxy endpoint";
|
||||||
reply->deleteLater(); // delete the previous reply
|
QNetworkReply *reply = requestFunction(endpoint.arg(proxyUrl));
|
||||||
reply = requestFunction(endpoint.arg(proxyUrl));
|
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
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)) {
|
auto result = replyProcessingFunction(reply, sslErrors);
|
||||||
|
reply->deleteLater();
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (m_proxyUrl.isEmpty()) {
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(1000);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
QNetworkReply *reply;
|
||||||
|
|
||||||
|
for (const QString &proxyUrl : proxyUrls) {
|
||||||
|
request.setUrl(proxyUrl + "lmbd-health");
|
||||||
|
reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
m_proxyUrl = proxyUrl;
|
||||||
|
if (!m_proxyUrl.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_proxyUrl.isEmpty()) {
|
||||||
|
if (bypassFunction(endpoint, m_proxyUrl, requestFunction, replyProcessingFunction)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString &proxyUrl : proxyUrls) {
|
||||||
|
if (bypassFunction(endpoint, proxyUrl, requestFunction, replyProcessingFunction)) {
|
||||||
|
m_proxyUrl = proxyUrl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,20 +18,22 @@ public:
|
|||||||
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||||
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
||||||
|
|
||||||
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
|
|
||||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QStringList getProxyUrls();
|
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
|
||||||
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
|
||||||
const QByteArray &iv = "", const QByteArray &salt = "");
|
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
|
||||||
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
|
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
||||||
|
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||||
|
|
||||||
int m_requestTimeoutMsecs;
|
int m_requestTimeoutMsecs;
|
||||||
QString m_gatewayEndpoint;
|
QString m_gatewayEndpoint;
|
||||||
bool m_isDevEnvironment = false;
|
bool m_isDevEnvironment = false;
|
||||||
bool m_isStrictKillSwitchEnabled = false;
|
bool m_isStrictKillSwitchEnabled = false;
|
||||||
|
|
||||||
|
inline static QString m_proxyUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GATEWAYCONTROLLER_H
|
#endif // GATEWAYCONTROLLER_H
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ namespace amnezia
|
|||||||
ApiNotFoundError = 1109,
|
ApiNotFoundError = 1109,
|
||||||
ApiMigrationError = 1110,
|
ApiMigrationError = 1110,
|
||||||
ApiUpdateRequestError = 1111,
|
ApiUpdateRequestError = 1111,
|
||||||
|
ApiSubscriptionExpiredError = 1112,
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
OpenError = 1200,
|
OpenError = 1200,
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ QString errorString(ErrorCode code) {
|
|||||||
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
||||||
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
|
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
|
||||||
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
|
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
|
||||||
|
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||||
|
|||||||
@@ -101,10 +101,10 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
|||||||
out << "MTU = " << m_deviceMTU << "\n";
|
out << "MTU = " << m_deviceMTU << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_primaryDnsServer.isNull()) {
|
if (!m_primaryDnsServer.isEmpty()) {
|
||||||
QStringList dnsServers;
|
QStringList dnsServers;
|
||||||
dnsServers.append(m_primaryDnsServer);
|
dnsServers.append(m_primaryDnsServer);
|
||||||
if (!m_secondaryDnsServer.isNull()) {
|
if (!m_secondaryDnsServer.isEmpty()) {
|
||||||
dnsServers.append(m_secondaryDnsServer);
|
dnsServers.append(m_secondaryDnsServer);
|
||||||
}
|
}
|
||||||
// If the DNS is not the Gateway, it's a user defined DNS
|
// If the DNS is not the Gateway, it's a user defined DNS
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 74 74" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_4_34)">
|
||||||
|
<path d="M55.5 12.3333H18.5C15.0942 12.3333 12.3333 15.0943 12.3333 18.5V55.5C12.3333 58.9058 15.0942 61.6667 18.5 61.6667H55.5C58.9057 61.6667 61.6666 58.9058 61.6666 55.5V18.5C61.6666 15.0943 58.9057 12.3333 55.5 12.3333Z" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M21.5833 24.6667H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M21.5833 37H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M21.5833 49.3333H40.0833" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<circle cx="61.5" cy="12.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_4_34">
|
||||||
|
<rect width="74" height="74" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 982 B |
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#CBCAC8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<!-- Основа газеты -->
|
||||||
|
<rect x="4" y="4" width="16" height="16" rx="2"/>
|
||||||
|
<!-- Линии текста -->
|
||||||
|
<line x1="7" y1="8" x2="17" y2="8"/>
|
||||||
|
<line x1="7" y1="12" x2="17" y2="12"/>
|
||||||
|
<line x1="7" y1="16" x2="13" y2="16"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 410 B |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="17.5" cy="17.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 188 B |
@@ -32,17 +32,41 @@
|
|||||||
<false/>
|
<false/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>AmneziaVPNLaunchScreen</string>
|
<string>AmneziaVPNLaunchScreen</string>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<true/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>Default Configuration</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>QIOSWindowSceneDelegate</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
<array/>
|
<array/>
|
||||||
<key>UIRequiresFullScreen</key>
|
<key>UIRequiresFullScreen</key>
|
||||||
<true/>
|
<false/>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array/>
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
<key>UIUserInterfaceStyle</key>
|
<key>UIUserInterfaceStyle</key>
|
||||||
<string>Light</string>
|
<string>Light</string>
|
||||||
<key>com.wireguard.ios.app_group_id</key>
|
<key>com.wireguard.ios.app_group_id</key>
|
||||||
|
|||||||
@@ -264,13 +264,13 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
|
// && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
|
// && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
|
/* && !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
|
||||||
@@ -278,27 +278,27 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
&& !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()) {
|
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()*/) {
|
||||||
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
||||||
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
||||||
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
||||||
json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize));
|
json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize));
|
||||||
json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize));
|
json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize));
|
||||||
json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
|
// json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
|
||||||
json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize));
|
// json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize));
|
||||||
json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader));
|
json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader));
|
||||||
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
|
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
|
||||||
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
|
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
|
||||||
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
|
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
|
||||||
json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
|
// json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
|
||||||
json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2));
|
// json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2));
|
||||||
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
// json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
||||||
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
// json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
||||||
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
// json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
||||||
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
// json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
||||||
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
// json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
||||||
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
// json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
||||||
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
// json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
write(json);
|
write(json);
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <objc/runtime.h>
|
||||||
|
#include <dispatch/dispatch.h>
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "ios_controller.h"
|
||||||
|
|
||||||
|
using SceneOpenURLContexts = void (*)(id, SEL, UIScene *, NSSet<UIOpenURLContext *> *);
|
||||||
|
|
||||||
|
static SceneOpenURLContexts g_originalSceneOpenURLContexts = nullptr;
|
||||||
|
|
||||||
|
static void amnezia_handleURL(NSURL *url)
|
||||||
|
{
|
||||||
|
if (!url || !url.isFileURL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString filePath(url.path.UTF8String);
|
||||||
|
if (filePath.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
|
if (filePath.contains("backup")) {
|
||||||
|
IosController::Instance()->importBackupFromOutside(filePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray data = file.readAll();
|
||||||
|
IosController::Instance()->importConfigFromOutside(QString::fromUtf8(data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void amnezia_scene_openURLContexts(id self, SEL _cmd, UIScene *scene, NSSet<UIOpenURLContext *> *contexts)
|
||||||
|
{
|
||||||
|
if (g_originalSceneOpenURLContexts) {
|
||||||
|
g_originalSceneOpenURLContexts(self, _cmd, scene, contexts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contexts || contexts.count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
for (UIOpenURLContext *context in contexts) {
|
||||||
|
amnezia_handleURL(context.URL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface AmneziaSceneDelegateHooks : NSObject
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AmneziaSceneDelegateHooks
|
||||||
|
|
||||||
|
+ (void)load
|
||||||
|
{
|
||||||
|
Class cls = objc_getClass("QIOSWindowSceneDelegate");
|
||||||
|
if (!cls) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEL selector = @selector(scene:openURLContexts:);
|
||||||
|
Method method = class_getInstanceMethod(cls, selector);
|
||||||
|
if (method) {
|
||||||
|
g_originalSceneOpenURLContexts = reinterpret_cast<SceneOpenURLContexts>(method_getImplementation(method));
|
||||||
|
method_setImplementation(method, reinterpret_cast<IMP>(amnezia_scene_openURLContexts));
|
||||||
|
} else {
|
||||||
|
const char *types = "v@:@@";
|
||||||
|
class_addMethod(cls, selector, reinterpret_cast<IMP>(amnezia_scene_openURLContexts), types);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -2,7 +2,8 @@ import Foundation
|
|||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
struct Log {
|
struct Log {
|
||||||
static let osLog = Logger()
|
private static let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
|
||||||
|
static let osLog = Logger(subsystem: subsystemIdentifier, category: "App")
|
||||||
|
|
||||||
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
|
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
|
||||||
static var isLoggingEnabled: Bool {
|
static var isLoggingEnabled: Bool {
|
||||||
@@ -77,9 +78,40 @@ struct Log {
|
|||||||
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
|
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
|
||||||
NSLog("\(title) \(message)")
|
NSLog("\(title) \(message)")
|
||||||
|
|
||||||
guard isLoggingEnabled else { return }
|
switch type {
|
||||||
|
case .debug:
|
||||||
|
if title.isEmpty {
|
||||||
|
osLog.debug("\(message, privacy: .public)")
|
||||||
|
} else {
|
||||||
|
osLog.debug("\(title, privacy: .public) \(message, privacy: .public)")
|
||||||
|
}
|
||||||
|
case .info:
|
||||||
|
if title.isEmpty {
|
||||||
|
osLog.info("\(message, privacy: .public)")
|
||||||
|
} else {
|
||||||
|
osLog.info("\(title, privacy: .public) \(message, privacy: .public)")
|
||||||
|
}
|
||||||
|
case .error:
|
||||||
|
if title.isEmpty {
|
||||||
|
osLog.error("\(message, privacy: .public)")
|
||||||
|
} else {
|
||||||
|
osLog.error("\(title, privacy: .public) \(message, privacy: .public)")
|
||||||
|
}
|
||||||
|
case .fault:
|
||||||
|
if title.isEmpty {
|
||||||
|
osLog.fault("\(message, privacy: .public)")
|
||||||
|
} else {
|
||||||
|
osLog.fault("\(title, privacy: .public) \(message, privacy: .public)")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if title.isEmpty {
|
||||||
|
osLog.log("\(message, privacy: .public)")
|
||||||
|
} else {
|
||||||
|
osLog.log("\(title, privacy: .public) \(message, privacy: .public)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
osLog.log(level: type, "\(title) \(message)")
|
guard isLoggingEnabled else { return }
|
||||||
|
|
||||||
let date = Date()
|
let date = Date()
|
||||||
let level = Record.Level(from: type)
|
let level = Record.Level(from: type)
|
||||||
|
|||||||
@@ -1,22 +1,76 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
|
private let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
|
||||||
|
private let wireGuardSystemLogger = Logger(subsystem: subsystemIdentifier, category: "WireGuard")
|
||||||
|
private let openVPNSystemLogger = Logger(subsystem: subsystemIdentifier, category: "OpenVPN")
|
||||||
|
private let xraySystemLogger = Logger(subsystem: subsystemIdentifier, category: "Xray")
|
||||||
|
private let networkExtensionLogger = Logger(subsystem: subsystemIdentifier, category: "NetworkExtension")
|
||||||
|
|
||||||
|
private func logToSystem(_ logger: Logger, type: OSLogType, prefix: String, title: String, message: String) {
|
||||||
|
let combinedTitle: String
|
||||||
|
if title.isEmpty {
|
||||||
|
combinedTitle = prefix
|
||||||
|
} else {
|
||||||
|
combinedTitle = "\(prefix): \(title)"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch type {
|
||||||
|
case .debug:
|
||||||
|
if combinedTitle.isEmpty {
|
||||||
|
logger.debug("\(message, privacy: .public)")
|
||||||
|
} else {
|
||||||
|
logger.debug("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
||||||
|
}
|
||||||
|
case .info:
|
||||||
|
if combinedTitle.isEmpty {
|
||||||
|
logger.info("\(message, privacy: .public)")
|
||||||
|
} else {
|
||||||
|
logger.info("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
||||||
|
}
|
||||||
|
case .error:
|
||||||
|
if combinedTitle.isEmpty {
|
||||||
|
logger.error("\(message, privacy: .public)")
|
||||||
|
} else {
|
||||||
|
logger.error("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
||||||
|
}
|
||||||
|
case .fault:
|
||||||
|
if combinedTitle.isEmpty {
|
||||||
|
logger.fault("\(message, privacy: .public)")
|
||||||
|
} else {
|
||||||
|
logger.fault("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if combinedTitle.isEmpty {
|
||||||
|
logger.log("\(message, privacy: .public)")
|
||||||
|
} else {
|
||||||
|
logger.log("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
||||||
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
|
let stringMessage = String(describing: staticMessage)
|
||||||
|
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: stringMessage)
|
||||||
|
neLog(type, title: "WG: \(title)", message: stringMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
||||||
|
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: message)
|
||||||
neLog(type, title: "WG: \(title)", message: message)
|
neLog(type, title: "WG: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
|
logToSystem(openVPNSystemLogger, type: type, prefix: "OVPN", title: title, message: message)
|
||||||
neLog(type, title: "OVPN: \(title)", message: message)
|
neLog(type, title: "OVPN: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
|
public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
|
logToSystem(xraySystemLogger, type: type, prefix: "XRAY", title: title, message: message)
|
||||||
neLog(type, title: "XRAY: \(title)", message: message)
|
neLog(type, title: "XRAY: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
|
logToSystem(networkExtensionLogger, type: type, prefix: "NE", title: title, message: message)
|
||||||
Log.log(type, title: "NE: \(title)", message: message)
|
Log.log(type, title: "NE: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import OpenVPNAdapter
|
import OpenVPNAdapter
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
struct OpenVPNConfig: Decodable {
|
struct OpenVPNConfig: Decodable {
|
||||||
let config: String
|
let config: String
|
||||||
@@ -27,26 +28,83 @@ extension PacketTunnelProvider {
|
|||||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||||
} catch {
|
} catch {
|
||||||
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
|
ovpnLog(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
|
||||||
|
|
||||||
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
|
||||||
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func logOpenVPNError(_ error: NSError) {
|
||||||
|
let fatalFlag = (error.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false
|
||||||
|
var lines: [String] = []
|
||||||
|
lines.append("domain=\(error.domain) code=\(error.code) fatal=\(fatalFlag)")
|
||||||
|
|
||||||
|
if let adapterMessage = error.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !adapterMessage.isEmpty {
|
||||||
|
lines.append("message=\(adapterMessage)")
|
||||||
|
}
|
||||||
|
|
||||||
|
let userInfoKeys = error.userInfo.keys.map { String(describing: $0) }.sorted()
|
||||||
|
if !userInfoKeys.isEmpty {
|
||||||
|
lines.append("userInfoKeys=[\(userInfoKeys.joined(separator: ","))]")
|
||||||
|
}
|
||||||
|
|
||||||
|
if let underlying = error.userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||||
|
lines.append("underlying=\(underlying.domain)#\(underlying.code) fatal=\((underlying.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false)")
|
||||||
|
if let underlyingMessage = underlying.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !underlyingMessage.isEmpty {
|
||||||
|
lines.append("underlyingMessage=\(underlyingMessage)")
|
||||||
|
} else if !underlying.localizedDescription.isEmpty {
|
||||||
|
lines.append("underlyingLocalized=\(underlying.localizedDescription)")
|
||||||
|
}
|
||||||
|
} else if let underlying = error.userInfo[NSUnderlyingErrorKey] {
|
||||||
|
lines.append("underlyingRaw=\(underlying)")
|
||||||
|
}
|
||||||
|
|
||||||
|
let formatted = lines.joined(separator: "\n ")
|
||||||
|
ovpnLog(.error, title: "Error", message: formatted)
|
||||||
|
}
|
||||||
|
|
||||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
||||||
withShadowSocks viaSS: Bool = false,
|
withShadowSocks viaSS: Bool = false,
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
ovpnLog(.info, message: "Setup and launch")
|
ovpnLog(.info, message: "Setup and launch")
|
||||||
|
|
||||||
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
var configString = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||||
|
|
||||||
|
let digest = SHA256.hash(data: ovpnConfiguration)
|
||||||
|
let digestString = digest.map { String(format: "%02x", $0) }.joined()
|
||||||
|
ovpnLog(.info, title: "ConfigDigest", message: digestString)
|
||||||
|
|
||||||
|
let hasTlsAuthOpen = configString.contains("<tls-auth>")
|
||||||
|
let hasTlsAuthClose = configString.contains("</tls-auth>")
|
||||||
|
ovpnLog(.info, title: "ConfigFlags", message: "tls-auth open=\(hasTlsAuthOpen) close=\(hasTlsAuthClose)")
|
||||||
|
|
||||||
|
let lines = configString.split(separator: "\n")
|
||||||
|
let head = lines.prefix(10).joined(separator: "\n")
|
||||||
|
let tail = lines.suffix(10).joined(separator: "\n")
|
||||||
|
ovpnLog(.debug, title: "ConfigHead", message: head)
|
||||||
|
ovpnLog(.debug, title: "ConfigTail", message: tail)
|
||||||
|
|
||||||
|
if let start = configString.range(of: "<tls-auth>"),
|
||||||
|
let end = configString.range(of: "</tls-auth>", range: start.upperBound..<configString.endIndex) {
|
||||||
|
let keyBody = String(configString[start.upperBound..<end.lowerBound])
|
||||||
|
ovpnLog(.debug, title: "TLSAuthInline", message: keyBody)
|
||||||
|
let sanitizedLines = keyBody
|
||||||
|
.split(whereSeparator: { $0.isNewline })
|
||||||
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||||
|
.filter { !$0.isEmpty }
|
||||||
|
.filter { !$0.hasPrefix("#") }
|
||||||
|
|
||||||
|
let sanitizedKey = sanitizedLines.joined(separator: "\n")
|
||||||
|
ovpnLog(.debug, title: "TLSAuthSanitized", message: sanitizedKey)
|
||||||
|
let sanitizedBlock = "<tls-auth>\n\(sanitizedKey)\n</tls-auth>"
|
||||||
|
configString.replaceSubrange(start.lowerBound..<end.upperBound, with: sanitizedBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
let normalizedConfig = configString.replacingOccurrences(of: "\r\n", with: "\n")
|
||||||
|
let sanitizedData = Data(normalizedConfig.utf8)
|
||||||
|
|
||||||
let configuration = OpenVPNConfiguration()
|
let configuration = OpenVPNConfiguration()
|
||||||
configuration.fileContent = ovpnConfiguration
|
configuration.fileContent = sanitizedData
|
||||||
if str.contains("cloak") {
|
if configString.contains("cloak") {
|
||||||
configuration.setPTCloak()
|
configuration.setPTCloak()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +115,8 @@ extension PacketTunnelProvider {
|
|||||||
evaluation = try ovpnAdapter?.apply(configuration: configuration)
|
evaluation = try ovpnAdapter?.apply(configuration: configuration)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
|
let nsError = error as NSError
|
||||||
|
ovpnLog(.error, title: "ApplyConfig", message: "domain=\(nsError.domain) code=\(nsError.code) info=\(nsError.userInfo)")
|
||||||
completionHandler(error)
|
completionHandler(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -208,8 +268,11 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
|||||||
|
|
||||||
// Handle errors thrown by the OpenVPN library
|
// Handle errors thrown by the OpenVPN library
|
||||||
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
|
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
|
||||||
|
let nsError = error as NSError
|
||||||
|
logOpenVPNError(nsError)
|
||||||
|
|
||||||
// Handle only fatal errors
|
// Handle only fatal errors
|
||||||
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
|
guard let fatal = nsError.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
|
||||||
fatal == true else { return }
|
fatal == true else { return }
|
||||||
|
|
||||||
if vpnReachability.isTracking {
|
if vpnReachability.isTracking {
|
||||||
|
|||||||
@@ -29,12 +29,46 @@ const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
|||||||
|
|
||||||
#if !MACOS_NE
|
#if !MACOS_NE
|
||||||
static UIViewController* getViewController() {
|
static UIViewController* getViewController() {
|
||||||
NSArray *windows = [[UIApplication sharedApplication]windows];
|
UIApplication *application = [UIApplication sharedApplication];
|
||||||
for (UIWindow *window in windows) {
|
|
||||||
if (window.isKeyWindow) {
|
if (@available(iOS 13.0, *)) {
|
||||||
|
for (UIScene *scene in application.connectedScenes) {
|
||||||
|
if (scene.activationState != UISceneActivationStateForegroundActive) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![scene isKindOfClass:[UIWindowScene class]]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
||||||
|
|
||||||
|
for (UIWindow *window in windowScene.windows) {
|
||||||
|
if (window.isKeyWindow && window.rootViewController) {
|
||||||
|
return window.rootViewController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UIWindow *window in windowScene.windows) {
|
||||||
|
if (!window.isHidden && window.rootViewController) {
|
||||||
|
return window.rootViewController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UIWindow *window in application.windows) {
|
||||||
|
if (window.isKeyWindow && window.rootViewController) {
|
||||||
return window.rootViewController;
|
return window.rootViewController;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (UIWindow *window in application.windows) {
|
||||||
|
if (window.rootViewController) {
|
||||||
|
return window.rootViewController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent)
|
|||||||
Ikev2Protocol::~Ikev2Protocol()
|
Ikev2Protocol::~Ikev2Protocol()
|
||||||
{
|
{
|
||||||
qDebug() << "IpsecProtocol::~IpsecProtocol()";
|
qDebug() << "IpsecProtocol::~IpsecProtocol()";
|
||||||
disconnect_vpn();
|
|
||||||
Ikev2Protocol::stop();
|
Ikev2Protocol::stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ void Ikev2Protocol::stop()
|
|||||||
{
|
{
|
||||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||||
{
|
{
|
||||||
if (! disconnect_vpn() ){
|
if (!disconnect_vpn()){
|
||||||
qDebug()<<"We don't disconnect";
|
qDebug()<<"We don't disconnect";
|
||||||
setConnectionState(Vpn::ConnectionState::Error);
|
setConnectionState(Vpn::ConnectionState::Error);
|
||||||
}
|
}
|
||||||
@@ -311,7 +310,9 @@ bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){
|
|||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
bool Ikev2Protocol::disconnect_vpn(){
|
bool Ikev2Protocol::disconnect_vpn(){
|
||||||
if ( hRasConn != nullptr ){
|
if ( hRasConn != nullptr ){
|
||||||
if ( RasHangUp(hRasConn) != ERROR_SUCCESS)
|
auto ret = RasHangUp(hRasConn);
|
||||||
|
qDebug() << "RasHangUp " << ret;
|
||||||
|
if (ret != ERROR_SUCCESS)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QThread::msleep(3000);
|
QThread::msleep(3000);
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ void XrayProtocol::stop()
|
|||||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||||
IpcClient::Interface()->disableKillSwitch();
|
IpcClient::Interface()->disableKillSwitch();
|
||||||
IpcClient::Interface()->StartRoutingIpv6();
|
IpcClient::Interface()->StartRoutingIpv6();
|
||||||
|
IpcClient::Interface()->restoreResolvers();
|
||||||
#endif
|
#endif
|
||||||
qDebug() << "XrayProtocol::stop()";
|
qDebug() << "XrayProtocol::stop()";
|
||||||
m_xrayProcess.disconnect();
|
m_xrayProcess.disconnect();
|
||||||
|
|||||||
@@ -35,6 +35,9 @@
|
|||||||
<file>images/controls/mail.svg</file>
|
<file>images/controls/mail.svg</file>
|
||||||
<file>images/controls/map-pin.svg</file>
|
<file>images/controls/map-pin.svg</file>
|
||||||
<file>images/controls/more-vertical.svg</file>
|
<file>images/controls/more-vertical.svg</file>
|
||||||
|
<file>images/controls/news.svg</file>
|
||||||
|
<file>images/controls/news-unread.svg</file>
|
||||||
|
<file>images/controls/unread-dot.svg</file>
|
||||||
<file>images/controls/plus.svg</file>
|
<file>images/controls/plus.svg</file>
|
||||||
<file>images/controls/qr-code.svg</file>
|
<file>images/controls/qr-code.svg</file>
|
||||||
<file>images/controls/radio-button-inner-circle-pressed.png</file>
|
<file>images/controls/radio-button-inner-circle-pressed.png</file>
|
||||||
@@ -49,6 +52,7 @@
|
|||||||
<file>images/controls/server.svg</file>
|
<file>images/controls/server.svg</file>
|
||||||
<file>images/controls/settings-2.svg</file>
|
<file>images/controls/settings-2.svg</file>
|
||||||
<file>images/controls/settings.svg</file>
|
<file>images/controls/settings.svg</file>
|
||||||
|
<file>images/controls/settings-news.svg</file>
|
||||||
<file>images/controls/share-2.svg</file>
|
<file>images/controls/share-2.svg</file>
|
||||||
<file>images/controls/split-tunneling.svg</file>
|
<file>images/controls/split-tunneling.svg</file>
|
||||||
<file>images/controls/tag.svg</file>
|
<file>images/controls/tag.svg</file>
|
||||||
@@ -212,6 +216,8 @@
|
|||||||
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
|
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
|
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
|
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageSettingsNewsNotifications.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageSettingsNewsDetail.qml</file>
|
||||||
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
|
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
|
||||||
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
|
||||||
|
|||||||
Executable
+519
@@ -0,0 +1,519 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
LOG_DATE=$(date -u +'%Y%m%d-%H%M%S')
|
||||||
|
SCRIPT_DIR=$(dirname "$0")
|
||||||
|
LOG_FILE="${SCRIPT_DIR}/server-diagnostics-${LOG_DATE}.log"
|
||||||
|
|
||||||
|
# Logging function (sh compatible)
|
||||||
|
log_and_display() {
|
||||||
|
if [ "$1" = "-n" ]; then
|
||||||
|
shift
|
||||||
|
printf "%s" "$*" | tee -a "$LOG_FILE"
|
||||||
|
else
|
||||||
|
echo "$1" | tee -a "$LOG_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redirect stderr to stdout for logging
|
||||||
|
exec 2>&1
|
||||||
|
|
||||||
|
header() {
|
||||||
|
log_and_display ""
|
||||||
|
log_and_display "=== $1 ==="
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pause for cancellation
|
||||||
|
log_and_display ""
|
||||||
|
log_and_display "VPN Server Diagnostics will start in 9s. Press Ctrl+C to cancel."
|
||||||
|
sleep 9
|
||||||
|
|
||||||
|
log_and_display ""
|
||||||
|
header "STARTING VPN SERVER DIAGNOSTICS"
|
||||||
|
log_and_display ""
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 1. Basic system information
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
header "System Information"
|
||||||
|
|
||||||
|
# Uptime
|
||||||
|
UPTIME_STR=$(awk '{printf "%d:%02d:%02d", int($1/3600), int(($1%3600)/60), int($1%60)}' /proc/uptime 2>/dev/null || echo "unknown")
|
||||||
|
log_and_display "Uptime (H:M:S): $UPTIME_STR"
|
||||||
|
|
||||||
|
# Date/time UTC
|
||||||
|
DATE_UTC=$(date -u +'%d %b %Y|%T' 2>/dev/null || echo "unknown")
|
||||||
|
log_and_display "Date|Time (UTC): $DATE_UTC"
|
||||||
|
|
||||||
|
# Init system (PID 1)
|
||||||
|
INIT_NAME=$(cat /proc/1/status 2>/dev/null | head -1 | awk '{print $2}' 2>/dev/null || echo "unknown")
|
||||||
|
log_and_display "Init system (PID 1): $INIT_NAME"
|
||||||
|
|
||||||
|
# Locale
|
||||||
|
if echo "$LANG" | grep -E '^(en_US.UTF-8|C.UTF-8|C)$' >/dev/null 2>&1; then
|
||||||
|
log_and_display "Locale: $LANG"
|
||||||
|
else
|
||||||
|
log_and_display "Locale: $LANG (not en_US.UTF-8, C.UTF-8 or C)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 2. Package manager detection
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
header "Package Manager Information"
|
||||||
|
|
||||||
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
|
log_and_display "Package Manager: APT"
|
||||||
|
PM="apt-get"
|
||||||
|
PM_VER_OPT="--version"
|
||||||
|
DOCKER_PKG="docker.io"
|
||||||
|
elif command -v dnf >/dev/null 2>&1; then
|
||||||
|
log_and_display "Package Manager: DNF"
|
||||||
|
PM="dnf"
|
||||||
|
PM_VER_OPT="--version"
|
||||||
|
DOCKER_PKG="docker"
|
||||||
|
elif command -v yum >/dev/null 2>&1; then
|
||||||
|
log_and_display "Package Manager: YUM"
|
||||||
|
PM="yum"
|
||||||
|
PM_VER_OPT="--version"
|
||||||
|
DOCKER_PKG="docker"
|
||||||
|
elif command -v zypper >/dev/null 2>&1; then
|
||||||
|
log_and_display "Package Manager: ZYPPER"
|
||||||
|
PM="zypper"
|
||||||
|
PM_VER_OPT="--version"
|
||||||
|
DOCKER_PKG="docker"
|
||||||
|
elif command -v pacman >/dev/null 2>&1; then
|
||||||
|
log_and_display "Package Manager: PACMAN"
|
||||||
|
PM="pacman"
|
||||||
|
PM_VER_OPT="--version"
|
||||||
|
DOCKER_PKG="docker"
|
||||||
|
elif command -v opkg >/dev/null 2>&1; then
|
||||||
|
log_and_display "Package Manager: OPKG - Not supported on this platform"
|
||||||
|
PM="opkg"
|
||||||
|
PM_VER_OPT="--version"
|
||||||
|
DOCKER_PKG="docker"
|
||||||
|
else
|
||||||
|
log_and_display "Package Manager: Unknown"
|
||||||
|
# fallback
|
||||||
|
PM="uname"
|
||||||
|
PM_VER_OPT="-a"
|
||||||
|
DOCKER_PKG="docker"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check package versions
|
||||||
|
log_and_display ""
|
||||||
|
log_and_display "Package versions:"
|
||||||
|
|
||||||
|
# Check sudo
|
||||||
|
if [ "$PM" = "apt-get" ]; then
|
||||||
|
sudo_version=$(dpkg -s "sudo" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||||
|
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
|
||||||
|
sudo_version=$(rpm -q "sudo" 2>/dev/null || echo "not installed")
|
||||||
|
elif [ "$PM" = "pacman" ]; then
|
||||||
|
sudo_version=$(pacman -Q "sudo" 2>/dev/null || echo "not installed")
|
||||||
|
elif [ "$PM" = "opkg" ]; then
|
||||||
|
sudo_version=$(opkg info "sudo" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||||
|
else
|
||||||
|
sudo_version="unknown"
|
||||||
|
fi
|
||||||
|
log_and_display " sudo: $sudo_version"
|
||||||
|
|
||||||
|
# Check Docker package
|
||||||
|
if [ "$PM" = "apt-get" ]; then
|
||||||
|
docker_pkg_version=$(dpkg -s "$DOCKER_PKG" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||||
|
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
|
||||||
|
docker_pkg_version=$(rpm -q "$DOCKER_PKG" 2>/dev/null || echo "not installed")
|
||||||
|
elif [ "$PM" = "pacman" ]; then
|
||||||
|
docker_pkg_version=$(pacman -Q "$DOCKER_PKG" 2>/dev/null || echo "not installed")
|
||||||
|
elif [ "$PM" = "opkg" ]; then
|
||||||
|
docker_pkg_version=$(opkg info "$DOCKER_PKG" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||||
|
else
|
||||||
|
docker_pkg_version="unknown"
|
||||||
|
fi
|
||||||
|
log_and_display " $DOCKER_PKG: $docker_pkg_version"
|
||||||
|
|
||||||
|
# Check lsof
|
||||||
|
if [ "$PM" = "apt-get" ]; then
|
||||||
|
lsof_version=$(dpkg -s "lsof" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||||
|
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
|
||||||
|
lsof_version=$(rpm -q "lsof" 2>/dev/null || echo "not installed")
|
||||||
|
elif [ "$PM" = "pacman" ]; then
|
||||||
|
lsof_version=$(pacman -Q "lsof" 2>/dev/null || echo "not installed")
|
||||||
|
elif [ "$PM" = "opkg" ]; then
|
||||||
|
lsof_version=$(opkg info "lsof" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||||
|
else
|
||||||
|
lsof_version="unknown"
|
||||||
|
fi
|
||||||
|
log_and_display " lsof: $lsof_version"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 3. Additional system information (hostnamectl / /proc/version)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
header "OS / Kernel Information"
|
||||||
|
|
||||||
|
if command -v hostnamectl >/dev/null 2>&1; then
|
||||||
|
hostnamectl 2>/dev/null | grep -E 'Operating System:|Virtualization:|Kernel:|Architecture:' | sed 's/^[ \t]*//;s/:/: /' | while read line; do
|
||||||
|
log_and_display " $line"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log_and_display "Operating System: $(cat /proc/version 2>/dev/null || echo 'unknown')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# CPU threads
|
||||||
|
CPU_THREADS=$(nproc 2>/dev/null || grep -c "^processor" /proc/cpuinfo 2>/dev/null || echo "unknown")
|
||||||
|
log_and_display " CPU threads: $CPU_THREADS"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 4. Memory (RAM) check
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
header "Memory Information"
|
||||||
|
|
||||||
|
if command -v free >/dev/null 2>&1; then
|
||||||
|
# Remove extra spaces in header
|
||||||
|
free -h 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
|
||||||
|
elif command -v vmstat >/dev/null 2>&1; then
|
||||||
|
vmstat -S M -s 2>/dev/null | grep -iE 'total memory|total swap' | sed 's/ *//' | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
|
||||||
|
else
|
||||||
|
grep -iE 'MemTotal|SwapTotal' /proc/meminfo 2>/dev/null | sed 's/ \+/ /' | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v free >/dev/null 2>&1; then
|
||||||
|
log_and_display ""
|
||||||
|
log_and_display "Detailed Memory Info:"
|
||||||
|
free -h 2>/dev/null | awk 'NR==2{printf " Used: %s / %s (%.1f%%)\n", $3, $2, $3/$2*100}' 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error calculating memory usage"
|
||||||
|
free -h 2>/dev/null | awk 'NR==3{printf " Swap: %s / %s (%.1f%%)\n", $3, $2, $2>0 ? $3/$2*100 : 0}' 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error calculating swap usage"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Disk usage
|
||||||
|
header "Disk Usage"
|
||||||
|
df -h 2>/dev/null | awk '
|
||||||
|
BEGIN {print " Filesystem Size Used Avail Use% Mounted"}
|
||||||
|
NR>1 {printf " %-10s %5s %5s %5s %4s %s\n", $1, $2, $3, $4, $5, $6}' | tee -a "$LOG_FILE" || log_and_display " Error getting disk usage"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 5. Current user and sudo check
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
header "User Check"
|
||||||
|
|
||||||
|
CUR_USER=$(whoami 2>/dev/null || echo ~ | sed 's/.*\///')
|
||||||
|
USER_GROUP=$(groups "$CUR_USER" 2>/dev/null || echo "")
|
||||||
|
USER_GOOD=0
|
||||||
|
|
||||||
|
log_and_display -n "Current user: $CUR_USER => "
|
||||||
|
|
||||||
|
if [ "$CUR_USER" = "root" ]; then
|
||||||
|
log_and_display "passed.. (is root)"
|
||||||
|
USER_GOOD="r" # root
|
||||||
|
else
|
||||||
|
if echo "$USER_GROUP" | grep -qE '(^|[[:space:]])sudo($|[[:space:]])'; then
|
||||||
|
log_and_display "passed.. (in sudo group)"
|
||||||
|
USER_GOOD=1
|
||||||
|
elif echo "$USER_GROUP" | grep -qE '(^|[[:space:]])wheel($|[[:space:]])'; then
|
||||||
|
log_and_display "passed.. (in wheel group)"
|
||||||
|
USER_GOOD=1
|
||||||
|
elif echo "$USER_GROUP" | grep -qE '(^|[[:space:]])docker($|[[:space:]])'; then
|
||||||
|
log_and_display "failed.. (only in docker group)"
|
||||||
|
USER_GOOD="d"
|
||||||
|
else
|
||||||
|
log_and_display "failed.. (not a member of the sudo or wheel groups)"
|
||||||
|
USER_GOOD=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if password is required for sudo
|
||||||
|
if [ "$USER_GOOD" = "0" ] || [ "$USER_GOOD" = "d" ]; then
|
||||||
|
log_and_display -n "Passwd request: "
|
||||||
|
log_and_display "check skipped (not sudoer)"
|
||||||
|
else
|
||||||
|
if command -v sudo >/dev/null 2>&1; then
|
||||||
|
# Try sudo without password - more thorough check
|
||||||
|
PASSWD_REQUEST=$(sudo -K 2>&1 && sudo -nu $CUR_USER $PM $PM_VER_OPT 2>&1 >/dev/null && sudo -n $PM $PM_VER_OPT 2>&1 >/dev/null)
|
||||||
|
if [ -n "$PASSWD_REQUEST" ]; then
|
||||||
|
USER_GOOD=0
|
||||||
|
log_and_display -n "Passwd request: "
|
||||||
|
log_and_display "failed.. ($PASSWD_REQUEST)" \
|
||||||
|
| sed "s/$CUR_USER/User/g;s/$(hostname 2>/dev/null || echo 'Server')/Server/g;s/ user / /g"
|
||||||
|
else
|
||||||
|
log_and_display -n "Passwd request: "
|
||||||
|
log_and_display "passed.. (not required)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$USER_GOOD" = "r" ]; then
|
||||||
|
log_and_display -n "Passwd request: "
|
||||||
|
log_and_display "check skipped (sudo not installed, but root user)"
|
||||||
|
else
|
||||||
|
log_and_display "Warning! The sudo package must be pre-installed!"
|
||||||
|
USER_GOOD=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Home directory check
|
||||||
|
log_and_display -n "Home dir: "
|
||||||
|
if cd ~ 2>/dev/null; then
|
||||||
|
log_and_display "passed.. (accessible)"
|
||||||
|
else
|
||||||
|
log_and_display "failed.. (not accessible)"
|
||||||
|
fi
|
||||||
|
log_and_display "Default shell: $SHELL"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 6. Important components check (sudo, lsof, fuser, apparmor)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
header "Component Checks"
|
||||||
|
|
||||||
|
log_and_display -n " sudo: "
|
||||||
|
if command -v sudo >/dev/null 2>&1; then
|
||||||
|
log_and_display "passed.. (installed)"
|
||||||
|
else
|
||||||
|
log_and_display "not installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_and_display -n " lsof: "
|
||||||
|
if command -v lsof >/dev/null 2>&1; then
|
||||||
|
log_and_display "passed.. (installed)"
|
||||||
|
else
|
||||||
|
log_and_display "not installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_and_display -n " fuser: "
|
||||||
|
if command -v fuser >/dev/null 2>&1; then
|
||||||
|
log_and_display "passed.. (installed)"
|
||||||
|
else
|
||||||
|
log_and_display "psmisc not installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_and_display -n "apparmor: "
|
||||||
|
AA_ENABLED=$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null || echo "N")
|
||||||
|
if [ "$AA_ENABLED" = "Y" ]; then
|
||||||
|
if command -v apparmor_parser >/dev/null 2>&1; then
|
||||||
|
log_and_display "passed.. (used)"
|
||||||
|
else
|
||||||
|
log_and_display "failed.. (installation required)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if command -v apparmor_parser >/dev/null 2>&1; then
|
||||||
|
log_and_display "passed.. (not used)"
|
||||||
|
else
|
||||||
|
log_and_display "passed.. (not required)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 7. SELinux check
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
header "SELinux Check"
|
||||||
|
|
||||||
|
if command -v getenforce >/dev/null 2>&1; then
|
||||||
|
SELINUX_STATUS=$(getenforce 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$SELINUX_STATUS" = "Enforcing" ]; then
|
||||||
|
log_and_display "SELinux status: $SELINUX_STATUS (strict mode)"
|
||||||
|
elif [ "$SELINUX_STATUS" = "Permissive" ]; then
|
||||||
|
log_and_display "SELinux status: $SELINUX_STATUS (permissive mode)"
|
||||||
|
else
|
||||||
|
log_and_display "SELinux status: $SELINUX_STATUS (disabled)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_and_display "SELinux: not found (or not applicable)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 8. Docker + Docker/Podman service check
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
header "Docker / Podman Status"
|
||||||
|
CHECK_CONTAINERS=0
|
||||||
|
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
log_and_display "Docker: $DOCKER_PKG not installed"
|
||||||
|
else
|
||||||
|
# If user is in sudoers, use sudo without password
|
||||||
|
if [ "$USER_GOOD" = "1" ]; then
|
||||||
|
SUD="sudo -n"
|
||||||
|
elif [ "$USER_GOOD" = "r" ]; then
|
||||||
|
SUD="" # root
|
||||||
|
else
|
||||||
|
SUD=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOCKER_VERSION=$($SUD docker -v 2>/dev/null || echo 'docker -v error')
|
||||||
|
log_and_display "Installed: $DOCKER_VERSION"
|
||||||
|
|
||||||
|
# Check for podman
|
||||||
|
if echo "$DOCKER_VERSION" | grep -qi "podman"; then
|
||||||
|
log_and_display " WARNING: Podman detected - not supported at the moment!"
|
||||||
|
log_and_display " Podman (podman-docker) is not supported and is installed by mistake"
|
||||||
|
docker_service="podman.socket"
|
||||||
|
else
|
||||||
|
docker_service="docker.service"
|
||||||
|
fi
|
||||||
|
log_and_display " service: $docker_service"
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
docker_status=$(systemctl is-active "$docker_service" 2>/dev/null || echo "unknown")
|
||||||
|
docker_loading=$(systemctl is-enabled "$docker_service" 2>/dev/null || echo "unknown")
|
||||||
|
else
|
||||||
|
docker_status="unknown (systemctl not found)"
|
||||||
|
docker_loading="unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$docker_status" = "active" ]; then
|
||||||
|
log_and_display " status: passed.. ($docker_status)"
|
||||||
|
CHECK_CONTAINERS=1
|
||||||
|
else
|
||||||
|
log_and_display " status: incorrect.. ($docker_status)"
|
||||||
|
CHECK_CONTAINERS=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$docker_loading" = "enabled" ]; then
|
||||||
|
log_and_display " loading: good (startup $docker_loading)"
|
||||||
|
else
|
||||||
|
log_and_display " loading: bad (startup $docker_loading)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 9. Docker pull test + container check with improved Docker Hub verification
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
header "Docker Hub: pull hello-world test"
|
||||||
|
|
||||||
|
if [ "$CHECK_CONTAINERS" = "1" ] && [ "$USER_GOOD" != "0" ]; then
|
||||||
|
# First check Docker Hub availability
|
||||||
|
log_and_display "Checking Docker Hub connectivity..."
|
||||||
|
|
||||||
|
# Try to execute docker pull with timeout
|
||||||
|
if timeout 30 $SUD docker pull docker.io/library/hello-world >/dev/null 2>&1; then
|
||||||
|
log_and_display "Docker Hub: available"
|
||||||
|
|
||||||
|
# Start container for testing
|
||||||
|
if $SUD docker run --rm docker.io/library/hello-world >/dev/null 2>&1; then
|
||||||
|
log_and_display "Hello-world container: successfully started and completed"
|
||||||
|
else
|
||||||
|
log_and_display "Hello-world container: startup error"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_and_display "Docker Hub: unavailable or blocked (possibly exceeded download limit)"
|
||||||
|
log_and_display "Docker Hub has download limits, try again later"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_and_display ""
|
||||||
|
total_cont=$($SUD docker ps -aq 2>/dev/null | wc -l || echo "0")
|
||||||
|
active_cont=$($SUD docker ps -q 2>/dev/null | wc -l || echo "0")
|
||||||
|
amnezia_cont=$($SUD docker ps -a 2>/dev/null | grep -c amnezia || echo "0")
|
||||||
|
|
||||||
|
log_and_display "Containers check: Total $total_cont / Active $active_cont / Amnezia $amnezia_cont"
|
||||||
|
$SUD docker ps -a --format "{{.Names}} ({{.Image}}) ({{.Status}}) ({{.Ports}})" 2>/dev/null | grep amnezia || true
|
||||||
|
|
||||||
|
# Peers check
|
||||||
|
if $SUD docker ps 2>/dev/null | grep -qE '\<(amnezia-awg|amnezia-wireguard)\>'; then
|
||||||
|
log_and_display ""
|
||||||
|
log_and_display "Peers check (beta):"
|
||||||
|
if $SUD docker ps 2>/dev/null | grep -q amnezia-awg; then
|
||||||
|
AMNEZIA_WG_CONTAINER=$($SUD docker ps 2>/dev/null | grep amnezia-awg | awk '{print $1}' | head -1)
|
||||||
|
if [ -n "$AMNEZIA_WG_CONTAINER" ]; then
|
||||||
|
WG_PEERS=$($SUD docker exec -it "$AMNEZIA_WG_CONTAINER" wg show 2>/dev/null | grep -c 'peer' || echo "0")
|
||||||
|
log_and_display "AmneziaWG peers: $WG_PEERS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if $SUD docker ps 2>/dev/null | grep -q amnezia-wireguard; then
|
||||||
|
WIREGUARD_CONTAINER=$($SUD docker ps 2>/dev/null | grep amnezia-wireguard | awk '{print $1}' | head -1)
|
||||||
|
if [ -n "$WIREGUARD_CONTAINER" ]; then
|
||||||
|
WG_PEERS=$($SUD docker exec -it "$WIREGUARD_CONTAINER" wg show 2>/dev/null | grep -c 'peer' || echo "0")
|
||||||
|
log_and_display "WireGuard peers: $WG_PEERS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_and_display "skipped.."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 10. Additional improvements
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# 10.1. CPU and memory load check (Load average, top processes)
|
||||||
|
#
|
||||||
|
header "CPU & Memory usage (top)"
|
||||||
|
|
||||||
|
# Load average (last 1,5,15 minutes)
|
||||||
|
LOAD_AVG=$(uptime 2>/dev/null | awk -F'load average:' '{print $2}' || echo "unknown")
|
||||||
|
log_and_display "Load average: $LOAD_AVG"
|
||||||
|
|
||||||
|
log_and_display ""
|
||||||
|
log_and_display "Top 5 processes by CPU:"
|
||||||
|
ps aux 2>/dev/null | sort -k3 -nr | head -n 6 | awk '{printf "%s %s %s %s %s\n", $1,$2,$3"%",$4"%",$11}' | column -t 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting CPU processes"
|
||||||
|
|
||||||
|
log_and_display ""
|
||||||
|
log_and_display "Top 5 processes by MEM:"
|
||||||
|
ps aux 2>/dev/null | sort -k4 -nr | head -n 6 | awk '{printf "%s %s %s %s %s\n", $1,$2,$3"%",$4"%",$11}' | column -t 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting MEM processes"
|
||||||
|
|
||||||
|
# 10.2. System logs check (latest critical messages)
|
||||||
|
header "Last 10 critical/error messages (journalctl)"
|
||||||
|
|
||||||
|
if command -v journalctl >/dev/null 2>&1; then
|
||||||
|
journalctl -p 3 -n 10 --no-pager 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting system logs"
|
||||||
|
else
|
||||||
|
log_and_display "journalctl not found (non-systemd system?)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 10.3. System package versions check (examples)
|
||||||
|
|
||||||
|
# Open ports check
|
||||||
|
header "Network Ports Check"
|
||||||
|
if command -v netstat >/dev/null 2>&1; then
|
||||||
|
log_and_display "Listening ports:"
|
||||||
|
netstat -tlnp 2>/dev/null | grep LISTEN | head -10 | while read line; do
|
||||||
|
log_and_display " $line"
|
||||||
|
done
|
||||||
|
elif command -v ss >/dev/null 2>&1; then
|
||||||
|
log_and_display "Listening ports:"
|
||||||
|
ss -tlnp 2>/dev/null | head -10 | while read line; do
|
||||||
|
log_and_display " $line"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log_and_display "netstat/ss not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# SSH check
|
||||||
|
header "SSH Service Check"
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
ssh_status=$(systemctl is-active ssh 2>/dev/null || systemctl is-active sshd 2>/dev/null || echo "not found")
|
||||||
|
if [ "$ssh_status" = "active" ]; then
|
||||||
|
log_and_display "SSH service: $ssh_status"
|
||||||
|
else
|
||||||
|
log_and_display "SSH service: $ssh_status"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_and_display "systemctl not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Time check
|
||||||
|
header "Time Synchronization"
|
||||||
|
if command -v timedatectl >/dev/null 2>&1; then
|
||||||
|
timedatectl status 2>/dev/null | grep -E "System clock|NTP service" | while read line; do
|
||||||
|
log_and_display " $line"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log_and_display " System time: $(date 2>/dev/null || echo 'unknown')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Kernel check
|
||||||
|
header "Kernel Information"
|
||||||
|
log_and_display "Kernel version: $(uname -r 2>/dev/null || echo 'unknown')"
|
||||||
|
log_and_display "Kernel architecture: $(uname -m 2>/dev/null || echo 'unknown')"
|
||||||
|
if [ -f /proc/cmdline ]; then
|
||||||
|
log_and_display "Kernel parameters:"
|
||||||
|
cat /proc/cmdline 2>/dev/null | tr ' ' '\n' | head -5 | while read param; do
|
||||||
|
log_and_display " $param"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Completion
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
log_and_display ""
|
||||||
|
header "FINISH"
|
||||||
|
log_and_display ""
|
||||||
|
log_and_display "Diagnostics completed. Log saved to: $LOG_FILE"
|
||||||
|
log_and_display ""
|
||||||
|
|
||||||
|
# Variable cleanup
|
||||||
|
pm="" && opt="" && docker_pkg="" && CUR_USER="" && USER_GOOD="" && USER_GROUP="" && PASSWD_REQUEST="" && CHECK_CONTAINERS="" && SUD="" && docker_service="" && docker_status="" && docker_loading=""
|
||||||
+12
-2
@@ -541,12 +541,12 @@ QString Settings::getGatewayEndpoint()
|
|||||||
|
|
||||||
bool Settings::isDevGatewayEnv()
|
bool Settings::isDevGatewayEnv()
|
||||||
{
|
{
|
||||||
return m_isDevGatewayEnv;
|
return value("Conf/devGatewayEnv", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::toggleDevGatewayEnv(bool enabled)
|
void Settings::toggleDevGatewayEnv(bool enabled)
|
||||||
{
|
{
|
||||||
m_isDevGatewayEnv = enabled;
|
setValue("Conf/devGatewayEnv", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isHomeAdLabelVisible()
|
bool Settings::isHomeAdLabelVisible()
|
||||||
@@ -578,3 +578,13 @@ void Settings::setAllowedDnsServers(const QStringList &servers)
|
|||||||
{
|
{
|
||||||
setValue("Conf/allowedDnsServers", servers);
|
setValue("Conf/allowedDnsServers", servers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList Settings::readNewsIds() const
|
||||||
|
{
|
||||||
|
return value("News/readIds").toStringList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::setReadNewsIds(const QStringList &ids)
|
||||||
|
{
|
||||||
|
setValue("News/readIds", ids);
|
||||||
|
}
|
||||||
|
|||||||
+4
-2
@@ -174,7 +174,7 @@ public:
|
|||||||
|
|
||||||
QLocale getAppLanguage()
|
QLocale getAppLanguage()
|
||||||
{
|
{
|
||||||
QString localeStr = m_settings.value("Conf/appLanguage").toString();
|
QString localeStr = m_settings.value("Conf/appLanguage", QLocale::system().name()).toString();
|
||||||
return QLocale(localeStr);
|
return QLocale(localeStr);
|
||||||
};
|
};
|
||||||
void setAppLanguage(QLocale locale)
|
void setAppLanguage(QLocale locale)
|
||||||
@@ -236,6 +236,9 @@ public:
|
|||||||
QStringList allowedDnsServers() const;
|
QStringList allowedDnsServers() const;
|
||||||
void setAllowedDnsServers(const QStringList &servers);
|
void setAllowedDnsServers(const QStringList &servers);
|
||||||
|
|
||||||
|
QStringList readNewsIds() const;
|
||||||
|
void setReadNewsIds(const QStringList &ids);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void saveLogsChanged(bool enabled);
|
void saveLogsChanged(bool enabled);
|
||||||
void screenshotsEnabledChanged(bool enabled);
|
void screenshotsEnabledChanged(bool enabled);
|
||||||
@@ -251,7 +254,6 @@ private:
|
|||||||
mutable SecureQSettings m_settings;
|
mutable SecureQSettings m_settings;
|
||||||
|
|
||||||
QString m_gatewayEndpoint;
|
QString m_gatewayEndpoint;
|
||||||
bool m_isDevGatewayEnv = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SETTINGS_H
|
#endif // SETTINGS_H
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,7 @@ namespace
|
|||||||
constexpr char uuid[] = "installation_uuid";
|
constexpr char uuid[] = "installation_uuid";
|
||||||
constexpr char osVersion[] = "os_version";
|
constexpr char osVersion[] = "os_version";
|
||||||
constexpr char appVersion[] = "app_version";
|
constexpr char appVersion[] = "app_version";
|
||||||
|
constexpr char appLanguage[] = "app_language";
|
||||||
|
|
||||||
constexpr char userCountryCode[] = "user_country_code";
|
constexpr char userCountryCode[] = "user_country_code";
|
||||||
constexpr char serverCountryCode[] = "server_country_code";
|
constexpr char serverCountryCode[] = "server_country_code";
|
||||||
@@ -43,6 +44,11 @@ namespace
|
|||||||
constexpr char authData[] = "auth_data";
|
constexpr char authData[] = "auth_data";
|
||||||
|
|
||||||
constexpr char config[] = "config";
|
constexpr char config[] = "config";
|
||||||
|
|
||||||
|
constexpr char subscription[] = "subscription";
|
||||||
|
constexpr char endDate[] = "end_date";
|
||||||
|
|
||||||
|
constexpr char isConnectEvent[] = "is_connect_event";
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProtocolData
|
struct ProtocolData
|
||||||
@@ -163,7 +169,7 @@ namespace
|
|||||||
auto clientProtocolConfig =
|
auto clientProtocolConfig =
|
||||||
QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
|
QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
|
||||||
|
|
||||||
//TODO looks like this block can be removed after v1 configs EOL
|
// TODO looks like this block can be removed after v1 configs EOL
|
||||||
|
|
||||||
serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount);
|
serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount);
|
||||||
serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize);
|
serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize);
|
||||||
@@ -223,6 +229,19 @@ namespace
|
|||||||
|
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isSubscriptionExpired(const QJsonObject &apiConfig)
|
||||||
|
{
|
||||||
|
auto subscription = apiConfig.value(configKey::subscription).toObject();
|
||||||
|
if (subscription.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto subscriptionEndDate = subscription.value(configKey::endDate).toString();
|
||||||
|
if (apiUtils::isSubscriptionExpired(subscriptionEndDate)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel,
|
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel,
|
||||||
@@ -232,6 +251,23 @@ ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &s
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ApiConfigsController::exportVpnKey(const QString &fileName)
|
||||||
|
{
|
||||||
|
if (fileName.isEmpty()) {
|
||||||
|
emit errorOccurred(ErrorCode::PermissionsError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareVpnKeyExport();
|
||||||
|
if (m_vpnKey.isEmpty()) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemController::saveFile(fileName, m_vpnKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName)
|
bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName)
|
||||||
{
|
{
|
||||||
if (fileName.isEmpty()) {
|
if (fileName.isEmpty()) {
|
||||||
@@ -242,6 +278,11 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
|
|||||||
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
|
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
|
||||||
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
|
if (isSubscriptionExpired(apiConfigObject)) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
m_settings->getInstallationUuid(true),
|
m_settings->getInstallationUuid(true),
|
||||||
@@ -277,6 +318,11 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
|
|||||||
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
|
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
|
||||||
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
|
if (isSubscriptionExpired(apiConfigObject)) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
m_settings->getInstallationUuid(true),
|
m_settings->getInstallationUuid(true),
|
||||||
@@ -303,6 +349,13 @@ void ApiConfigsController::prepareVpnKeyExport()
|
|||||||
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString();
|
auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString();
|
||||||
|
if (vpnKey.isEmpty()) {
|
||||||
|
vpnKey = apiUtils::getPremiumV2VpnKey(serverConfigObject);
|
||||||
|
apiConfigObject.insert(apiDefs::key::vpnKey, vpnKey);
|
||||||
|
serverConfigObject.insert(configKey::apiConfig, apiConfigObject);
|
||||||
|
m_serversModel->editServer(serverConfigObject, m_serversModel->getProcessedServerIndex());
|
||||||
|
}
|
||||||
|
|
||||||
m_vpnKey = vpnKey;
|
m_vpnKey = vpnKey;
|
||||||
|
|
||||||
vpnKey.replace("vpn://", "");
|
vpnKey.replace("vpn://", "");
|
||||||
@@ -322,6 +375,7 @@ bool ApiConfigsController::fillAvailableServices()
|
|||||||
{
|
{
|
||||||
QJsonObject apiPayload;
|
QJsonObject apiPayload;
|
||||||
apiPayload[configKey::osVersion] = QSysInfo::productType();
|
apiPayload[configKey::osVersion] = QSysInfo::productType();
|
||||||
|
apiPayload[configKey::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
||||||
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
|
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
|
||||||
@@ -396,6 +450,11 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
|||||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||||
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
|
if (isSubscriptionExpired(apiConfig)) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
m_settings->getInstallationUuid(true),
|
m_settings->getInstallationUuid(true),
|
||||||
@@ -410,6 +469,10 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
|||||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||||
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
|
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
|
||||||
|
|
||||||
|
if (newCountryCode.isEmpty() && newCountryName.isEmpty() && !reloadServiceConfig) {
|
||||||
|
apiPayload.insert(configKey::isConnectEvent, true);
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||||
|
|
||||||
@@ -429,6 +492,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
|||||||
|
|
||||||
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
||||||
newServerConfig.insert(configKey::authData, gatewayRequestData.authData);
|
newServerConfig.insert(configKey::authData, gatewayRequestData.authData);
|
||||||
|
newServerConfig.insert(config_key::crc, serverConfig.value(config_key::crc));
|
||||||
|
|
||||||
if (serverConfig.value(config_key::nameOverriddenByUser).toBool()) {
|
if (serverConfig.value(config_key::nameOverriddenByUser).toBool()) {
|
||||||
newServerConfig.insert(config_key::name, serverConfig.value(config_key::name));
|
newServerConfig.insert(config_key::name, serverConfig.value(config_key::name));
|
||||||
@@ -492,7 +556,7 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::deactivateDevice()
|
bool ApiConfigsController::deactivateDevice(const bool isRemoveEvent)
|
||||||
{
|
{
|
||||||
auto serverIndex = m_serversModel->getProcessedServerIndex();
|
auto serverIndex = m_serversModel->getProcessedServerIndex();
|
||||||
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
||||||
@@ -502,6 +566,15 @@ bool ApiConfigsController::deactivateDevice()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSubscriptionExpired(apiConfigObject)) {
|
||||||
|
if (isRemoveEvent) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
m_settings->getInstallationUuid(true),
|
m_settings->getInstallationUuid(true),
|
||||||
@@ -536,6 +609,11 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSubscriptionExpired(apiConfigObject)) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
uuid,
|
uuid,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public:
|
|||||||
public slots:
|
public slots:
|
||||||
bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName);
|
bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName);
|
||||||
bool revokeNativeConfig(const QString &serverCountryCode);
|
bool revokeNativeConfig(const QString &serverCountryCode);
|
||||||
// bool exportVpnKey(const QString &fileName);
|
bool exportVpnKey(const QString &fileName);
|
||||||
void prepareVpnKeyExport();
|
void prepareVpnKeyExport();
|
||||||
void copyVpnKeyToClipboard();
|
void copyVpnKeyToClipboard();
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ public slots:
|
|||||||
bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
||||||
bool reloadServiceConfig = false);
|
bool reloadServiceConfig = false);
|
||||||
bool updateServiceFromTelegram(const int serverIndex);
|
bool updateServiceFromTelegram(const int serverIndex);
|
||||||
bool deactivateDevice();
|
bool deactivateDevice(const bool isRemoveEvent);
|
||||||
bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
|
bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
|
||||||
|
|
||||||
bool isConfigValid();
|
bool isConfigValid();
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#include "apiNewsController.h"
|
||||||
|
|
||||||
|
#include "core/api/apiUtils.h"
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
namespace configKey
|
||||||
|
{
|
||||||
|
constexpr char userCountryCode[] = "user_country_code";
|
||||||
|
constexpr char serviceType[] = "service_type";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiNewsController::ApiNewsController(const QSharedPointer<NewsModel> &newsModel, const std::shared_ptr<Settings> &settings,
|
||||||
|
const QSharedPointer<ServersModel> &serversModel, QObject *parent)
|
||||||
|
: QObject(parent), m_newsModel(newsModel), m_settings(settings), m_serversModel(serversModel)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiNewsController::fetchNews()
|
||||||
|
{
|
||||||
|
if (m_serversModel.isNull()) {
|
||||||
|
qWarning() << "ServersModel is null, skip fetchNews";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto stacks = m_serversModel->gatewayStacks();
|
||||||
|
if (stacks.isEmpty()) {
|
||||||
|
qDebug() << "No Gateway stacks, skip fetchNews";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||||
|
m_settings->isStrictKillSwitchEnabled());
|
||||||
|
QByteArray responseBody;
|
||||||
|
QJsonObject payload;
|
||||||
|
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
||||||
|
|
||||||
|
const QJsonObject stacksJson = stacks.toJson();
|
||||||
|
if (stacksJson.contains(configKey::userCountryCode)) {
|
||||||
|
payload.insert(configKey::userCountryCode, stacksJson.value(configKey::userCountryCode));
|
||||||
|
}
|
||||||
|
if (stacksJson.contains(configKey::serviceType)) {
|
||||||
|
payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType));
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/news"), payload, responseBody);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
|
||||||
|
QJsonArray newsArray;
|
||||||
|
if (doc.isArray()) {
|
||||||
|
newsArray = doc.array();
|
||||||
|
} else if (doc.isObject()) {
|
||||||
|
QJsonObject obj = doc.object();
|
||||||
|
if (obj.value("news").isArray()) {
|
||||||
|
newsArray = obj.value("news").toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_newsModel->updateModel(newsArray);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef APINEWSCONTROLLER_H
|
||||||
|
#define APINEWSCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "core/api/apiDefs.h"
|
||||||
|
#include "core/controllers/gatewayController.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "ui/models/newsModel.h"
|
||||||
|
#include "ui/models/servers_model.h"
|
||||||
|
|
||||||
|
class ApiNewsController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ApiNewsController(const QSharedPointer<NewsModel> &newsModel, const std::shared_ptr<Settings> &settings,
|
||||||
|
const QSharedPointer<ServersModel> &serversModel, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
Q_INVOKABLE void fetchNews();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void errorOccurred(ErrorCode errorCode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSharedPointer<NewsModel> m_newsModel;
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APINEWSCONTROLLER_H
|
||||||
@@ -297,10 +297,11 @@ void ExportController::revokeConfig(const int row, const DockerContainer contain
|
|||||||
{
|
{
|
||||||
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
||||||
ErrorCode errorCode =
|
ErrorCode errorCode =
|
||||||
m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex(), serverController);
|
m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex(), serverController);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
emit exportErrorOccurred(errorCode);
|
emit exportErrorOccurred(errorCode);
|
||||||
}
|
}
|
||||||
|
emit revokeConfigCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExportController::renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials)
|
void ExportController::renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials)
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public slots:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void generateConfig(int type);
|
void generateConfig(int type);
|
||||||
|
void revokeConfigCompleted();
|
||||||
void exportErrorOccurred(const QString &errorMessage);
|
void exportErrorOccurred(const QString &errorMessage);
|
||||||
void exportErrorOccurred(ErrorCode errorCode);
|
void exportErrorOccurred(ErrorCode errorCode);
|
||||||
|
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ void ImportController::processNativeWireGuardConfig()
|
|||||||
auto serverProtocolConfig = container.value(ContainerProps::containerTypeToString(DockerContainer::WireGuard)).toObject();
|
auto serverProtocolConfig = container.value(ContainerProps::containerTypeToString(DockerContainer::WireGuard)).toObject();
|
||||||
auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
|
auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
|
||||||
|
|
||||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(2, 5));
|
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
|
||||||
QString junkPacketMinSize = QString::number(10);
|
QString junkPacketMinSize = QString::number(10);
|
||||||
QString junkPacketMaxSize = QString::number(50);
|
QString junkPacketMaxSize = QString::number(50);
|
||||||
clientProtocolConfig[config_key::junkPacketCount] = junkPacketCount;
|
clientProtocolConfig[config_key::junkPacketCount] = junkPacketCount;
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
|||||||
containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, protocol));
|
containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, protocol));
|
||||||
|
|
||||||
if (container == DockerContainer::Awg) {
|
if (container == DockerContainer::Awg) {
|
||||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(2, 5));
|
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
|
||||||
QString junkPacketMinSize = QString::number(10);
|
QString junkPacketMinSize = QString::number(10);
|
||||||
QString junkPacketMaxSize = QString::number(50);
|
QString junkPacketMaxSize = QString::number(50);
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ void ListViewFocusController::previousDelegate()
|
|||||||
case Section::Default: {
|
case Section::Default: {
|
||||||
if (hasFooter()) {
|
if (hasFooter()) {
|
||||||
m_currentSection = Section::Footer;
|
m_currentSection = Section::Footer;
|
||||||
|
viewAtCurrentIndex();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
@@ -127,9 +128,11 @@ void ListViewFocusController::previousDelegate()
|
|||||||
case Section::Delegate: {
|
case Section::Delegate: {
|
||||||
if (m_delegateIndex > 0) {
|
if (m_delegateIndex > 0) {
|
||||||
setDelegateIndex(m_delegateIndex - 1);
|
setDelegateIndex(m_delegateIndex - 1);
|
||||||
|
viewAtCurrentIndex();
|
||||||
break;
|
break;
|
||||||
} else if (hasHeader()) {
|
} else if (hasHeader()) {
|
||||||
m_currentSection = Section::Header;
|
m_currentSection = Section::Header;
|
||||||
|
viewAtCurrentIndex();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
@@ -137,6 +140,7 @@ void ListViewFocusController::previousDelegate()
|
|||||||
case Section::Header: {
|
case Section::Header: {
|
||||||
m_isReturnNeeded = true;
|
m_isReturnNeeded = true;
|
||||||
m_currentSection = Section::Default;
|
m_currentSection = Section::Default;
|
||||||
|
viewAtCurrentIndex();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -275,7 +279,7 @@ bool ListViewFocusController::isFirstFocusItemInListView() const
|
|||||||
return isFirstFocusItemInDelegate() && (m_delegateIndex == 0) && !hasHeader();
|
return isFirstFocusItemInDelegate() && (m_delegateIndex == 0) && !hasHeader();
|
||||||
}
|
}
|
||||||
case Section::Header: {
|
case Section::Header: {
|
||||||
isFirstFocusItemInDelegate();
|
return isFirstFocusItemInDelegate();
|
||||||
}
|
}
|
||||||
case Section::Default: {
|
case Section::Default: {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ void PageController::onShowErrorMessage(ErrorCode errorCode)
|
|||||||
{
|
{
|
||||||
const auto fullErrorMessage = errorString(errorCode);
|
const auto fullErrorMessage = errorString(errorCode);
|
||||||
const auto errorMessage = fullErrorMessage.mid(fullErrorMessage.indexOf(". ") + 1); // remove ErrorCode %1.
|
const auto errorMessage = fullErrorMessage.mid(fullErrorMessage.indexOf(". ") + 1); // remove ErrorCode %1.
|
||||||
const auto errorUrl = QStringLiteral("https://docs.amnezia.org/troubleshooting/error-codes/#error-%1-%2").arg(static_cast<int>(errorCode)).arg(utils::enumToString(errorCode).toLower());
|
const auto errorUrl = QStringLiteral("troubleshooting/error-codes/#error-%1-%2").arg(static_cast<int>(errorCode)).arg(utils::enumToString(errorCode).toLower());
|
||||||
const auto fullMessage = QStringLiteral("<a href=\"%1\" style=\"color: #FBB26A;\">ErrorCode: %2</a>. %3").arg(errorUrl).arg(static_cast<int>(errorCode)).arg(errorMessage);
|
const auto fullMessage = QStringLiteral("<a href=\"%1\" style=\"color: #FBB26A;\">ErrorCode: %2</a>. %3").arg(errorUrl).arg(static_cast<int>(errorCode)).arg(errorMessage);
|
||||||
|
|
||||||
emit showErrorMessage(fullMessage);
|
emit showErrorMessage(fullMessage);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ namespace PageLoader
|
|||||||
PageSettingsConnection,
|
PageSettingsConnection,
|
||||||
PageSettingsDns,
|
PageSettingsDns,
|
||||||
PageSettingsApplication,
|
PageSettingsApplication,
|
||||||
|
PageSettingsNewsNotifications,
|
||||||
|
PageSettingsNewsDetail,
|
||||||
PageSettingsBackup,
|
PageSettingsBackup,
|
||||||
PageSettingsAbout,
|
PageSettingsAbout,
|
||||||
PageSettingsLogging,
|
PageSettingsLogging,
|
||||||
@@ -125,6 +127,8 @@ signals:
|
|||||||
void goToPageViewConfig();
|
void goToPageViewConfig();
|
||||||
void goToPageSettingsServerServices();
|
void goToPageSettingsServerServices();
|
||||||
void goToPageSettingsBackup();
|
void goToPageSettingsBackup();
|
||||||
|
void goToShareConnectionPage(QString headerText, QString configContentHeaderText, QString configCaption, QString configExtension,
|
||||||
|
QString configFileName);
|
||||||
|
|
||||||
void closePage();
|
void closePage();
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "systemController.h"
|
#include "systemController.h"
|
||||||
#include "ui/qautostart.h"
|
#include "ui/qautostart.h"
|
||||||
|
#include "amnezia_application.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
#include "platforms/android/android_controller.h"
|
#include "platforms/android/android_controller.h"
|
||||||
@@ -33,6 +34,9 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
|
|||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsController::onNotificationStateChanged);
|
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsController::onNotificationStateChanged);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
m_isDevModeEnabled = m_settings->isDevGatewayEnv();
|
||||||
|
toggleDevGatewayEnv(m_isDevModeEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString getPlatformName()
|
QString getPlatformName()
|
||||||
@@ -139,6 +143,10 @@ void SettingsController::clearLogs()
|
|||||||
Logger::clearLogs(false);
|
Logger::clearLogs(false);
|
||||||
Logger::clearServiceLogs();
|
Logger::clearServiceLogs();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
|
||||||
|
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
|
||||||
|
qInfo().noquote() << QString("SSL backend: %1").arg(QSslSocket::sslLibraryVersionString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsController::backupAppConfig(const QString &fileName)
|
void SettingsController::backupAppConfig(const QString &fileName)
|
||||||
@@ -151,6 +159,7 @@ void SettingsController::backupAppConfig(const QString &fileName)
|
|||||||
config["Conf/autoStart"] = Autostart::isAutostart();
|
config["Conf/autoStart"] = Autostart::isAutostart();
|
||||||
config["Conf/killSwitchEnabled"] = isKillSwitchEnabled();
|
config["Conf/killSwitchEnabled"] = isKillSwitchEnabled();
|
||||||
config["Conf/strictKillSwitchEnabled"] = isStrictKillSwitchEnabled();
|
config["Conf/strictKillSwitchEnabled"] = isStrictKillSwitchEnabled();
|
||||||
|
config["Conf/useAmneziaDns"] = isAmneziaDnsEnabled();
|
||||||
|
|
||||||
SystemController::saveFile(fileName, QJsonDocument(config).toJson());
|
SystemController::saveFile(fileName, QJsonDocument(config).toJson());
|
||||||
}
|
}
|
||||||
@@ -186,7 +195,8 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data)
|
|||||||
|
|
||||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID)
|
#if defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID)
|
||||||
int appSplitTunnelingRouteMode = newConfigData.value("Conf/appsRouteMode").toInt();
|
int appSplitTunnelingRouteMode = newConfigData.value("Conf/appsRouteMode").toInt();
|
||||||
bool appSplittunnelingEnabled = newConfigData.value("Conf/appsSplitTunnelingEnabled").toString().toLower() == "true";
|
bool appSplittunnelingEnabled =
|
||||||
|
newConfigData.value("Conf/appsSplitTunnelingEnabled").toVariant().toString().toLower() == "true";
|
||||||
m_appSplitTunnelingModel->setRouteMode(appSplitTunnelingRouteMode);
|
m_appSplitTunnelingModel->setRouteMode(appSplitTunnelingRouteMode);
|
||||||
|
|
||||||
#if defined(Q_OS_WINDOWS)
|
#if defined(Q_OS_WINDOWS)
|
||||||
@@ -203,7 +213,8 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
int siteSplitTunnelingRouteMode = newConfigData.value("Conf/routeMode").toInt();
|
int siteSplitTunnelingRouteMode = newConfigData.value("Conf/routeMode").toInt();
|
||||||
bool siteSplittunnelingEnabled = newConfigData.value("Conf/sitesSplitTunnelingEnabled").toString().toLower() == "true";
|
bool siteSplittunnelingEnabled =
|
||||||
|
newConfigData.value("Conf/sitesSplitTunnelingEnabled").toVariant().toString().toLower() == "true";
|
||||||
m_sitesModel->setRouteMode(siteSplitTunnelingRouteMode);
|
m_sitesModel->setRouteMode(siteSplitTunnelingRouteMode);
|
||||||
m_sitesModel->toggleSplitTunneling(siteSplittunnelingEnabled);
|
m_sitesModel->toggleSplitTunneling(siteSplittunnelingEnabled);
|
||||||
|
|
||||||
@@ -214,6 +225,11 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data)
|
|||||||
m_settings->setStrictKillSwitchEnabled(false);
|
m_settings->setStrictKillSwitchEnabled(false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool amneziaDnsEnabled = newConfigData.contains("Conf/useAmneziaDns")
|
||||||
|
? newConfigData.value("Conf/useAmneziaDns").toBool()
|
||||||
|
: m_settings->useAmneziaDns();
|
||||||
|
emit amneziaDnsToggled(amneziaDnsEnabled);
|
||||||
|
|
||||||
emit restoreBackupFinished();
|
emit restoreBackupFinished();
|
||||||
} else {
|
} else {
|
||||||
emit changeSettingsErrorOccurred(tr("Backup file is corrupted"));
|
emit changeSettingsErrorOccurred(tr("Backup file is corrupted"));
|
||||||
@@ -264,6 +280,9 @@ bool SettingsController::isAutoStartEnabled()
|
|||||||
void SettingsController::toggleAutoStart(bool enable)
|
void SettingsController::toggleAutoStart(bool enable)
|
||||||
{
|
{
|
||||||
Autostart::setAutostart(enable);
|
Autostart::setAutostart(enable);
|
||||||
|
if (!enable) {
|
||||||
|
toggleStartMinimized(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SettingsController::isStartMinimizedEnabled()
|
bool SettingsController::isStartMinimizedEnabled()
|
||||||
@@ -274,6 +293,7 @@ bool SettingsController::isStartMinimizedEnabled()
|
|||||||
void SettingsController::toggleStartMinimized(bool enable)
|
void SettingsController::toggleStartMinimized(bool enable)
|
||||||
{
|
{
|
||||||
m_settings->setStartMinimized(enable);
|
m_settings->setStartMinimized(enable);
|
||||||
|
emit startMinimizedChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SettingsController::isScreenshotsEnabled()
|
bool SettingsController::isScreenshotsEnabled()
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public:
|
|||||||
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
|
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
|
||||||
|
|
||||||
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
||||||
|
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void toggleAmneziaDns(bool enable);
|
void toggleAmneziaDns(bool enable);
|
||||||
@@ -125,6 +126,7 @@ signals:
|
|||||||
void devGatewayEnvChanged(bool enabled);
|
void devGatewayEnvChanged(bool enabled);
|
||||||
|
|
||||||
void isHomeAdLabelVisibleChanged(bool visible);
|
void isHomeAdLabelVisibleChanged(bool visible);
|
||||||
|
void startMinimizedChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
return tr("Active");
|
return tr("Active");
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active");
|
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>") : tr("Active");
|
||||||
}
|
}
|
||||||
case EndDateRole: {
|
case EndDateRole: {
|
||||||
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace
|
|||||||
constexpr char serviceInfo[] = "service_info";
|
constexpr char serviceInfo[] = "service_info";
|
||||||
constexpr char serviceType[] = "service_type";
|
constexpr char serviceType[] = "service_type";
|
||||||
constexpr char serviceProtocol[] = "service_protocol";
|
constexpr char serviceProtocol[] = "service_protocol";
|
||||||
|
constexpr char serviceDescription[] = "service_description";
|
||||||
|
|
||||||
constexpr char name[] = "name";
|
constexpr char name[] = "name";
|
||||||
constexpr char price[] = "price";
|
constexpr char price[] = "price";
|
||||||
@@ -22,6 +23,10 @@ namespace
|
|||||||
constexpr char timelimit[] = "timelimit";
|
constexpr char timelimit[] = "timelimit";
|
||||||
constexpr char region[] = "region";
|
constexpr char region[] = "region";
|
||||||
|
|
||||||
|
constexpr char description[] = "description";
|
||||||
|
constexpr char cardDescription[] = "card_description";
|
||||||
|
constexpr char features[] = "features";
|
||||||
|
|
||||||
constexpr char availableCountries[] = "available_countries";
|
constexpr char availableCountries[] = "available_countries";
|
||||||
|
|
||||||
constexpr char storeEndpoint[] = "store_endpoint";
|
constexpr char storeEndpoint[] = "store_endpoint";
|
||||||
@@ -65,11 +70,9 @@ 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 apiServiceData.serviceInfo.cardDescription.arg(speed);
|
||||||
"Access all websites and online resources. Speeds up to %1 Mbps.")
|
|
||||||
.arg(speed);
|
|
||||||
} else if (serviceType == serviceType::amneziaFree) {
|
} else if (serviceType == serviceType::amneziaFree) {
|
||||||
QString description = tr("Amnezia Free provides unlimited, free access to a basic set of websites and apps, including Facebook, Instagram, Twitter (X), Discord, Telegram, and more. YouTube is not included in the free plan.");
|
QString description = apiServiceData.serviceInfo.cardDescription;
|
||||||
if (!isServiceAvailable) {
|
if (!isServiceAvailable) {
|
||||||
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, "
|
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, "
|
||||||
"return to the previous screen, and try again.</a>");
|
"return to the previous screen, and try again.</a>");
|
||||||
@@ -78,12 +81,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ServiceDescriptionRole: {
|
case ServiceDescriptionRole: {
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
return apiServiceData.serviceInfo.description;
|
||||||
return tr("Amnezia Premium is classic VPN for for seamless work, downloading large files, and watching videos. "
|
|
||||||
"Access all websites and online resources.");
|
|
||||||
} else {
|
|
||||||
return tr("Amnezia Free provides unlimited, free access to a basic set of websites and apps, including Facebook, Instagram, Twitter (X), Discord, Telegram, and more. YouTube is not included in the free plan.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case IsServiceAvailableRole: {
|
case IsServiceAvailableRole: {
|
||||||
if (serviceType == serviceType::amneziaFree) {
|
if (serviceType == serviceType::amneziaFree) {
|
||||||
@@ -107,13 +105,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
return apiServiceData.serviceInfo.region;
|
return apiServiceData.serviceInfo.region;
|
||||||
}
|
}
|
||||||
case FeaturesRole: {
|
case FeaturesRole: {
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
return apiServiceData.serviceInfo.features;
|
||||||
return tr("");
|
|
||||||
} else {
|
|
||||||
return tr("VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. "
|
|
||||||
"Other sites will be opened from your real IP address, "
|
|
||||||
"<a href=\"%1\" style=\"color: #FBB26A;\">more details on the website.</a>");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case PriceRole: {
|
case PriceRole: {
|
||||||
auto price = apiServiceData.serviceInfo.price;
|
auto price = apiServiceData.serviceInfo.price;
|
||||||
@@ -125,6 +117,13 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
case EndDateRole: {
|
case EndDateRole: {
|
||||||
return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
|
return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
|
||||||
}
|
}
|
||||||
|
case OrderRole: {
|
||||||
|
if (serviceType == serviceType::amneziaPremium) {
|
||||||
|
return 0;
|
||||||
|
} else if (serviceType == serviceType::amneziaFree) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -224,6 +223,7 @@ QHash<int, QByteArray> ApiServicesModel::roleNames() const
|
|||||||
roles[FeaturesRole] = "features";
|
roles[FeaturesRole] = "features";
|
||||||
roles[PriceRole] = "price";
|
roles[PriceRole] = "price";
|
||||||
roles[EndDateRole] = "endDate";
|
roles[EndDateRole] = "endDate";
|
||||||
|
roles[OrderRole] = "order";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
@@ -234,6 +234,7 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs
|
|||||||
auto serviceType = data.value(configKey::serviceType).toString();
|
auto serviceType = data.value(configKey::serviceType).toString();
|
||||||
auto serviceProtocol = data.value(configKey::serviceProtocol).toString();
|
auto serviceProtocol = data.value(configKey::serviceProtocol).toString();
|
||||||
auto availableCountries = data.value(configKey::availableCountries).toArray();
|
auto availableCountries = data.value(configKey::availableCountries).toArray();
|
||||||
|
auto serviceDescription = data.value(configKey::serviceDescription).toObject();
|
||||||
|
|
||||||
auto subscriptionObject = data.value(configKey::subscription).toObject();
|
auto subscriptionObject = data.value(configKey::subscription).toObject();
|
||||||
|
|
||||||
@@ -244,6 +245,10 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs
|
|||||||
serviceData.serviceInfo.speed = serviceInfo.value(configKey::speed).toString();
|
serviceData.serviceInfo.speed = serviceInfo.value(configKey::speed).toString();
|
||||||
serviceData.serviceInfo.timeLimit = serviceInfo.value(configKey::timelimit).toString();
|
serviceData.serviceInfo.timeLimit = serviceInfo.value(configKey::timelimit).toString();
|
||||||
|
|
||||||
|
serviceData.serviceInfo.cardDescription = serviceDescription.value(configKey::cardDescription).toString();
|
||||||
|
serviceData.serviceInfo.description = serviceDescription.value(configKey::description).toString();
|
||||||
|
serviceData.serviceInfo.features = serviceDescription.value(configKey::features).toString();
|
||||||
|
|
||||||
serviceData.type = serviceType;
|
serviceData.type = serviceType;
|
||||||
serviceData.protocol = serviceProtocol;
|
serviceData.protocol = serviceProtocol;
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ public:
|
|||||||
RegionRole,
|
RegionRole,
|
||||||
FeaturesRole,
|
FeaturesRole,
|
||||||
PriceRole,
|
PriceRole,
|
||||||
EndDateRole
|
EndDateRole,
|
||||||
|
OrderRole
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ApiServicesModel(QObject *parent = nullptr);
|
explicit ApiServicesModel(QObject *parent = nullptr);
|
||||||
@@ -58,6 +59,10 @@ private:
|
|||||||
QString region;
|
QString region;
|
||||||
QString price;
|
QString price;
|
||||||
|
|
||||||
|
QString description;
|
||||||
|
QString features;
|
||||||
|
QString cardDescription;
|
||||||
|
|
||||||
QJsonObject object;
|
QJsonObject object;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -497,7 +497,8 @@ ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QSt
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ClientManagementModel::renameClient(const int row, const QString &clientName, const DockerContainer container,
|
ErrorCode ClientManagementModel::renameClient(const int row, const QString &clientName,
|
||||||
|
const DockerContainer container,
|
||||||
const ServerCredentials &credentials,
|
const ServerCredentials &credentials,
|
||||||
const QSharedPointer<ServerController> &serverController, bool addTimeStamp)
|
const QSharedPointer<ServerController> &serverController, bool addTimeStamp)
|
||||||
{
|
{
|
||||||
@@ -529,7 +530,8 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container, const ServerCredentials &credentials,
|
ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container,
|
||||||
|
const ServerCredentials &credentials,
|
||||||
const int serverIndex, const QSharedPointer<ServerController> &serverController)
|
const int serverIndex, const QSharedPointer<ServerController> &serverController)
|
||||||
{
|
{
|
||||||
ErrorCode errorCode = ErrorCode::NoError;
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ public slots:
|
|||||||
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController);
|
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController);
|
||||||
ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
|
ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
|
||||||
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController);
|
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController);
|
||||||
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, const ServerCredentials &credentials,
|
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container,
|
||||||
const QSharedPointer<ServerController> &serverController, bool addTimeStamp = false);
|
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController, bool addTimeStamp = false);
|
||||||
ErrorCode revokeClient(const int index, const DockerContainer container, const ServerCredentials &credentials, const int serverIndex,
|
ErrorCode revokeClient(const int index, const DockerContainer container, const ServerCredentials &credentials,
|
||||||
const QSharedPointer<ServerController> &serverController);
|
const int serverIndex, const QSharedPointer<ServerController> &serverController);
|
||||||
ErrorCode revokeClient(const QJsonObject &containerConfig, const DockerContainer container, const ServerCredentials &credentials,
|
ErrorCode revokeClient(const QJsonObject &containerConfig, const DockerContainer container, const ServerCredentials &credentials,
|
||||||
const int serverIndex, const QSharedPointer<ServerController> &serverController);
|
const int serverIndex, const QSharedPointer<ServerController> &serverController);
|
||||||
|
|
||||||
@@ -60,6 +60,8 @@ signals:
|
|||||||
private:
|
private:
|
||||||
bool isClientExists(const QString &clientId);
|
bool isClientExists(const QString &clientId);
|
||||||
|
|
||||||
|
int clientIndexById(const QString &clientId);
|
||||||
|
|
||||||
void migration(const QByteArray &clientsTableString);
|
void migration(const QByteArray &clientsTableString);
|
||||||
|
|
||||||
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials, const int serverIndex,
|
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials, const int serverIndex,
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
#include "ui/models/newsModel.h"
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
NewsModel::NewsModel(const std::shared_ptr<Settings> &settings, QObject *parent) : QAbstractListModel(parent), m_settings(settings)
|
||||||
|
{
|
||||||
|
loadReadIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
int NewsModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
return m_items.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant NewsModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
const NewsItem &item = m_items.at(index.row());
|
||||||
|
switch (role) {
|
||||||
|
case IdRole: return item.id;
|
||||||
|
case TitleRole: return item.title;
|
||||||
|
case ContentRole: return item.content;
|
||||||
|
case TimestampRole: return item.timestamp.toString(Qt::ISODate);
|
||||||
|
case IsReadRole: return item.read;
|
||||||
|
case IsProcessedRole: return index.row() == m_processedIndex;
|
||||||
|
default: return QVariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> NewsModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
roles[IdRole] = "id";
|
||||||
|
roles[TitleRole] = "title";
|
||||||
|
roles[ContentRole] = "content";
|
||||||
|
roles[TimestampRole] = "timestamp";
|
||||||
|
roles[IsReadRole] = "read";
|
||||||
|
roles[IsProcessedRole] = "isProcessed";
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewsModel::markAsRead(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= m_items.size())
|
||||||
|
return;
|
||||||
|
if (!m_items[index].read) {
|
||||||
|
m_items[index].read = true;
|
||||||
|
m_readIds.insert(m_items[index].id);
|
||||||
|
saveReadIds();
|
||||||
|
QModelIndex idx = createIndex(index, 0);
|
||||||
|
emit dataChanged(idx, idx, { IsReadRole });
|
||||||
|
emit hasUnreadChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int NewsModel::processedIndex() const
|
||||||
|
{
|
||||||
|
return m_processedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewsModel::setProcessedIndex(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= m_items.size() || m_processedIndex == index)
|
||||||
|
return;
|
||||||
|
m_processedIndex = index;
|
||||||
|
emit processedIndexChanged(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewsModel::updateModel(const QJsonArray &serverItems)
|
||||||
|
{
|
||||||
|
QSet<QString> existingIds;
|
||||||
|
for (const NewsItem &item : m_items) {
|
||||||
|
existingIds.insert(item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<NewsItem> newItems;
|
||||||
|
for (const QJsonValue &value : serverItems) {
|
||||||
|
if (!value.isObject())
|
||||||
|
continue;
|
||||||
|
const QJsonObject obj = value.toObject();
|
||||||
|
QString id = obj.value("id").toString();
|
||||||
|
|
||||||
|
if (!existingIds.contains(id)) {
|
||||||
|
NewsItem item;
|
||||||
|
item.id = id;
|
||||||
|
item.title = obj.value("title").toString();
|
||||||
|
item.content = obj.value("content").toString();
|
||||||
|
item.timestamp = QDateTime::fromString(obj.value("timestamp").toString(), Qt::ISODate);
|
||||||
|
item.read = m_readIds.contains(id);
|
||||||
|
newItems.append(item);
|
||||||
|
existingIds.insert(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
m_items.append(newItems);
|
||||||
|
std::sort(m_items.begin(), m_items.end(), [](const NewsItem &a, const NewsItem &b) { return a.timestamp > b.timestamp; });
|
||||||
|
endResetModel();
|
||||||
|
emit hasUnreadChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NewsModel::hasUnread() const
|
||||||
|
{
|
||||||
|
for (const NewsItem &item : m_items) {
|
||||||
|
if (!item.read)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewsModel::loadReadIds()
|
||||||
|
{
|
||||||
|
QStringList ids = m_settings->readNewsIds();
|
||||||
|
m_readIds = QSet<QString>(ids.begin(), ids.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewsModel::saveReadIds() const
|
||||||
|
{
|
||||||
|
m_settings->setReadNewsIds(QStringList(m_readIds.begin(), m_readIds.end()));
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#ifndef NEWSMODEL_H
|
||||||
|
#define NEWSMODEL_H
|
||||||
|
|
||||||
|
#include "settings.h"
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
struct NewsItem
|
||||||
|
{
|
||||||
|
QString id;
|
||||||
|
QString title;
|
||||||
|
QString content;
|
||||||
|
QDateTime timestamp;
|
||||||
|
bool read;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NewsModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum Roles {
|
||||||
|
IdRole = Qt::UserRole + 1,
|
||||||
|
TitleRole,
|
||||||
|
ContentRole,
|
||||||
|
TimestampRole,
|
||||||
|
IsReadRole,
|
||||||
|
IsProcessedRole
|
||||||
|
};
|
||||||
|
explicit NewsModel(const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
|
||||||
|
Q_INVOKABLE void markAsRead(int index);
|
||||||
|
|
||||||
|
Q_PROPERTY(int processedIndex READ processedIndex WRITE setProcessedIndex NOTIFY processedIndexChanged)
|
||||||
|
Q_PROPERTY(bool hasUnread READ hasUnread NOTIFY hasUnreadChanged)
|
||||||
|
int processedIndex() const;
|
||||||
|
void setProcessedIndex(int index);
|
||||||
|
|
||||||
|
void updateModel(const QJsonArray &items);
|
||||||
|
bool hasUnread() const;
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void processedIndexChanged(int index);
|
||||||
|
void hasUnreadChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVector<NewsItem> m_items;
|
||||||
|
int m_processedIndex = -1;
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
QSet<QString> m_readIds;
|
||||||
|
void loadReadIds();
|
||||||
|
void saveReadIds() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NEWSMODEL_H
|
||||||
@@ -191,14 +191,14 @@ QJsonObject AwgConfigModel::getConfig()
|
|||||||
jsonConfig[config_key::junkPacketCount] = m_clientProtocolConfig[config_key::junkPacketCount];
|
jsonConfig[config_key::junkPacketCount] = m_clientProtocolConfig[config_key::junkPacketCount];
|
||||||
jsonConfig[config_key::junkPacketMinSize] = m_clientProtocolConfig[config_key::junkPacketMinSize];
|
jsonConfig[config_key::junkPacketMinSize] = m_clientProtocolConfig[config_key::junkPacketMinSize];
|
||||||
jsonConfig[config_key::junkPacketMaxSize] = m_clientProtocolConfig[config_key::junkPacketMaxSize];
|
jsonConfig[config_key::junkPacketMaxSize] = m_clientProtocolConfig[config_key::junkPacketMaxSize];
|
||||||
jsonConfig[config_key::specialJunk1] = m_clientProtocolConfig[config_key::specialJunk1];
|
jsonConfig[config_key::specialJunk1] = m_clientProtocolConfig[config_key::specialJunk1].toString().trimmed();
|
||||||
jsonConfig[config_key::specialJunk2] = m_clientProtocolConfig[config_key::specialJunk2];
|
jsonConfig[config_key::specialJunk2] = m_clientProtocolConfig[config_key::specialJunk2].toString().trimmed();
|
||||||
jsonConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3];
|
jsonConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3].toString().trimmed();
|
||||||
jsonConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4];
|
jsonConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4].toString().trimmed();
|
||||||
jsonConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5];
|
jsonConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5].toString().trimmed();
|
||||||
jsonConfig[config_key::controlledJunk1] = m_clientProtocolConfig[config_key::controlledJunk1];
|
jsonConfig[config_key::controlledJunk1] = m_clientProtocolConfig[config_key::controlledJunk1].toString().trimmed();
|
||||||
jsonConfig[config_key::controlledJunk2] = m_clientProtocolConfig[config_key::controlledJunk2];
|
jsonConfig[config_key::controlledJunk2] = m_clientProtocolConfig[config_key::controlledJunk2].toString().trimmed();
|
||||||
jsonConfig[config_key::controlledJunk3] = m_clientProtocolConfig[config_key::controlledJunk3];
|
jsonConfig[config_key::controlledJunk3] = m_clientProtocolConfig[config_key::controlledJunk3].toString().trimmed();
|
||||||
jsonConfig[config_key::specialHandshakeTimeout] = m_clientProtocolConfig[config_key::specialHandshakeTimeout];
|
jsonConfig[config_key::specialHandshakeTimeout] = m_clientProtocolConfig[config_key::specialHandshakeTimeout];
|
||||||
|
|
||||||
m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
|
m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent)
|
|||||||
|
|
||||||
connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged);
|
connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged);
|
||||||
connect(this, &ServersModel::dataChanged, this, &ServersModel::processedServerChanged);
|
connect(this, &ServersModel::dataChanged, this, &ServersModel::processedServerChanged);
|
||||||
|
|
||||||
|
connect(this, &QAbstractItemModel::modelReset, this, &ServersModel::recomputeGatewayStacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ServersModel::rowCount(const QModelIndex &parent) const
|
int ServersModel::rowCount(const QModelIndex &parent) const
|
||||||
@@ -173,6 +175,7 @@ void ServersModel::resetModel()
|
|||||||
m_servers = m_settings->serversArray();
|
m_servers = m_settings->serversArray();
|
||||||
m_defaultServerIndex = m_settings->defaultServerIndex();
|
m_defaultServerIndex = m_settings->defaultServerIndex();
|
||||||
m_processedServerIndex = m_defaultServerIndex;
|
m_processedServerIndex = m_defaultServerIndex;
|
||||||
|
m_isAmneziaDnsEnabled = m_settings->useAmneziaDns();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
emit defaultServerIndexChanged(m_defaultServerIndex);
|
emit defaultServerIndexChanged(m_defaultServerIndex);
|
||||||
}
|
}
|
||||||
@@ -374,7 +377,6 @@ QHash<int, QByteArray> ServersModel::roleNames() const
|
|||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
|
|
||||||
roles[NameRole] = "serverName";
|
|
||||||
roles[NameRole] = "name";
|
roles[NameRole] = "name";
|
||||||
roles[ServerDescriptionRole] = "serverDescription";
|
roles[ServerDescriptionRole] = "serverDescription";
|
||||||
roles[CollapsedServerDescriptionRole] = "collapsedServerDescription";
|
roles[CollapsedServerDescriptionRole] = "collapsedServerDescription";
|
||||||
@@ -755,6 +757,68 @@ bool ServersModel::isServerFromApi(const int serverIndex)
|
|||||||
return data(serverIndex, IsServerFromTelegramApiRole).toBool() || data(serverIndex, IsServerFromGatewayApiRole).toBool();
|
return data(serverIndex, IsServerFromTelegramApiRole).toBool() || data(serverIndex, IsServerFromGatewayApiRole).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ServersModel::hasServersFromGatewayApi()
|
||||||
|
{
|
||||||
|
return !m_gatewayStacks.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServersModel::GatewayStacks::operator==(const GatewayStacks &other) const
|
||||||
|
{
|
||||||
|
return userCountryCodes == other.userCountryCodes && serviceTypes == other.serviceTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject ServersModel::GatewayStacks::toJson() const
|
||||||
|
{
|
||||||
|
QJsonObject obj;
|
||||||
|
if (!userCountryCodes.isEmpty()) {
|
||||||
|
obj.insert(configKey::userCountryCode, QJsonArray::fromStringList(userCountryCodes.values()));
|
||||||
|
}
|
||||||
|
if (!serviceTypes.isEmpty()) {
|
||||||
|
obj.insert(configKey::serviceType, QJsonArray::fromStringList(serviceTypes.values()));
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServersModel::recomputeGatewayStacks()
|
||||||
|
{
|
||||||
|
const bool wasEmpty = m_gatewayStacks.isEmpty();
|
||||||
|
GatewayStacks computed;
|
||||||
|
bool hasNewTags = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_servers.count(); ++i) {
|
||||||
|
if (data(i, IsServerFromGatewayApiRole).toBool()) {
|
||||||
|
const QJsonObject server = m_servers.at(i).toObject();
|
||||||
|
const QJsonObject apiConfig = server.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
|
const QString userCountryCode = apiConfig.value(configKey::userCountryCode).toString();
|
||||||
|
const QString serviceType = apiConfig.value(configKey::serviceType).toString();
|
||||||
|
|
||||||
|
if (!userCountryCode.isEmpty()) {
|
||||||
|
if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) {
|
||||||
|
hasNewTags = true;
|
||||||
|
}
|
||||||
|
computed.userCountryCodes.insert(userCountryCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serviceType.isEmpty()) {
|
||||||
|
if (!m_gatewayStacks.serviceTypes.contains(serviceType)) {
|
||||||
|
hasNewTags = true;
|
||||||
|
}
|
||||||
|
computed.serviceTypes.insert(serviceType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_gatewayStacks = std::move(computed);
|
||||||
|
if (hasNewTags) {
|
||||||
|
emit gatewayStacksExpanded();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasEmpty != m_gatewayStacks.isEmpty()) {
|
||||||
|
emit hasServersFromGatewayApiChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool ServersModel::isApiKeyExpired(const int serverIndex)
|
bool ServersModel::isApiKeyExpired(const int serverIndex)
|
||||||
{
|
{
|
||||||
auto serverConfig = m_servers.at(serverIndex).toObject();
|
auto serverConfig = m_servers.at(serverIndex).toObject();
|
||||||
|
|||||||
@@ -10,6 +10,16 @@ class ServersModel : public QAbstractListModel
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
struct GatewayStacks
|
||||||
|
{
|
||||||
|
QSet<QString> userCountryCodes;
|
||||||
|
QSet<QString> serviceTypes;
|
||||||
|
|
||||||
|
bool isEmpty() const { return userCountryCodes.isEmpty() && serviceTypes.isEmpty(); }
|
||||||
|
bool operator==(const GatewayStacks &other) const;
|
||||||
|
QJsonObject toJson() const;
|
||||||
|
};
|
||||||
|
|
||||||
enum Roles {
|
enum Roles {
|
||||||
NameRole = Qt::UserRole + 1,
|
NameRole = Qt::UserRole + 1,
|
||||||
ServerDescriptionRole,
|
ServerDescriptionRole,
|
||||||
@@ -52,6 +62,8 @@ public:
|
|||||||
|
|
||||||
void resetModel();
|
void resetModel();
|
||||||
|
|
||||||
|
GatewayStacks gatewayStacks() const { return m_gatewayStacks; }
|
||||||
|
|
||||||
Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged)
|
Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged)
|
||||||
Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerNameChanged)
|
Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerNameChanged)
|
||||||
Q_PROPERTY(QString defaultServerDefaultContainerName READ getDefaultServerDefaultContainerName NOTIFY defaultServerDefaultContainerChanged)
|
Q_PROPERTY(QString defaultServerDefaultContainerName READ getDefaultServerDefaultContainerName NOTIFY defaultServerDefaultContainerChanged)
|
||||||
@@ -62,6 +74,8 @@ public:
|
|||||||
defaultServerDefaultContainerChanged)
|
defaultServerDefaultContainerChanged)
|
||||||
Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged)
|
Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(bool hasServersFromGatewayApi READ hasServersFromGatewayApi NOTIFY hasServersFromGatewayApiChanged)
|
||||||
|
|
||||||
Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged)
|
Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged)
|
||||||
Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerChanged)
|
Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerChanged)
|
||||||
|
|
||||||
@@ -82,6 +96,8 @@ public slots:
|
|||||||
bool isDefaultServerHasWriteAccess();
|
bool isDefaultServerHasWriteAccess();
|
||||||
bool hasServerWithWriteAccess();
|
bool hasServerWithWriteAccess();
|
||||||
|
|
||||||
|
bool hasServersFromGatewayApi();
|
||||||
|
|
||||||
const int getServersCount();
|
const int getServersCount();
|
||||||
|
|
||||||
void setProcessedServerIndex(const int index);
|
void setProcessedServerIndex(const int index);
|
||||||
@@ -147,6 +163,9 @@ signals:
|
|||||||
void updateApiCountryModel();
|
void updateApiCountryModel();
|
||||||
void updateApiServicesModel();
|
void updateApiServicesModel();
|
||||||
|
|
||||||
|
void hasServersFromGatewayApiChanged();
|
||||||
|
void gatewayStacksExpanded();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ServerCredentials serverCredentials(int index) const;
|
ServerCredentials serverCredentials(int index) const;
|
||||||
|
|
||||||
@@ -167,6 +186,9 @@ private:
|
|||||||
int m_processedServerIndex;
|
int m_processedServerIndex;
|
||||||
|
|
||||||
bool m_isAmneziaDnsEnabled = m_settings->useAmneziaDns();
|
bool m_isAmneziaDnsEnabled = m_settings->useAmneziaDns();
|
||||||
|
|
||||||
|
GatewayStacks m_gatewayStacks;
|
||||||
|
void recomputeGatewayStacks();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SERVERSMODEL_H
|
#endif // SERVERSMODEL_H
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ DrawerType2 {
|
|||||||
var str = qsTr("We'll preserve all remaining days of your current subscription and give you an extra month as a thank you. ")
|
var str = qsTr("We'll preserve all remaining days of your current subscription and give you an extra month as a thank you. ")
|
||||||
str += qsTr("This new subscription type will be actively developed with more locations and features added regularly. Currently available:")
|
str += qsTr("This new subscription type will be actively developed with more locations and features added regularly. Currently available:")
|
||||||
str += "<ul style='margin-left: -16px;'>"
|
str += "<ul style='margin-left: -16px;'>"
|
||||||
str += qsTr("<li>13 locations (with more coming soon)</li>")
|
str += qsTr("<li>20 locations (with more coming soon)</li>")
|
||||||
str += qsTr("<li>Easier switching between countries in the app</li>")
|
str += qsTr("<li>Easier switching between countries in the app</li>")
|
||||||
str += qsTr("<li>Personal dashboard to manage your subscription</li>")
|
str += qsTr("<li>Personal dashboard to manage your subscription</li>")
|
||||||
str += "</ul>"
|
str += "</ul>"
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ Menu {
|
|||||||
MenuItem {
|
MenuItem {
|
||||||
text: qsTr("&Paste")
|
text: qsTr("&Paste")
|
||||||
shortcut: StandardKey.Paste
|
shortcut: StandardKey.Paste
|
||||||
// Fix calling paste from clipboard when launching app on android
|
// Fix calling paste from clipboard when launching app on android/ios
|
||||||
enabled: Qt.platform.os === "android" ? true : textObj.canPaste
|
enabled: (Qt.platform.os === "android" || Qt.platform.os === "ios") ? true : textObj.canPaste
|
||||||
onTriggered: textObj.paste()
|
onTriggered: textObj.paste()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,20 @@ import Style 1.0
|
|||||||
import "TextTypes"
|
import "TextTypes"
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
property string imageSource
|
property string imageSource
|
||||||
property string leftText
|
property string leftText
|
||||||
property var rightText
|
property var rightText
|
||||||
property bool isRightTextUndefined: rightText === undefined
|
property bool isRightTextUndefined: rightText === undefined
|
||||||
|
property int rightTextFormat: Text.PlainText
|
||||||
|
|
||||||
visible: !isRightTextUndefined
|
visible: !isRightTextUndefined
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
Layout.preferredHeight: 18
|
Layout.preferredHeight: 18
|
||||||
Layout.preferredWidth: 18
|
Layout.preferredWidth: 18
|
||||||
source: imageSource
|
source: root.imageSource
|
||||||
}
|
}
|
||||||
|
|
||||||
ListItemTitleType {
|
ListItemTitleType {
|
||||||
@@ -25,14 +28,15 @@ RowLayout {
|
|||||||
Layout.rightMargin: 10
|
Layout.rightMargin: 10
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
|
|
||||||
text: leftText
|
text: root.leftText
|
||||||
}
|
}
|
||||||
|
|
||||||
ParagraphTextType {
|
ParagraphTextType {
|
||||||
visible: rightText !== ""
|
visible: root.rightText !== ""
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
|
||||||
text: isRightTextUndefined ? "" : rightText
|
text: root.isRightTextUndefined ? "" : root.rightText
|
||||||
|
textFormat: root.rightTextFormat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ Popup {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
onLinkActivated: function(link) {
|
onLinkActivated: function(link) {
|
||||||
Qt.openUrlExternally(link)
|
Qt.openUrlExternally(LanguageModel.getCurrentDocsUrl(link))
|
||||||
}
|
}
|
||||||
|
|
||||||
text: root.text
|
text: root.text
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ Switch {
|
|||||||
function handleSwitch(event) {
|
function handleSwitch(event) {
|
||||||
if (!event.isAutoRepeat) {
|
if (!event.isAutoRepeat) {
|
||||||
root.checked = !root.checked
|
root.checked = !root.checked
|
||||||
root.checkedChanged()
|
root.toggled()
|
||||||
}
|
}
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,22 @@ Item {
|
|||||||
implicitWidth: content.implicitWidth
|
implicitWidth: content.implicitWidth
|
||||||
implicitHeight: content.implicitHeight
|
implicitHeight: content.implicitHeight
|
||||||
|
|
||||||
|
Keys.onTabPressed: {
|
||||||
|
FocusController.nextKeyTabItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onBacktabPressed: {
|
||||||
|
FocusController.previousKeyTabItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onUpPressed: {
|
||||||
|
FocusController.nextKeyUpItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onDownPressed: {
|
||||||
|
FocusController.nextKeyDownItem()
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: content
|
id: content
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -109,6 +109,34 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
id: devGatewayButton
|
||||||
|
objectName: "devGatewayButton"
|
||||||
|
|
||||||
|
property bool isDevGatewayEnabled: SettingsController.isDevGatewayEnv
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
implicitHeight: 36
|
||||||
|
|
||||||
|
defaultColor: AmneziaStyle.color.transparent
|
||||||
|
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||||
|
pressedColor: AmneziaStyle.color.sheerWhite
|
||||||
|
disabledColor: AmneziaStyle.color.mutedGray
|
||||||
|
textColor: AmneziaStyle.color.mutedGray
|
||||||
|
borderWidth: 0
|
||||||
|
|
||||||
|
visible: SettingsController.isDevModeEnabled && isDevGatewayEnabled
|
||||||
|
text: qsTr("Dev gateway enabled")
|
||||||
|
|
||||||
|
Keys.onEnterPressed: this.clicked()
|
||||||
|
Keys.onReturnPressed: this.clicked()
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
PageController.goToPage(PageEnum.PageDevMenu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ConnectButton {
|
ConnectButton {
|
||||||
id: connectButton
|
id: connectButton
|
||||||
objectName: "connectButton"
|
objectName: "connectButton"
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ PageType {
|
|||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
anchors.top: backButtonLayout.bottom
|
anchors.top: backButton.bottom
|
||||||
anchors.bottom: saveButton.top
|
anchors.bottom: saveButton.top
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ PageType {
|
|||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
anchors.top: backButtonLayout.bottom
|
anchors.top: backButton.bottom
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|||||||
@@ -17,428 +17,414 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
ColumnLayout {
|
BackButtonType {
|
||||||
id: backButtonLayout
|
id: backButton
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20
|
||||||
|
|
||||||
BackButtonType {
|
onActiveFocusChanged: {
|
||||||
id: backButton
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
listView.positionViewAtBeginning()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FlickableType {
|
ListViewType {
|
||||||
id: fl
|
id: listView
|
||||||
anchors.top: backButtonLayout.bottom
|
|
||||||
|
anchors.top: backButton.bottom
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
contentHeight: content.implicitHeight
|
anchors.right: parent.right
|
||||||
|
anchors.left: parent.left
|
||||||
|
|
||||||
Column {
|
enabled: ServersModel.isProcessedServerHasWriteAccess()
|
||||||
id: content
|
|
||||||
|
|
||||||
anchors.top: parent.top
|
header: ColumnLayout {
|
||||||
anchors.left: parent.left
|
width: listView.width
|
||||||
anchors.right: parent.right
|
|
||||||
|
|
||||||
enabled: ServersModel.isProcessedServerHasWriteAccess()
|
BaseHeaderType {
|
||||||
|
id: header
|
||||||
|
|
||||||
ListView {
|
Layout.fillWidth: true
|
||||||
id: listview
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
width: parent.width
|
headerText: qsTr("OpenVPN Settings")
|
||||||
height: listview.contentItem.height
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clip: true
|
model: OpenVpnConfigModel
|
||||||
interactive: false
|
|
||||||
|
|
||||||
model: OpenVpnConfigModel
|
delegate: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
delegate: Item {
|
spacing: 0
|
||||||
id: delegateItem
|
|
||||||
|
|
||||||
property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField
|
TextFieldWithHeaderType {
|
||||||
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
|
id: vpnAddressSubnetTextField
|
||||||
|
|
||||||
implicitWidth: listview.width
|
Layout.fillWidth: true
|
||||||
implicitHeight: col.implicitHeight
|
Layout.topMargin: 32
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
ColumnLayout {
|
enabled: listView.enabled
|
||||||
id: col
|
|
||||||
|
|
||||||
anchors.top: parent.top
|
headerText: qsTr("VPN address subnet")
|
||||||
anchors.left: parent.left
|
textField.text: subnetAddress
|
||||||
anchors.right: parent.right
|
|
||||||
|
|
||||||
anchors.leftMargin: 16
|
textField.onEditingFinished: {
|
||||||
anchors.rightMargin: 16
|
if (textField.text !== subnetAddress) {
|
||||||
|
subnetAddress = textField.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
spacing: 0
|
checkEmptyText: true
|
||||||
|
}
|
||||||
|
|
||||||
BaseHeaderType {
|
ParagraphTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
headerText: qsTr("OpenVPN settings")
|
Layout.topMargin: 32
|
||||||
}
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
text: qsTr("Network protocol")
|
||||||
id: vpnAddressSubnetTextField
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
TransportProtoSelector {
|
||||||
Layout.topMargin: 32
|
id: transportProtoSelector
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
enabled: delegateItem.isEnabled
|
rootWidth: root.width
|
||||||
|
|
||||||
headerText: qsTr("VPN address subnet")
|
enabled: isTransportProtoEditable
|
||||||
textField.text: subnetAddress
|
|
||||||
|
|
||||||
parentFlickable: fl
|
currentIndex: {
|
||||||
|
return transportProto === "tcp" ? 1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
onCurrentIndexChanged: {
|
||||||
if (textField.text !== subnetAddress) {
|
if (transportProto === "tcp" && currentIndex === 0) {
|
||||||
subnetAddress = textField.text
|
transportProto = "udp"
|
||||||
}
|
} else if (transportProto === "udp" && currentIndex === 1) {
|
||||||
|
transportProto = "tcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFieldWithHeaderType {
|
||||||
|
id: portTextField
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 40
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
enabled: listView.enabled
|
||||||
|
|
||||||
|
headerText: qsTr("Port")
|
||||||
|
textField.text: port
|
||||||
|
textField.maximumLength: 5
|
||||||
|
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||||
|
|
||||||
|
textField.onEditingFinished: {
|
||||||
|
if (textField.text !== port) {
|
||||||
|
port = textField.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkEmptyText: true
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitcherType {
|
||||||
|
id: autoNegotiateEncryprionSwitcher
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 24
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
text: qsTr("Auto-negotiate encryption")
|
||||||
|
checked: autoNegotiateEncryprion
|
||||||
|
|
||||||
|
onToggled: function() {
|
||||||
|
if (checked !== autoNegotiateEncryprion) {
|
||||||
|
autoNegotiateEncryprion = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DropDownType {
|
||||||
|
id: hashDropDown
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 20
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
enabled: !autoNegotiateEncryprionSwitcher.checked
|
||||||
|
|
||||||
|
descriptionText: qsTr("Hash")
|
||||||
|
headerText: qsTr("Hash")
|
||||||
|
|
||||||
|
drawerParent: root
|
||||||
|
|
||||||
|
listView: ListViewWithRadioButtonType {
|
||||||
|
id: hashListView
|
||||||
|
|
||||||
|
rootWidth: root.width
|
||||||
|
|
||||||
|
model: ListModel {
|
||||||
|
ListElement { name : qsTr("SHA512") }
|
||||||
|
ListElement { name : qsTr("SHA384") }
|
||||||
|
ListElement { name : qsTr("SHA256") }
|
||||||
|
ListElement { name : qsTr("SHA3-512") }
|
||||||
|
ListElement { name : qsTr("SHA3-384") }
|
||||||
|
ListElement { name : qsTr("SHA3-256") }
|
||||||
|
ListElement { name : qsTr("whirlpool") }
|
||||||
|
ListElement { name : qsTr("BLAKE2b512") }
|
||||||
|
ListElement { name : qsTr("BLAKE2s256") }
|
||||||
|
ListElement { name : qsTr("SHA1") }
|
||||||
|
}
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
hashDropDown.text = selectedText
|
||||||
|
hash = hashDropDown.text
|
||||||
|
hashDropDown.closeTriggered()
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
hashDropDown.text = hash
|
||||||
|
|
||||||
|
for (var i = 0; i < hashListView.model.count; i++) {
|
||||||
|
if (hashListView.model.get(i).name === hashDropDown.text) {
|
||||||
|
currentIndex = i
|
||||||
}
|
}
|
||||||
|
|
||||||
checkEmptyText: true
|
|
||||||
}
|
|
||||||
|
|
||||||
ParagraphTextType {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 32
|
|
||||||
|
|
||||||
text: qsTr("Network protocol")
|
|
||||||
}
|
|
||||||
|
|
||||||
TransportProtoSelector {
|
|
||||||
id: transportProtoSelector
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 16
|
|
||||||
rootWidth: root.width
|
|
||||||
|
|
||||||
enabled: isTransportProtoEditable
|
|
||||||
|
|
||||||
currentIndex: {
|
|
||||||
return transportProto === "tcp" ? 1 : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
onCurrentIndexChanged: {
|
|
||||||
if (transportProto === "tcp" && currentIndex === 0) {
|
|
||||||
transportProto = "udp"
|
|
||||||
} else if (transportProto === "udp" && currentIndex === 1) {
|
|
||||||
transportProto = "tcp"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
|
||||||
id: portTextField
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 40
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
enabled: delegateItem.isEnabled
|
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
|
||||||
textField.text: port
|
|
||||||
textField.maximumLength: 5
|
|
||||||
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
|
||||||
if (textField.text !== port) {
|
|
||||||
port = textField.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkEmptyText: true
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitcherType {
|
|
||||||
id: autoNegotiateEncryprionSwitcher
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 24
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
text: qsTr("Auto-negotiate encryption")
|
|
||||||
checked: autoNegotiateEncryprion
|
|
||||||
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (checked !== autoNegotiateEncryprion) {
|
|
||||||
autoNegotiateEncryprion = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DropDownType {
|
|
||||||
id: hashDropDown
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 20
|
|
||||||
|
|
||||||
enabled: !autoNegotiateEncryprionSwitcher.checked
|
|
||||||
|
|
||||||
descriptionText: qsTr("Hash")
|
|
||||||
headerText: qsTr("Hash")
|
|
||||||
|
|
||||||
drawerParent: root
|
|
||||||
|
|
||||||
listView: ListViewWithRadioButtonType {
|
|
||||||
id: hashListView
|
|
||||||
|
|
||||||
rootWidth: root.width
|
|
||||||
|
|
||||||
model: ListModel {
|
|
||||||
ListElement { name : qsTr("SHA512") }
|
|
||||||
ListElement { name : qsTr("SHA384") }
|
|
||||||
ListElement { name : qsTr("SHA256") }
|
|
||||||
ListElement { name : qsTr("SHA3-512") }
|
|
||||||
ListElement { name : qsTr("SHA3-384") }
|
|
||||||
ListElement { name : qsTr("SHA3-256") }
|
|
||||||
ListElement { name : qsTr("whirlpool") }
|
|
||||||
ListElement { name : qsTr("BLAKE2b512") }
|
|
||||||
ListElement { name : qsTr("BLAKE2s256") }
|
|
||||||
ListElement { name : qsTr("SHA1") }
|
|
||||||
}
|
|
||||||
|
|
||||||
clickedFunction: function() {
|
|
||||||
hashDropDown.text = selectedText
|
|
||||||
hash = hashDropDown.text
|
|
||||||
hashDropDown.closeTriggered()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
hashDropDown.text = hash
|
|
||||||
|
|
||||||
for (var i = 0; i < hashListView.model.count; i++) {
|
|
||||||
if (hashListView.model.get(i).name === hashDropDown.text) {
|
|
||||||
currentIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DropDownType {
|
|
||||||
id: cipherDropDown
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 16
|
|
||||||
|
|
||||||
enabled: !autoNegotiateEncryprionSwitcher.checked
|
|
||||||
|
|
||||||
descriptionText: qsTr("Cipher")
|
|
||||||
headerText: qsTr("Cipher")
|
|
||||||
|
|
||||||
drawerParent: root
|
|
||||||
|
|
||||||
listView: ListViewWithRadioButtonType {
|
|
||||||
id: cipherListView
|
|
||||||
|
|
||||||
rootWidth: root.width
|
|
||||||
|
|
||||||
model: ListModel {
|
|
||||||
ListElement { name : qsTr("AES-256-GCM") }
|
|
||||||
ListElement { name : qsTr("AES-192-GCM") }
|
|
||||||
ListElement { name : qsTr("AES-128-GCM") }
|
|
||||||
ListElement { name : qsTr("AES-256-CBC") }
|
|
||||||
ListElement { name : qsTr("AES-192-CBC") }
|
|
||||||
ListElement { name : qsTr("AES-128-CBC") }
|
|
||||||
ListElement { name : qsTr("ChaCha20-Poly1305") }
|
|
||||||
ListElement { name : qsTr("ARIA-256-CBC") }
|
|
||||||
ListElement { name : qsTr("CAMELLIA-256-CBC") }
|
|
||||||
ListElement { name : qsTr("none") }
|
|
||||||
}
|
|
||||||
|
|
||||||
clickedFunction: function() {
|
|
||||||
cipherDropDown.text = selectedText
|
|
||||||
cipher = cipherDropDown.text
|
|
||||||
cipherDropDown.closeTriggered()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
cipherDropDown.text = cipher
|
|
||||||
|
|
||||||
for (var i = 0; i < cipherListView.model.count; i++) {
|
|
||||||
if (cipherListView.model.get(i).name === cipherDropDown.text) {
|
|
||||||
currentIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: contentRect
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 32
|
|
||||||
Layout.preferredHeight: checkboxLayout.implicitHeight
|
|
||||||
color: AmneziaStyle.color.onyxBlack
|
|
||||||
radius: 16
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: tlsAuthCheckBox
|
|
||||||
enabled: !GC.isMobile()
|
|
||||||
|
|
||||||
function onFocusChanged() {
|
|
||||||
if (tlsAuthCheckBox.activeFocus) {
|
|
||||||
fl.ensureVisible(contentRect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: checkboxLayout
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
CheckBoxType {
|
|
||||||
id: tlsAuthCheckBox
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: qsTr("TLS auth")
|
|
||||||
checked: tlsAuth
|
|
||||||
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (checked !== tlsAuth) {
|
|
||||||
console.log("tlsAuth changed to: " + checked)
|
|
||||||
tlsAuth = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DividerType {}
|
|
||||||
|
|
||||||
CheckBoxType {
|
|
||||||
id: blockDnsCheckBox
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: qsTr("Block DNS requests outside of VPN")
|
|
||||||
checked: blockDns
|
|
||||||
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (checked !== blockDns) {
|
|
||||||
blockDns = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitcherType {
|
|
||||||
id: additionalClientCommandsSwitcher
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 32
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
checked: additionalClientCommands !== ""
|
|
||||||
|
|
||||||
text: qsTr("Additional client configuration commands")
|
|
||||||
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (!checked) {
|
|
||||||
additionalClientCommands = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextAreaType {
|
|
||||||
id: additionalClientCommandsTextArea
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 16
|
|
||||||
|
|
||||||
visible: additionalClientCommandsSwitcher.checked
|
|
||||||
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
textAreaText: additionalClientCommands
|
|
||||||
placeholderText: qsTr("Commands:")
|
|
||||||
|
|
||||||
textArea.onEditingFinished: {
|
|
||||||
if (additionalClientCommands !== textAreaText) {
|
|
||||||
additionalClientCommands = textAreaText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitcherType {
|
|
||||||
id: additionalServerCommandsSwitcher
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 16
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
checked: additionalServerCommands !== ""
|
|
||||||
|
|
||||||
text: qsTr("Additional server configuration commands")
|
|
||||||
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (!checked) {
|
|
||||||
additionalServerCommands = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextAreaType {
|
|
||||||
id: additionalServerCommandsTextArea
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 16
|
|
||||||
|
|
||||||
visible: additionalServerCommandsSwitcher.checked
|
|
||||||
|
|
||||||
textAreaText: additionalServerCommands
|
|
||||||
placeholderText: qsTr("Commands:")
|
|
||||||
parentFlickable: fl
|
|
||||||
textArea.onEditingFinished: {
|
|
||||||
if (additionalServerCommands !== textAreaText) {
|
|
||||||
additionalServerCommands = textAreaText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicButtonType {
|
|
||||||
id: saveButton
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 24
|
|
||||||
Layout.bottomMargin: 24
|
|
||||||
|
|
||||||
enabled: vpnAddressSubnetTextField.errorText === "" &&
|
|
||||||
portTextField.errorText === ""
|
|
||||||
|
|
||||||
text: qsTr("Save")
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
onClicked: function() {
|
|
||||||
forceActiveFocus()
|
|
||||||
|
|
||||||
var headerText = qsTr("Save settings?")
|
|
||||||
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
|
||||||
var yesButtonText = qsTr("Continue")
|
|
||||||
var noButtonText = qsTr("Cancel")
|
|
||||||
|
|
||||||
var yesButtonFunction = function() {
|
|
||||||
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
|
|
||||||
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
|
||||||
InstallController.updateContainer(OpenVpnConfigModel.getConfig())
|
|
||||||
}
|
|
||||||
var noButtonFunction = function() {
|
|
||||||
if (!GC.isMobile()) {
|
|
||||||
saveButton.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onEnterPressed: saveButton.clicked()
|
|
||||||
Keys.onReturnPressed: saveButton.clicked()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DropDownType {
|
||||||
|
id: cipherDropDown
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
enabled: !autoNegotiateEncryprionSwitcher.checked
|
||||||
|
|
||||||
|
descriptionText: qsTr("Cipher")
|
||||||
|
headerText: qsTr("Cipher")
|
||||||
|
|
||||||
|
drawerParent: root
|
||||||
|
|
||||||
|
listView: ListViewWithRadioButtonType {
|
||||||
|
id: cipherListView
|
||||||
|
|
||||||
|
rootWidth: root.width
|
||||||
|
|
||||||
|
model: ListModel {
|
||||||
|
ListElement { name : qsTr("AES-256-GCM") }
|
||||||
|
ListElement { name : qsTr("AES-192-GCM") }
|
||||||
|
ListElement { name : qsTr("AES-128-GCM") }
|
||||||
|
ListElement { name : qsTr("AES-256-CBC") }
|
||||||
|
ListElement { name : qsTr("AES-192-CBC") }
|
||||||
|
ListElement { name : qsTr("AES-128-CBC") }
|
||||||
|
ListElement { name : qsTr("ChaCha20-Poly1305") }
|
||||||
|
ListElement { name : qsTr("ARIA-256-CBC") }
|
||||||
|
ListElement { name : qsTr("CAMELLIA-256-CBC") }
|
||||||
|
ListElement { name : qsTr("none") }
|
||||||
|
}
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
cipherDropDown.text = selectedText
|
||||||
|
cipher = cipherDropDown.text
|
||||||
|
cipherDropDown.closeTriggered()
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
cipherDropDown.text = cipher
|
||||||
|
|
||||||
|
for (var i = 0; i < cipherListView.model.count; i++) {
|
||||||
|
if (cipherListView.model.get(i).name === cipherDropDown.text) {
|
||||||
|
currentIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: contentRect
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 32
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
Layout.preferredHeight: checkboxLayout.implicitHeight
|
||||||
|
color: AmneziaStyle.color.onyxBlack
|
||||||
|
radius: 16
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: checkboxLayout
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
CheckBoxType {
|
||||||
|
id: tlsAuthCheckBox
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("TLS auth")
|
||||||
|
checked: tlsAuth
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked !== tlsAuth) {
|
||||||
|
console.log("tlsAuth changed to: " + checked)
|
||||||
|
tlsAuth = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
|
||||||
|
CheckBoxType {
|
||||||
|
id: blockDnsCheckBox
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Block DNS requests outside of VPN")
|
||||||
|
checked: blockDns
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked !== blockDns) {
|
||||||
|
blockDns = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitcherType {
|
||||||
|
id: additionalClientCommandsSwitcher
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 32
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
checked: additionalClientCommands !== ""
|
||||||
|
|
||||||
|
text: qsTr("Additional client configuration commands")
|
||||||
|
|
||||||
|
onToggled: function() {
|
||||||
|
if (!checked) {
|
||||||
|
additionalClientCommands = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAreaType {
|
||||||
|
id: additionalClientCommandsTextArea
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
visible: additionalClientCommandsSwitcher.checked
|
||||||
|
|
||||||
|
textAreaText: additionalClientCommands
|
||||||
|
placeholderText: qsTr("Commands:")
|
||||||
|
|
||||||
|
textArea.onEditingFinished: {
|
||||||
|
if (additionalClientCommands !== textAreaText) {
|
||||||
|
additionalClientCommands = textAreaText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitcherType {
|
||||||
|
id: additionalServerCommandsSwitcher
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
checked: additionalServerCommands !== ""
|
||||||
|
|
||||||
|
text: qsTr("Additional server configuration commands")
|
||||||
|
|
||||||
|
onToggled: function() {
|
||||||
|
if (!checked) {
|
||||||
|
additionalServerCommands = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAreaType {
|
||||||
|
id: additionalServerCommandsTextArea
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
visible: additionalServerCommandsSwitcher.checked
|
||||||
|
|
||||||
|
textAreaText: additionalServerCommands
|
||||||
|
placeholderText: qsTr("Commands:")
|
||||||
|
|
||||||
|
textArea.onEditingFinished: {
|
||||||
|
if (additionalServerCommands !== textAreaText) {
|
||||||
|
additionalServerCommands = textAreaText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
id: saveButton
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 24
|
||||||
|
Layout.bottomMargin: 24
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
enabled: vpnAddressSubnetTextField.errorText === "" &&
|
||||||
|
portTextField.errorText === ""
|
||||||
|
|
||||||
|
text: qsTr("Save")
|
||||||
|
|
||||||
|
onClicked: function() {
|
||||||
|
forceActiveFocus()
|
||||||
|
|
||||||
|
var headerText = qsTr("Save settings?")
|
||||||
|
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
||||||
|
var yesButtonText = qsTr("Continue")
|
||||||
|
var noButtonText = qsTr("Cancel")
|
||||||
|
|
||||||
|
var yesButtonFunction = function() {
|
||||||
|
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
|
||||||
|
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||||
|
InstallController.updateContainer(OpenVpnConfigModel.getConfig())
|
||||||
|
}
|
||||||
|
var noButtonFunction = function() {
|
||||||
|
if (!GC.isMobile()) {
|
||||||
|
saveButton.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onEnterPressed: saveButton.clicked()
|
||||||
|
Keys.onReturnPressed: saveButton.clicked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,8 +66,6 @@ PageType {
|
|||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
enabled: delegateItem.isEnabled
|
|
||||||
|
|
||||||
headerText: qsTr("VPN address subnet")
|
headerText: qsTr("VPN address subnet")
|
||||||
textField.text: subnetAddress
|
textField.text: subnetAddress
|
||||||
|
|
||||||
@@ -87,8 +85,6 @@ PageType {
|
|||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
enabled: delegateItem.isEnabled
|
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
headerText: qsTr("Port")
|
||||||
textField.text: port
|
textField.text: port
|
||||||
textField.maximumLength: 5
|
textField.maximumLength: 5
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ PageType {
|
|||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
|
|
||||||
visible: isVisible
|
visible: isVisible
|
||||||
|
|
||||||
@@ -68,8 +66,6 @@ PageType {
|
|||||||
|
|
||||||
visible: GC.isDesktop()
|
visible: GC.isDesktop()
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
|
|
||||||
text: qsTr("Close application")
|
text: qsTr("Close application")
|
||||||
leftImageSource: "qrc:/images/controls/x-circle.svg"
|
leftImageSource: "qrc:/images/controls/x-circle.svg"
|
||||||
@@ -94,6 +90,7 @@ PageType {
|
|||||||
servers,
|
servers,
|
||||||
connection,
|
connection,
|
||||||
application,
|
application,
|
||||||
|
news,
|
||||||
backup,
|
backup,
|
||||||
about,
|
about,
|
||||||
devConsole
|
devConsole
|
||||||
@@ -132,6 +129,23 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: news
|
||||||
|
|
||||||
|
property string title: qsTr("News & Notifications")
|
||||||
|
readonly property string leftImagePath: NewsModel.hasUnread ? "qrc:/images/controls/news-unread.svg" : "qrc:/images/controls/news.svg"
|
||||||
|
property bool isVisible: ServersModel.hasServersFromGatewayApi
|
||||||
|
readonly property var clickedHandler: function() {
|
||||||
|
if (!ServersModel.hasServersFromGatewayApi) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
ApiNewsController.fetchNews();
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsNewsNotifications)
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: backup
|
id: backup
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ PageType {
|
|||||||
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/info.svg"
|
||||||
|
readonly property bool isRichText: true
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
@@ -37,6 +38,7 @@ PageType {
|
|||||||
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"
|
||||||
|
readonly property bool isRichText: false
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
@@ -45,6 +47,7 @@ PageType {
|
|||||||
readonly property string title: qsTr("Active Connections")
|
readonly property string title: qsTr("Active Connections")
|
||||||
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/monitor.svg"
|
||||||
|
readonly property bool isRichText: false
|
||||||
}
|
}
|
||||||
|
|
||||||
property var processedServer
|
property var processedServer
|
||||||
@@ -134,6 +137,7 @@ PageType {
|
|||||||
imageSource: objectImageSource
|
imageSource: objectImageSource
|
||||||
leftText: title
|
leftText: title
|
||||||
rightText: ApiAccountInfoModel.data(contentKey)
|
rightText: ApiAccountInfoModel.data(contentKey)
|
||||||
|
rightTextFormat: isRichText ? Text.RichText : Text.PlainText
|
||||||
|
|
||||||
visible: rightText !== ""
|
visible: rightText !== ""
|
||||||
}
|
}
|
||||||
@@ -214,9 +218,6 @@ PageType {
|
|||||||
ApiConfigsController.prepareVpnKeyExport()
|
ApiConfigsController.prepareVpnKeyExport()
|
||||||
|
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
|
|
||||||
// Navigate to PageShareConnection page
|
|
||||||
//PageController.goToPage(PageEnum.PageShareConnection)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,7 +359,7 @@ PageType {
|
|||||||
PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection"))
|
PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection"))
|
||||||
} else {
|
} else {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
if (ApiConfigsController.deactivateDevice()) {
|
if (ApiConfigsController.deactivateDevice(false)) {
|
||||||
ApiSettingsController.getAccountInfo(true)
|
ApiSettingsController.getAccountInfo(true)
|
||||||
}
|
}
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
@@ -395,7 +396,7 @@ PageType {
|
|||||||
PageController.showNotificationMessage(qsTr("Cannot remove server during active connection"))
|
PageController.showNotificationMessage(qsTr("Cannot remove server during active connection"))
|
||||||
} else {
|
} else {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
if (ApiConfigsController.deactivateDevice()) {
|
if (ApiConfigsController.deactivateDevice(true)) {
|
||||||
InstallController.removeProcessedServer()
|
InstallController.removeProcessedServer()
|
||||||
}
|
}
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import Qt.labs.platform 1.1
|
|||||||
|
|
||||||
import QtCore
|
import QtCore
|
||||||
|
|
||||||
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
import PageEnum 1.0
|
import PageEnum 1.0
|
||||||
import Style 1.0
|
import Style 1.0
|
||||||
|
|
||||||
@@ -17,6 +19,33 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property var processedServer
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ServersModel
|
||||||
|
|
||||||
|
function onProcessedServerChanged() {
|
||||||
|
root.processedServer = proxyServersModel.get(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SortFilterProxyModel {
|
||||||
|
id: proxyServersModel
|
||||||
|
objectName: "proxyServersModel"
|
||||||
|
|
||||||
|
sourceModel: ServersModel
|
||||||
|
filters: [
|
||||||
|
ValueFilter {
|
||||||
|
roleName: "isCurrentlyProcessed"
|
||||||
|
value: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.processedServer = proxyServersModel.get(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
ApiConfigsController.prepareVpnKeyExport()
|
ApiConfigsController.prepareVpnKeyExport()
|
||||||
@@ -40,7 +69,7 @@ PageType {
|
|||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
text: qsTr("Amnezia Premium\nsubscription key")
|
text: qsTr(root.processedServer.name + "\nsubscription key")
|
||||||
font.pixelSize: 32
|
font.pixelSize: 32
|
||||||
font.bold: true
|
font.bold: true
|
||||||
color: AmneziaStyle.color.paleGray
|
color: AmneziaStyle.color.paleGray
|
||||||
@@ -53,18 +82,10 @@ PageType {
|
|||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
defaultColor: AmneziaStyle.color.paleGray
|
|
||||||
hoveredColor: AmneziaStyle.color.sheerWhite
|
|
||||||
pressedColor: AmneziaStyle.color.translucentWhite
|
|
||||||
disabledColor: AmneziaStyle.color.mutedGray
|
|
||||||
textColor: AmneziaStyle.color.black
|
|
||||||
leftImageColor: "black"
|
|
||||||
borderWidth: 1
|
|
||||||
|
|
||||||
text: qsTr("Copy key")
|
text: qsTr("Copy key")
|
||||||
leftImageSource: "qrc:/images/controls/copy.svg"
|
leftImageSource: "qrc:/images/controls/copy.svg"
|
||||||
|
|
||||||
onClicked: {
|
clickedFunc: function() {
|
||||||
ApiConfigsController.copyVpnKeyToClipboard()
|
ApiConfigsController.copyVpnKeyToClipboard()
|
||||||
PageController.showNotificationMessage(qsTr("Copied"))
|
PageController.showNotificationMessage(qsTr("Copied"))
|
||||||
}
|
}
|
||||||
@@ -85,20 +106,20 @@ PageType {
|
|||||||
text: qsTr("Save key as a file")
|
text: qsTr("Save key as a file")
|
||||||
leftImageSource: "qrc:/images/controls/share-2.svg"
|
leftImageSource: "qrc:/images/controls/share-2.svg"
|
||||||
|
|
||||||
onClicked: {
|
clickedFunc: function() {
|
||||||
var fileName = GC.isMobile()
|
var fileName = GC.isMobile()
|
||||||
? "amnezia_vpn_key.vpn"
|
? root.processedServer.name.toLowerCase().replace(/\s+/g, "_") + "_key.vpn"
|
||||||
: SystemController.getFileName(
|
: SystemController.getFileName(
|
||||||
qsTr("Save AmneziaVPN config"),
|
qsTr("Save AmneziaVPN config"),
|
||||||
qsTr("Config files (*.vpn)"),
|
qsTr("Config files (*.vpn)"),
|
||||||
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/amnezia_vpn_key",
|
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + root.processedServer.name.toLowerCase().replace(/\s+/g, "_") + "_key",
|
||||||
true,
|
true,
|
||||||
".vpn"
|
".vpn"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (fileName !== "") {
|
if (fileName !== "") {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
ExportController.exportConfig(fileName)
|
ApiConfigsController.exportVpnKey(fileName)
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +139,7 @@ PageType {
|
|||||||
text: qsTr("Show key text")
|
text: qsTr("Show key text")
|
||||||
leftImageSource: "qrc:/images/controls/eye.svg"
|
leftImageSource: "qrc:/images/controls/eye.svg"
|
||||||
|
|
||||||
onClicked: {
|
clickedFunc: function() {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
ApiConfigsController.prepareVpnKeyExport()
|
ApiConfigsController.prepareVpnKeyExport()
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
@@ -127,8 +148,9 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.preferredWidth: Math.min(Math.min(root.width - (Layout.leftMargin + Layout.rightMargin), root.height * 0.5), 360)
|
||||||
Layout.preferredHeight: width
|
Layout.preferredHeight: Layout.preferredWidth
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: 20
|
Layout.topMargin: 20
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
@@ -140,6 +162,9 @@ PageType {
|
|||||||
Image {
|
Image {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
smooth: false
|
smooth: false
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
sourceSize.width: parent.width
|
||||||
|
sourceSize.height: parent.height
|
||||||
source: ApiConfigsController.qrCodesCount > 0 && ApiConfigsController.qrCodes[0] ? ApiConfigsController.qrCodes[0] : ""
|
source: ApiConfigsController.qrCodesCount > 0 && ApiConfigsController.qrCodes[0] ? ApiConfigsController.qrCodes[0] : ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +206,7 @@ PageType {
|
|||||||
|
|
||||||
Header2Type {
|
Header2Type {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
headerText: qsTr("Amnezia Premium Subscription key")
|
headerText: qsTr(root.processedServer.name + " Subscription key")
|
||||||
}
|
}
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
@@ -194,7 +219,7 @@ PageType {
|
|||||||
font.pixelSize: 16
|
font.pixelSize: 16
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
font.family: "PT Root UI VF"
|
font.family: "PT Root UI VF"
|
||||||
text: ApiConfigsController.vpnKey //|| ""
|
text: ApiConfigsController.vpnKey
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
background: Rectangle { color: AmneziaStyle.color.transparent }
|
background: Rectangle { color: AmneziaStyle.color.transparent }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,11 +145,25 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WarningType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 8
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
textString: qsTr("Only \"Apps from the list should not have access via VPN\" mode is available on Windows")
|
||||||
|
iconPath: "qrc:/images/controls/alert-circle.svg"
|
||||||
|
|
||||||
|
visible: (Qt.platform.os === "windows") && root.pageEnabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBarType { policy: ScrollBar.AlwaysOn }
|
||||||
|
|
||||||
anchors.top: header.bottom
|
anchors.top: header.bottom
|
||||||
anchors.bottom: addAppButton.top
|
anchors.bottom: addAppButton.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ PageType {
|
|||||||
text: qsTr("Allow application screenshots")
|
text: qsTr("Allow application screenshots")
|
||||||
|
|
||||||
checked: SettingsController.isScreenshotsEnabled()
|
checked: SettingsController.isScreenshotsEnabled()
|
||||||
onCheckedChanged: {
|
onToggled: function() {
|
||||||
if (checked !== SettingsController.isScreenshotsEnabled()) {
|
if (checked !== SettingsController.isScreenshotsEnabled()) {
|
||||||
SettingsController.toggleScreenshotsEnabled(checked)
|
SettingsController.toggleScreenshotsEnabled(checked)
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ PageType {
|
|||||||
descriptionText: qsTr("Launch the application every time the device is starts")
|
descriptionText: qsTr("Launch the application every time the device is starts")
|
||||||
|
|
||||||
checked: SettingsController.isAutoStartEnabled()
|
checked: SettingsController.isAutoStartEnabled()
|
||||||
onCheckedChanged: {
|
onToggled: function() {
|
||||||
if (checked !== SettingsController.isAutoStartEnabled()) {
|
if (checked !== SettingsController.isAutoStartEnabled()) {
|
||||||
SettingsController.toggleAutoStart(checked)
|
SettingsController.toggleAutoStart(checked)
|
||||||
}
|
}
|
||||||
@@ -132,7 +132,7 @@ PageType {
|
|||||||
descriptionText: qsTr("Connect to VPN on app start")
|
descriptionText: qsTr("Connect to VPN on app start")
|
||||||
|
|
||||||
checked: SettingsController.isAutoConnectEnabled()
|
checked: SettingsController.isAutoConnectEnabled()
|
||||||
onCheckedChanged: {
|
onToggled: function() {
|
||||||
if (checked !== SettingsController.isAutoConnectEnabled()) {
|
if (checked !== SettingsController.isAutoConnectEnabled()) {
|
||||||
SettingsController.toggleAutoConnect(checked)
|
SettingsController.toggleAutoConnect(checked)
|
||||||
}
|
}
|
||||||
@@ -157,9 +157,9 @@ PageType {
|
|||||||
enabled: switcherAutoStart.checked
|
enabled: switcherAutoStart.checked
|
||||||
opacity: enabled ? 1.0 : 0.5
|
opacity: enabled ? 1.0 : 0.5
|
||||||
|
|
||||||
checked: SettingsController.isStartMinimizedEnabled()
|
checked: SettingsController.startMinimized
|
||||||
onCheckedChanged: {
|
onToggled: function() {
|
||||||
if (checked !== SettingsController.isStartMinimizedEnabled()) {
|
if (checked !== SettingsController.startMinimized) {
|
||||||
SettingsController.toggleStartMinimized(checked)
|
SettingsController.toggleStartMinimized(checked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ PageType {
|
|||||||
descriptionText: qsTr("If AmneziaDNS is installed on the server")
|
descriptionText: qsTr("If AmneziaDNS is installed on the server")
|
||||||
|
|
||||||
checked: SettingsController.isAmneziaDnsEnabled()
|
checked: SettingsController.isAmneziaDnsEnabled()
|
||||||
onCheckedChanged: {
|
onToggled: function() {
|
||||||
if (checked !== SettingsController.isAmneziaDnsEnabled()) {
|
if (checked !== SettingsController.isAmneziaDnsEnabled()) {
|
||||||
SettingsController.toggleAmneziaDns(checked)
|
SettingsController.toggleAmneziaDns(checked)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,13 @@ PageType {
|
|||||||
|
|
||||||
text: qsTr("If AmneziaDNS is not used or installed")
|
text: qsTr("If AmneziaDNS is not used or installed")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model: 1 // fake model to force the ListView to be created without a model
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: primaryDns
|
id: primaryDns
|
||||||
@@ -96,13 +103,6 @@ PageType {
|
|||||||
regularExpression: InstallController.ipAddressRegExp()
|
regularExpression: InstallController.ipAddressRegExp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
model: 1 // fake model to force the ListView to be created without a model
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
|
||||||
width: listView.width
|
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: restoreDefaultButton
|
id: restoreDefaultButton
|
||||||
@@ -139,10 +139,6 @@ PageType {
|
|||||||
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
footer: ColumnLayout {
|
|
||||||
width: listView.width
|
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: saveButton
|
id: saveButton
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ PageType {
|
|||||||
|
|
||||||
checked: SettingsController.isLoggingEnabled
|
checked: SettingsController.isLoggingEnabled
|
||||||
|
|
||||||
onCheckedChanged: {
|
onToggled: function() {
|
||||||
if (checked !== SettingsController.isLoggingEnabled) {
|
if (checked !== SettingsController.isLoggingEnabled) {
|
||||||
SettingsController.isLoggingEnabled = checked
|
SettingsController.isLoggingEnabled = checked
|
||||||
}
|
}
|
||||||
@@ -167,7 +167,8 @@ PageType {
|
|||||||
|
|
||||||
// Show service logs only if this is NOT a macOS build with
|
// Show service logs only if this is NOT a macOS build with
|
||||||
// Network-Extension (IsMacOsNeBuild is injected from C++ at run-time)
|
// Network-Extension (IsMacOsNeBuild is injected from C++ at run-time)
|
||||||
property list<QtObject> logTypes: IsMacOsNeBuild ? [
|
// or if this is NOT a mobile build
|
||||||
|
property list<QtObject> logTypes: (IsMacOsNeBuild || GC.isMobile()) ? [
|
||||||
clientLogs
|
clientLogs
|
||||||
] : [
|
] : [
|
||||||
clientLogs,
|
clientLogs,
|
||||||
@@ -214,15 +215,11 @@ PageType {
|
|||||||
}
|
}
|
||||||
readonly property var exportLogsHandler: function() {
|
readonly property var exportLogsHandler: function() {
|
||||||
var fileName = ""
|
var fileName = ""
|
||||||
if (GC.isMobile()) {
|
fileName = SystemController.getFileName(qsTr("Save"),
|
||||||
fileName = "AmneziaVPN-service.log"
|
qsTr("Logs files (*.log)"),
|
||||||
} else {
|
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN-service",
|
||||||
fileName = SystemController.getFileName(qsTr("Save"),
|
true,
|
||||||
qsTr("Logs files (*.log)"),
|
".log")
|
||||||
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN-service",
|
|
||||||
true,
|
|
||||||
".log")
|
|
||||||
}
|
|
||||||
if (fileName !== "") {
|
if (fileName !== "") {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
SettingsController.exportServiceLogsFile(fileName)
|
SettingsController.exportServiceLogsFile(fileName)
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
property var newsItem
|
||||||
|
|
||||||
|
SortFilterProxyModel {
|
||||||
|
id: proxyNews
|
||||||
|
sourceModel: NewsModel
|
||||||
|
filters: [ ValueFilter { roleName: "isProcessed"; value: true } ]
|
||||||
|
Component.onCompleted: root.newsItem = proxyNews.get(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: NewsModel
|
||||||
|
function onProcessedIndexChanged() {
|
||||||
|
root.newsItem = proxyNews.get(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.topMargin: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
FlickableType {
|
||||||
|
id: fl
|
||||||
|
anchors.top: backButton.bottom
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
contentHeight: content.height
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: content
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
BaseHeaderType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
headerText: newsItem.title
|
||||||
|
}
|
||||||
|
|
||||||
|
ParagraphTextType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
text: newsItem.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: header
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
anchors.topMargin: 20
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseHeaderType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
headerText: qsTr("News & Notifications")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: newsList
|
||||||
|
width: parent.width
|
||||||
|
anchors.top: header.bottom
|
||||||
|
anchors.topMargin: 16
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
|
property bool isFocusable: true
|
||||||
|
|
||||||
|
model: NewsModel
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
reuseItems: true
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
implicitWidth: newsList.width
|
||||||
|
implicitHeight: content.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: content
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
leftImageSource: read ? "" : "qrc:/images/controls/unread-dot.svg"
|
||||||
|
isSmallLeftImage: !read
|
||||||
|
text: title
|
||||||
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
NewsModel.markAsRead(index)
|
||||||
|
NewsModel.processedIndex = index
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsNewsDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ PageType {
|
|||||||
|
|
||||||
signal lastItemTabClickedSignal()
|
signal lastItemTabClickedSignal()
|
||||||
|
|
||||||
|
property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess()
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: InstallController
|
target: InstallController
|
||||||
|
|
||||||
@@ -59,15 +61,13 @@ PageType {
|
|||||||
target: ServersModel
|
target: ServersModel
|
||||||
|
|
||||||
function onProcessedServerIndexChanged() {
|
function onProcessedServerIndexChanged() {
|
||||||
listView.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess()
|
root.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess()
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
model: serverActions
|
model: serverActions
|
||||||
@@ -107,7 +107,7 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: check
|
id: check
|
||||||
|
|
||||||
property bool isVisible: true
|
property bool isVisible: root.isServerWithWriteAccess
|
||||||
readonly property string title: qsTr("Check the server for previously installed Amnezia services")
|
readonly property string title: qsTr("Check the server for previously installed Amnezia services")
|
||||||
readonly property string description: qsTr("Add them to the application if they were not displayed")
|
readonly property string description: qsTr("Add them to the application if they were not displayed")
|
||||||
readonly property var tColor: AmneziaStyle.color.paleGray
|
readonly property var tColor: AmneziaStyle.color.paleGray
|
||||||
@@ -121,7 +121,7 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: reboot
|
id: reboot
|
||||||
|
|
||||||
property bool isVisible: true
|
property bool isVisible: root.isServerWithWriteAccess
|
||||||
readonly property string title: qsTr("Reboot server")
|
readonly property string title: qsTr("Reboot server")
|
||||||
readonly property string description: ""
|
readonly property string description: ""
|
||||||
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
||||||
@@ -181,7 +181,7 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: clear
|
id: clear
|
||||||
|
|
||||||
property bool isVisible: true
|
property bool isVisible: root.isServerWithWriteAccess
|
||||||
readonly property string title: qsTr("Clear server from Amnezia software")
|
readonly property string title: qsTr("Clear server from Amnezia software")
|
||||||
readonly property string description: ""
|
readonly property string description: ""
|
||||||
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
||||||
@@ -240,7 +240,7 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: switch_to_premium
|
id: switch_to_premium
|
||||||
|
|
||||||
property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
|
property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi") && ServersModel.processedServerIsPremium
|
||||||
readonly property string title: qsTr("Switch to the new Amnezia Premium subscription")
|
readonly property string title: qsTr("Switch to the new Amnezia Premium subscription")
|
||||||
readonly property string description: ""
|
readonly property string description: ""
|
||||||
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
||||||
|
|||||||
@@ -164,6 +164,8 @@ PageType {
|
|||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBarType { policy: ScrollBar.AlwaysOn }
|
||||||
|
|
||||||
anchors.top: header.bottom
|
anchors.top: header.bottom
|
||||||
anchors.topMargin: 16
|
anchors.topMargin: 16
|
||||||
anchors.bottom: addSiteButton.top
|
anchors.bottom: addSiteButton.top
|
||||||
@@ -348,7 +350,6 @@ PageType {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
text: qsTr("Clear site list")
|
text: qsTr("Clear site list")
|
||||||
rightImageSource: "qrc:/images/controls/trash.svg"
|
|
||||||
|
|
||||||
clickedFunction: function() {
|
clickedFunction: function() {
|
||||||
var headerText = qsTr("Clear site list?")
|
var headerText = qsTr("Clear site list?")
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ PageType {
|
|||||||
imageSource: imagePath
|
imageSource: imagePath
|
||||||
leftText: lText
|
leftText: lText
|
||||||
rightText: rText
|
rightText: rText
|
||||||
|
|
||||||
|
visible: isVisible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ PageType {
|
|||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
text: {
|
text: {
|
||||||
var text = ApiServicesModel.getSelectedServiceData("features")
|
var text = ApiServicesModel.getSelectedServiceData("features")
|
||||||
return text.replace("%1", LanguageModel.getCurrentSiteUrl("free"))
|
return text.replace("%1", LanguageModel.getCurrentSiteUrl("free")).replace("/free", "") // todo link should come from gateway
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import QtQuick.Controls
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Dialogs
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
import PageEnum 1.0
|
import PageEnum 1.0
|
||||||
import Style 1.0
|
import Style 1.0
|
||||||
|
|
||||||
@@ -54,7 +56,15 @@ PageType {
|
|||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
model: ApiServicesModel
|
model: SortFilterProxyModel {
|
||||||
|
id: proxyApiServicesModel
|
||||||
|
|
||||||
|
sourceModel: ApiServicesModel
|
||||||
|
sorters: RoleSorter {
|
||||||
|
roleName: "order"
|
||||||
|
sortOrder: Qt.AscendingOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
delegate: ColumnLayout {
|
||||||
|
|
||||||
@@ -78,7 +88,7 @@ PageType {
|
|||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (isServiceAvailable) {
|
if (isServiceAvailable) {
|
||||||
ApiServicesModel.setServiceIndex(index)
|
ApiServicesModel.setServiceIndex(proxyApiServicesModel.mapToSource(index))
|
||||||
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
|
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ PageType {
|
|||||||
|
|
||||||
visible: PageController.isStartPageVisible()
|
visible: PageController.isStartPageVisible()
|
||||||
checked: SettingsController.isLoggingEnabled
|
checked: SettingsController.isLoggingEnabled
|
||||||
onCheckedChanged: {
|
onToggled: function() {
|
||||||
if (checked !== SettingsController.isLoggingEnabled) {
|
if (checked !== SettingsController.isLoggingEnabled) {
|
||||||
SettingsController.isLoggingEnabled = checked
|
SettingsController.isLoggingEnabled = checked
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ PageType {
|
|||||||
PageController.restorePageHomeState(true)
|
PageController.restorePageHomeState(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSetupWizardProtocols)) {
|
||||||
|
PageController.goToPage(PageEnum.PageHome)
|
||||||
|
}
|
||||||
|
|
||||||
PageController.showNotificationMessage(finishedMessage)
|
PageController.showNotificationMessage(finishedMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ PageType {
|
|||||||
target: ImportController
|
target: ImportController
|
||||||
|
|
||||||
function onImportErrorOccurred(error, goToPageHome) {
|
function onImportErrorOccurred(error, goToPageHome) {
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
if (goToPageHome) {
|
if (goToPageHome) {
|
||||||
PageController.goToStartPage()
|
PageController.goToStartPage()
|
||||||
} else {
|
} else {
|
||||||
@@ -43,6 +44,7 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onImportFinished() {
|
function onImportFinished() {
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
if (!ConnectionController.isConnected) {
|
if (!ConnectionController.isConnected) {
|
||||||
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1);
|
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1);
|
||||||
ServersModel.processedIndex = ServersModel.defaultIndex
|
ServersModel.processedIndex = ServersModel.defaultIndex
|
||||||
@@ -216,6 +218,7 @@ PageType {
|
|||||||
if (cloakingCheckBoxItem.checked) {
|
if (cloakingCheckBoxItem.checked) {
|
||||||
ImportController.processNativeWireGuardConfig()
|
ImportController.processNativeWireGuardConfig()
|
||||||
}
|
}
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
ImportController.importConfig()
|
ImportController.importConfig()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,56 +29,78 @@ PageType {
|
|||||||
Xray
|
Xray
|
||||||
}
|
}
|
||||||
|
|
||||||
signal revokeConfig(int index)
|
|
||||||
onRevokeConfig: function(index) {
|
|
||||||
PageController.showBusyIndicator(true)
|
|
||||||
ExportController.revokeConfig(index,
|
|
||||||
ContainersModel.getProcessedContainerIndex(),
|
|
||||||
ServersModel.getProcessedServerCredentials())
|
|
||||||
PageController.showBusyIndicator(false)
|
|
||||||
PageController.showNotificationMessage(qsTr("Config revoked"))
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: ExportController
|
target: ExportController
|
||||||
|
|
||||||
|
function onRevokeConfigCompleted() {
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
PageController.showNotificationMessage(qsTr("Config revoked"))
|
||||||
|
}
|
||||||
|
|
||||||
function onGenerateConfig(type) {
|
function onGenerateConfig(type) {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
|
|
||||||
|
var configCaption
|
||||||
|
var configExtension
|
||||||
|
var configFileName
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PageShare.ConfigType.AmneziaConnection: {
|
case PageShare.ConfigType.AmneziaConnection: {
|
||||||
ExportController.generateConnectionConfig(clientNameTextField.textField.text);
|
ExportController.generateConnectionConfig(clientNameTextField.textField.text);
|
||||||
|
configCaption = qsTr("Save AmneziaVPN config")
|
||||||
|
configExtension = ".vpn"
|
||||||
|
configFileName = "amnezia_config"
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PageShare.ConfigType.OpenVpn: {
|
case PageShare.ConfigType.OpenVpn: {
|
||||||
ExportController.generateOpenVpnConfig(clientNameTextField.textField.text)
|
ExportController.generateOpenVpnConfig(clientNameTextField.textField.text)
|
||||||
|
configCaption = qsTr("Save OpenVPN config")
|
||||||
|
configExtension = ".ovpn"
|
||||||
|
configFileName = "amnezia_for_openvpn"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case PageShare.ConfigType.WireGuard: {
|
case PageShare.ConfigType.WireGuard: {
|
||||||
ExportController.generateWireGuardConfig(clientNameTextField.textField.text)
|
ExportController.generateWireGuardConfig(clientNameTextField.textField.text)
|
||||||
|
configCaption = qsTr("Save WireGuard config")
|
||||||
|
configExtension = ".conf"
|
||||||
|
configFileName = "amnezia_for_wireguard"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case PageShare.ConfigType.Awg: {
|
case PageShare.ConfigType.Awg: {
|
||||||
ExportController.generateAwgConfig(clientNameTextField.textField.text)
|
ExportController.generateAwgConfig(clientNameTextField.textField.text)
|
||||||
|
configCaption = qsTr("Save AmneziaWG config")
|
||||||
|
configExtension = ".conf"
|
||||||
|
configFileName = "amnezia_for_awg"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case PageShare.ConfigType.ShadowSocks: {
|
case PageShare.ConfigType.ShadowSocks: {
|
||||||
ExportController.generateShadowSocksConfig()
|
ExportController.generateShadowSocksConfig()
|
||||||
|
configCaption = qsTr("Save Shadowsocks config")
|
||||||
|
configExtension = ".json"
|
||||||
|
configFileName = "amnezia_for_shadowsocks"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case PageShare.ConfigType.Cloak: {
|
case PageShare.ConfigType.Cloak: {
|
||||||
ExportController.generateCloakConfig()
|
ExportController.generateCloakConfig()
|
||||||
|
configCaption = qsTr("Save Cloak config")
|
||||||
|
configExtension = ".json"
|
||||||
|
configFileName = "amnezia_for_cloak"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case PageShare.ConfigType.Xray: {
|
case PageShare.ConfigType.Xray: {
|
||||||
ExportController.generateXrayConfig(clientNameTextField.textField.text)
|
ExportController.generateXrayConfig(clientNameTextField.textField.text)
|
||||||
|
configCaption = qsTr("Save XRay config")
|
||||||
|
configExtension = ".json"
|
||||||
|
configFileName = "amnezia_for_xray"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
|
|
||||||
PageController.goToPage(PageEnum.PageShareConnection)
|
var headerText = qsTr("Connection to ") + serverSelector.text
|
||||||
|
var configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
|
||||||
|
PageController.goToShareConnectionPage(headerText, configContentHeaderText, configCaption, configExtension, configFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onExportErrorOccurred(error) {
|
function onExportErrorOccurred(error) {
|
||||||
@@ -581,12 +603,14 @@ PageType {
|
|||||||
visible: accessTypeSelector.currentIndex === 1
|
visible: accessTypeSelector.currentIndex === 1
|
||||||
|
|
||||||
property bool isFocusable: true
|
property bool isFocusable: true
|
||||||
|
property bool freezeFilter: false
|
||||||
|
|
||||||
model: SortFilterProxyModel {
|
model: SortFilterProxyModel {
|
||||||
id: proxyClientManagementModel
|
id: proxyClientManagementModel
|
||||||
sourceModel: ClientManagementModel
|
sourceModel: ClientManagementModel
|
||||||
filters: RegExpFilter {
|
filters: RegExpFilter {
|
||||||
roleName: "clientName"
|
roleName: "clientName"
|
||||||
|
enabled: !clientsListView.freezeFilter
|
||||||
pattern: ".*" + searchTextField.textField.text + ".*"
|
pattern: ".*" + searchTextField.textField.text + ".*"
|
||||||
caseSensitivity: Qt.CaseInsensitive
|
caseSensitivity: Qt.CaseInsensitive
|
||||||
}
|
}
|
||||||
@@ -769,12 +793,14 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (clientNameEditor.textField.text !== clientName) {
|
if (clientNameEditor.textField.text !== clientName) {
|
||||||
|
clientsListView.freezeFilter = true
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
ExportController.renameClient(index,
|
ExportController.renameClient(proxyClientManagementModel.mapToSource(index),
|
||||||
clientNameEditor.textField.text,
|
clientNameEditor.textField.text,
|
||||||
ContainersModel.getProcessedContainerIndex(),
|
ContainersModel.getProcessedContainerIndex(),
|
||||||
ServersModel.getProcessedServerCredentials())
|
ServersModel.getProcessedServerCredentials())
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
|
Qt.callLater(function(){ clientsListView.freezeFilter = false })
|
||||||
clientNameEditDrawer.closeTriggered()
|
clientNameEditDrawer.closeTriggered()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -805,7 +831,10 @@ PageType {
|
|||||||
|
|
||||||
var yesButtonFunction = function() {
|
var yesButtonFunction = function() {
|
||||||
clientInfoDrawer.closeTriggered()
|
clientInfoDrawer.closeTriggered()
|
||||||
root.revokeConfig(index)
|
PageController.showBusyIndicator(true)
|
||||||
|
ExportController.revokeConfig(proxyClientManagementModel.mapToSource(index),
|
||||||
|
ContainersModel.getProcessedContainerIndex(),
|
||||||
|
ServersModel.getProcessedServerCredentials())
|
||||||
}
|
}
|
||||||
var noButtonFunction = function() {
|
var noButtonFunction = function() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,6 @@ PageType {
|
|||||||
id: pageShareConnection
|
id: pageShareConnection
|
||||||
|
|
||||||
property string headerText
|
property string headerText
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
var serverName = ServersModel.getProcessedServerData("name") || ServersModel.getProcessedServerData("hostName") || "Server"
|
|
||||||
headerText = qsTr("Connection to ") + serverName
|
|
||||||
configContentHeaderText = qsTr("File with connection settings to ") + serverName
|
|
||||||
}
|
|
||||||
property string configContentHeaderText
|
property string configContentHeaderText
|
||||||
property string shareButtonText: qsTr("Share")
|
property string shareButtonText: qsTr("Share")
|
||||||
property string copyButtonText: qsTr("Copy")
|
property string copyButtonText: qsTr("Copy")
|
||||||
@@ -36,17 +30,17 @@ PageType {
|
|||||||
property string configCaption: qsTr("Save AmneziaVPN config")
|
property string configCaption: qsTr("Save AmneziaVPN config")
|
||||||
property string configFileName: "amnezia_config"
|
property string configFileName: "amnezia_config"
|
||||||
|
|
||||||
onVisibleChanged: {
|
// onVisibleChanged: {
|
||||||
configExtension = ".vpn"
|
// configExtension = ".vpn"
|
||||||
configCaption = qsTr("Save AmneziaVPN config")
|
// configCaption = qsTr("Save AmneziaVPN config")
|
||||||
configFileName = "amnezia_config"
|
// configFileName = "amnezia_config"
|
||||||
|
|
||||||
if (visible) {
|
// if (visible) {
|
||||||
var serverName = ServersModel.getProcessedServerData("name") || ServersModel.getProcessedServerData("hostName") || "Server"
|
// var serverName = ServersModel.getProcessedServerData("name") || ServersModel.getProcessedServerData("hostName") || "Server"
|
||||||
headerText = qsTr("Connection to ") + serverName
|
// headerText = qsTr("Connection to ") + serverName
|
||||||
configContentHeaderText = qsTr("File with connection settings to ") + serverName
|
// configContentHeaderText = qsTr("File with connection settings to ") + serverName
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
@@ -275,8 +269,9 @@ PageType {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: qrCodeContainer
|
id: qrCodeContainer
|
||||||
Layout.fillWidth: true
|
Layout.preferredWidth: Math.min(Math.min(listView.width - (Layout.leftMargin + Layout.rightMargin), pageShareConnection.height * 0.5), 360)
|
||||||
Layout.preferredHeight: width
|
Layout.preferredHeight: Layout.preferredWidth
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: 20
|
Layout.topMargin: 20
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
@@ -286,6 +281,9 @@ PageType {
|
|||||||
Image {
|
Image {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
smooth: false
|
smooth: false
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
sourceSize.width: parent.width
|
||||||
|
sourceSize.height: parent.height
|
||||||
source: pageShareConnection.isSelfHostedConfig ? (isQrCodeVisible ? ExportController.qrCodes[0] : "") : (isQrCodeVisible ? ApiConfigsController.qrCodes[0] : "")
|
source: pageShareConnection.isSelfHostedConfig ? (isQrCodeVisible ? ExportController.qrCodes[0] : "") : (isQrCodeVisible ? ApiConfigsController.qrCodes[0] : "")
|
||||||
property bool isFocusable: true
|
property bool isFocusable: true
|
||||||
Keys.onTabPressed: FocusController.nextKeyTabItem()
|
Keys.onTabPressed: FocusController.nextKeyTabItem()
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ PageType {
|
|||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
|
property string headerText: ""
|
||||||
|
property string configContentHeaderText: ""
|
||||||
|
|
||||||
anchors.top: backButton.bottom
|
anchors.top: backButton.bottom
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -104,12 +107,13 @@ PageType {
|
|||||||
clickedFunction: function() {
|
clickedFunction: function() {
|
||||||
handler()
|
handler()
|
||||||
|
|
||||||
if (serverSelector.currentIndex !== serverSelectorListView.currentIndex) {
|
if (serverSelector.currentIndex !== serverSelectorListView.selectedIndex) {
|
||||||
serverSelector.currentIndex = serverSelectorListView.currentIndex
|
serverSelector.currentIndex = serverSelectorListView.selectedIndex
|
||||||
|
serverSelector.severSelectorIndexChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
shareConnectionPage.headerText = qsTr("Accessing ") + serverSelector.text
|
listView.headerText = qsTr("Accessing ") + serverSelector.text
|
||||||
shareConnectionPage.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text
|
listView.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text
|
||||||
serverSelector.closeTriggered()
|
serverSelector.closeTriggered()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +125,7 @@ PageType {
|
|||||||
|
|
||||||
function handler() {
|
function handler() {
|
||||||
serverSelector.text = selectedText
|
serverSelector.text = selectedText
|
||||||
ServersModel.processedIndex = proxyServersModel.mapToSource(currentIndex)
|
ServersModel.processedIndex = proxyServersModel.mapToSource(selectedIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,15 +160,9 @@ PageType {
|
|||||||
|
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
|
|
||||||
PageController.goToPage(PageEnum.PageShareConnection)
|
PageController.goToShareConnectionPage(listView.headerText, listView.configContentHeaderText, "", ".vpn", "amnezia_config")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ShareConnectionDrawer {
|
|
||||||
id: shareConnectionDrawer
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,19 @@ PageType {
|
|||||||
tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition)
|
tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onGoToShareConnectionPage(headerText, configContentHeaderText, configCaption, configExtension, configFileName) {
|
||||||
|
var pagePath = PageController.getPagePath(PageEnum.PageShareConnection)
|
||||||
|
tabBarStackView.push(pagePath,
|
||||||
|
{ "objectName" : pagePath,
|
||||||
|
"headerText" : headerText,
|
||||||
|
"configContentHeaderText" : configContentHeaderText,
|
||||||
|
"configCaption" : configCaption,
|
||||||
|
"configExtension" : configExtension,
|
||||||
|
"configFileName" : configFileName
|
||||||
|
},
|
||||||
|
StackView.PushTransition)
|
||||||
|
}
|
||||||
|
|
||||||
function onDisableControls(disabled) {
|
function onDisableControls(disabled) {
|
||||||
isControlsDisabled = disabled
|
isControlsDisabled = disabled
|
||||||
}
|
}
|
||||||
@@ -367,7 +380,13 @@ PageType {
|
|||||||
objectName: "settingsTabButton"
|
objectName: "settingsTabButton"
|
||||||
|
|
||||||
isSelected: tabBar.currentIndex === 2
|
isSelected: tabBar.currentIndex === 2
|
||||||
image: "qrc:/images/controls/settings.svg"
|
image: (ServersModel.hasServersFromGatewayApi && NewsModel.hasUnread) ? "qrc:/images/controls/settings-news.svg" : "qrc:/images/controls/settings.svg"
|
||||||
|
Binding {
|
||||||
|
target: settingsTabButton
|
||||||
|
property: "defaultColor"
|
||||||
|
value: "transparent"
|
||||||
|
when: (ServersModel.hasServersFromGatewayApi && NewsModel.hasUnread)
|
||||||
|
}
|
||||||
clickedFunc: function () {
|
clickedFunc: function () {
|
||||||
tabBarStackView.goToTabBarPage(PageEnum.PageSettings)
|
tabBarStackView.goToTabBarPage(PageEnum.PageSettings)
|
||||||
tabBar.currentIndex = 2
|
tabBar.currentIndex = 2
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
APP_NAME=AmneziaVPN
|
APP_NAME=AmneziaVPN
|
||||||
|
ORG_NAME=AmneziaVPN.ORG
|
||||||
LOG_FOLDER=/var/log/$APP_NAME
|
LOG_FOLDER=/var/log/$APP_NAME
|
||||||
LOG_FILE="$LOG_FOLDER/post-uninstall.log"
|
LOG_FILE="$LOG_FOLDER/post-uninstall.log"
|
||||||
APP_PATH=/opt/$APP_NAME
|
APP_PATH=/opt/$APP_NAME
|
||||||
@@ -64,6 +65,24 @@ if test -f /usr/share/pixmaps/$APP_NAME.png; then
|
|||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
### Remove the service log file (keep post-uninstall.log)
|
||||||
|
if test -f "$LOG_FOLDER/AmneziaVPN-service.log"; then
|
||||||
|
sudo rm -f "$LOG_FOLDER/AmneziaVPN-service.log" >> $LOG_FILE 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
### Remove user logs for current user only
|
||||||
|
TARGET_HOME="$HOME"
|
||||||
|
if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
|
||||||
|
TARGET_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
|
||||||
|
fi
|
||||||
|
if test -d "$TARGET_HOME/.local/share/$ORG_NAME/$APP_NAME/log"; then
|
||||||
|
rm -rf "$TARGET_HOME/.local/share/$ORG_NAME/$APP_NAME/log" >> $LOG_FILE 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to remove empty app and organization directories under user share
|
||||||
|
if rmdir "$TARGET_HOME/.local/share/$ORG_NAME/$APP_NAME" 2>/dev/null; then :; fi
|
||||||
|
if rmdir "$TARGET_HOME/.local/share/$ORG_NAME" 2>/dev/null; then :; fi
|
||||||
|
|
||||||
if command -v steamos-readonly &> /dev/null; then
|
if command -v steamos-readonly &> /dev/null; then
|
||||||
sudo steamos-readonly enable >> $LOG_FILE
|
sudo steamos-readonly enable >> $LOG_FILE
|
||||||
echo "steamos-readonly enabled" >> $LOG_FILE
|
echo "steamos-readonly enabled" >> $LOG_FILE
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
set AmneziaPath=%~dp0
|
set AmneziaPath=%~dp0
|
||||||
echo %AmneziaPath%
|
echo %AmneziaPath%
|
||||||
|
|
||||||
"%AmneziaPath%\AmneziaVPN.exe" -c
|
rem Define directories for logs
|
||||||
|
set "ORG_DIR=%AppData%\AmneziaVPN.ORG"
|
||||||
|
set "USER_APP_DIR=%ORG_DIR%\AmneziaVPN"
|
||||||
|
set "USER_LOG_DIR=%USER_APP_DIR%\log"
|
||||||
|
set "SYS_APP_DIR=%ProgramData%\AmneziaVPN"
|
||||||
|
set "SYS_LOG_DIR=%SYS_APP_DIR%\log"
|
||||||
|
set "SYS_LOG_FILE=%SYS_LOG_DIR%\AmneziaVPN-service.log"
|
||||||
|
|
||||||
timeout /t 1
|
timeout /t 1
|
||||||
sc stop AmneziaVPN-service
|
sc stop AmneziaVPN-service
|
||||||
sc delete AmneziaVPN-service
|
sc delete AmneziaVPN-service
|
||||||
@@ -9,4 +16,17 @@ sc stop AmneziaWGTunnel$AmneziaVPN
|
|||||||
sc delete AmneziaWGTunnel$AmneziaVPN
|
sc delete AmneziaWGTunnel$AmneziaVPN
|
||||||
taskkill /IM "AmneziaVPN-service.exe" /F
|
taskkill /IM "AmneziaVPN-service.exe" /F
|
||||||
taskkill /IM "AmneziaVPN.exe" /F
|
taskkill /IM "AmneziaVPN.exe" /F
|
||||||
|
|
||||||
|
rem Delete the service log file under ProgramData
|
||||||
|
if exist "%SYS_LOG_FILE%" del /F /Q "%SYS_LOG_FILE%"
|
||||||
|
if exist "%SYS_LOG_DIR%" rmdir /S /Q "%SYS_LOG_DIR%"
|
||||||
|
rem Try to remove application dir if empty
|
||||||
|
rd "%SYS_APP_DIR%" 2>nul
|
||||||
|
|
||||||
|
rem Delete client logs under current user's AppData\Roaming (Organization\Application)
|
||||||
|
if exist "%USER_LOG_DIR%" rmdir /S /Q "%USER_LOG_DIR%"
|
||||||
|
rem Try to remove app and org directories if empty
|
||||||
|
rd "%USER_APP_DIR%" 2>nul
|
||||||
|
rd "%ORG_DIR%" 2>nul
|
||||||
|
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
set AmneziaPath=%~dp0
|
set AmneziaPath=%~dp0
|
||||||
echo %AmneziaPath%
|
echo %AmneziaPath%
|
||||||
|
|
||||||
"%AmneziaPath%\AmneziaVPN.exe" -c
|
rem Define directories for logs
|
||||||
|
set "ORG_DIR=%AppData%\AmneziaVPN.ORG"
|
||||||
|
set "USER_APP_DIR=%ORG_DIR%\AmneziaVPN"
|
||||||
|
set "USER_LOG_DIR=%USER_APP_DIR%\log"
|
||||||
|
set "SYS_APP_DIR=%ProgramData%\AmneziaVPN"
|
||||||
|
set "SYS_LOG_DIR=%SYS_APP_DIR%\log"
|
||||||
|
set "SYS_LOG_FILE=%SYS_LOG_DIR%\AmneziaVPN-service.log"
|
||||||
|
|
||||||
timeout /t 1
|
timeout /t 1
|
||||||
sc stop AmneziaVPN-service
|
sc stop AmneziaVPN-service
|
||||||
sc delete AmneziaVPN-service
|
sc delete AmneziaVPN-service
|
||||||
@@ -9,4 +16,17 @@ sc stop AmneziaWGTunnel$AmneziaVPN
|
|||||||
sc delete AmneziaWGTunnel$AmneziaVPN
|
sc delete AmneziaWGTunnel$AmneziaVPN
|
||||||
taskkill /IM "AmneziaVPN-service.exe" /F
|
taskkill /IM "AmneziaVPN-service.exe" /F
|
||||||
taskkill /IM "AmneziaVPN.exe" /F
|
taskkill /IM "AmneziaVPN.exe" /F
|
||||||
|
|
||||||
|
rem Delete the service log file under ProgramData
|
||||||
|
if exist "%SYS_LOG_FILE%" del /F /Q "%SYS_LOG_FILE%"
|
||||||
|
if exist "%SYS_LOG_DIR%" rmdir /S /Q "%SYS_LOG_DIR%"
|
||||||
|
rem Try to remove application dir if empty
|
||||||
|
rd "%SYS_APP_DIR%" 2>nul
|
||||||
|
|
||||||
|
rem Delete client logs under current user's AppData\Roaming (Organization\Application)
|
||||||
|
if exist "%USER_LOG_DIR%" rmdir /S /Q "%USER_LOG_DIR%"
|
||||||
|
rem Try to remove app and org directories if empty
|
||||||
|
rd "%USER_APP_DIR%" 2>nul
|
||||||
|
rd "%ORG_DIR%" 2>nul
|
||||||
|
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|||||||
@@ -36,5 +36,6 @@ class IpcInterface
|
|||||||
SLOT( bool enablePeerTraffic( const QJsonObject &configStr) );
|
SLOT( bool enablePeerTraffic( const QJsonObject &configStr) );
|
||||||
SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) );
|
SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) );
|
||||||
SLOT( bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) );
|
SLOT( bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) );
|
||||||
|
SLOT( bool restoreResolvers() );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,10 @@ bool IpcServer::updateResolvers(const QString &ifname, const QList<QHostAddress>
|
|||||||
return Router::updateResolvers(ifname, resolvers);
|
return Router::updateResolvers(ifname, resolvers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IpcServer::restoreResolvers() {
|
||||||
|
return Router::restoreResolvers();
|
||||||
|
}
|
||||||
|
|
||||||
void IpcServer::StartRoutingIpv6()
|
void IpcServer::StartRoutingIpv6()
|
||||||
{
|
{
|
||||||
Router::StartRoutingIpv6();
|
Router::StartRoutingIpv6();
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public:
|
|||||||
virtual bool disableKillSwitch() override;
|
virtual bool disableKillSwitch() override;
|
||||||
virtual bool refreshKillSwitch( bool enabled ) override;
|
virtual bool refreshKillSwitch( bool enabled ) override;
|
||||||
virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) override;
|
virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) override;
|
||||||
|
virtual bool restoreResolvers() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_localpid = 0;
|
int m_localpid = 0;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ void IpcProcessTun2Socks::start()
|
|||||||
QString XrayConStr = "socks5://127.0.0.1:10808";
|
QString XrayConStr = "socks5://127.0.0.1:10808";
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
QStringList arguments({"-device", "tun://tun2", "-proxy", XrayConStr, "-tun-post-up",
|
QStringList arguments({"-device", "tun://tun2?guid={081A8A84-8D12-4DF5-B8C4-396D5B0053E4}", "-proxy", XrayConStr, "-tun-post-up",
|
||||||
QString("cmd /c netsh interface ip set address name=\"tun2\" static %1 255.255.255.255")
|
QString("cmd /c netsh interface ip set address name=\"tun2\" static %1 255.255.255.255")
|
||||||
.arg(amnezia::protocols::xray::defaultLocalAddr)});
|
.arg(amnezia::protocols::xray::defaultLocalAddr)});
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -99,6 +99,17 @@ bool Router::updateResolvers(const QString& ifname, const QList<QHostAddress>& r
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Router::restoreResolvers() {
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
return RouterLinux::Instance().restoreResolvers();
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
return RouterMac::Instance().restoreResolvers();
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
return RouterWin::Instance().restoreResolvers();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void Router::StopRoutingIpv6()
|
void Router::StopRoutingIpv6()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public:
|
|||||||
static void StartRoutingIpv6();
|
static void StartRoutingIpv6();
|
||||||
static void StopRoutingIpv6();
|
static void StopRoutingIpv6();
|
||||||
static bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers);
|
static bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers);
|
||||||
|
static bool restoreResolvers();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ROUTER_H
|
#endif // ROUTER_H
|
||||||
|
|||||||
@@ -279,6 +279,10 @@ bool RouterLinux::updateResolvers(const QString& ifname, const QList<QHostAddres
|
|||||||
return m_dnsUtil->updateResolvers(ifname, resolvers);
|
return m_dnsUtil->updateResolvers(ifname, resolvers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RouterLinux::restoreResolvers() {
|
||||||
|
return m_dnsUtil->restoreResolvers();
|
||||||
|
}
|
||||||
|
|
||||||
void RouterLinux::StartRoutingIpv6()
|
void RouterLinux::StartRoutingIpv6()
|
||||||
{
|
{
|
||||||
QProcess process;
|
QProcess process;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public:
|
|||||||
void StartRoutingIpv6();
|
void StartRoutingIpv6();
|
||||||
void StopRoutingIpv6();
|
void StopRoutingIpv6();
|
||||||
bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers);
|
bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers);
|
||||||
|
bool restoreResolvers();
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user