mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-14 06:14:01 +03:00
Compare commits
38 Commits
fix/async-
...
feature/lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be4df3594b | ||
|
|
5c8c143833 | ||
|
|
24f545d001 | ||
|
|
2b0d86c626 | ||
|
|
fe773a108e | ||
|
|
a0997156f6 | ||
|
|
9a6e975622 | ||
|
|
78f09634a4 | ||
|
|
be692001b0 | ||
|
|
850b8ea03b | ||
|
|
c645d07a87 | ||
|
|
7f8786720e | ||
|
|
25c70b5bf6 | ||
|
|
7bf16f075c | ||
|
|
6518d4866e | ||
|
|
4c2010244b | ||
|
|
e946ee2430 | ||
|
|
5fab8363e7 | ||
|
|
35c2e1564b | ||
|
|
ca5bca085b | ||
|
|
33b2a3d2fc | ||
|
|
917f5858a8 | ||
|
|
e98c11079a | ||
|
|
84d908d4d8 | ||
|
|
412e69af9b | ||
|
|
4492b0af7e | ||
|
|
806b1d75af | ||
|
|
9740e7557a | ||
|
|
5ab82e2196 | ||
|
|
ae44de7101 | ||
|
|
2be6079e21 | ||
|
|
2a653f8876 | ||
|
|
41ab51a5ef | ||
|
|
300558c33c | ||
|
|
774deb87f5 | ||
|
|
bae7fbd222 | ||
|
|
97e4b95673 | ||
|
|
2ae97c5cda |
12
.github/workflows/deploy.yml
vendored
12
.github/workflows/deploy.yml
vendored
@@ -88,7 +88,7 @@ jobs:
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
arch: 'linux_gcc_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
@@ -201,7 +201,7 @@ jobs:
|
||||
host: 'windows'
|
||||
target: 'desktop'
|
||||
arch: 'win64_msvc2022_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
@@ -365,7 +365,7 @@ jobs:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia qthttpserver qtwebsockets'
|
||||
arch: 'clang_64'
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: 'true'
|
||||
@@ -377,7 +377,7 @@ jobs:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'ios'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia qthttpserver qtwebsockets'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
@@ -506,7 +506,7 @@ jobs:
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
arch: 'clang_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
@@ -739,7 +739,7 @@ jobs:
|
||||
ANDROID_PLATFORM: android-28
|
||||
NDK_VERSION: 27.0.11718014
|
||||
QT_VERSION: 6.10.3
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools qthttpserver qtwebsockets'
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -10,7 +10,9 @@ deploy/build_64/*
|
||||
winbuild*.bat
|
||||
.cache/
|
||||
.vscode/
|
||||
|
||||
.cursorignore
|
||||
.cursor/
|
||||
.venv/
|
||||
|
||||
# Qt-es
|
||||
/.qmake.cache
|
||||
|
||||
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.2)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.1)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2123)
|
||||
set(APP_ANDROID_VERSION_CODE 2122)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -14,6 +14,10 @@ set(PACKAGES
|
||||
Core5Compat Concurrent LinguistTools
|
||||
)
|
||||
|
||||
if(NOT ANDROID AND NOT IOS)
|
||||
list(APPEND PACKAGES HttpServer)
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMAND git rev-parse --short HEAD
|
||||
@@ -47,6 +51,10 @@ set(LIBS ${LIBS}
|
||||
Qt6::Core5Compat Qt6::Concurrent
|
||||
)
|
||||
|
||||
if(NOT ANDROID AND NOT IOS)
|
||||
list(APPEND LIBS Qt6::HttpServer)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(LIBS ${LIBS} Qt6::Widgets)
|
||||
endif()
|
||||
|
||||
@@ -22,7 +22,6 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshExecutor.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/serversController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.h
|
||||
@@ -100,7 +99,6 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshExecutor.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/serversController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.cpp
|
||||
@@ -203,6 +201,11 @@ file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/configurat
|
||||
file(GLOB_RECURSE CORE_MODELS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.h)
|
||||
file(GLOB_RECURSE CORE_MODELS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.cpp)
|
||||
|
||||
if(NOT ANDROID AND NOT IOS)
|
||||
file(GLOB LOCAL_PROXY_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/local-proxy/*.h)
|
||||
file(GLOB LOCAL_PROXY_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/local-proxy/*.cpp)
|
||||
endif()
|
||||
|
||||
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/models/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
|
||||
@@ -248,6 +251,11 @@ set(SOURCES ${SOURCES}
|
||||
${UI_CONTROLLERS_CPP}
|
||||
)
|
||||
|
||||
if(NOT ANDROID AND NOT IOS)
|
||||
list(APPEND HEADERS ${LOCAL_PROXY_H})
|
||||
list(APPEND SOURCES ${LOCAL_PROXY_CPP})
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolWindows.h
|
||||
|
||||
@@ -49,92 +49,14 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
|
||||
{
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::isConnectionSupported(const QString &serverId) const
|
||||
{
|
||||
if (serverId.isEmpty()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
if (serverConfigUtils::isLegacyApiSubscription(m_serversRepository->serverKind(serverId))) {
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
}
|
||||
|
||||
DockerContainer container = DockerContainer::None;
|
||||
const ErrorCode errorCode = defaultContainerForServer(serverId, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
return ErrorCode::NoInstalledContainersError;
|
||||
}
|
||||
|
||||
if (ContainerUtils::isUnsupportedContainer(container)) {
|
||||
return ErrorCode::LegacyContainerNotSupportedError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
{
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
ContainerConfig containerConfigModel;
|
||||
QPair<QString, QString> dns;
|
||||
QString hostName;
|
||||
@@ -198,6 +120,10 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
|
||||
containerConfigModel, container);
|
||||
|
||||
@@ -214,6 +140,13 @@ ErrorCode ConnectionController::openConnection(const QString &serverId)
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->isLocalProxyHttpEnabled()) {
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
emit localProxyStoppedBecauseVpnTurnedOn(tr("Local proxy stopped because VPN was turned on"));
|
||||
}
|
||||
#endif
|
||||
|
||||
emit openConnectionRequested(serverId, container, vpnConfiguration);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -34,8 +34,6 @@ public:
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container);
|
||||
|
||||
ErrorCode isConnectionSupported(const QString &serverId) const;
|
||||
|
||||
ErrorCode openConnection(const QString &serverId);
|
||||
|
||||
void closeConnection();
|
||||
@@ -69,14 +67,13 @@ signals:
|
||||
void closeConnectionRequested();
|
||||
void setConnectionStateRequested(Vpn::ConnectionState state);
|
||||
void killSwitchModeChangedRequested(bool enabled);
|
||||
void localProxyStoppedBecauseVpnTurnedOn(const QString &message);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void restoreConnectionRequested();
|
||||
#endif
|
||||
|
||||
private:
|
||||
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
VpnConnection* m_vpnConnection;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "coreController.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDirIterator>
|
||||
#include <QDebug>
|
||||
#include <QTranslator>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
@@ -35,6 +38,10 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
||||
initAppleController();
|
||||
initLogging();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
initLocalProxy();
|
||||
#endif
|
||||
|
||||
m_translator = new QTranslator(this);
|
||||
if (m_appSettingsRepository) {
|
||||
updateTranslator(m_appSettingsRepository->getAppLanguage());
|
||||
@@ -48,6 +55,69 @@ void CoreController::setQmlContextProperty(const QString &name, QObject *value)
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void CoreController::initLocalProxy()
|
||||
{
|
||||
constexpr quint16 kLocalProxyApiPort = 49490;
|
||||
|
||||
m_proxyServer.reset(new ProxyServer(m_serversRepository, m_appSettingsRepository, this));
|
||||
|
||||
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->isLocalProxyHttpEnabled()) {
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
}
|
||||
});
|
||||
|
||||
auto syncLocalProxy = [this]() {
|
||||
if (!m_proxyServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool httpEnabled = m_appSettingsRepository->isLocalProxyHttpEnabled();
|
||||
|
||||
if (!httpEnabled) {
|
||||
qInfo() << "Local proxy: HTTP API disabled";
|
||||
m_proxyServer->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_proxyServer->start(kLocalProxyApiPort)) {
|
||||
qWarning() << "Local proxy: failed to start on port" << kLocalProxyApiPort;
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
emit m_appSettingsRepository->localProxyStartFailed(tr("Local proxy failed to start. Check if the port is available."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_proxyServer->syncSettings()) {
|
||||
qWarning() << "Local proxy: failed to start proxy core (Xray)";
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
emit m_appSettingsRepository->localProxyStartFailed(tr("Couldn’t start the proxy due to an internal error. Try restarting the app."));
|
||||
return;
|
||||
}
|
||||
|
||||
qInfo() << "Local proxy: running on 127.0.0.1:" << kLocalProxyApiPort;
|
||||
};
|
||||
|
||||
syncLocalProxy();
|
||||
|
||||
connect(m_appSettingsRepository, &SecureAppSettingsRepository::localProxySettingsChanged, this, syncLocalProxy);
|
||||
|
||||
connect(m_serversRepository, &SecureServersRepository::serverEdited, this, [this](const QString &serverId) {
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->isLocalProxyHttpEnabled()
|
||||
&& m_appSettingsRepository->localProxyOwnerId() == serverId) {
|
||||
m_appSettingsRepository->bumpLocalProxyRestartToken();
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_serversRepository, &SecureServersRepository::serverRemoved, this, [this](const QString &serverId, int) {
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->localProxyOwnerId() == serverId) {
|
||||
m_appSettingsRepository->setLocalProxyOwnerId(QString());
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
void CoreController::initModels()
|
||||
{
|
||||
m_containersModel = new ContainersModel(this);
|
||||
@@ -191,12 +261,17 @@ void CoreController::initControllers()
|
||||
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
|
||||
setQmlContextProperty("LanguageUiController", m_languageUiController);
|
||||
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
|
||||
setQmlContextProperty("SettingsController", m_settingsUiController);
|
||||
|
||||
m_pageController = new PageController(m_serversController, m_settingsController, this);
|
||||
setQmlContextProperty("PageController", m_pageController);
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
connect(m_connectionController, &ConnectionController::localProxyStoppedBecauseVpnTurnedOn, m_pageController,
|
||||
&PageController::showNotificationMessage);
|
||||
#endif
|
||||
|
||||
m_serversUiController = new ServersUiController(m_serversController, m_settingsController, m_serversModel, m_containersModel, m_defaultServerContainersModel, this);
|
||||
setQmlContextProperty("ServersUiController", m_serversUiController);
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
#include "ui/models/newsModel.h"
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "core/local-proxy/proxyserver.h"
|
||||
#include "ui/utils/notificationHandler.h"
|
||||
#endif
|
||||
|
||||
@@ -142,6 +143,9 @@ private:
|
||||
void initAppleController();
|
||||
void initLogging();
|
||||
void initSignalHandlers();
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void initLocalProxy();
|
||||
#endif
|
||||
void setQmlContextProperty(const QString &name, QObject *value);
|
||||
|
||||
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
||||
@@ -227,6 +231,10 @@ private:
|
||||
TelemtConfigModel* m_telemtConfigModel;
|
||||
|
||||
CoreSignalHandlers* m_signalHandlers;
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
QScopedPointer<ProxyServer> m_proxyServer;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // CORECONTROLLER_H
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/selfhosted/sshExecutor.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/controllers/coreController.h"
|
||||
@@ -34,6 +33,7 @@
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/controllers/api/apiNewsUiController.h"
|
||||
#include "ui/models/api/apiCountryModel.h"
|
||||
#include "ui/models/containersModel.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
@@ -145,9 +145,7 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
|
||||
[this](const QString &serverId, int row, DockerContainer container) {
|
||||
SshExecutor::instance().run(serverId, [this, serverId, row, container]() {
|
||||
m_coreController->m_usersController->revokeClient(serverId, row, container);
|
||||
});
|
||||
m_coreController->m_usersController->revokeClient(serverId, row, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this,
|
||||
[this](const QString &serverId, int row, const QString &clientName, DockerContainer container) {
|
||||
@@ -158,17 +156,15 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
void CoreSignalHandlers::initImportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
|
||||
if (m_coreController->m_connectionUiController->isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
if (!m_coreController->m_connectionController->isConnected()) {
|
||||
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -180,14 +176,17 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
if (processedServerId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray availableCountries;
|
||||
QString serverCountryCode;
|
||||
|
||||
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
|
||||
if (!apiV2.has_value()) {
|
||||
return;
|
||||
if (apiV2.has_value()) {
|
||||
availableCountries = apiV2->apiConfig.availableCountries;
|
||||
serverCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
}
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
|
||||
apiV2->apiConfig.serverCountryCode);
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -205,9 +204,7 @@ void CoreSignalHandlers::initAdminConfigRevokedHandler()
|
||||
{
|
||||
connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this,
|
||||
[this](const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container) {
|
||||
SshExecutor::instance().run(serverId, [this, serverId, containerConfig, container]() {
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
});
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this,
|
||||
@@ -240,16 +237,13 @@ void CoreSignalHandlers::initLanguageHandler()
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
|
||||
});
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::appLanguageChanged, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->onAppLanguageChanged(m_coreController->m_settingsController->getAppLanguage());
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAutoConnectHandler()
|
||||
{
|
||||
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
|
||||
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,9 +348,6 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
|
||||
{
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initStrictKillSwitchHandler()
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/selfhosted/sshExecutor.h"
|
||||
#include "core/installers/awgInstaller.h"
|
||||
#include "core/installers/installerBase.h"
|
||||
#include "core/installers/openvpnInstaller.h"
|
||||
@@ -73,16 +72,6 @@ namespace
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString buildRemoveContainerScript(const amnezia::ScriptVars &vars, bool removeDataVolume)
|
||||
{
|
||||
QString script = SshSession::replaceVars(amnezia::scriptData(SharedScriptType::remove_container), vars);
|
||||
if (removeDataVolume) {
|
||||
script += QLatin1String("\nsudo docker volume rm -f $CONTAINER_NAME-data 2>/dev/null || true");
|
||||
script = SshSession::replaceVars(script, vars);
|
||||
}
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
InstallController::InstallController(SecureServersRepository *serversRepository,
|
||||
@@ -104,7 +93,7 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
bool isUpdate)
|
||||
{
|
||||
qDebug().noquote() << "InstallController::setupContainer" << ContainerUtils::containerToString(container);
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
|
||||
e = isUserInSudo(credentials, sshSession);
|
||||
@@ -131,10 +120,14 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return e;
|
||||
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
|
||||
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
const bool removeDataVolume = !isUpdate && (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
if (!isUpdate) {
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
}
|
||||
sshSession.runScript(credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
||||
removeContainerVars));
|
||||
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
|
||||
|
||||
qDebug().noquote() << "buildContainerWorker start";
|
||||
@@ -159,8 +152,8 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return startupContainerWorker(credentials, container, config, sshSession);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
{
|
||||
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
@@ -169,11 +162,11 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
||||
}
|
||||
if (container == DockerContainer::MtProxy) {
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
} else if (container == DockerContainer::Telemt) {
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
}
|
||||
adminConfig->updateContainerConfig(container, newConfig);
|
||||
@@ -189,10 +182,10 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
|
||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
|
||||
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||
|
||||
bool xrayServerSettingsChanged = false;
|
||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||
@@ -220,11 +213,11 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
||||
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
|
||||
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
|
||||
XrayConfigurator xrayConfigurator(&sshSession);
|
||||
qDebug() << "InstallController::updateServerConfig applying Xray server inbound sync, reinstall="
|
||||
qDebug() << "InstallController::updateContainer applying Xray server inbound sync, reinstall="
|
||||
<< reinstallRequired;
|
||||
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
|
||||
qDebug() << "InstallController::updateContainer Xray inbound sync failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
}
|
||||
}
|
||||
@@ -243,41 +236,6 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig)
|
||||
{
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
auto config = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
auto config = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedUser);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
auto config = m_serversRepository->nativeConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::Native);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
|
||||
{
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
||||
@@ -373,7 +331,7 @@ ErrorCode InstallController::validateAndPrepareConfig(const QString &serverId)
|
||||
|
||||
void InstallController::validateConfig(const QString &serverId)
|
||||
{
|
||||
QFuture<ErrorCode> future = SshExecutor::instance().run(serverId, [this, serverId]() {
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([this, serverId]() {
|
||||
return validateAndPrepareConfig(serverId);
|
||||
});
|
||||
|
||||
@@ -971,7 +929,7 @@ ErrorCode InstallController::rebootServer(const QString &serverId)
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
|
||||
QString script = QString("sudo reboot");
|
||||
|
||||
@@ -999,7 +957,7 @@ ErrorCode InstallController::removeAllContainers(const QString &serverId)
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
ErrorCode errorCode = sshSession.runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
@@ -1021,12 +979,13 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
SshSession sshSession(this);
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
ErrorCode errorCode =
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
ErrorCode errorCode = sshSession.runScript(
|
||||
credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
|
||||
@@ -1130,7 +1089,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
@@ -1173,7 +1132,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
|
||||
ErrorCode InstallController::installServer(const ServerCredentials &credentials, DockerContainer container, int port,
|
||||
TransportProto transportProto, bool &wasContainerInstalled)
|
||||
{
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
if (errorCode) {
|
||||
@@ -1242,7 +1201,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
@@ -1284,7 +1243,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
ErrorCode InstallController::checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback)
|
||||
{
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) {
|
||||
@@ -1504,7 +1463,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = containerAndPortMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
if (container == DockerContainer::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1529,7 +1488,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = torOrDnsRegMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
if (container == DockerContainer::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1565,7 +1524,7 @@ ErrorCode InstallController::setDockerContainerEnabledState(const QString &serve
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
const QString script = enabled ? QStringLiteral("sudo docker start %1").arg(containerName)
|
||||
: QStringLiteral("sudo docker stop %1").arg(containerName);
|
||||
const ErrorCode runError = sshSession.runScript(credentials, script);
|
||||
@@ -1605,7 +1564,7 @@ ErrorCode InstallController::queryDockerContainerStatus(const QString &serverId,
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
const QString script = QStringLiteral(
|
||||
"sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'")
|
||||
.arg(containerName);
|
||||
@@ -1639,7 +1598,7 @@ ErrorCode InstallController::queryMtProxyDiagnostics(const QString &serverId, Do
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
return MtProxyInstaller::queryDiagnostics(sshSession, credentials, container, listenPort, out);
|
||||
}
|
||||
|
||||
@@ -1662,7 +1621,7 @@ QString InstallController::fetchDockerContainerSecret(const QString &serverId, D
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
const QString path = QStringLiteral("/data/secret");
|
||||
const QString cmd = QStringLiteral("sudo docker exec %1 cat %2").arg(containerName, path);
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, cmd, cbReadStdOut);
|
||||
|
||||
@@ -34,12 +34,7 @@ public:
|
||||
~InstallController();
|
||||
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
|
||||
|
||||
// Updates server-side container settings (admin self-hosted only): reconfigures the container over SSH.
|
||||
ErrorCode updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// Updates client-local settings only: rewrites the stored container config for any self-hosted/native server. No SSH.
|
||||
ErrorCode updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig);
|
||||
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode rebootServer(const QString &serverId);
|
||||
ErrorCode removeAllContainers(const QString &serverId);
|
||||
|
||||
@@ -8,10 +8,19 @@
|
||||
#include "version.h"
|
||||
#include "ui/utils/qAutoStart.h"
|
||||
#include "logger.h"
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "core/local-proxy/portavailabilityhelper.h"
|
||||
#endif
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr int kDefaultProxyPort = 10808;
|
||||
constexpr int kLocalProxyPortMin = 1024;
|
||||
constexpr int kLocalProxyPortMax = 65535;
|
||||
}
|
||||
|
||||
QString getPlatformName()
|
||||
{
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
@@ -38,6 +47,11 @@ SettingsController::SettingsController(SecureServersRepository* serversRepositor
|
||||
{
|
||||
m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH);
|
||||
m_isDevModeEnabled = m_appSettingsRepository->isDevGatewayEnv();
|
||||
|
||||
connect(m_appSettingsRepository, &SecureAppSettingsRepository::localProxySettingsChanged, this,
|
||||
&SettingsController::localProxySettingsUpdated);
|
||||
connect(m_appSettingsRepository, &SecureAppSettingsRepository::localProxyStartFailed, this,
|
||||
&SettingsController::localProxyStartFailed);
|
||||
}
|
||||
|
||||
void SettingsController::toggleAmneziaDns(bool enable)
|
||||
@@ -366,3 +380,114 @@ QString SettingsController::nextAvailableServerName() const
|
||||
return m_serversRepository->nextAvailableServerName();
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxySupported() const
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxyHttpEnabled() const
|
||||
{
|
||||
return m_appSettingsRepository->isLocalProxyHttpEnabled();
|
||||
}
|
||||
|
||||
int SettingsController::localProxyPort() const
|
||||
{
|
||||
return static_cast<int>(m_appSettingsRepository->localProxyPort());
|
||||
}
|
||||
|
||||
QString SettingsController::localProxyOwnerId() const
|
||||
{
|
||||
return m_appSettingsRepository->localProxyOwnerId();
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxyPortUserDefined() const
|
||||
{
|
||||
return m_appSettingsRepository->isLocalProxyPortUserDefined();
|
||||
}
|
||||
|
||||
bool SettingsController::setLocalProxyPort(int port)
|
||||
{
|
||||
if (port < kLocalProxyPortMin || port > kLocalProxyPortMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_appSettingsRepository->localProxyPort() == static_cast<quint16>(port)) {
|
||||
m_appSettingsRepository->setLocalProxyPortUserDefined(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_appSettingsRepository->setLocalProxyPort(static_cast<quint16>(port));
|
||||
m_appSettingsRepository->setLocalProxyPortUserDefined(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxyPortBusy(int port) const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
return !PortAvailabilityHelper::isPortAvailable(port);
|
||||
#else
|
||||
Q_UNUSED(port);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
int SettingsController::findFirstAvailableLocalProxyPort(int startPort) const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
const auto port = PortAvailabilityHelper::findFirstAvailablePort(startPort, kLocalProxyPortMax);
|
||||
return port ? *port : -1;
|
||||
#else
|
||||
Q_UNUSED(startPort);
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SettingsController::enableLocalProxy(const QString &ownerId, int port)
|
||||
{
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
Q_UNUSED(ownerId);
|
||||
Q_UNUSED(port);
|
||||
return false;
|
||||
#else
|
||||
if (port < kLocalProxyPortMin || port > kLocalProxyPortMax || ownerId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_appSettingsRepository->isLocalProxyHttpEnabled() && m_appSettingsRepository->localProxyOwnerId() != ownerId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int selectedPort = port;
|
||||
|
||||
const bool isUserDefinedPort = m_appSettingsRepository->isLocalProxyPortUserDefined();
|
||||
if (isUserDefinedPort) {
|
||||
if (!PortAvailabilityHelper::isPortAvailable(selectedPort)) {
|
||||
return false;
|
||||
}
|
||||
} else if (selectedPort != kDefaultProxyPort && !PortAvailabilityHelper::isPortAvailable(selectedPort)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_appSettingsRepository->localProxyPort() != static_cast<quint16>(selectedPort)) {
|
||||
m_appSettingsRepository->setLocalProxyPort(static_cast<quint16>(selectedPort));
|
||||
}
|
||||
m_appSettingsRepository->setLocalProxyPortUserDefined(isUserDefinedPort);
|
||||
|
||||
m_appSettingsRepository->setLocalProxyOwnerId(ownerId);
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(true);
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SettingsController::disableLocalProxy()
|
||||
{
|
||||
if (m_appSettingsRepository->isLocalProxyHttpEnabled()) {
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,18 @@ public:
|
||||
QLocale getAppLanguage() const;
|
||||
void setAppLanguage(const QLocale &locale);
|
||||
|
||||
// Local proxy
|
||||
bool isLocalProxySupported() const;
|
||||
bool isLocalProxyHttpEnabled() const;
|
||||
int localProxyPort() const;
|
||||
QString localProxyOwnerId() const;
|
||||
bool isLocalProxyPortUserDefined() const;
|
||||
bool setLocalProxyPort(int port);
|
||||
bool isLocalProxyPortBusy(int port) const;
|
||||
int findFirstAvailableLocalProxyPort(int startPort) const;
|
||||
bool enableLocalProxy(const QString &ownerId, int port);
|
||||
void disableLocalProxy();
|
||||
|
||||
signals:
|
||||
void siteSplitTunnelingRouteModeChanged(RouteMode mode);
|
||||
void siteSplitTunnelingToggled(bool enabled);
|
||||
@@ -96,6 +108,9 @@ signals:
|
||||
void appSplitTunnelingToggled(bool enabled);
|
||||
void appSplitTunnelingClearAppsList();
|
||||
|
||||
void localProxySettingsUpdated();
|
||||
void localProxyStartFailed(const QString &message);
|
||||
|
||||
private:
|
||||
QString getPlatform() const;
|
||||
|
||||
|
||||
444
client/core/local-proxy/configmanager.cpp
Normal file
444
client/core/local-proxy/configmanager.cpp
Normal file
@@ -0,0 +1,444 @@
|
||||
#include "configmanager.h"
|
||||
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "portavailabilityhelper.h"
|
||||
#include "proxylogger.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
#include <QSaveFile>
|
||||
#include <QSysInfo>
|
||||
#include <QStandardPaths>
|
||||
#include <QUuid>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
ConfigManager::ConfigManager(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository)
|
||||
: m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
ProxyLogger::getInstance().debug("ConfigManager initialized (repository-backed)");
|
||||
}
|
||||
|
||||
namespace {
|
||||
namespace gateway_key {
|
||||
constexpr char vless[] = "vless";
|
||||
} // namespace gateway_key
|
||||
|
||||
constexpr quint16 kDefaultProxyPort = 10808;
|
||||
constexpr int kProxyPortMin = 1024;
|
||||
constexpr int kProxyPortMax = 65535;
|
||||
|
||||
int resolveProxyPort(SecureAppSettingsRepository *appSettings)
|
||||
{
|
||||
if (!appSettings) {
|
||||
return kDefaultProxyPort;
|
||||
}
|
||||
|
||||
const quint16 port = appSettings->localProxyPort();
|
||||
if (port < kProxyPortMin || port > kProxyPortMax) {
|
||||
return kDefaultProxyPort;
|
||||
}
|
||||
|
||||
return static_cast<int>(port);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ConfigManager::applyProxyPortToConfig(QJsonObject &config, int port) const
|
||||
{
|
||||
if (!config.contains("inbounds") || !config.value("inbounds").isArray()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonArray inbounds = config.value("inbounds").toArray();
|
||||
if (inbounds.isEmpty() || !inbounds.at(0).isObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject firstInbound = inbounds.at(0).toObject();
|
||||
firstInbound.insert("port", port);
|
||||
inbounds[0] = firstInbound;
|
||||
config.insert("inbounds", inbounds);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ConfigManager::serializeConfig(const QJsonObject &config) const
|
||||
{
|
||||
return QString::fromUtf8(QJsonDocument(config).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
std::optional<ConfigManager::ConfigData> ConfigManager::buildConfig(QString &errorDescription) const
|
||||
{
|
||||
errorDescription.clear();
|
||||
|
||||
if (!m_serversRepository || !m_appSettingsRepository) {
|
||||
const QString message = QStringLiteral("Local proxy repositories are not available");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QString ownerId = m_appSettingsRepository->localProxyOwnerId();
|
||||
if (ownerId.isEmpty()) {
|
||||
const QString message = QStringLiteral("Local proxy owner server id is not configured");
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto ownerServer = m_serversRepository->serverJsonById(ownerId);
|
||||
if (!ownerServer) {
|
||||
const QString message = QStringLiteral("Owner server with id %1 not found").arg(ownerId);
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!apiUtils::isPremiumServer(*ownerServer)) {
|
||||
const QString message = QStringLiteral("Server %1 is not premium, local proxy is unavailable")
|
||||
.arg(ownerServer->value(configKey::name).toString());
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto serializedConfig = extractSerializedXrayConfig(*ownerServer);
|
||||
if (!serializedConfig || serializedConfig->isEmpty()) {
|
||||
const QString message = QStringLiteral("Server %1 lacks Xray last_config payload")
|
||||
.arg(ownerServer->value(configKey::name).toString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(serializedConfig->toUtf8(), &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
const QString message = QStringLiteral("Failed to parse Xray config JSON: %1").arg(parseError.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ConfigData data;
|
||||
data.ownerId = ownerId;
|
||||
data.serverName = ownerServer->value(configKey::name).toString();
|
||||
data.parsedConfig = doc.object();
|
||||
const int proxyPort = resolveProxyPort(m_appSettingsRepository);
|
||||
if (applyProxyPortToConfig(data.parsedConfig, proxyPort)) {
|
||||
data.serializedConfig = serializeConfig(data.parsedConfig);
|
||||
} else {
|
||||
ProxyLogger::getInstance().warning(QStringLiteral("Failed to override local proxy inbound port; using original config"));
|
||||
data.serializedConfig = *serializedConfig;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
std::optional<ConfigManager::ConfigData> ConfigManager::buildConfigWithFetch(QString &errorDescription) const
|
||||
{
|
||||
errorDescription.clear();
|
||||
|
||||
if (!m_serversRepository || !m_appSettingsRepository) {
|
||||
const QString message = QStringLiteral("Local proxy repositories are not available");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QString ownerId = m_appSettingsRepository->localProxyOwnerId();
|
||||
if (ownerId.isEmpty()) {
|
||||
const QString message = QStringLiteral("Local proxy owner server id is not configured");
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto ownerServer = m_serversRepository->serverJsonById(ownerId);
|
||||
if (!ownerServer) {
|
||||
const QString message = QStringLiteral("Owner server with id %1 not found").arg(ownerId);
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!apiUtils::isPremiumServer(*ownerServer)) {
|
||||
const QString message = QStringLiteral("Server %1 is not premium, local proxy is unavailable")
|
||||
.arg(ownerServer->value(configKey::name).toString());
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto serializedConfig = extractSerializedXrayConfig(*ownerServer);
|
||||
if (!serializedConfig || serializedConfig->isEmpty()) {
|
||||
auto fetchedConfig = fetchSerializedXrayConfigFromGateway(*ownerServer, errorDescription);
|
||||
if (!fetchedConfig || fetchedConfig->isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
serializedConfig = fetchedConfig;
|
||||
}
|
||||
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(serializedConfig->toUtf8(), &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
const QString message = QStringLiteral("Failed to parse Xray config JSON: %1").arg(parseError.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ConfigData data;
|
||||
data.ownerId = ownerId;
|
||||
data.serverName = ownerServer->value(configKey::name).toString();
|
||||
data.parsedConfig = doc.object();
|
||||
|
||||
int selectedPort = resolveProxyPort(m_appSettingsRepository);
|
||||
const bool isUserDefinedPort = m_appSettingsRepository->isLocalProxyPortUserDefined();
|
||||
|
||||
if (!PortAvailabilityHelper::isPortAvailable(selectedPort)) {
|
||||
const bool canAutoSelect = !isUserDefinedPort && selectedPort == kDefaultProxyPort;
|
||||
if (canAutoSelect) {
|
||||
const auto freePort = PortAvailabilityHelper::findFirstAvailablePort(kDefaultProxyPort + 1, kProxyPortMax);
|
||||
if (!freePort) {
|
||||
errorDescription = QStringLiteral("No available local proxy port in range %1-%2")
|
||||
.arg(kDefaultProxyPort + 1)
|
||||
.arg(kProxyPortMax);
|
||||
ProxyLogger::getInstance().error(errorDescription);
|
||||
return std::nullopt;
|
||||
}
|
||||
selectedPort = *freePort;
|
||||
} else {
|
||||
errorDescription = QStringLiteral("Local proxy port %1 is already in use")
|
||||
.arg(selectedPort);
|
||||
ProxyLogger::getInstance().error(errorDescription);
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
if (applyProxyPortToConfig(data.parsedConfig, selectedPort)) {
|
||||
data.serializedConfig = serializeConfig(data.parsedConfig);
|
||||
} else {
|
||||
ProxyLogger::getInstance().warning(QStringLiteral("Failed to override local proxy inbound port; using original config"));
|
||||
data.serializedConfig = *serializedConfig;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
bool ConfigManager::writeTempConfig(const QString &serializedConfig, QString &configPath, QString &errorDescription) const
|
||||
{
|
||||
errorDescription.clear();
|
||||
configPath.clear();
|
||||
|
||||
const QString directory = tempDirectory();
|
||||
if (!QDir().mkpath(directory)) {
|
||||
const QString message = QStringLiteral("Failed to create temp config directory: %1").arg(directory);
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString path = tempConfigPath();
|
||||
QSaveFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
const QString message = QStringLiteral("Failed to open temp config file %1: %2").arg(path, file.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.write(serializedConfig.toUtf8()) == -1) {
|
||||
const QString message = QStringLiteral("Failed to write temp config file %1: %2").arg(path, file.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file.commit()) {
|
||||
const QString message = QStringLiteral("Failed to commit temp config file %1").arg(path);
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info(QStringLiteral("Xray config saved to %1").arg(path));
|
||||
configPath = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigManager::removeTempConfig() const
|
||||
{
|
||||
const QString path = tempConfigPath();
|
||||
QFile file(path);
|
||||
if (!file.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!file.remove()) {
|
||||
ProxyLogger::getInstance().warning(QStringLiteral("Failed to remove temp config file %1: %2").arg(path, file.errorString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug(QStringLiteral("Removed temp config file %1").arg(path));
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ConfigManager::tempConfigPath() const
|
||||
{
|
||||
return QDir(tempDirectory()).filePath(QStringLiteral("xray_active.json"));
|
||||
}
|
||||
|
||||
std::optional<QString> ConfigManager::extractSerializedXrayConfig(const QJsonObject &server) const
|
||||
{
|
||||
const QJsonArray containers = server.value(configKey::containers).toArray();
|
||||
const QString targetContainer = ContainerUtils::containerToString(DockerContainer::Xray);
|
||||
const QString protoKey = QString(configKey::xray);
|
||||
|
||||
for (const QJsonValue &value : containers) {
|
||||
const QJsonObject container = value.toObject();
|
||||
if (container.value(configKey::container).toString() != targetContainer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QJsonObject proto = container.value(protoKey).toObject();
|
||||
const QString serialized = proto.value(configKey::lastConfig).toString();
|
||||
if (!serialized.isEmpty()) {
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<QString> ConfigManager::fetchSerializedXrayConfigFromGateway(const QJsonObject &server, QString &errorDescription) const
|
||||
{
|
||||
errorDescription.clear();
|
||||
|
||||
if (!m_appSettingsRepository) {
|
||||
const QString message = QStringLiteral("App settings repository is not available");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
|
||||
if (apiConfig.isEmpty()) {
|
||||
const QString message = QStringLiteral("Server API config is missing");
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QString userCountryCode = apiConfig.value(apiDefs::key::userCountryCode).toString();
|
||||
const QString serviceType = apiConfig.value(apiDefs::key::serviceType).toString();
|
||||
if (userCountryCode.isEmpty() || serviceType.isEmpty()) {
|
||||
const QString message = QStringLiteral("Server API config lacks service identifiers");
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
|
||||
apiPayload[apiDefs::key::appVersion] = QString(APP_VERSION);
|
||||
|
||||
const QString appLanguage = m_appSettingsRepository->getAppLanguage().name().split("_").first();
|
||||
if (!appLanguage.isEmpty()) {
|
||||
apiPayload[apiDefs::key::appLanguage] = appLanguage;
|
||||
}
|
||||
|
||||
apiPayload[apiDefs::key::uuid] = m_appSettingsRepository->getInstallationUuid(true);
|
||||
apiPayload[apiDefs::key::userCountryCode] = userCountryCode;
|
||||
apiPayload[apiDefs::key::serviceType] = serviceType;
|
||||
apiPayload[apiDefs::key::serviceProtocol] = QString(gateway_key::vless);
|
||||
apiPayload[apiDefs::key::publicKey] = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
const QJsonObject authData = server.value(apiDefs::key::authData).toObject();
|
||||
if (!authData.isEmpty()) {
|
||||
apiPayload[apiDefs::key::authData] = authData;
|
||||
}
|
||||
|
||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs, m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QByteArray responseBody;
|
||||
const amnezia::ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody);
|
||||
if (errorCode != amnezia::ErrorCode::NoError) {
|
||||
const QString message = QStringLiteral("Gateway request failed with error code %1").arg(static_cast<int>(errorCode));
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QJsonParseError responseError;
|
||||
const QJsonDocument responseDoc = QJsonDocument::fromJson(responseBody, &responseError);
|
||||
if (responseError.error != QJsonParseError::NoError || !responseDoc.isObject()) {
|
||||
const QString message = QStringLiteral("Failed to parse gateway response: %1").arg(responseError.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QString data = responseDoc.object().value(configKey::config).toString();
|
||||
if (data.isEmpty()) {
|
||||
const QString message = QStringLiteral("Gateway response lacks config payload");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
data.replace("vpn://", "");
|
||||
QByteArray decoded = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
if (decoded.isEmpty()) {
|
||||
const QString message = QStringLiteral("Gateway config payload is empty");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QByteArray uncompressed = qUncompress(decoded);
|
||||
if (!uncompressed.isEmpty()) {
|
||||
decoded = uncompressed;
|
||||
}
|
||||
|
||||
QJsonParseError configError;
|
||||
const QJsonDocument configDoc = QJsonDocument::fromJson(decoded, &configError);
|
||||
if (configError.error != QJsonParseError::NoError || !configDoc.isObject()) {
|
||||
const QString message = QStringLiteral("Failed to parse gateway config JSON: %1").arg(configError.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto serializedConfig = extractSerializedXrayConfig(configDoc.object());
|
||||
if (!serializedConfig || serializedConfig->isEmpty()) {
|
||||
const QString message = QStringLiteral("Gateway response lacks Xray last_config payload");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info("Fetched Xray config from gateway");
|
||||
return serializedConfig;
|
||||
}
|
||||
|
||||
QString ConfigManager::tempDirectory() const
|
||||
{
|
||||
const QString baseDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
||||
if (baseDir.isEmpty()) {
|
||||
return QDir::temp().filePath(QStringLiteral("amnezia_local_proxy"));
|
||||
}
|
||||
return QDir(baseDir).filePath(QStringLiteral("local_proxy"));
|
||||
}
|
||||
37
client/core/local-proxy/configmanager.h
Normal file
37
client/core/local-proxy/configmanager.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
class SecureServersRepository;
|
||||
class SecureAppSettingsRepository;
|
||||
|
||||
class ConfigManager {
|
||||
public:
|
||||
struct ConfigData {
|
||||
QString ownerId;
|
||||
QString serverName;
|
||||
QString serializedConfig;
|
||||
QJsonObject parsedConfig;
|
||||
};
|
||||
|
||||
ConfigManager(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository);
|
||||
|
||||
std::optional<ConfigData> buildConfig(QString &errorDescription) const;
|
||||
std::optional<ConfigData> buildConfigWithFetch(QString &errorDescription) const;
|
||||
bool writeTempConfig(const QString &serializedConfig, QString &configPath, QString &errorDescription) const;
|
||||
bool removeTempConfig() const;
|
||||
QString tempConfigPath() const;
|
||||
|
||||
private:
|
||||
std::optional<QString> extractSerializedXrayConfig(const QJsonObject &server) const;
|
||||
std::optional<QString> fetchSerializedXrayConfigFromGateway(const QJsonObject &server, QString &errorDescription) const;
|
||||
QString tempDirectory() const;
|
||||
bool applyProxyPortToConfig(QJsonObject &config, int port) const;
|
||||
QString serializeConfig(const QJsonObject &config) const;
|
||||
|
||||
SecureServersRepository *m_serversRepository;
|
||||
SecureAppSettingsRepository *m_appSettingsRepository;
|
||||
};
|
||||
190
client/core/local-proxy/httpapi.cpp
Normal file
190
client/core/local-proxy/httpapi.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
#include "httpapi.h"
|
||||
#include "proxylogger.h"
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QHostAddress>
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
|
||||
std::optional<int> extractInboundPort(const QJsonObject &config)
|
||||
{
|
||||
if (!config.contains("inbounds") || !config["inbounds"].isArray()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QJsonArray inbounds = config["inbounds"].toArray();
|
||||
if (inbounds.isEmpty() || !inbounds.at(0).isObject()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QJsonObject firstInbound = inbounds.at(0).toObject();
|
||||
if (!firstInbound.contains("port")) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return firstInbound.value("port").toInt();
|
||||
}
|
||||
|
||||
QJsonValue proxyPortValue(const std::optional<int> &port)
|
||||
{
|
||||
if (port.has_value()) {
|
||||
return QJsonValue(*port);
|
||||
}
|
||||
return QJsonValue::Null;
|
||||
}
|
||||
|
||||
QHttpServerResponse makeServiceUnavailablePingResponse()
|
||||
{
|
||||
QJsonObject payload{
|
||||
{"status", "error"},
|
||||
{"proxyPort", QJsonValue::Null}
|
||||
};
|
||||
return QHttpServerResponse(payload, QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
|
||||
QHttpServerResponse makeServiceUnavailableStatusResponse()
|
||||
{
|
||||
QJsonObject payload{
|
||||
{"status", "error"}
|
||||
};
|
||||
return QHttpServerResponse(payload, QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
HttpApi::HttpApi(QWeakPointer<IProxyService> service, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_tcpServer(new QTcpServer(this))
|
||||
, m_service(service)
|
||||
{
|
||||
ProxyLogger::getInstance().debug("HttpApi initialized");
|
||||
}
|
||||
|
||||
HttpApi::~HttpApi()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool HttpApi::start(quint16 port)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Starting HTTP API server on port %1").arg(port));
|
||||
|
||||
if (!m_tcpServer->listen(QHostAddress::LocalHost, port)) {
|
||||
ProxyLogger::getInstance().error(QString("Failed to start HTTP API server on port %1").arg(port));
|
||||
return false;
|
||||
}
|
||||
|
||||
setupRoutes();
|
||||
m_server.bind(m_tcpServer.data());
|
||||
|
||||
ProxyLogger::getInstance().info(QString("HTTP API server is running on localhost:%1").arg(m_tcpServer->serverPort()));
|
||||
ProxyLogger::getInstance().debug("Available endpoints:\n"
|
||||
" POST /api/v1/up\n"
|
||||
" POST /api/v1/down\n"
|
||||
" GET /api/v1/ping");
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpApi::stop()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Stopping HTTP API server");
|
||||
if (m_tcpServer) {
|
||||
m_tcpServer->close();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpApi::setupRoutes()
|
||||
{
|
||||
ProxyLogger::getInstance().debug("Setting up HTTP API routes");
|
||||
|
||||
m_server.route("/api/v1/up", QHttpServerRequest::Method::Post,
|
||||
[this] {
|
||||
ProxyLogger::getInstance().debug("Handling POST /api/v1/up request");
|
||||
return handlePostUp();
|
||||
});
|
||||
|
||||
m_server.route("/api/v1/down", QHttpServerRequest::Method::Post,
|
||||
[this] {
|
||||
ProxyLogger::getInstance().debug("Handling POST /api/v1/down request");
|
||||
return handlePostDown();
|
||||
});
|
||||
|
||||
m_server.route("/api/v1/ping", QHttpServerRequest::Method::Get,
|
||||
[this] {
|
||||
ProxyLogger::getInstance().debug("Handling GET /api/v1/ping request");
|
||||
return handleGetPing();
|
||||
});
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handlePostUp()
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
const bool started = service->startXray();
|
||||
QJsonObject response;
|
||||
response["status"] = started ? "ok" : "error";
|
||||
|
||||
const auto port = started ? extractInboundPort(service->getConfig()) : std::optional<int>{};
|
||||
response["proxyPort"] = proxyPortValue(port);
|
||||
|
||||
if (started) {
|
||||
if (port.has_value()) {
|
||||
ProxyLogger::getInstance().info(QString("Xray process started on port %1").arg(*port));
|
||||
} else {
|
||||
ProxyLogger::getInstance().warning("Xray started but inbound port is unknown (local proxy owner may be missing)");
|
||||
}
|
||||
} else {
|
||||
ProxyLogger::getInstance().warning("Failed to start Xray process via HTTP API");
|
||||
}
|
||||
|
||||
return QHttpServerResponse(response);
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable: proxy backend is not initialized");
|
||||
return makeServiceUnavailablePingResponse();
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handlePostDown()
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
const bool stopped = service->stopXray();
|
||||
QJsonObject response;
|
||||
response["status"] = stopped ? "ok" : "error";
|
||||
if (!stopped) {
|
||||
ProxyLogger::getInstance().warning("Failed to stop Xray process via HTTP API");
|
||||
} else {
|
||||
ProxyLogger::getInstance().info("Xray process stopped via HTTP API");
|
||||
}
|
||||
|
||||
return QHttpServerResponse(response);
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable: proxy backend is not initialized");
|
||||
return makeServiceUnavailableStatusResponse();
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handleGetPing() const
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
QJsonObject response;
|
||||
response["status"] = "ok";
|
||||
const bool isRunning = service->isXrayRunning();
|
||||
if (isRunning) {
|
||||
const auto port = extractInboundPort(service->getConfig());
|
||||
response["proxyPort"] = proxyPortValue(port);
|
||||
if (port.has_value()) {
|
||||
ProxyLogger::getInstance().debug(QString("Xray port: %1").arg(*port));
|
||||
} else {
|
||||
ProxyLogger::getInstance().warning("Unable to detect inbound port while Xray is running");
|
||||
}
|
||||
} else {
|
||||
response["proxyPort"] = QJsonValue::Null;
|
||||
ProxyLogger::getInstance().debug("Xray is not running");
|
||||
}
|
||||
|
||||
return QHttpServerResponse(response);
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable: proxy backend is not initialized");
|
||||
return makeServiceUnavailablePingResponse();
|
||||
}
|
||||
32
client/core/local-proxy/httpapi.h
Normal file
32
client/core/local-proxy/httpapi.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
#include <QHttpServer>
|
||||
#include <QHttpServerRequest>
|
||||
#include <QHttpServerResponse>
|
||||
#include <QTcpServer>
|
||||
#include <QWeakPointer>
|
||||
#include "iproxyservice.h"
|
||||
|
||||
class HttpApi : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HttpApi(QWeakPointer<IProxyService> service, QObject* parent = nullptr);
|
||||
~HttpApi();
|
||||
|
||||
bool start(quint16 port);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void setupRoutes();
|
||||
|
||||
QHttpServerResponse handlePostUp();
|
||||
QHttpServerResponse handlePostDown();
|
||||
QHttpServerResponse handleGetPing() const;
|
||||
|
||||
QHttpServer m_server;
|
||||
QScopedPointer<QTcpServer> m_tcpServer;
|
||||
QWeakPointer<IProxyService> m_service;
|
||||
};
|
||||
17
client/core/local-proxy/iproxyservice.h
Normal file
17
client/core/local-proxy/iproxyservice.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
class IProxyService {
|
||||
public:
|
||||
virtual ~IProxyService() = default;
|
||||
|
||||
virtual QJsonObject getConfig() = 0;
|
||||
|
||||
virtual bool startXray() = 0;
|
||||
virtual bool stopXray() = 0;
|
||||
virtual bool isXrayRunning() const = 0;
|
||||
virtual qint64 getXrayProcessId() const = 0;
|
||||
virtual QString getXrayError() const = 0;
|
||||
};
|
||||
43
client/core/local-proxy/portavailabilityhelper.cpp
Normal file
43
client/core/local-proxy/portavailabilityhelper.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "portavailabilityhelper.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QTcpServer>
|
||||
|
||||
namespace {
|
||||
constexpr int kProxyPortMin = 1024;
|
||||
constexpr int kProxyPortMax = 65535;
|
||||
}
|
||||
|
||||
bool PortAvailabilityHelper::isPortAvailable(int port)
|
||||
{
|
||||
if (port < kProxyPortMin || port > kProxyPortMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTcpServer server;
|
||||
const bool success = server.listen(QHostAddress::LocalHost, static_cast<quint16>(port));
|
||||
server.close();
|
||||
return success;
|
||||
}
|
||||
|
||||
std::optional<int> PortAvailabilityHelper::findFirstAvailablePort(int startPort, int endPort)
|
||||
{
|
||||
if (startPort < kProxyPortMin) {
|
||||
startPort = kProxyPortMin;
|
||||
}
|
||||
if (endPort > kProxyPortMax) {
|
||||
endPort = kProxyPortMax;
|
||||
}
|
||||
if (startPort > endPort) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (int port = startPort; port <= endPort; ++port) {
|
||||
if (isPortAvailable(port)) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
11
client/core/local-proxy/portavailabilityhelper.h
Normal file
11
client/core/local-proxy/portavailabilityhelper.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
class PortAvailabilityHelper
|
||||
{
|
||||
public:
|
||||
static bool isPortAvailable(int port);
|
||||
static std::optional<int> findFirstAvailablePort(int startPort, int endPort);
|
||||
};
|
||||
|
||||
133
client/core/local-proxy/proxylogger.cpp
Normal file
133
client/core/local-proxy/proxylogger.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include "proxylogger.h"
|
||||
#include <QDir>
|
||||
#include <QTextStream>
|
||||
|
||||
ProxyLogger::ProxyLogger() : m_maxFileSize(0), m_currentLevel(LogLevel::Info)
|
||||
{
|
||||
}
|
||||
|
||||
ProxyLogger::~ProxyLogger()
|
||||
{
|
||||
}
|
||||
|
||||
ProxyLogger& ProxyLogger::getInstance()
|
||||
{
|
||||
static ProxyLogger instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ProxyLogger::init(const QString& logPath, qint64 maxFileSize)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_logPath = logPath;
|
||||
m_maxFileSize = maxFileSize;
|
||||
|
||||
// Create logs directory if it doesn't exist
|
||||
QDir dir = QFileInfo(m_logPath).dir();
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyLogger::setLogLevel(LogLevel level)
|
||||
{
|
||||
m_currentLevel = level;
|
||||
}
|
||||
|
||||
void ProxyLogger::log(LogLevel level, const QString& message)
|
||||
{
|
||||
logInternal(level, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::debug(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::Debug, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::info(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::Info, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::warning(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::Warning, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::error(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::Error, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::logInternal(LogLevel level, const QString& message)
|
||||
{
|
||||
if (m_logPath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (level < m_currentLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
checkRotation();
|
||||
|
||||
QFile file(m_logPath);
|
||||
if (!openLogFile(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
|
||||
stream << QString("[%1] [%2] %3\n").arg(timestamp, levelToString(level), message);
|
||||
stream.flush();
|
||||
file.close();
|
||||
}
|
||||
|
||||
QString ProxyLogger::levelToString(LogLevel level)
|
||||
{
|
||||
switch (level) {
|
||||
case LogLevel::Debug: return "DEBUG";
|
||||
case LogLevel::Info: return "INFO";
|
||||
case LogLevel::Warning: return "WARNING";
|
||||
case LogLevel::Error: return "ERROR";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
qint64 ProxyLogger::getCurrentFileSize() const
|
||||
{
|
||||
QFile file(m_logPath);
|
||||
if (file.exists()) {
|
||||
return file.size();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ProxyLogger::checkRotation()
|
||||
{
|
||||
if (m_maxFileSize > 0 && getCurrentFileSize() >= m_maxFileSize) {
|
||||
// Delete the oldest file
|
||||
QFile::remove(QString("%1.%2").arg(m_logPath).arg(MAX_BACKUP_FILES));
|
||||
|
||||
// Shift existing files
|
||||
for (int i = MAX_BACKUP_FILES - 1; i >= 1; --i) {
|
||||
QString oldName = QString("%1.%2").arg(m_logPath).arg(i);
|
||||
QString newName = QString("%1.%2").arg(m_logPath).arg(i + 1);
|
||||
QFile::rename(oldName, newName);
|
||||
}
|
||||
|
||||
// Rename current file
|
||||
QFile::rename(m_logPath, m_logPath + ".1");
|
||||
}
|
||||
}
|
||||
|
||||
bool ProxyLogger::openLogFile(QFile& file)
|
||||
{
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
qDebug() << "Failed to open log file:" << m_logPath;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
54
client/core/local-proxy/proxylogger.h
Normal file
54
client/core/local-proxy/proxylogger.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef LOCAL_PROXY_LOGGER_H
|
||||
#define LOCAL_PROXY_LOGGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
|
||||
class ProxyLogger
|
||||
{
|
||||
|
||||
public:
|
||||
enum class LogLevel {
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
};
|
||||
|
||||
static ProxyLogger& getInstance();
|
||||
|
||||
void init(const QString& logPath, qint64 maxFileSize = 1024 * 1024 * 10); // 10MB by default
|
||||
void setLogLevel(LogLevel level);
|
||||
|
||||
// Main logging method
|
||||
void log(LogLevel level, const QString& message);
|
||||
|
||||
// Helper methods for convenience
|
||||
void debug(const QString& message);
|
||||
void info(const QString& message);
|
||||
void warning(const QString& message);
|
||||
void error(const QString& message);
|
||||
|
||||
private:
|
||||
ProxyLogger();
|
||||
~ProxyLogger();
|
||||
ProxyLogger(const ProxyLogger&) = delete;
|
||||
ProxyLogger& operator=(const ProxyLogger&) = delete;
|
||||
|
||||
void logInternal(LogLevel level, const QString& message);
|
||||
void checkRotation();
|
||||
QString levelToString(LogLevel level);
|
||||
bool openLogFile(QFile& file);
|
||||
qint64 getCurrentFileSize() const;
|
||||
|
||||
QString m_logPath;
|
||||
qint64 m_maxFileSize;
|
||||
LogLevel m_currentLevel;
|
||||
QMutex m_mutex;
|
||||
static const int MAX_BACKUP_FILES = 3;
|
||||
};
|
||||
|
||||
#endif // LOCAL_PROXY_LOGGER_H
|
||||
114
client/core/local-proxy/proxyserver.cpp
Normal file
114
client/core/local-proxy/proxyserver.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "proxyserver.h"
|
||||
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
ProxyServer::ProxyServer(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_appSettingsRepository(appSettingsRepository)
|
||||
, m_service(new ProxyService(serversRepository, appSettingsRepository, this))
|
||||
{
|
||||
m_lastRestartToken = m_appSettingsRepository ? m_appSettingsRepository->localProxyRestartToken() : 0;
|
||||
}
|
||||
|
||||
ProxyServer::~ProxyServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool ProxyServer::start(quint16 port)
|
||||
{
|
||||
if (m_isRunning) {
|
||||
if (m_currentApiPort == port) {
|
||||
qInfo() << "Local proxy: already running on port" << port;
|
||||
return true;
|
||||
}
|
||||
|
||||
qInfo() << "Local proxy: restarting on new port" << port;
|
||||
stop();
|
||||
}
|
||||
|
||||
m_api.reset(new HttpApi(m_service.toWeakRef()));
|
||||
const bool apiStarted = m_api->start(port);
|
||||
if (!apiStarted) {
|
||||
qWarning() << "Local proxy: port is busy:" << port;
|
||||
m_api.reset();
|
||||
m_isRunning = false;
|
||||
m_currentApiPort = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isRunning = true;
|
||||
m_currentApiPort = port;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProxyServer::stop()
|
||||
{
|
||||
stopXrayProcess();
|
||||
if (m_api) {
|
||||
m_api->stop();
|
||||
m_api.reset();
|
||||
}
|
||||
m_isRunning = false;
|
||||
m_currentApiPort = 0;
|
||||
m_currentProxyPort = 0;
|
||||
}
|
||||
|
||||
bool ProxyServer::startXrayProcess()
|
||||
{
|
||||
return m_service->startXray();
|
||||
}
|
||||
|
||||
void ProxyServer::stopXrayProcess()
|
||||
{
|
||||
m_service->stopXray();
|
||||
}
|
||||
|
||||
bool ProxyServer::syncSettings()
|
||||
{
|
||||
if (!m_isRunning) {
|
||||
qDebug() << "Local proxy: syncSettings called but server is not running";
|
||||
return false;
|
||||
}
|
||||
|
||||
const quint16 newProxyPort = m_appSettingsRepository ? m_appSettingsRepository->localProxyPort() : 0;
|
||||
const int restartToken = m_appSettingsRepository ? m_appSettingsRepository->localProxyRestartToken() : 0;
|
||||
const bool xrayRunning = m_service->isXrayRunning();
|
||||
|
||||
if (!xrayRunning) {
|
||||
qInfo() << "Local proxy: starting Xray on port" << newProxyPort;
|
||||
const bool started = startXrayProcess();
|
||||
if (started) {
|
||||
m_currentProxyPort = newProxyPort;
|
||||
m_lastRestartToken = restartToken;
|
||||
}
|
||||
return started;
|
||||
}
|
||||
|
||||
if (m_lastRestartToken != restartToken) {
|
||||
qInfo() << "Local proxy: restarting Xray due to config change token";
|
||||
const bool restarted = m_service->restartXray();
|
||||
if (restarted) {
|
||||
m_currentProxyPort = newProxyPort;
|
||||
m_lastRestartToken = restartToken;
|
||||
}
|
||||
return restarted;
|
||||
}
|
||||
|
||||
if (m_currentProxyPort != newProxyPort) {
|
||||
qInfo() << "Local proxy: proxy port changed from" << m_currentProxyPort << "to" << newProxyPort;
|
||||
const bool restarted = m_service->restartXray();
|
||||
if (restarted) {
|
||||
m_currentProxyPort = newProxyPort;
|
||||
m_lastRestartToken = restartToken;
|
||||
}
|
||||
return restarted;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
37
client/core/local-proxy/proxyserver.h
Normal file
37
client/core/local-proxy/proxyserver.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "httpapi.h"
|
||||
#include "proxyservice.h"
|
||||
|
||||
class SecureServersRepository;
|
||||
class SecureAppSettingsRepository;
|
||||
|
||||
class ProxyServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ProxyServer(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent = nullptr);
|
||||
~ProxyServer();
|
||||
|
||||
bool start(quint16 port = 49490);
|
||||
void stop();
|
||||
bool syncSettings();
|
||||
|
||||
private:
|
||||
bool startXrayProcess();
|
||||
void stopXrayProcess();
|
||||
|
||||
SecureAppSettingsRepository *m_appSettingsRepository;
|
||||
QScopedPointer<HttpApi> m_api;
|
||||
QSharedPointer<ProxyService> m_service;
|
||||
bool m_isRunning {false};
|
||||
quint16 m_currentApiPort {0};
|
||||
quint16 m_currentProxyPort {0};
|
||||
int m_lastRestartToken {0};
|
||||
};
|
||||
118
client/core/local-proxy/proxyservice.cpp
Normal file
118
client/core/local-proxy/proxyservice.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "proxyservice.h"
|
||||
|
||||
#include "proxylogger.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void logConfigError(const QString &errorMessage)
|
||||
{
|
||||
if (!errorMessage.isEmpty()) {
|
||||
ProxyLogger::getInstance().error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ProxyService::ProxyService(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_configManager(new ConfigManager(serversRepository, appSettingsRepository))
|
||||
, m_xrayController(new XrayController())
|
||||
{
|
||||
ProxyLogger::getInstance().debug("ProxyService initialized");
|
||||
}
|
||||
|
||||
QJsonObject ProxyService::getConfig()
|
||||
{
|
||||
if (!m_cachedConfig.isEmpty()) {
|
||||
return m_cachedConfig;
|
||||
}
|
||||
|
||||
QString error;
|
||||
const auto configData = m_configManager->buildConfigWithFetch(error);
|
||||
if (!configData) {
|
||||
logConfigError(error);
|
||||
return {};
|
||||
}
|
||||
|
||||
m_cachedConfig = configData->parsedConfig;
|
||||
return m_cachedConfig;
|
||||
}
|
||||
|
||||
bool ProxyService::startXray()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Starting Xray");
|
||||
|
||||
if (m_xrayController->isXrayRunning()) {
|
||||
ProxyLogger::getInstance().info("Xray is already running");
|
||||
return true;
|
||||
}
|
||||
|
||||
QString error;
|
||||
const auto configData = m_configManager->buildConfigWithFetch(error);
|
||||
if (!configData) {
|
||||
logConfigError(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool success = m_xrayController->start(configData->serializedConfig);
|
||||
if (success) {
|
||||
m_cachedConfig = configData->parsedConfig;
|
||||
ProxyLogger::getInstance().info("Xray started successfully");
|
||||
emit xrayStatusChanged(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error(QStringLiteral("Failed to start Xray: %1").arg(m_xrayController->getError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProxyService::stopXray()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Stopping Xray");
|
||||
const bool stopped = m_xrayController->stop();
|
||||
if (stopped) {
|
||||
ProxyLogger::getInstance().info("Xray stopped");
|
||||
emit xrayStatusChanged(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().warning(QStringLiteral("Failed to stop Xray: %1").arg(m_xrayController->getError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProxyService::isXrayRunning() const
|
||||
{
|
||||
return m_xrayController->isXrayRunning();
|
||||
}
|
||||
|
||||
qint64 ProxyService::getXrayProcessId() const
|
||||
{
|
||||
return m_xrayController->getProcessId();
|
||||
}
|
||||
|
||||
QString ProxyService::getXrayError() const
|
||||
{
|
||||
return m_xrayController->getError();
|
||||
}
|
||||
|
||||
void ProxyService::clearCache()
|
||||
{
|
||||
m_cachedConfig = QJsonObject();
|
||||
ProxyLogger::getInstance().debug("ProxyService cache cleared");
|
||||
}
|
||||
|
||||
bool ProxyService::restartXray()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Restarting Xray with updated config");
|
||||
clearCache();
|
||||
|
||||
if (m_xrayController->isXrayRunning()) {
|
||||
if (!stopXray()) {
|
||||
ProxyLogger::getInstance().error("Failed to stop Xray during restart, aborting");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return startXray();
|
||||
}
|
||||
39
client/core/local-proxy/proxyservice.h
Normal file
39
client/core/local-proxy/proxyservice.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "configmanager.h"
|
||||
#include "iproxyservice.h"
|
||||
#include "xraycontroller.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
#include <QJsonObject>
|
||||
|
||||
class SecureServersRepository;
|
||||
class SecureAppSettingsRepository;
|
||||
|
||||
class ProxyService : public QObject, public IProxyService {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ProxyService(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent = nullptr);
|
||||
~ProxyService() = default;
|
||||
|
||||
QJsonObject getConfig() override;
|
||||
bool startXray() override;
|
||||
bool stopXray() override;
|
||||
bool isXrayRunning() const override;
|
||||
qint64 getXrayProcessId() const override;
|
||||
QString getXrayError() const override;
|
||||
|
||||
void clearCache();
|
||||
bool restartXray();
|
||||
|
||||
signals:
|
||||
void xrayStatusChanged(bool running);
|
||||
|
||||
private:
|
||||
QScopedPointer<ConfigManager> m_configManager;
|
||||
QScopedPointer<XrayController> m_xrayController;
|
||||
QJsonObject m_cachedConfig;
|
||||
};
|
||||
105
client/core/local-proxy/xraycontroller.cpp
Normal file
105
client/core/local-proxy/xraycontroller.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "xraycontroller.h"
|
||||
|
||||
#include "proxylogger.h"
|
||||
#include "core/utils/ipcClient.h"
|
||||
|
||||
namespace {
|
||||
const QString kIpcUnavailableError = QStringLiteral("Failed to communicate with IPC service");
|
||||
}
|
||||
|
||||
XrayController::XrayController(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
ProxyLogger::getInstance().debug("XrayController initialized");
|
||||
}
|
||||
|
||||
XrayController::~XrayController()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool XrayController::start(const QString &configJson)
|
||||
{
|
||||
if (m_isRunning) {
|
||||
ProxyLogger::getInstance().info("Xray is already running");
|
||||
return true;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info("Request to start Xray via IPC");
|
||||
|
||||
m_lastError.clear();
|
||||
|
||||
if (configJson.trimmed().isEmpty()) {
|
||||
m_lastError = QStringLiteral("Config content is empty");
|
||||
ProxyLogger::getInstance().error(m_lastError);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool ipcResult = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto xrayStart = iface->xrayStart(configJson);
|
||||
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
||||
ProxyLogger::getInstance().warning("Failed to start Xray via IPC");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, []() {
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!ipcResult) {
|
||||
m_lastError = kIpcUnavailableError;
|
||||
ProxyLogger::getInstance().error(m_lastError);
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info("Xray start command sent to IPC service");
|
||||
m_isRunning = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XrayController::stop()
|
||||
{
|
||||
if (!m_isRunning) {
|
||||
ProxyLogger::getInstance().debug("Skipping Xray stop via IPC: local proxy Xray is not running");
|
||||
return true;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info("Stopping Xray via IPC");
|
||||
|
||||
const bool ipcResult = IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto xrayStop = iface->xrayStop();
|
||||
if (!xrayStop.waitForFinished() || !xrayStop.returnValue()) {
|
||||
ProxyLogger::getInstance().warning("Failed to stop Xray via IPC");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, []() {
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!ipcResult) {
|
||||
m_lastError = kIpcUnavailableError;
|
||||
ProxyLogger::getInstance().warning(m_lastError);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isRunning = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XrayController::isXrayRunning() const
|
||||
{
|
||||
return m_isRunning;
|
||||
}
|
||||
|
||||
qint64 XrayController::getProcessId() const
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString XrayController::getError() const
|
||||
{
|
||||
return m_lastError;
|
||||
}
|
||||
22
client/core/local-proxy/xraycontroller.h
Normal file
22
client/core/local-proxy/xraycontroller.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class XrayController : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit XrayController(QObject* parent = nullptr);
|
||||
~XrayController();
|
||||
|
||||
bool start(const QString& configJson);
|
||||
bool stop();
|
||||
bool isXrayRunning() const;
|
||||
qint64 getProcessId() const;
|
||||
QString getError() const;
|
||||
|
||||
private:
|
||||
bool m_isRunning {false};
|
||||
QString m_lastError;
|
||||
};
|
||||
@@ -29,11 +29,6 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void NativeServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
|
||||
{
|
||||
QString d1 = dns1;
|
||||
|
||||
@@ -27,8 +27,6 @@ struct NativeServerConfig {
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
@@ -43,11 +43,6 @@ ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer cont
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void SelfHostedUserServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns,
|
||||
const QString &secondaryDns) const
|
||||
{
|
||||
|
||||
@@ -32,8 +32,6 @@ struct SelfHostedUserServerConfig {
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
@@ -39,44 +39,33 @@ QString OpenVpnProtocol::defaultConfigPath()
|
||||
return p;
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::cleanupResources()
|
||||
void OpenVpnProtocol::stop()
|
||||
{
|
||||
if (m_openVpnProcess || openVpnProcessIsRunning()) {
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
|
||||
// TODO: need refactoring
|
||||
// sendTermSignal() will even return true while server connected ???
|
||||
if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Connected)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Reconnecting)) {
|
||||
if (!sendTermSignal()) {
|
||||
killOpenVpnProcess();
|
||||
}
|
||||
QThread::msleep(10);
|
||||
m_managementServer.stop();
|
||||
}
|
||||
m_managementServer.stop();
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
|
||||
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
|
||||
qWarning() << "OpenVpnProtocol::cleanupResources(): Failed to disable killswitch";
|
||||
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::stop()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
|
||||
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
|
||||
|| m_connectionState == Vpn::ConnectionState::Connecting
|
||||
|| m_connectionState == Vpn::ConnectionState::Connected
|
||||
|| m_connectionState == Vpn::ConnectionState::Reconnecting;
|
||||
|
||||
if (wasActive) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
}
|
||||
|
||||
cleanupResources();
|
||||
|
||||
if (wasActive || m_connectionState == Vpn::ConnectionState::Disconnecting) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnProtocol::prepare()
|
||||
@@ -179,7 +168,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
|
||||
|
||||
ErrorCode OpenVpnProtocol::start()
|
||||
{
|
||||
cleanupResources();
|
||||
OpenVpnProtocol::stop();
|
||||
|
||||
if (!QFileInfo::exists(configPath())) {
|
||||
setLastError(ErrorCode::OpenVpnConfigMissing);
|
||||
|
||||
@@ -29,7 +29,6 @@ protected slots:
|
||||
void onReadyReadDataFromManagementServer();
|
||||
|
||||
private:
|
||||
void cleanupResources();
|
||||
QString configPath() const;
|
||||
bool openVpnProcessIsRunning() const;
|
||||
bool sendTermSignal();
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
@@ -440,3 +442,59 @@ void SecureAppSettingsRepository::setXraySavedConfigs(const QByteArray &data)
|
||||
{
|
||||
setValue("Xray/savedConfigs", data);
|
||||
}
|
||||
|
||||
QString SecureAppSettingsRepository::localProxyOwnerId() const
|
||||
{
|
||||
return value("Conf/localProxyOwnerId", "").toString();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setLocalProxyOwnerId(const QString &serverId)
|
||||
{
|
||||
setValue("Conf/localProxyOwnerId", serverId);
|
||||
emit localProxySettingsChanged();
|
||||
}
|
||||
|
||||
quint16 SecureAppSettingsRepository::localProxyPort() const
|
||||
{
|
||||
return static_cast<quint16>(value("Conf/localProxyPort", 10808).toUInt());
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setLocalProxyPort(quint16 port)
|
||||
{
|
||||
setValue("Conf/localProxyPort", port);
|
||||
emit localProxySettingsChanged();
|
||||
}
|
||||
|
||||
bool SecureAppSettingsRepository::isLocalProxyPortUserDefined() const
|
||||
{
|
||||
return value("Conf/localProxyPortUserDefined", false).toBool();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setLocalProxyPortUserDefined(bool userDefined)
|
||||
{
|
||||
setValue("Conf/localProxyPortUserDefined", userDefined);
|
||||
}
|
||||
|
||||
bool SecureAppSettingsRepository::isLocalProxyHttpEnabled() const
|
||||
{
|
||||
return value("Conf/localProxyHttpEnabled", false).toBool();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setLocalProxyHttpEnabled(bool enabled)
|
||||
{
|
||||
setValue("Conf/localProxyHttpEnabled", enabled);
|
||||
emit localProxySettingsChanged();
|
||||
}
|
||||
|
||||
int SecureAppSettingsRepository::localProxyRestartToken() const
|
||||
{
|
||||
return value("Conf/localProxyRestartToken", 0).toInt();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::bumpLocalProxyRestartToken()
|
||||
{
|
||||
const int current = localProxyRestartToken();
|
||||
const int next = (current == std::numeric_limits<int>::max()) ? 0 : (current + 1);
|
||||
setValue("Conf/localProxyRestartToken", next);
|
||||
emit localProxySettingsChanged();
|
||||
}
|
||||
|
||||
@@ -93,6 +93,18 @@ public:
|
||||
QByteArray xraySavedConfigs() const;
|
||||
void setXraySavedConfigs(const QByteArray &data);
|
||||
|
||||
// Local proxy
|
||||
QString localProxyOwnerId() const;
|
||||
void setLocalProxyOwnerId(const QString &serverId);
|
||||
quint16 localProxyPort() const;
|
||||
void setLocalProxyPort(quint16 port);
|
||||
bool isLocalProxyPortUserDefined() const;
|
||||
void setLocalProxyPortUserDefined(bool userDefined);
|
||||
bool isLocalProxyHttpEnabled() const;
|
||||
void setLocalProxyHttpEnabled(bool enabled);
|
||||
int localProxyRestartToken() const;
|
||||
void bumpLocalProxyRestartToken();
|
||||
|
||||
signals:
|
||||
void appLanguageChanged(QLocale locale);
|
||||
void allowedDnsServersChanged(const QStringList &servers);
|
||||
@@ -106,6 +118,8 @@ signals:
|
||||
void saveLogsChanged(bool enabled);
|
||||
void screenshotsEnabledChanged(bool enabled);
|
||||
void settingsCleared();
|
||||
void localProxySettingsChanged();
|
||||
void localProxyStartFailed(const QString &message);
|
||||
|
||||
private:
|
||||
void setVpnSites(RouteMode mode, const QVariantMap &sites);
|
||||
|
||||
@@ -309,6 +309,15 @@ serverConfigUtils::ConfigType SecureServersRepository::serverKind(const QString
|
||||
return serverConfigUtils::configTypeFromJson(withoutStorageServerId(it.value()));
|
||||
}
|
||||
|
||||
std::optional<QJsonObject> SecureServersRepository::serverJsonById(const QString &serverId) const
|
||||
{
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
if (it == m_serverJsonById.constEnd()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return it.value();
|
||||
}
|
||||
|
||||
std::optional<SelfHostedAdminServerConfig> SecureServersRepository::selfHostedAdminConfig(const QString &serverId) const
|
||||
{
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
void removeServer(const QString &serverId);
|
||||
serverConfigUtils::ConfigType serverKind(const QString &serverId) const;
|
||||
|
||||
std::optional<QJsonObject> serverJsonById(const QString &serverId) const;
|
||||
|
||||
std::optional<SelfHostedAdminServerConfig> selfHostedAdminConfig(const QString &serverId) const;
|
||||
std::optional<SelfHostedUserServerConfig> selfHostedUserConfig(const QString &serverId) const;
|
||||
std::optional<NativeServerConfig> nativeConfig(const QString &serverId) const;
|
||||
|
||||
@@ -15,8 +15,6 @@ namespace amnezia
|
||||
Awg2,
|
||||
WireGuard,
|
||||
OpenVpn,
|
||||
Cloak,
|
||||
ShadowSocks,
|
||||
Ipsec,
|
||||
Xray,
|
||||
SSXray,
|
||||
|
||||
@@ -21,8 +21,6 @@ QString ContainerUtils::containerToString(DockerContainer c)
|
||||
{
|
||||
if (c == DockerContainer::None)
|
||||
return "none";
|
||||
if (c == DockerContainer::Cloak)
|
||||
return "amnezia-openvpn-cloak";
|
||||
if (c == DockerContainer::Awg)
|
||||
return "amnezia-awg";
|
||||
if (c == DockerContainer::Awg2)
|
||||
@@ -64,8 +62,6 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
|
||||
{
|
||||
return { { DockerContainer::None, "Not installed" },
|
||||
{ DockerContainer::OpenVpn, "OpenVPN" },
|
||||
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
|
||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||
{ DockerContainer::WireGuard, "WireGuard" },
|
||||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Awg2, "AmneziaWG" },
|
||||
@@ -87,10 +83,6 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
|
||||
return { { DockerContainer::OpenVpn,
|
||||
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
|
||||
"own security protocol with SSL/TLS for key exchange.") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("This protocol is no longer supported.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("This protocol is no longer supported.") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
|
||||
"consumption.") },
|
||||
@@ -202,9 +194,6 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
|
||||
|
||||
ServiceType ContainerUtils::containerService(DockerContainer c)
|
||||
{
|
||||
if (isUnsupportedContainer(c)) {
|
||||
return ServiceType::Vpn;
|
||||
}
|
||||
return ProtocolUtils::protocolService(defaultProtocol(c));
|
||||
}
|
||||
|
||||
@@ -213,8 +202,6 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
|
||||
switch (c) {
|
||||
case DockerContainer::None: return Proto::Unknown;
|
||||
case DockerContainer::OpenVpn: return Proto::OpenVpn;
|
||||
case DockerContainer::Cloak:
|
||||
case DockerContainer::ShadowSocks: return Proto::Unknown;
|
||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||
case DockerContainer::Awg2: return Proto::Awg;
|
||||
case DockerContainer::Awg: return Proto::Awg;
|
||||
@@ -265,8 +252,6 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
// macOS build using Network Extension – allow OpenVPN for parity with iOS.
|
||||
switch (c) {
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::Cloak: return false;
|
||||
case DockerContainer::ShadowSocks: return false;
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg2: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
@@ -351,10 +336,6 @@ int ContainerUtils::easySetupOrder(DockerContainer container)
|
||||
|
||||
bool ContainerUtils::isShareable(DockerContainer container)
|
||||
{
|
||||
if (isUnsupportedContainer(container)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (container) {
|
||||
case DockerContainer::TorWebSite: return false;
|
||||
case DockerContainer::Dns: return false;
|
||||
@@ -371,11 +352,6 @@ bool ContainerUtils::isAwgContainer(DockerContainer container)
|
||||
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
|
||||
}
|
||||
|
||||
bool ContainerUtils::isUnsupportedContainer(DockerContainer container)
|
||||
{
|
||||
return container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks;
|
||||
}
|
||||
|
||||
QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||
{
|
||||
QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol))
|
||||
|
||||
@@ -45,8 +45,6 @@ namespace amnezia
|
||||
|
||||
bool isAwgContainer(DockerContainer container);
|
||||
|
||||
bool isUnsupportedContainer(DockerContainer container);
|
||||
|
||||
QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig);
|
||||
|
||||
int installPageOrder(DockerContainer container);
|
||||
|
||||
@@ -79,7 +79,6 @@ namespace amnezia
|
||||
ImportBackupFileUseRestoreInstead = 903,
|
||||
RestoreBackupInvalidError = 904,
|
||||
LegacyApiV1NotSupportedError = 905,
|
||||
LegacyContainerNotSupportedError = 906,
|
||||
|
||||
// Android errors
|
||||
AndroidError = 1000,
|
||||
|
||||
@@ -69,7 +69,6 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
|
||||
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
|
||||
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
|
||||
case (ErrorCode::LegacyContainerNotSupportedError): errorMessage = QObject::tr("This protocol is no longer supported. Please select another protocol or remove this container from the server settings."); break;
|
||||
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
|
||||
case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#include "sshExecutor.h"
|
||||
|
||||
SshExecutor &SshExecutor::instance()
|
||||
{
|
||||
static SshExecutor executor;
|
||||
return executor;
|
||||
}
|
||||
|
||||
SshExecutor::~SshExecutor()
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
for (QThreadPool *pool : std::as_const(m_pools)) {
|
||||
pool->waitForDone();
|
||||
delete pool;
|
||||
}
|
||||
m_pools.clear();
|
||||
}
|
||||
|
||||
QThreadPool *SshExecutor::poolFor(const QString &serverId)
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
|
||||
auto it = m_pools.find(serverId);
|
||||
if (it != m_pools.end()) {
|
||||
return it.value();
|
||||
}
|
||||
|
||||
auto *pool = new QThreadPool();
|
||||
pool->setMaxThreadCount(1); // serialize all SSH ops for this server
|
||||
pool->setExpiryTimeout(-1); // keep the single worker thread alive between ops
|
||||
m_pools.insert(serverId, pool);
|
||||
return pool;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
#ifndef SSHEXECUTOR_H
|
||||
#define SSHEXECUTOR_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
#include <QThreadPool>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include <utility>
|
||||
|
||||
// Per-server serial executor for long-running self-hosted SSH operations.
|
||||
//
|
||||
// All SSH work for a given serverId is queued onto a dedicated single-thread
|
||||
// pool, so operations to the same server run strictly one at a time (no
|
||||
// concurrent SSH sessions to one host => no races, and a natural guard against
|
||||
// double-run). Operations to different servers still run in parallel.
|
||||
//
|
||||
// NOTE: do NOT route nested workers that are awaited from within an already
|
||||
// queued operation (e.g. InstallController::isServerDpkgBusy) through the same
|
||||
// per-server pool — the single worker thread would block waiting on itself.
|
||||
// Such nested helpers must keep using the global QThreadPool.
|
||||
class SshExecutor
|
||||
{
|
||||
public:
|
||||
static SshExecutor &instance();
|
||||
|
||||
SshExecutor(const SshExecutor &) = delete;
|
||||
SshExecutor &operator=(const SshExecutor &) = delete;
|
||||
|
||||
template <typename Functor>
|
||||
auto run(const QString &serverId, Functor &&functor)
|
||||
{
|
||||
return QtConcurrent::run(poolFor(serverId), std::forward<Functor>(functor));
|
||||
}
|
||||
|
||||
private:
|
||||
SshExecutor() = default;
|
||||
~SshExecutor();
|
||||
|
||||
QThreadPool *poolFor(const QString &serverId);
|
||||
|
||||
QHash<QString, QThreadPool *> m_pools;
|
||||
QMutex m_mutex;
|
||||
};
|
||||
|
||||
#endif // SSHEXECUTOR_H
|
||||
@@ -1,6 +1,5 @@
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
|
||||
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
|
||||
sudo docker volume ls | grep amnezia | awk '{print $2}' | xargs sudo docker volume rm -f;\
|
||||
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
||||
sudo rm -frd /opt/amnezia
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
sudo docker stop $CONTAINER_NAME;\
|
||||
sudo docker rm -fv $CONTAINER_NAME;\
|
||||
sudo docker rmi $CONTAINER_NAME;
|
||||
sudo docker rmi $CONTAINER_NAME;\
|
||||
test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true
|
||||
|
||||
@@ -475,7 +475,8 @@ bool SubscriptionUiController::deactivateExternalDevice(const QString &serverId,
|
||||
void SubscriptionUiController::validateConfig()
|
||||
{
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
if (!serverId.isEmpty() && m_serversController->isLegacyApiV1Server(serverId)) {
|
||||
emit unsupportedConnectDrawerRequested();
|
||||
emit configValidated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
|
||||
ServersController* serversController,
|
||||
@@ -35,7 +33,7 @@ void ConnectionUiController::openConnection()
|
||||
ErrorCode errorCode = m_connectionController->openConnection(serverId);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
notifyConnectionBlocked(errorCode);
|
||||
emit connectionErrorOccurred(errorCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -132,36 +130,10 @@ void ConnectionUiController::toggleConnection()
|
||||
} else if (isConnected()) {
|
||||
closeConnection();
|
||||
} else {
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ErrorCode errorCode = m_connectionController->isConnectionSupported(serverId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
notifyConnectionBlocked(errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
emit prepareConfig();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionUiController::notifyConnectionBlocked(ErrorCode errorCode)
|
||||
{
|
||||
if (errorCode == ErrorCode::LegacyApiV1NotSupportedError) {
|
||||
emit unsupportedConnectDrawerRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorCode == ErrorCode::NoInstalledContainersError) {
|
||||
emit noInstalledContainers();
|
||||
return;
|
||||
}
|
||||
|
||||
emit connectionErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
bool ConnectionUiController::isConnectionInProgress() const
|
||||
{
|
||||
return m_isConnectionInProgress;
|
||||
@@ -171,32 +143,3 @@ bool ConnectionUiController::isConnected() const
|
||||
{
|
||||
return m_isConnected;
|
||||
}
|
||||
|
||||
bool ConnectionUiController::isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex,
|
||||
const QString &clientId) const
|
||||
{
|
||||
if (clientId.isEmpty() || (!isConnected() && !isConnectionInProgress())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_serversController->getDefaultServerId() != serverId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static_cast<int>(m_serversController->getDefaultContainer(serverId)) != containerIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto adminConfig = m_serversController->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString connectionClientId =
|
||||
adminConfig->containerConfig(static_cast<DockerContainer>(containerIndex)).protocolConfig.clientId();
|
||||
if (connectionClientId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return connectionClientId == clientId || connectionClientId.contains(clientId);
|
||||
}
|
||||
|
||||
@@ -35,8 +35,6 @@ public slots:
|
||||
void openConnection();
|
||||
void closeConnection();
|
||||
|
||||
bool isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex, const QString &clientId) const;
|
||||
|
||||
ErrorCode getLastConnectionError();
|
||||
void onConnectionStateChanged(Vpn::ConnectionState state);
|
||||
|
||||
@@ -50,12 +48,9 @@ signals:
|
||||
void connectButtonClicked();
|
||||
void preparingConfig();
|
||||
void prepareConfig();
|
||||
void unsupportedConnectDrawerRequested();
|
||||
void noInstalledContainers();
|
||||
|
||||
private:
|
||||
Vpn::ConnectionState getCurrentConnectionState();
|
||||
void notifyConnectionBlocked(ErrorCode errorCode);
|
||||
|
||||
ConnectionController* m_connectionController;
|
||||
ServersController* m_serversController;
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace PageLoader
|
||||
PageSettingsApiDevices,
|
||||
PageSettingsApiSubscriptionKey,
|
||||
PageSettingsKillSwitchExceptions,
|
||||
PageSettingsConnectionType,
|
||||
PageSettingsLocalProxy,
|
||||
|
||||
PageServiceSftpSettings,
|
||||
PageServiceTorWebsiteSettings,
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/selfhosted/sshExecutor.h"
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
@@ -76,7 +75,13 @@ InstallUiController::InstallUiController(InstallController *installController,
|
||||
m_connectionController(connectionController)
|
||||
{
|
||||
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, &InstallUiController::installationErrorOccurred);
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) {
|
||||
if (errorCode == ErrorCode::NoInstalledContainersError) {
|
||||
emit noInstalledContainers();
|
||||
} else {
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
InstallUiController::~InstallUiController()
|
||||
@@ -212,13 +217,15 @@ void InstallUiController::scanServerForInstalledContainers(const QString &server
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig)
|
||||
void InstallUiController::updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
containerConfig.container = container;
|
||||
|
||||
|
||||
switch (protocolType) {
|
||||
case Proto::Awg: {
|
||||
containerConfig.protocolConfig = m_awgConfigModel->getProtocolConfig();
|
||||
@@ -264,41 +271,6 @@ bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallUiController::updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateClientConfig(serverId, container, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
m_protocolModel->updateModel(updatedConfig);
|
||||
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolType));
|
||||
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
||||
return;
|
||||
}
|
||||
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
@@ -331,15 +303,15 @@ void InstallUiController::updateServerConfig(const QString &serverId, int contai
|
||||
ContainerConfig oldConfigCopy = oldContainerConfig;
|
||||
InstallController *installController = m_installController;
|
||||
QFuture<ErrorCode> future =
|
||||
SshExecutor::instance().run(serverId, [installController, serverId, container, oldConfigCopy,
|
||||
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
|
||||
newConfigCopy]() mutable -> ErrorCode {
|
||||
return installController->updateServerConfig(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateServerConfig(serverId, container, oldContainerConfig, containerConfig);
|
||||
ErrorCode errorCode = m_installController->updateContainer(serverId, container, oldContainerConfig, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
@@ -479,7 +451,7 @@ void InstallUiController::removeContainer(const QString &serverId, int container
|
||||
});
|
||||
|
||||
InstallController *installController = m_installController;
|
||||
QFuture<ErrorCode> future = SshExecutor::instance().run(serverId,
|
||||
QFuture<ErrorCode> future = QtConcurrent::run(
|
||||
[installController, serverId, container]() -> ErrorCode {
|
||||
return installController->removeContainer(serverId, container);
|
||||
});
|
||||
|
||||
@@ -64,8 +64,7 @@ public slots:
|
||||
|
||||
void scanServerForInstalledContainers(const QString &serverId);
|
||||
|
||||
void updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
|
||||
void removeServer(const QString &serverId);
|
||||
void rebootServer(const QString &serverId);
|
||||
@@ -133,6 +132,7 @@ signals:
|
||||
void cachedProfileCleared(const QString &message);
|
||||
void apiConfigRemoved(const QString &message);
|
||||
|
||||
void noInstalledContainers();
|
||||
void configValidated(bool isValid);
|
||||
|
||||
private:
|
||||
@@ -162,8 +162,6 @@ private:
|
||||
QString m_privateKeyPassphrase;
|
||||
|
||||
void updateProtocolConfigModel(const QString &serverId, int containerIndex, int protocolIndex);
|
||||
|
||||
bool buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig);
|
||||
};
|
||||
|
||||
#endif // INSTALLUICONTROLLER_H
|
||||
|
||||
@@ -156,17 +156,7 @@ void ServersUiController::updateModel()
|
||||
|
||||
m_serversModel->updateModel(m_orderedServerDescriptions, defaultServerId);
|
||||
|
||||
if (!m_processedServerId.isEmpty()) {
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
} else {
|
||||
updateContainersModel();
|
||||
}
|
||||
}
|
||||
updateContainersModel();
|
||||
updateDefaultServerContainersModel();
|
||||
|
||||
if (hadServersFromGatewayBefore != hasServersFromGatewayNow) {
|
||||
@@ -360,14 +350,19 @@ void ServersUiController::setProcessedServerId(const QString &serverId)
|
||||
m_processedServerId = normalizedServerId;
|
||||
|
||||
if (newIndex >= 0) {
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
updateContainersModel();
|
||||
|
||||
for (const auto &description : m_orderedServerDescriptions) {
|
||||
if (description.serverId != normalizedServerId) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
updateContainersModel();
|
||||
if (description.isApiV2) {
|
||||
if (description.isCountrySelectionAvailable && !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
emit updateApiServicesModel();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ signals:
|
||||
void processedContainerIndexChanged(int index);
|
||||
void hasServersFromGatewayApiChanged();
|
||||
void updateApiCountryModel();
|
||||
void updateApiServicesModel();
|
||||
|
||||
public:
|
||||
void updateModel();
|
||||
|
||||
@@ -22,10 +22,12 @@
|
||||
|
||||
SettingsUiController::SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
m_settingsController(settingsController),
|
||||
m_serversController(serversController)
|
||||
m_serversController(serversController),
|
||||
m_languageUiController(languageUiController)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsUiController::onNotificationStateChanged);
|
||||
@@ -37,6 +39,11 @@ SettingsUiController::SettingsUiController(SettingsController* settingsControlle
|
||||
if (m_settingsController->isDevGatewayEnv()) {
|
||||
m_settingsController->enableDevMode();
|
||||
}
|
||||
|
||||
connect(m_settingsController, &SettingsController::localProxySettingsUpdated, this,
|
||||
&SettingsUiController::localProxySettingsUpdated);
|
||||
connect(m_settingsController, &SettingsController::localProxyStartFailed, this,
|
||||
&SettingsUiController::localProxyStartFailed);
|
||||
}
|
||||
|
||||
void SettingsUiController::toggleAmneziaDns(bool enable)
|
||||
@@ -155,13 +162,13 @@ void SettingsUiController::restoreAppConfigFromData(const QByteArray &data)
|
||||
{
|
||||
ErrorCode errorCode = m_settingsController->restoreAppConfigFromData(data);
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
emit appLanguageChanged();
|
||||
emit appLanguageChanged(
|
||||
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageUiController->getCurrentLanguageIndex()));
|
||||
|
||||
bool amneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled();
|
||||
emit amneziaDnsToggled(amneziaDnsEnabled);
|
||||
|
||||
emit restoreBackupFinished();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
} else {
|
||||
emit errorOccurred(errorCode);
|
||||
@@ -176,7 +183,6 @@ QString SettingsUiController::getAppVersion()
|
||||
void SettingsUiController::clearSettings()
|
||||
{
|
||||
m_settingsController->clearSettings();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
emit resetLanguageToSystem();
|
||||
|
||||
@@ -205,8 +211,9 @@ bool SettingsUiController::isAutoStartEnabled()
|
||||
void SettingsUiController::toggleAutoStart(bool enable)
|
||||
{
|
||||
m_settingsController->toggleAutoStart(enable);
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
if (!enable) {
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool SettingsUiController::isStartMinimizedEnabled()
|
||||
@@ -358,3 +365,53 @@ void SettingsUiController::disableHomeAdLabel()
|
||||
m_settingsController->disableHomeAdLabel();
|
||||
emit isHomeAdLabelVisibleChanged(false);
|
||||
}
|
||||
|
||||
bool SettingsUiController::isLocalProxySupported() const
|
||||
{
|
||||
return m_settingsController->isLocalProxySupported();
|
||||
}
|
||||
|
||||
bool SettingsUiController::isLocalProxyHttpEnabled() const
|
||||
{
|
||||
return m_settingsController->isLocalProxyHttpEnabled();
|
||||
}
|
||||
|
||||
int SettingsUiController::localProxyPort() const
|
||||
{
|
||||
return m_settingsController->localProxyPort();
|
||||
}
|
||||
|
||||
QString SettingsUiController::localProxyOwnerId() const
|
||||
{
|
||||
return m_settingsController->localProxyOwnerId();
|
||||
}
|
||||
|
||||
bool SettingsUiController::setLocalProxyPort(int port)
|
||||
{
|
||||
return m_settingsController->setLocalProxyPort(port);
|
||||
}
|
||||
|
||||
bool SettingsUiController::isLocalProxyPortBusy(int port) const
|
||||
{
|
||||
return m_settingsController->isLocalProxyPortBusy(port);
|
||||
}
|
||||
|
||||
bool SettingsUiController::isLocalProxyPortUserDefined() const
|
||||
{
|
||||
return m_settingsController->isLocalProxyPortUserDefined();
|
||||
}
|
||||
|
||||
int SettingsUiController::findFirstAvailableLocalProxyPort(int startPort) const
|
||||
{
|
||||
return m_settingsController->findFirstAvailableLocalProxyPort(startPort);
|
||||
}
|
||||
|
||||
bool SettingsUiController::enableLocalProxy(const QString &ownerId, int port)
|
||||
{
|
||||
return m_settingsController->enableLocalProxy(ownerId, port);
|
||||
}
|
||||
|
||||
void SettingsUiController::disableLocalProxy()
|
||||
{
|
||||
m_settingsController->disableLocalProxy();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "core/controllers/settingsController.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
@@ -15,6 +17,7 @@ class SettingsUiController : public QObject
|
||||
public:
|
||||
explicit SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
|
||||
@@ -29,8 +32,11 @@ public:
|
||||
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
|
||||
|
||||
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
||||
Q_PROPERTY(bool autoStartEnabled READ isAutoStartEnabled NOTIFY autoStartChanged)
|
||||
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
||||
Q_PROPERTY(bool isLocalProxySupported READ isLocalProxySupported CONSTANT)
|
||||
Q_PROPERTY(bool isLocalProxyHttpEnabled READ isLocalProxyHttpEnabled NOTIFY localProxySettingsUpdated)
|
||||
Q_PROPERTY(int localProxyPort READ localProxyPort WRITE setLocalProxyPort NOTIFY localProxySettingsUpdated)
|
||||
Q_PROPERTY(QString localProxyOwnerId READ localProxyOwnerId NOTIFY localProxySettingsUpdated)
|
||||
|
||||
public slots:
|
||||
void toggleAmneziaDns(bool enable);
|
||||
@@ -101,6 +107,17 @@ public slots:
|
||||
bool isHomeAdLabelVisible();
|
||||
void disableHomeAdLabel();
|
||||
|
||||
bool isLocalProxySupported() const;
|
||||
bool isLocalProxyHttpEnabled() const;
|
||||
int localProxyPort() const;
|
||||
QString localProxyOwnerId() const;
|
||||
bool setLocalProxyPort(int port);
|
||||
bool isLocalProxyPortBusy(int port) const;
|
||||
bool isLocalProxyPortUserDefined() const;
|
||||
int findFirstAvailableLocalProxyPort(int startPort) const;
|
||||
bool enableLocalProxy(const QString &ownerId, int port);
|
||||
void disableLocalProxy();
|
||||
|
||||
signals:
|
||||
void primaryDnsChanged();
|
||||
void secondaryDnsChanged();
|
||||
@@ -120,7 +137,7 @@ signals:
|
||||
|
||||
void loggingDisableByWatcher();
|
||||
|
||||
void appLanguageChanged();
|
||||
void appLanguageChanged(const LanguageSettings::AvailableLanguageEnum language);
|
||||
void resetLanguageToSystem();
|
||||
|
||||
void onNotificationStateChanged();
|
||||
@@ -133,12 +150,14 @@ signals:
|
||||
void activityResumed();
|
||||
|
||||
void isHomeAdLabelVisibleChanged(bool visible);
|
||||
void autoStartChanged();
|
||||
void startMinimizedChanged();
|
||||
void localProxySettingsUpdated();
|
||||
void localProxyStartFailed(const QString &message);
|
||||
|
||||
private:
|
||||
SettingsController* m_settingsController;
|
||||
ServersController* m_serversController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,7 +30,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||
switch (role) {
|
||||
case SubscriptionStatusRole: {
|
||||
if (m_accountInfoData.configType == serverConfigUtils::ConfigType::AmneziaFreeV3) {
|
||||
return QStringLiteral("<p><a style=\"color: #28c840;\">%1</a>").arg(tr("Active"));
|
||||
return tr("Active");
|
||||
}
|
||||
|
||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate)
|
||||
|
||||
@@ -27,7 +27,6 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
|
||||
auto userData = client.value(configKey::userData).toObject();
|
||||
|
||||
switch (role) {
|
||||
case ClientIdRole: return client.value(configKey::clientId).toString();
|
||||
case ClientNameRole: return userData.value(configKey::clientName).toString();
|
||||
case CreationDateRole: return userData.value(configKey::creationDate).toString();
|
||||
case LatestHandshakeRole: return userData.value(configKey::latestHandshake).toString();
|
||||
@@ -63,7 +62,6 @@ void ClientManagementModel::updateClientName(int row, const QString &newName)
|
||||
QHash<int, QByteArray> ClientManagementModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[ClientIdRole] = "clientId";
|
||||
roles[ClientNameRole] = "clientName";
|
||||
roles[CreationDateRole] = "creationDate";
|
||||
roles[LatestHandshakeRole] = "latestHandshake";
|
||||
|
||||
@@ -10,8 +10,7 @@ class ClientManagementModel : public QAbstractListModel
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
ClientIdRole = Qt::UserRole + 1,
|
||||
ClientNameRole,
|
||||
ClientNameRole = Qt::UserRole + 1,
|
||||
CreationDateRole,
|
||||
LatestHandshakeRole,
|
||||
DataReceivedRole,
|
||||
|
||||
@@ -23,10 +23,6 @@ public:
|
||||
Q_INVOKABLE int containerFromString(const QString &container) const {
|
||||
return static_cast<int>(amnezia::ContainerUtils::containerFromString(container));
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool isUnsupportedContainer(int containerIndex) const {
|
||||
return amnezia::ContainerUtils::isUnsupportedContainer(static_cast<amnezia::DockerContainer>(containerIndex));
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CONTAINERPROPS_H
|
||||
|
||||
@@ -67,7 +67,6 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
|
||||
case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex);
|
||||
case IsSupportedRole: return ContainerUtils::isSupportedByCurrentPlatform(container);
|
||||
case IsShareableRole: return ContainerUtils::isShareable(container);
|
||||
case IsUnsupportedContainerRole: return ContainerUtils::isUnsupportedContainer(container);
|
||||
case IsVpnContainerRole: return ContainerUtils::containerService(container) == ServiceType::Vpn;
|
||||
case IsServiceContainerRole: return ContainerUtils::containerService(container) == ServiceType::Other;
|
||||
case IsIpsecRole: return container == DockerContainer::Ipsec;
|
||||
@@ -143,8 +142,7 @@ bool ContainersModel::hasInstalledProtocols()
|
||||
|
||||
bool ContainersModel::isInstallationAllowed(DockerContainer container)
|
||||
{
|
||||
return container != DockerContainer::Awg
|
||||
&& !ContainerUtils::isUnsupportedContainer(container);
|
||||
return container != DockerContainer::Awg;
|
||||
}
|
||||
|
||||
void ContainersModel::openContainerSettings(int containerIndex)
|
||||
@@ -178,7 +176,6 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
|
||||
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
|
||||
roles[IsSupportedRole] = "isSupported";
|
||||
roles[IsShareableRole] = "isShareable";
|
||||
roles[IsUnsupportedContainerRole] = "isUnsupportedContainer";
|
||||
roles[IsInstallationAllowedRole] = "isInstallationAllowed";
|
||||
roles[InstallPageOrderRole] = "installPageOrder";
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ public:
|
||||
IsSupportedRole,
|
||||
IsShareableRole,
|
||||
|
||||
IsUnsupportedContainerRole,
|
||||
|
||||
InstallPageOrderRole,
|
||||
|
||||
// Container type check roles
|
||||
|
||||
@@ -152,4 +152,3 @@ ServerCredentials ServersModel::serverCredentials(int index) const
|
||||
}
|
||||
return m_descriptions.at(index).selfHostedSshCredentials;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
class ServersModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
NameRole = Qt::UserRole + 1,
|
||||
|
||||
@@ -56,17 +56,14 @@ ListViewType {
|
||||
return
|
||||
}
|
||||
|
||||
var containerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
|
||||
if (!isInstalled) {
|
||||
ServersUiController.processedContainerIndex = containerIndex
|
||||
if (checked) {
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, proxyDefaultServerContainersModel.mapToSource(index))
|
||||
} else {
|
||||
ServersUiController.processedContainerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
|
||||
containersDropDown.closeTriggered()
|
||||
return
|
||||
}
|
||||
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, containerIndex)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -5,6 +5,7 @@ import QtQuick.Layouts
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import PageEnum 1.0
|
||||
import ContainerProps 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
@@ -12,6 +12,7 @@ Item {
|
||||
property int headerTextMaximumLineCount: 2
|
||||
property int headerTextElide: Qt.ElideRight
|
||||
property string descriptionText
|
||||
property string descriptionColor: AmneziaStyle.color.mutedGray
|
||||
property string descriptionLinkText
|
||||
property string descriptionLinkUrl
|
||||
property alias headerRow: headerRow
|
||||
@@ -42,7 +43,7 @@ Item {
|
||||
Layout.topMargin: 16
|
||||
Layout.fillWidth: true
|
||||
text: root.descriptionText
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
color: root.descriptionColor
|
||||
visible: root.descriptionText !== ""
|
||||
}
|
||||
|
||||
|
||||
@@ -6,36 +6,8 @@ Menu {
|
||||
|
||||
popupType: Popup.Native
|
||||
|
||||
property Item inputBlocker: null
|
||||
|
||||
Component {
|
||||
id: inputBlockerComponent
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
onAboutToShow: {
|
||||
if (!textObj || !textObj.window) {
|
||||
return
|
||||
}
|
||||
|
||||
const contentItem = textObj.window.contentItem
|
||||
if (!inputBlocker) {
|
||||
inputBlocker = inputBlockerComponent.createObject(contentItem)
|
||||
} else {
|
||||
inputBlocker.parent = contentItem
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (inputBlocker) {
|
||||
inputBlocker.destroy()
|
||||
inputBlocker = null
|
||||
}
|
||||
}
|
||||
onAboutToShow: blocker.enabled = true
|
||||
onClosed: blocker.enabled = false
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("C&ut")
|
||||
@@ -59,4 +31,11 @@ Menu {
|
||||
enabled: textObj.length > 0
|
||||
onTriggered: textObj.selectAll()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: blocker
|
||||
z: 2
|
||||
enabled: false
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ Item {
|
||||
property string headerTextColor: AmneziaStyle.color.mutedGray
|
||||
|
||||
property alias errorText: errorField.text
|
||||
property bool clearErrorOnTextChanged: true
|
||||
property bool checkEmptyText: false
|
||||
property bool rightButtonClickedOnEnter: false
|
||||
|
||||
@@ -135,7 +136,9 @@ Item {
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
root.errorText = ""
|
||||
if (root.clearErrorOnTextChanged) {
|
||||
root.errorText = ""
|
||||
}
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
|
||||
@@ -25,8 +25,8 @@ PageType {
|
||||
|
||||
filters: [
|
||||
ValueFilter {
|
||||
roleName: "serverId"
|
||||
value: ServersUiController.processedServerId
|
||||
roleName: "isCurrentlyProcessed"
|
||||
value: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -440,7 +440,8 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
}
|
||||
|
||||
var noButtonFunction = function() {}
|
||||
|
||||
@@ -561,7 +561,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
}
|
||||
|
||||
var noButtonFunction = function() {}
|
||||
|
||||
@@ -434,7 +434,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
|
||||
@@ -128,7 +128,8 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
}
|
||||
var noButtonFunction = function() {}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
|
||||
@@ -129,7 +129,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
|
||||
@@ -112,7 +112,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -279,7 +279,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -17,10 +17,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
enableTimer: false
|
||||
|
||||
property bool portDirty: false
|
||||
|
||||
function formatTransport(value) {
|
||||
if (value === "raw") return "RAW (TCP)"
|
||||
if (value === "xhttp") return "XHTTP"
|
||||
@@ -43,8 +39,8 @@ PageType {
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (backButton.enabled && backButton.activeFocus) {
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
@@ -64,6 +60,8 @@ PageType {
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
property alias focusItemId: textFieldWithHeaderType.textField
|
||||
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
@@ -109,32 +107,13 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
enabled: listView.enabled
|
||||
headerText: qsTr("Port")
|
||||
|
||||
Binding {
|
||||
target: textFieldWithHeaderType.textField
|
||||
property: "text"
|
||||
value: port
|
||||
when: !textFieldWithHeaderType.textField.activeFocus
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
textField.text: port
|
||||
textField.maximumLength: 5
|
||||
textField.validator: IntValidator {
|
||||
bottom: 1; top: 65535
|
||||
}
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus && textField.text === "" && port !== "") {
|
||||
textField.text = port
|
||||
}
|
||||
}
|
||||
textField.onTextChanged: {
|
||||
root.portDirty = (textField.text !== port)
|
||||
}
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== port) {
|
||||
port = textField.text
|
||||
}
|
||||
root.portDirty = false
|
||||
if (textField.text !== port) port = textField.text
|
||||
}
|
||||
checkEmptyText: true
|
||||
}
|
||||
@@ -193,8 +172,9 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
visible: listView.enabled
|
||||
&& (XrayConfigModel.hasUnsavedChanges || root.portDirty)
|
||||
enabled: visible && textFieldWithHeaderType.textField.text !== ""
|
||||
&& (XrayConfigModel.hasUnsavedChanges
|
||||
|| textFieldWithHeaderType.textField.text !== port)
|
||||
enabled: visible && textFieldWithHeaderType.errorText === ""
|
||||
text: qsTr("Save")
|
||||
onClicked: function() {
|
||||
forceActiveFocus()
|
||||
@@ -213,7 +193,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) saveButton.forceActiveFocus()
|
||||
|
||||
@@ -742,7 +742,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -95,7 +95,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -211,7 +211,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -208,7 +208,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -179,7 +179,7 @@ PageType {
|
||||
function mtProxyScheduleUpdate(closePage) {
|
||||
var cp = closePage === undefined ? false : closePage
|
||||
Qt.callLater(function () {
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
|
||||
tempPort = portTextField.textField.text
|
||||
tempUsername = usernameTextField.textField.text
|
||||
tempPassword = passwordTextField.textField.text
|
||||
|
||||
@@ -154,7 +154,7 @@ PageType {
|
||||
function telemtScheduleUpdate(closePage) {
|
||||
var cp = closePage === undefined ? false : closePage
|
||||
Qt.callLater(function () {
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -100,12 +100,6 @@ PageType {
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
textFormat: Text.RichText
|
||||
text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this URL.")
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
|
||||
@@ -30,16 +30,6 @@ PageType {
|
||||
root.isInAppPurchase = ApiAccountInfoModel.data("isInAppPurchase")
|
||||
}
|
||||
|
||||
function selectConnectionCountry(countryIndex, countryCode, countryName) {
|
||||
if (countryIndex === ApiCountryModel.currentIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
PageController.showBusyIndicator(true)
|
||||
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.updateSubscriptionState()
|
||||
}
|
||||
@@ -93,7 +83,7 @@ PageType {
|
||||
|
||||
model: ApiCountryModel
|
||||
|
||||
currentIndex: ApiCountryModel.currentIndex
|
||||
currentIndex: 0
|
||||
|
||||
ButtonGroup {
|
||||
id: containersRadioButtonGroup
|
||||
@@ -214,7 +204,15 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
root.selectConnectionCountry(index, countryCode, countryName)
|
||||
if (index !== ApiCountryModel.currentIndex) {
|
||||
PageController.showBusyIndicator(true)
|
||||
var prevIndex = ApiCountryModel.currentIndex
|
||||
ApiCountryModel.currentIndex = index
|
||||
if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)) {
|
||||
ApiCountryModel.currentIndex = prevIndex
|
||||
}
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: {
|
||||
|
||||
@@ -320,11 +320,27 @@ PageType {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: vpnKey
|
||||
id: connectionSwitcher
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: warning.visible ? 16 : 0
|
||||
text: qsTr("Connection")
|
||||
descriptionText: SettingsController.isLocalProxySupported
|
||||
? qsTr("Protocol selection and local proxy setup")
|
||||
: qsTr("Protocol selection")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsConnectionType)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: vpnKey
|
||||
|
||||
Layout.fillWidth: true
|
||||
visible: footer.isVisibleForAmneziaFree
|
||||
|
||||
text: qsTr("Subscription Key")
|
||||
|
||||
@@ -108,9 +108,9 @@ PageType {
|
||||
text: qsTr("Auto start")
|
||||
descriptionText: qsTr("Launch the application every time the device is starts")
|
||||
|
||||
checked: SettingsController.autoStartEnabled
|
||||
checked: SettingsController.isAutoStartEnabled()
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.autoStartEnabled) {
|
||||
if (checked !== SettingsController.isAutoStartEnabled()) {
|
||||
SettingsController.toggleAutoStart(checked)
|
||||
}
|
||||
}
|
||||
@@ -154,10 +154,10 @@ PageType {
|
||||
text: qsTr("Start minimized")
|
||||
descriptionText: qsTr("Launch application minimized (works with autostart option turned on)")
|
||||
|
||||
enabled: SettingsController.autoStartEnabled
|
||||
enabled: SettingsController.isAutoStartEnabled()
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
|
||||
checked: SettingsController.autoStartEnabled && SettingsController.startMinimized
|
||||
checked: SettingsController.isAutoStartEnabled() && SettingsController.startMinimized
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.startMinimized) {
|
||||
SettingsController.toggleStartMinimized(checked)
|
||||
@@ -166,7 +166,7 @@ PageType {
|
||||
}
|
||||
|
||||
DividerType {
|
||||
visible: !GC.isMobile() && ServersUiController.hasServersFromGatewayApi
|
||||
visible: !GC.isMobile()
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
|
||||
98
client/ui/qml/Pages2/PageSettingsConnectionType.qml
Normal file
98
client/ui/qml/Pages2/PageSettingsConnectionType.qml
Normal file
@@ -0,0 +1,98 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("Connection")
|
||||
}
|
||||
}
|
||||
|
||||
model: 1
|
||||
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
LabelWithButtonType {
|
||||
id: vpnProtocolButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("VPN protocol")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsServerProtocols)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: localProxyButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
visible: SettingsController.isLocalProxySupported && ServersUiController.processedServerIsPremium
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
|
||||
text: qsTr("Local proxy")
|
||||
descriptionText: SettingsController.isLocalProxyHttpEnabled ? qsTr("Running: 127.0.0.1:%1").arg(SettingsController.localProxyPort || 0)
|
||||
: qsTr("Off")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsLocalProxy)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
visible: SettingsController.isLocalProxySupported
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
426
client/ui/qml/Pages2/PageSettingsLocalProxy.qml
Normal file
426
client/ui/qml/Pages2/PageSettingsLocalProxy.qml
Normal file
@@ -0,0 +1,426 @@
|
||||
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
|
||||
|
||||
readonly property int localProxyPortMin: 1024
|
||||
readonly property int localProxyPortMax: 65535
|
||||
readonly property int defaultLocalProxyPort: 10808
|
||||
|
||||
property string portValidationError: ""
|
||||
property int pendingStartRequestedPort: -1
|
||||
property int pendingStartAutoSelectedPort: -1
|
||||
property bool pendingStartVpnWasActive: false
|
||||
property bool pendingEnableAfterVpnDisconnect: false
|
||||
property string pendingEnableServerId: ""
|
||||
property int pendingEnableRequestedPort: -1
|
||||
property int pendingEnableAutoSelectedPort: -1
|
||||
property int pendingEnablePortToUse: -1
|
||||
|
||||
function clearPendingEnableAfterVpnDisconnect() {
|
||||
root.pendingEnableAfterVpnDisconnect = false
|
||||
root.pendingEnableServerId = ""
|
||||
root.pendingEnableRequestedPort = -1
|
||||
root.pendingEnableAutoSelectedPort = -1
|
||||
root.pendingEnablePortToUse = -1
|
||||
}
|
||||
|
||||
function enableLocalProxyNow(serverId, requestedPort, autoSelectedPort, portToEnable, vpnWasActive) {
|
||||
if (!SettingsController.enableLocalProxy(serverId, portToEnable)) {
|
||||
PageController.showNotificationMessage(qsTr("Failed to enable local proxy. Check the port (%1-%2).")
|
||||
.arg(root.localProxyPortMin)
|
||||
.arg(root.localProxyPortMax))
|
||||
return false
|
||||
}
|
||||
|
||||
root.pendingStartRequestedPort = requestedPort
|
||||
root.pendingStartAutoSelectedPort = autoSelectedPort
|
||||
root.pendingStartVpnWasActive = vpnWasActive
|
||||
startSuccessToastTimer.restart()
|
||||
return true
|
||||
}
|
||||
|
||||
function getPortField() {
|
||||
var item = listView.itemAtIndex(0)
|
||||
return item !== null ? item.children[0] : null
|
||||
}
|
||||
|
||||
function computePortErrorText() {
|
||||
var portField = getPortField()
|
||||
if (portField === null) return ""
|
||||
const text = portField.textField.text.trim()
|
||||
if (text === "") {
|
||||
return qsTr("Enter a port")
|
||||
}
|
||||
const value = parseInt(text)
|
||||
if (isNaN(value) || value < root.localProxyPortMin || value > root.localProxyPortMax) {
|
||||
return qsTr("Port must be between %1 and %2")
|
||||
.arg(root.localProxyPortMin)
|
||||
.arg(root.localProxyPortMax)
|
||||
}
|
||||
if (SettingsController.isLocalProxyPortBusy(value)) {
|
||||
return qsTr("Port %1 is already in use on this device. Choose another one")
|
||||
.arg(value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function handleLocalProxyToggle(checked) {
|
||||
if (checked) {
|
||||
if (!ServersUiController.processedServerIsPremium) {
|
||||
PageController.showNotificationMessage(qsTr("Local proxy is available only for Amnezia Premium"))
|
||||
return
|
||||
}
|
||||
const wasVpnActive = ConnectionController.isConnected || ConnectionController.isConnectionInProgress
|
||||
|
||||
let serverId = ServersUiController.processedServerId
|
||||
if (!serverId) {
|
||||
serverId = ServersUiController.defaultServerId
|
||||
}
|
||||
if (!serverId) {
|
||||
PageController.showNotificationMessage(qsTr("Unable to determine the current server"))
|
||||
return
|
||||
}
|
||||
|
||||
if (SettingsController.isLocalProxyHttpEnabled
|
||||
&& SettingsController.localProxyOwnerId
|
||||
&& SettingsController.localProxyOwnerId !== serverId) {
|
||||
PageController.showNotificationMessage(qsTr("Local proxy is already enabled for another server"))
|
||||
return
|
||||
}
|
||||
|
||||
const requestedPort = SettingsController.localProxyPort
|
||||
if (requestedPort < root.localProxyPortMin || requestedPort > root.localProxyPortMax) {
|
||||
PageController.showNotificationMessage(qsTr("Port must be between %1 and %2")
|
||||
.arg(root.localProxyPortMin)
|
||||
.arg(root.localProxyPortMax))
|
||||
return
|
||||
}
|
||||
|
||||
let autoSelectedPort = -1
|
||||
if (SettingsController.isLocalProxyPortBusy(requestedPort)) {
|
||||
if (SettingsController.isLocalProxyPortUserDefined()
|
||||
|| requestedPort !== root.defaultLocalProxyPort) {
|
||||
PageController.showNotificationMessage(qsTr("Port %1 is already in use on this device. Choose another one")
|
||||
.arg(requestedPort))
|
||||
return
|
||||
}
|
||||
|
||||
autoSelectedPort = SettingsController.findFirstAvailableLocalProxyPort(root.defaultLocalProxyPort + 1)
|
||||
if (autoSelectedPort <= 0) {
|
||||
PageController.showNotificationMessage(qsTr("Port %1 is already in use on this device. Choose another one")
|
||||
.arg(requestedPort))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const portToEnable = autoSelectedPort > 0 ? autoSelectedPort : requestedPort
|
||||
if (wasVpnActive) {
|
||||
root.pendingEnableAfterVpnDisconnect = true
|
||||
root.pendingEnableServerId = serverId
|
||||
root.pendingEnableRequestedPort = requestedPort
|
||||
root.pendingEnableAutoSelectedPort = autoSelectedPort
|
||||
root.pendingEnablePortToUse = portToEnable
|
||||
ConnectionController.closeConnection()
|
||||
return
|
||||
}
|
||||
|
||||
root.enableLocalProxyNow(serverId, requestedPort, autoSelectedPort, portToEnable, false)
|
||||
} else {
|
||||
startSuccessToastTimer.stop()
|
||||
root.clearPendingEnableAfterVpnDisconnect()
|
||||
root.pendingStartRequestedPort = -1
|
||||
root.pendingStartAutoSelectedPort = -1
|
||||
root.pendingStartVpnWasActive = false
|
||||
SettingsController.disableLocalProxy()
|
||||
PageController.showNotificationMessage(qsTr("Local proxy stopped"))
|
||||
}
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
HeaderTypeWithSwitcher {
|
||||
id: localProxyHeader
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
headerText: qsTr("Local Proxy")
|
||||
descriptionText: qsTr("Use a proxy to route selected apps (for example, the CensorTracker extension) through Amnezia Premium.")
|
||||
showSwitcher: ServersUiController.processedServerIsPremium
|
||||
switcher {
|
||||
checked: SettingsController.isLocalProxyHttpEnabled
|
||||
}
|
||||
switcherFunction: function(checked) {
|
||||
// Ignore UI sync toggles; react only to real state change intent.
|
||||
if (checked === SettingsController.isLocalProxyHttpEnabled) {
|
||||
return
|
||||
}
|
||||
root.handleLocalProxyToggle(checked)
|
||||
// Keep checked declaratively linked after any user interaction path.
|
||||
localProxyHeader.switcher.checked = Qt.binding(function() {
|
||||
return SettingsController.isLocalProxyHttpEnabled
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 12
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
color: localProxyHeader.descriptionColor
|
||||
text: qsTr("Only one can be on at a time: VPN or local proxy.")
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 8
|
||||
Layout.bottomMargin: 28
|
||||
implicitHeight: 32
|
||||
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
disabledColor: AmneziaStyle.color.mutedGray
|
||||
textColor: AmneziaStyle.color.goldenApricot
|
||||
|
||||
text: qsTr("Learn more")
|
||||
clickedFunc: function() {
|
||||
const path = LanguageModel.currentLanguageName === "Русский"
|
||||
? "ru/documentation/instructions/local-proxy"
|
||||
: "documentation/instructions/local-proxy"
|
||||
Qt.openUrlExternally(LanguageModel.getCurrentDocsUrl(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model: 1 // fake model to force the ListView to be created without a model
|
||||
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
spacing: 16
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: portField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("Address and port")
|
||||
buttonText: qsTr("Copy")
|
||||
errorText: root.portValidationError
|
||||
clearErrorOnTextChanged: false
|
||||
|
||||
enabled: true
|
||||
rightButtonClickedOnEnter: false
|
||||
|
||||
clickedFunc: function() {
|
||||
const portText = portField.effectivePortText()
|
||||
GC.copyToClipBoard("127.0.0.1:" + portText)
|
||||
PageController.showNotificationMessage(qsTr("Copied: 127.0.0.1:%1").arg(portText))
|
||||
}
|
||||
|
||||
textField.validator: RegularExpressionValidator {
|
||||
regularExpression: /^[0-9]{0,5}$/
|
||||
}
|
||||
textField.leftPadding: portPrefix.implicitWidth
|
||||
textField.placeholderText: root.defaultLocalProxyPort.toString()
|
||||
textField.inputMethodHints: Qt.ImhDigitsOnly | Qt.ImhNoPredictiveText
|
||||
|
||||
function syncPortValue() {
|
||||
const port = SettingsController.localProxyPort
|
||||
const isValidPort = port >= root.localProxyPortMin && port <= root.localProxyPortMax
|
||||
textField.text = isValidPort ? port.toString() : ""
|
||||
}
|
||||
|
||||
function portValue() {
|
||||
const value = parseInt(textField.text)
|
||||
return isNaN(value) ? -1 : value
|
||||
}
|
||||
|
||||
function effectivePortText() {
|
||||
const value = portValue()
|
||||
if (value >= root.localProxyPortMin && value <= root.localProxyPortMax) {
|
||||
return value.toString()
|
||||
}
|
||||
const fallback = SettingsController.localProxyPort
|
||||
if (fallback >= root.localProxyPortMin && fallback <= root.localProxyPortMax) {
|
||||
return fallback.toString()
|
||||
}
|
||||
return root.defaultLocalProxyPort.toString()
|
||||
}
|
||||
|
||||
Component.onCompleted: syncPortValue()
|
||||
|
||||
textField.onTextChanged: {
|
||||
if (textField.activeFocus) {
|
||||
root.portValidationError = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: portPrefix
|
||||
|
||||
parent: portField.textField
|
||||
text: "127.0.0.1:"
|
||||
color: AmneziaStyle.color.paleGray
|
||||
font.pixelSize: portField.textField.font.pixelSize
|
||||
font.weight: portField.textField.font.weight
|
||||
font.family: portField.textField.font.family
|
||||
z: 1
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("Save")
|
||||
enabled: true
|
||||
|
||||
clickedFunc: function() {
|
||||
if (SettingsController.isLocalProxyHttpEnabled) {
|
||||
PageController.showNotificationMessage(qsTr("Disable Local Proxy to change the port"))
|
||||
return
|
||||
}
|
||||
const validationError = root.computePortErrorText()
|
||||
root.portValidationError = validationError
|
||||
if (validationError !== "") {
|
||||
return
|
||||
}
|
||||
|
||||
const value = portField.portValue()
|
||||
if (!SettingsController.setLocalProxyPort(value)) {
|
||||
PageController.showNotificationMessage(qsTr("Failed to save port. Valid range: %1-%2")
|
||||
.arg(root.localProxyPortMin)
|
||||
.arg(root.localProxyPortMax))
|
||||
} else {
|
||||
PageController.showNotificationMessage(qsTr("Port saved: %1").arg(value))
|
||||
}
|
||||
portField.syncPortValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: startSuccessToastTimer
|
||||
interval: 250
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (!SettingsController.isLocalProxyHttpEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (root.pendingStartAutoSelectedPort > 0) {
|
||||
PageController.showNotificationMessage(qsTr("Port %1 is in use — selected free port %2.")
|
||||
.arg(root.defaultLocalProxyPort)
|
||||
.arg(root.pendingStartAutoSelectedPort))
|
||||
} else if (root.pendingStartVpnWasActive && root.pendingStartRequestedPort > 0) {
|
||||
PageController.showNotificationMessage(qsTr("VPN turned off. Local proxy is running: 127.0.0.1:%1")
|
||||
.arg(root.pendingStartRequestedPort))
|
||||
} else if (root.pendingStartRequestedPort > 0) {
|
||||
PageController.showNotificationMessage(qsTr("Local proxy is running: 127.0.0.1:%1")
|
||||
.arg(root.pendingStartRequestedPort))
|
||||
}
|
||||
|
||||
root.pendingStartRequestedPort = -1
|
||||
root.pendingStartAutoSelectedPort = -1
|
||||
root.pendingStartVpnWasActive = false
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ConnectionController
|
||||
|
||||
function onConnectionStateChanged() {
|
||||
if (!root.pendingEnableAfterVpnDisconnect) {
|
||||
return
|
||||
}
|
||||
|
||||
if (ConnectionController.isConnected || ConnectionController.isConnectionInProgress) {
|
||||
return
|
||||
}
|
||||
|
||||
const serverId = root.pendingEnableServerId
|
||||
const requestedPort = root.pendingEnableRequestedPort
|
||||
const autoSelectedPort = root.pendingEnableAutoSelectedPort
|
||||
const portToEnable = root.pendingEnablePortToUse
|
||||
root.clearPendingEnableAfterVpnDisconnect()
|
||||
|
||||
root.enableLocalProxyNow(serverId, requestedPort, autoSelectedPort, portToEnable, true)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsController
|
||||
|
||||
function onLocalProxySettingsUpdated() {
|
||||
var portField = root.getPortField()
|
||||
if (portField !== null && !portField.textField.activeFocus) {
|
||||
portField.syncPortValue()
|
||||
}
|
||||
}
|
||||
|
||||
function onLocalProxyStartFailed(message) {
|
||||
startSuccessToastTimer.stop()
|
||||
root.pendingStartRequestedPort = -1
|
||||
root.pendingStartAutoSelectedPort = -1
|
||||
root.pendingStartVpnWasActive = false
|
||||
PageController.showNotificationMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ServersUiController
|
||||
|
||||
function onProcessedServerIdChanged() {
|
||||
var portField = root.getPortField()
|
||||
if (portField !== null && !portField.textField.activeFocus) {
|
||||
portField.syncPortValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,17 @@ PageType {
|
||||
function onRebootServerFinished(finishedMessage) {
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
function onRemoveAllContainersFinished(finishedMessage) {
|
||||
PageController.closePage() // close deInstalling page
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
function onRemoveContainerFinished(finishedMessage) {
|
||||
PageController.closePage() // close deInstalling page
|
||||
PageController.closePage() // close page with remove button
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -17,8 +17,7 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool isUnsupportedContainer: ContainerProps.isUnsupportedContainer(ServersUiController.processedContainerIndex)
|
||||
property bool isClearCacheVisible: !isUnsupportedContainer && ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex)
|
||||
property bool isClearCacheVisible: ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex)
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
@@ -53,11 +52,10 @@ PageType {
|
||||
Layout.bottomMargin: 32
|
||||
|
||||
headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings")
|
||||
descriptionText: root.isUnsupportedContainer ? qsTr("This protocol is no longer supported.") : ""
|
||||
}
|
||||
}
|
||||
|
||||
model: root.isUnsupportedContainer ? null : ProtocolsModel
|
||||
model: ProtocolsModel
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: delegateContent
|
||||
|
||||
@@ -29,10 +29,6 @@ PageType {
|
||||
ValueFilter {
|
||||
roleName: "isInstallationAllowed"
|
||||
value: true
|
||||
},
|
||||
ValueFilter {
|
||||
roleName: "isUnsupportedContainer"
|
||||
value: false
|
||||
}
|
||||
]
|
||||
sorters: RoleSorter {
|
||||
|
||||
@@ -382,10 +382,6 @@ PageType {
|
||||
ValueFilter {
|
||||
roleName: "isShareable"
|
||||
value: true
|
||||
},
|
||||
ValueFilter {
|
||||
roleName: "isUnsupportedContainer"
|
||||
value: false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -400,19 +396,9 @@ PageType {
|
||||
target: serverSelector
|
||||
|
||||
function onServerSelectorIndexChanged() {
|
||||
if (!proxyContainersModel.count) {
|
||||
root.shareButtonEnabled = false
|
||||
return
|
||||
}
|
||||
|
||||
var defaultContainer = proxyContainersModel.mapFromSource(
|
||||
ServersUiController.serverDefaultContainer(ServersUiController.processedServerId))
|
||||
if (defaultContainer < 0) {
|
||||
defaultContainer = 0
|
||||
}
|
||||
|
||||
var defaultContainer = proxyContainersModel.mapFromSource(ServersUiController.serverDefaultContainer(ServersUiController.processedServerId))
|
||||
containerSelectorListView.selectedIndex = defaultContainer
|
||||
containerSelectorListView.positionViewAtIndex(defaultContainer, ListView.Beginning)
|
||||
containerSelectorListView.positionViewAtIndex(selectedIndex, ListView.Beginning)
|
||||
containerSelectorListView.triggerCurrentItem()
|
||||
}
|
||||
}
|
||||
@@ -851,10 +837,11 @@ PageType {
|
||||
var noButtonFunction = function() {
|
||||
}
|
||||
|
||||
if (ConnectionController.isRevokeBlockedDuringActiveConnection(
|
||||
ServersUiController.processedServerId,
|
||||
ServersUiController.processedContainerIndex,
|
||||
clientId)) {
|
||||
var isActiveConfigForCurrentClient = ServersUiController.isDefaultServerCurrentlyProcessed()
|
||||
&& ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex
|
||||
|
||||
if ((ConnectionController.isConnectionInProgress || ConnectionController.isConnected)
|
||||
&& isActiveConfigForCurrentClient) {
|
||||
PageController.showNotificationMessage("Unable to revoke current config during active connection")
|
||||
} else {
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
|
||||
@@ -105,19 +105,6 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
objectName: "connectionControllerConnections"
|
||||
|
||||
target: ConnectionController
|
||||
|
||||
function onNoInstalledContainers() {
|
||||
PageController.setTriggeredByConnectButton(true)
|
||||
|
||||
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardEasy)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
objectName: "installControllerConnections"
|
||||
|
||||
@@ -166,19 +153,11 @@ PageType {
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
function onRemoveAllContainersFinished(finishedMessage) {
|
||||
if (tabBarStackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageDeinstalling)) {
|
||||
PageController.closePage()
|
||||
}
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
function onNoInstalledContainers() {
|
||||
PageController.setTriggeredByConnectButton(true)
|
||||
|
||||
function onRemoveContainerFinished(finishedMessage) {
|
||||
if (tabBarStackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageDeinstalling)) {
|
||||
PageController.closePage()
|
||||
}
|
||||
PageController.closePage()
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardEasy)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -234,8 +234,6 @@ Window {
|
||||
DrawerType2 {
|
||||
id: privateKeyPassphraseDrawer
|
||||
|
||||
property bool isCloseByUser: false
|
||||
|
||||
anchors.fill: parent
|
||||
expandedHeight: root.height * 0.35 + PageController.safeAreaBottomMargin + PageController.imeHeight
|
||||
|
||||
@@ -255,11 +253,6 @@ Window {
|
||||
}
|
||||
|
||||
function onAboutToHide() {
|
||||
if (privateKeyPassphraseDrawer.isCloseByUser === false) {
|
||||
privateKeyPassphraseDrawer.isCloseByUser = true
|
||||
PageController.passphraseRequestDrawerClosed("")
|
||||
}
|
||||
|
||||
if (passphrase.textField.text !== "") {
|
||||
PageController.showBusyIndicator(true)
|
||||
}
|
||||
@@ -300,7 +293,6 @@ Window {
|
||||
text: qsTr("Save")
|
||||
|
||||
clickedFunc: function() {
|
||||
privateKeyPassphraseDrawer.isCloseByUser = true
|
||||
privateKeyPassphraseDrawer.closeTriggered()
|
||||
PageController.passphraseRequestDrawerClosed(passphrase.textField.text)
|
||||
}
|
||||
|
||||
@@ -105,6 +105,8 @@
|
||||
<file>Pages2/PageSettingsDns.qml</file>
|
||||
<file>Pages2/PageSettingsKillSwitch.qml</file>
|
||||
<file>Pages2/PageSettingsKillSwitchExceptions.qml</file>
|
||||
<file>Pages2/PageSettingsConnectionType.qml</file>
|
||||
<file>Pages2/PageSettingsLocalProxy.qml</file>
|
||||
<file>Pages2/PageSettingsLogging.qml</file>
|
||||
<file>Pages2/PageSettingsServerData.qml</file>
|
||||
<file>Pages2/PageSettingsServerInfo.qml</file>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user