diff --git a/.gitignore b/.gitignore index 88a3b397..7de64e4b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ deploy/build/* deploy/build_32/* deploy/build_64/* winbuild*.bat +.cache/ # Qt-es diff --git a/.gitmodules b/.gitmodules index 453a8ee4..c96dd6bc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "client/3rd/wireguard-apple"] - path = client/3rd/wireguard-apple - url = https://github.com/WireGuard/wireguard-apple [submodule "client/3rd/OpenVPNAdapter"] path = client/3rd/OpenVPNAdapter url = https://github.com/amnezia-vpn/OpenVPNAdapter.git @@ -25,3 +22,6 @@ [submodule "client/3rd-prebuilt"] path = client/3rd-prebuilt url = https://github.com/amnezia-vpn/3rd-prebuilt +[submodule "client/3rd/awg-apple"] + path = client/3rd/awg-apple + url = https://github.com/amnezia-vpn/awg-apple diff --git a/CMakeLists.txt b/CMakeLists.txt index f6f94a23..2e7be435 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,14 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 3.1.0.1 +project(${PROJECT} VERSION 4.0.8.6 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-09-21") + +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}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index e8795854..ac32d335 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit e8795854a5cf27004fe78caecc90a961688d1d41 +Subproject commit ac32d33555bd62f0b0af314b1e5119d6d78a1a4e diff --git a/client/3rd/awg-apple b/client/3rd/awg-apple new file mode 160000 index 00000000..fab07138 --- /dev/null +++ b/client/3rd/awg-apple @@ -0,0 +1 @@ +Subproject commit fab07138dbab06ac0de256021e47e273f4df8e88 diff --git a/client/3rd/qtkeychain b/client/3rd/qtkeychain index c6f0b663..8bbaa6d8 160000 --- a/client/3rd/qtkeychain +++ b/client/3rd/qtkeychain @@ -1 +1 @@ -Subproject commit c6f0b66318f8da6917fb4681103f7303b1836194 +Subproject commit 8bbaa6d8302cf0747d9786ace4dd13c7fb746502 diff --git a/client/3rd/wireguard-apple b/client/3rd/wireguard-apple deleted file mode 160000 index 23618f99..00000000 --- a/client/3rd/wireguard-apple +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 23618f994f17d8ad8f2f65d79b4a1e8a0830b334 diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 7e7837ac..6c4f1ae4 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -10,30 +10,34 @@ set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen") set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen") set(PACKAGES - Widgets Core Gui Network Xml + Core Gui Network Xml RemoteObjects Quick Svg QuickControls2 Core5Compat Concurrent LinguistTools ) + if(IOS) - set(PACKAGES - ${PACKAGES} - Multimedia - ) + set(PACKAGES ${PACKAGES} Multimedia) +endif() + +if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) + set(PACKAGES ${PACKAGES} Widgets) endif() find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES}) set(LIBS ${LIBS} - Qt6::Widgets Qt6::Core Qt6::Gui + Qt6::Core Qt6::Gui Qt6::Network Qt6::Xml Qt6::RemoteObjects Qt6::Quick Qt6::Svg Qt6::QuickControls2 Qt6::Core5Compat Qt6::Concurrent ) + if(IOS) - set(LIBS - ${LIBS} - Qt6::Multimedia - ) + set(LIBS ${LIBS} Qt6::Multimedia) +endif() + +if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) + set(LIBS ${LIBS} Qt6::Widgets) endif() qt_standard_project_setup() @@ -46,12 +50,30 @@ endif() qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) -qt6_add_translations(${PROJECT} TS_FILES +# -- i18n begin +set(CMAKE_AUTORCC ON) + +set(AMNEZIAVPN_TS_FILES ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts + ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts ) +file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui) + +qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES}) + +set(QM_FILE_LIST "") +foreach(FILE ${AMNEZIAVPN_QM_FILES}) + get_filename_component(QM_FILE_NAME ${FILE} NAME) + list(APPEND QM_FILE_LIST "${QM_FILE_NAME}") +endforeach() +string(REPLACE ";" "" QM_FILE_LIST ${QM_FILE_LIST}) + +configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) +qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) +# -- i18n end + if(IOS) - #execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/scripts/run-build-cloak.sh) execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) endif() @@ -91,7 +113,6 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h ${CMAKE_CURRENT_LIST_DIR}/ui/pages.h ${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h - ${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.h ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h ${CMAKE_CURRENT_BINARY_DIR}/version.h @@ -128,7 +149,6 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/core/servercontroller.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp - ${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp @@ -162,20 +182,33 @@ file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/ file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h) file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp) -file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h) -file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp) +file(GLOB UI_MODELS_H CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h + ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h + ${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h +) +file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp +) + +file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h) +file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp) set(HEADERS ${HEADERS} ${COMMON_FILES_H} ${PAGE_LOGIC_H} ${CONFIGURATORS_H} ${UI_MODELS_H} + ${UI_CONTROLLERS_H} ) set(SOURCES ${SOURCES} ${COMMON_FILES_CPP} ${PAGE_LOGIC_CPP} ${CONFIGURATORS_CPP} ${UI_MODELS_CPP} + ${UI_CONTROLLERS_CPP} ) if(WIN32) @@ -248,6 +281,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h ) set(SOURCES ${SOURCES} @@ -258,6 +292,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp ) endif() @@ -307,16 +342,5 @@ if(NOT IOS AND NOT ANDROID) endif() -if(WIN32) - add_custom_command( - TARGET ${PROJECT} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E $,copy,true> - $/../service/wireguard-service/wireguard-service.exe - $/wireguard/wireguard-service.exe - COMMAND_EXPAND_LISTS - ) -endif() - - -target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC}) +target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC}) qt_finalize_target(${PROJECT}) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 491a68b8..3e227863 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -3,55 +3,35 @@ #include #include #include +#include +#include #include #include #include #include +#include -#include "core/servercontroller.h" #include "logger.h" #include "version.h" -#include #include "platforms/ios/QRCodeReaderBase.h" - -#include "ui/pages.h" - -#include "ui/pages_logic/AppSettingsLogic.h" -#include "ui/pages_logic/GeneralSettingsLogic.h" -#include "ui/pages_logic/NetworkSettingsLogic.h" -#include "ui/pages_logic/NewServerProtocolsLogic.h" -#include "ui/pages_logic/QrDecoderLogic.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" -#include "ui/pages_logic/ServerContainersLogic.h" -#include "ui/pages_logic/ServerListLogic.h" -#include "ui/pages_logic/ServerSettingsLogic.h" -#include "ui/pages_logic/ServerContainersLogic.h" -#include "ui/pages_logic/ShareConnectionLogic.h" -#include "ui/pages_logic/SitesLogic.h" -#include "ui/pages_logic/StartPageLogic.h" -#include "ui/pages_logic/VpnLogic.h" -#include "ui/pages_logic/WizardLogic.h" - -#include "ui/pages_logic/protocols/CloakLogic.h" -#include "ui/pages_logic/protocols/OpenVpnLogic.h" -#include "ui/pages_logic/protocols/ShadowSocksLogic.h" +#if defined(Q_OS_ANDROID) + #include "platforms/android/android_controller.h" +#endif #include "protocols/qml_register_protocols.h" #if defined(Q_OS_IOS) -#include "platforms/ios/QtAppDelegate-C-Interface.h" -#include "platforms/ios/ios_controller.h" + #include "platforms/ios/ios_controller.h" #endif #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - AmneziaApplication::AmneziaApplication(int &argc, char *argv[]): - AMNEZIA_BASE_CLASS(argc, argv) +AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv) #else - AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, - SingleApplication::Options options, int timeout, const QString &userData): - SingleApplication(argc, argv, allowSecondary, options, timeout, userData) +AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, + int timeout, const QString &userData) + : SingleApplication(argc, argv, allowSecondary, options, timeout, userData) #endif { setQuitOnLastWindowClosed(false); @@ -73,49 +53,97 @@ #endif m_settings = std::shared_ptr(new Settings); - m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); } AmneziaApplication::~AmneziaApplication() { + m_vpnConnectionThread.quit(); + m_vpnConnectionThread.wait(3000); + if (m_engine) { - QObject::disconnect(m_engine, 0,0,0); + QObject::disconnect(m_engine, 0, 0, 0); delete m_engine; } - if (m_uiLogic) { - QObject::disconnect(m_uiLogic, 0,0,0); - delete m_uiLogic; - } - - if (m_protocolProps) delete m_protocolProps; - if (m_containerProps) delete m_containerProps; } void AmneziaApplication::init() { m_engine = new QQmlApplicationEngine; - m_uiLogic = new UiLogic(m_settings, m_configurator); - const QUrl url(QStringLiteral("qrc:/ui/qml/main.qml")); - QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, - this, [url](QObject *obj, const QUrl &objUrl) { - if (!obj && url == objUrl) - QCoreApplication::exit(-1); - }, Qt::QueuedConnection); + const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml")); + QObject::connect( + m_engine, &QQmlApplicationEngine::objectCreated, this, + [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, + Qt::QueuedConnection); m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); - m_uiLogic->registerPagesLogic(); -#if defined(Q_OS_IOS) - setStartPageLogic(m_uiLogic->pageLogic()); - IosController::Instance()->initialize(); + m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); + m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); + m_vpnConnection->moveToThread(&m_vpnConnectionThread); + m_vpnConnectionThread.start(); + + initModels(); + loadTranslator(); + initControllers(); + +#ifdef Q_OS_ANDROID + connect(AndroidController::instance(), &AndroidController::initialized, this, + [this](bool status, bool connected, const QDateTime &connectionDate) { + if (connected) { + m_connectionController->onConnectionStateChanged(Vpn::ConnectionState::Connected); + if (m_vpnConnection) + m_vpnConnection->restoreConnection(); + } + }); + if (!AndroidController::instance()->initialize()) { + qCritical() << QString("Init failed"); + if (m_vpnConnection) + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); + return; + } + + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { + m_pageController->replaceStartPage(); + m_importController->extractConfigFromData(data); + m_pageController->goToPageViewConfig(); + }); #endif - m_engine->load(url); +#ifdef Q_OS_IOS + IosController::Instance()->initialize(); + connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) { + m_pageController->replaceStartPage(); + m_importController->extractConfigFromData(data); + m_pageController->goToPageViewConfig(); + }); - if (m_engine->rootObjects().size() > 0) { - m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0)); - } + connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) { + m_pageController->replaceStartPage(); + m_pageController->goToPageSettingsBackup(); + m_settingsController->importBackupFromOutside(filePath); + }); +#endif + + m_notificationHandler.reset(NotificationHandler::create(nullptr)); + + connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), + &NotificationHandler::setConnectionState); + + connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), + &PageController::raiseMainWindow); + connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), + &ConnectionController::openConnection); + connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), + &ConnectionController::closeConnection); + connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), + &NotificationHandler::onTranslationsUpdated); + + m_engine->load(url); + m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); if (m_settings->isSaveLogs()) { if (!Logger::init()) { @@ -124,19 +152,20 @@ void AmneziaApplication::init() } #ifdef Q_OS_WIN - if (m_parser.isSet("a")) m_uiLogic->showOnStartup(); - else emit m_uiLogic->show(); + if (m_parser.isSet("a")) + m_pageController->showOnStartup(); + else + emit m_pageController->raiseMainWindow(); #else - m_uiLogic->showOnStartup(); + m_pageController->showOnStartup(); #endif - // TODO - fix + // TODO - fix #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) if (isPrimary()) { - QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ + QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() { qDebug() << "Secondary instance started, showing this window instead"; - emit m_uiLogic->show(); - emit m_uiLogic->raise(); + emit m_pageController->raiseMainWindow(); }); } #endif @@ -144,7 +173,7 @@ void AmneziaApplication::init() // Android TextField clipboard workaround // https://bugreports.qt.io/browse/QTBUG-113461 #ifdef Q_OS_ANDROID - QObject::connect(qApp, &QApplication::applicationStateChanged, [](Qt::ApplicationState state) { + QObject::connect(qApp, &QGuiApplication::applicationStateChanged, [](Qt::ApplicationState state) { if (state == Qt::ApplicationActive) { if (qApp->clipboard()->mimeData()->formats().contains("text/html")) { QTextDocument doc; @@ -158,55 +187,64 @@ void AmneziaApplication::init() void AmneziaApplication::registerTypes() { - qRegisterMetaType("VpnProtocol::VpnConnectionState"); qRegisterMetaType("ServerCredentials"); qRegisterMetaType("DockerContainer"); qRegisterMetaType("TransportProto"); qRegisterMetaType("Proto"); qRegisterMetaType("ServiceType"); - qRegisterMetaType("Page"); - qRegisterMetaType("ConnectionState"); - qRegisterMetaType("PageProtocolLogicBase *"); - - - declareQmlPageEnum(); declareQmlProtocolEnum(); declareQmlContainerEnum(); - qmlRegisterType("PageType", 1, 0, "PageType"); qmlRegisterType("QRCodeReader", 1, 0, "QRCodeReader"); - m_containerProps = new ContainerProps; - qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps); + m_containerProps.reset(new ContainerProps()); + qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get()); - m_protocolProps = new ProtocolProps; - qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps); + m_protocolProps.reset(new ProtocolProps()); + qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get()); + + qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0, + "ContainersModelFilters"); + + // + Vpn::declareQmlVpnConnectionStateEnum(); + PageLoader::declareQmlPageEnum(); } void AmneziaApplication::loadFonts() { QQuickStyle::setStyle("Basic"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Black.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-BlackItalic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Bold.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-BoldItalic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Italic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Light.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-LightItalic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Regular.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Thin.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-ThinItalic.ttf"); + QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf"); } void AmneziaApplication::loadTranslator() { - m_translator = new QTranslator; - if (m_translator->load(QLocale(), QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/translations"))) { - installTranslator(m_translator); + auto locale = m_settings->getAppLanguage(); + m_translator.reset(new QTranslator()); + updateTranslator(locale); +} + +void AmneziaApplication::updateTranslator(const QLocale &locale) +{ + if (!m_translator->isEmpty()) { + QCoreApplication::removeTranslator(m_translator.get()); } + + QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm"; + if (m_translator->load(strFileName)) { + if (QCoreApplication::installTranslator(m_translator.get())) { + m_settings->setAppLanguage(locale); + } + } else { + m_settings->setAppLanguage(QLocale::English); + } + + m_engine->retranslate(); + + emit translationsUpdated(); } bool AmneziaApplication::parseCommands() @@ -215,19 +253,17 @@ bool AmneziaApplication::parseCommands() m_parser.addHelpOption(); m_parser.addVersionOption(); - QCommandLineOption c_autostart {{"a", "autostart"}, "System autostart"}; + QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" }; m_parser.addOption(c_autostart); - QCommandLineOption c_cleanup {{"c", "cleanup"}, "Cleanup logs"}; + QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" }; m_parser.addOption(c_cleanup); m_parser.process(*this); if (m_parser.isSet(c_cleanup)) { Logger::cleanUp(); - QTimer::singleShot(100, this, [this]{ - quit(); - }); + QTimer::singleShot(100, this, [this] { quit(); }); exec(); return false; } @@ -239,3 +275,100 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const return m_engine; } +void AmneziaApplication::initModels() +{ + m_containersModel.reset(new ContainersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + connect(m_vpnConnection.get(), &VpnConnection::newVpnConfigurationCreated, m_containersModel.get(), + &ContainersModel::updateContainersConfig); + + m_serversModel.reset(new ServersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(), + &ContainersModel::setCurrentlyProcessedServerIndex); + connect(m_serversModel.get(), &ServersModel::defaultServerIndexChanged, m_containersModel.get(), + &ContainersModel::setCurrentlyProcessedServerIndex); + + m_languageModel.reset(new LanguageModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); + connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); + connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); + + m_sitesModel.reset(new SitesModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); + connect(m_containersModel.get(), &ContainersModel::defaultContainerChanged, this, [this]() { + if ((m_containersModel->getDefaultContainer() == DockerContainer::WireGuard + || m_containersModel->getDefaultContainer() == DockerContainer::Awg) + && m_sitesModel->isSplitTunnelingEnabled()) { + m_sitesModel->toggleSplitTunneling(false); + emit m_pageController->showNotificationMessage( + tr("Split tunneling for %1 is not implemented, the option was disabled") + .arg(ContainerProps::containerHumanNames().value(m_containersModel->getDefaultContainer()))); + } + }); + + m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); + + m_openVpnConfigModel.reset(new OpenVpnConfigModel(this)); + m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get()); + + m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this)); + m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get()); + + m_cloakConfigModel.reset(new CloakConfigModel(this)); + m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get()); + + m_wireGuardConfigModel.reset(new WireGuardConfigModel(this)); + m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get()); + + m_awgConfigModel.reset(new AwgConfigModel(this)); + m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get()); + +#ifdef Q_OS_WINDOWS + m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); + m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); +#endif + + m_sftpConfigModel.reset(new SftpConfigModel(this)); + m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get()); +} + +void AmneziaApplication::initControllers() +{ + m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); + m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + + connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), + &ConnectionController::onTranslationsUpdated); + + m_pageController.reset(new PageController(m_serversModel, m_settings)); + m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings)); + m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), + &PageController::showPassphraseRequestDrawer); + connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), + &InstallController::setEncryptedPassphrase); + connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(), + &ConnectionController::onCurrentContainerUpdated); + + m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); + + m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); + m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); + + m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_settings)); + m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + if (m_settingsController->isAutoStartEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { + QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); + } + + m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); + m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); + + m_systemController.reset(new SystemController(m_settings)); + m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); +} diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 1ac6e772..32300421 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -1,27 +1,53 @@ #ifndef AMNEZIA_APPLICATION_H #define AMNEZIA_APPLICATION_H -#include -#include - #include #include #include +#include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif #include "settings.h" +#include "vpnconnection.h" -#include "ui/uilogic.h" #include "configurators/vpn_configurator.h" +#include "ui/controllers/connectionController.h" +#include "ui/controllers/exportController.h" +#include "ui/controllers/importController.h" +#include "ui/controllers/installController.h" +#include "ui/controllers/pageController.h" +#include "ui/controllers/settingsController.h" +#include "ui/controllers/sitesController.h" +#include "ui/controllers/systemController.h" +#include "ui/models/containers_model.h" +#include "ui/models/languageModel.h" +#include "ui/models/protocols/cloakConfigModel.h" +#include "ui/notificationhandler.h" +#ifdef Q_OS_WINDOWS + #include "ui/models/protocols/ikev2ConfigModel.h" +#endif +#include "ui/models/protocols/awgConfigModel.h" +#include "ui/models/protocols/openvpnConfigModel.h" +#include "ui/models/protocols/shadowsocksConfigModel.h" +#include "ui/models/protocols/wireguardConfigModel.h" +#include "ui/models/protocols_model.h" +#include "ui/models/servers_model.h" +#include "ui/models/services/sftpConfigModel.h" +#include "ui/models/sites_model.h" + #define amnApp (static_cast(QCoreApplication::instance())) - #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - #define AMNEZIA_BASE_CLASS QApplication + #define AMNEZIA_BASE_CLASS QGuiApplication #else - #define AMNEZIA_BASE_CLASS SingleApplication - #define QAPPLICATION_CLASS QApplication - #include "singleapplication.h" + #define AMNEZIA_BASE_CLASS SingleApplication + #define QAPPLICATION_CLASS QApplication + #include "singleapplication.h" #endif class AmneziaApplication : public AMNEZIA_BASE_CLASS @@ -32,7 +58,8 @@ public: AmneziaApplication(int &argc, char *argv[]); #else AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false, - SingleApplication::Options options = SingleApplication::User, int timeout = 1000, const QString &userData = {} ); + SingleApplication::Options options = SingleApplication::User, int timeout = 1000, + const QString &userData = {}); #endif virtual ~AmneziaApplication(); @@ -40,22 +67,57 @@ public: void registerTypes(); void loadFonts(); void loadTranslator(); + void updateTranslator(const QLocale &locale); bool parseCommands(); QQmlApplicationEngine *qmlEngine() const; +signals: + void translationsUpdated(); + private: + void initModels(); + void initControllers(); + QQmlApplicationEngine *m_engine {}; - UiLogic *m_uiLogic {}; std::shared_ptr m_settings; std::shared_ptr m_configurator; - ContainerProps* m_containerProps {}; - ProtocolProps* m_protocolProps {}; + QSharedPointer m_containerProps; + QSharedPointer m_protocolProps; - QTranslator* m_translator; + QSharedPointer m_translator; QCommandLineParser m_parser; + QSharedPointer m_containersModel; + QSharedPointer m_serversModel; + QSharedPointer m_languageModel; + QSharedPointer m_protocolsModel; + QSharedPointer m_sitesModel; + + QScopedPointer m_openVpnConfigModel; + QScopedPointer m_shadowSocksConfigModel; + QScopedPointer m_cloakConfigModel; + QScopedPointer m_wireGuardConfigModel; + QScopedPointer m_awgConfigModel; +#ifdef Q_OS_WINDOWS + QScopedPointer m_ikev2ConfigModel; +#endif + + QScopedPointer m_sftpConfigModel; + + QSharedPointer m_vpnConnection; + QThread m_vpnConnectionThread; + QScopedPointer m_notificationHandler; + + QScopedPointer m_connectionController; + QScopedPointer m_pageController; + QScopedPointer m_installController; + QScopedPointer m_importController; + QScopedPointer m_exportController; + QScopedPointer m_settingsController; + QScopedPointer m_sitesController; + QScopedPointer m_systemController; }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index bf431ec6..1115b74d 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -36,7 +36,8 @@ android:requestLegacyExternalStorage="true" android:allowNativeHeapPointerTagging="false" android:theme="@style/Theme.AppCompat.NoActionBar" - android:icon="@drawable/icon"> + android:icon="@drawable/icon" + android:roundIcon="@drawable/icon_round"> diff --git a/client/android/build.gradle b/client/android/build.gradle index cfc53460..a6b3f651 100644 --- a/client/android/build.gradle +++ b/client/android/build.gradle @@ -138,8 +138,8 @@ android { resConfig "en" minSdkVersion = 24 targetSdkVersion = 34 - versionCode 32 // Change to a higher number - versionName "3.0.9" // Change to a higher number + versionCode 37 // Change to a higher number + versionName "4.0.8" // Change to a higher number javaCompileOptions.annotationProcessorOptions.arguments = [ "room.schemaLocation": "${qtAndroidDir}/schemas".toString() diff --git a/client/android/res/drawable-hdpi/icon_round.png b/client/android/res/drawable-hdpi/icon_round.png new file mode 100644 index 00000000..418a71cc Binary files /dev/null and b/client/android/res/drawable-hdpi/icon_round.png differ diff --git a/client/android/res/drawable-ldpi/icon_round.png b/client/android/res/drawable-ldpi/icon_round.png new file mode 100644 index 00000000..371994ed Binary files /dev/null and b/client/android/res/drawable-ldpi/icon_round.png differ diff --git a/client/android/res/drawable-mdpi/icon_round.png b/client/android/res/drawable-mdpi/icon_round.png new file mode 100644 index 00000000..0ed71d4c Binary files /dev/null and b/client/android/res/drawable-mdpi/icon_round.png differ diff --git a/client/android/res/drawable-xhdpi/icon_round.png b/client/android/res/drawable-xhdpi/icon_round.png new file mode 100644 index 00000000..a3e18823 Binary files /dev/null and b/client/android/res/drawable-xhdpi/icon_round.png differ diff --git a/client/android/res/drawable-xxhdpi/icon_round.png b/client/android/res/drawable-xxhdpi/icon_round.png new file mode 100644 index 00000000..ca2a6362 Binary files /dev/null and b/client/android/res/drawable-xxhdpi/icon_round.png differ diff --git a/client/android/res/drawable-xxxhdpi/icon_round.png b/client/android/res/drawable-xxxhdpi/icon_round.png new file mode 100644 index 00000000..b2b33777 Binary files /dev/null and b/client/android/res/drawable-xxxhdpi/icon_round.png differ diff --git a/client/android/src/com/wireguard/config/BadConfigException.java b/client/android/src/com/wireguard/config/BadConfigException.java index 33910501..af909b0d 100644 --- a/client/android/src/com/wireguard/config/BadConfigException.java +++ b/client/android/src/com/wireguard/config/BadConfigException.java @@ -70,6 +70,15 @@ public class BadConfigException extends Exception { EXCLUDED_APPLICATIONS("ExcludedApplications"), INCLUDED_APPLICATIONS("IncludedApplications"), LISTEN_PORT("ListenPort"), + JC("Jc"), + JMIN("Jmin"), + JMAX("Jmax"), + S1("S1"), + S2("S2"), + H1("H1"), + H2("H2"), + H3("H3"), + H4("H4"), MTU("MTU"), PERSISTENT_KEEPALIVE("PersistentKeepalive"), PRE_SHARED_KEY("PresharedKey"), diff --git a/client/android/src/com/wireguard/config/Interface.java b/client/android/src/com/wireguard/config/Interface.java index 2594d701..4b561680 100644 --- a/client/android/src/com/wireguard/config/Interface.java +++ b/client/android/src/com/wireguard/config/Interface.java @@ -44,6 +44,15 @@ public final class Interface { private final KeyPair keyPair; private final Optional listenPort; private final Optional mtu; + private final Optional jc; + private final Optional jmin; + private final Optional jmax; + private final Optional s1; + private final Optional s2; + private final Optional h1; + private final Optional h2; + private final Optional h3; + private final Optional h4; private Interface(final Builder builder) { // Defensively copy to ensure immutability even if the Builder is reused. @@ -56,6 +65,15 @@ public final class Interface { keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key"); listenPort = builder.listenPort; mtu = builder.mtu; + jc = builder.jc; + jmax = builder.jmax; + jmin = builder.jmin; + s1 = builder.s1; + s2 = builder.s2; + h1 = builder.h1; + h2 = builder.h2; + h3 = builder.h3; + h4 = builder.h4; } /** @@ -95,6 +113,33 @@ public final class Interface { case "privatekey": builder.parsePrivateKey(attribute.getValue()); break; + case "jc": + builder.parseJc(attribute.getValue()); + break; + case "jmin": + builder.parseJmin(attribute.getValue()); + break; + case "jmax": + builder.parseJmax(attribute.getValue()); + break; + case "s1": + builder.parseS1(attribute.getValue()); + break; + case "s2": + builder.parseS2(attribute.getValue()); + break; + case "h1": + builder.parseH1(attribute.getValue()); + break; + case "h2": + builder.parseH2(attribute.getValue()); + break; + case "h3": + builder.parseH3(attribute.getValue()); + break; + case "h4": + builder.parseH4(attribute.getValue()); + break; default: throw new BadConfigException( Section.INTERFACE, Location.TOP_LEVEL, Reason.UNKNOWN_ATTRIBUTE, attribute.getKey()); @@ -111,7 +156,9 @@ public final class Interface { return addresses.equals(other.addresses) && dnsServers.equals(other.dnsServers) && excludedApplications.equals(other.excludedApplications) && includedApplications.equals(other.includedApplications) && keyPair.equals(other.keyPair) - && listenPort.equals(other.listenPort) && mtu.equals(other.mtu); + && listenPort.equals(other.listenPort) && mtu.equals(other.mtu) && jc.equals(other.jc) && jmin.equals(other.jmin) + && jmax.equals(other.jmax) && s1.equals(other.s1) && s2.equals(other.s2) && h1.equals(other.h1) && h2.equals(other.h2) + && h3.equals(other.h3) && h4.equals(other.h4); } /** @@ -180,6 +227,42 @@ public final class Interface { public Optional getMtu() { return mtu; } + + public Optional getJc() { + return jc; + } + + public Optional getJmin() { + return jmin; + } + + public Optional getJmax() { + return jmax; + } + + public Optional getS1() { + return s1; + } + + public Optional getS2() { + return s2; + } + + public Optional getH1() { + return h1; + } + + public Optional getH2() { + return h2; + } + + public Optional getH3() { + return h3; + } + + public Optional getH4() { + return h4; + } @Override public int hashCode() { @@ -191,6 +274,15 @@ public final class Interface { hash = 31 * hash + keyPair.hashCode(); hash = 31 * hash + listenPort.hashCode(); hash = 31 * hash + mtu.hashCode(); + hash = 31 * hash + jc.hashCode(); + hash = 31 * hash + jmin.hashCode(); + hash = 31 * hash + jmax.hashCode(); + hash = 31 * hash + s1.hashCode(); + hash = 31 * hash + s2.hashCode(); + hash = 31 * hash + h1.hashCode(); + hash = 31 * hash + h2.hashCode(); + hash = 31 * hash + h3.hashCode(); + hash = 31 * hash + h4.hashCode(); return hash; } @@ -234,6 +326,19 @@ public final class Interface { .append('\n'); listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n')); mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n')); + + jc.ifPresent(t_jc -> sb.append("Jc = ").append(t_jc).append('\n')); + jmin.ifPresent(t_jmin -> sb.append("Jmin = ").append(t_jmin).append('\n')); + jmax.ifPresent(t_jmax -> sb.append("Jmax = ").append(t_jmax).append('\n')); + + s1.ifPresent(t_s1 -> sb.append("S1 = ").append(t_s1).append('\n')); + s2.ifPresent(t_s2 -> sb.append("S2 = ").append(t_s2).append('\n')); + + h1.ifPresent(t_h1 -> sb.append("H1 = ").append(t_h1).append('\n')); + h2.ifPresent(t_h2 -> sb.append("H2 = ").append(t_h2).append('\n')); + h3.ifPresent(t_h3 -> sb.append("H3 = ").append(t_h3).append('\n')); + h4.ifPresent(t_h4 -> sb.append("H4 = ").append(t_h4).append('\n')); + sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n'); return sb.toString(); } @@ -248,6 +353,18 @@ public final class Interface { final StringBuilder sb = new StringBuilder(); sb.append("private_key=").append(keyPair.getPrivateKey().toHex()).append('\n'); listenPort.ifPresent(lp -> sb.append("listen_port=").append(lp).append('\n')); + + jc.ifPresent(t_jc -> sb.append("jc=").append(t_jc).append('\n')); + jmin.ifPresent(t_jmin -> sb.append("jmin=").append(t_jmin).append('\n')); + jmax.ifPresent(t_jmax -> sb.append("jmax=").append(t_jmax).append('\n')); + + s1.ifPresent(t_s1 -> sb.append("s1=").append(t_s1).append('\n')); + s2.ifPresent(t_s2 -> sb.append("s2=").append(t_s2).append('\n')); + + h1.ifPresent(t_h1 -> sb.append("h1=").append(t_h1).append('\n')); + h2.ifPresent(t_h2 -> sb.append("h2=").append(t_h2).append('\n')); + h3.ifPresent(t_h3 -> sb.append("h3=").append(t_h3).append('\n')); + h4.ifPresent(t_h4 -> sb.append("h4=").append(t_h4).append('\n')); return sb.toString(); } @@ -267,6 +384,17 @@ public final class Interface { private Optional listenPort = Optional.empty(); // Defaults to not present. private Optional mtu = Optional.empty(); + private Optional jc = Optional.empty(); + private Optional jmin = Optional.empty(); + private Optional jmax = Optional.empty(); + + private Optional s1 = Optional.empty(); + private Optional s2 = Optional.empty(); + + private Optional h1 = Optional.empty(); + private Optional h2 = Optional.empty(); + private Optional h3 = Optional.empty(); + private Optional h4 = Optional.empty(); public Builder addAddress(final InetNetwork address) { addresses.add(address); @@ -362,6 +490,78 @@ public final class Interface { } } + public Builder parseJc(final String jc) throws BadConfigException { + try { + return setJc(Integer.parseInt(jc)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.JC, jc, e); + } + } + + public Builder parseJmax(final String jmax) throws BadConfigException { + try { + return setJmax(Integer.parseInt(jmax)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.JMAX, jmax, e); + } + } + + public Builder parseJmin(final String jmin) throws BadConfigException { + try { + return setJmin(Integer.parseInt(jmin)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.JMIN, jmin, e); + } + } + + public Builder parseS1(final String s1) throws BadConfigException { + try { + return setS1(Integer.parseInt(s1)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.S1, s1, e); + } + } + + public Builder parseS2(final String s2) throws BadConfigException { + try { + return setS2(Integer.parseInt(s2)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.S2, s2, e); + } + } + + public Builder parseH1(final String h1) throws BadConfigException { + try { + return setH1(Long.parseLong(h1)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H1, h1, e); + } + } + + public Builder parseH2(final String h2) throws BadConfigException { + try { + return setH2(Long.parseLong(h2)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H2, h2, e); + } + } + + public Builder parseH3(final String h3) throws BadConfigException { + try { + return setH3(Long.parseLong(h3)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H3, h3, e); + } + } + + public Builder parseH4(final String h4) throws BadConfigException { + try { + return setH4(Long.parseLong(h4)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H4, h4, e); + } + } + public Builder parsePrivateKey(final String privateKey) throws BadConfigException { try { return setKeyPair(new KeyPair(Key.fromBase64(privateKey))); @@ -386,9 +586,81 @@ public final class Interface { public Builder setMtu(final int mtu) throws BadConfigException { if (mtu < 0) throw new BadConfigException( - Section.INTERFACE, Location.LISTEN_PORT, Reason.INVALID_VALUE, String.valueOf(mtu)); + Section.INTERFACE, Location.MTU, Reason.INVALID_VALUE, String.valueOf(mtu)); this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu); return this; } + + public Builder setJc(final int jc) throws BadConfigException { + if (jc < 0) + throw new BadConfigException( + Section.INTERFACE, Location.JC, Reason.INVALID_VALUE, String.valueOf(jc)); + this.jc = Optional.of(jc); + return this; + } + + public Builder setJmin(final int jmin) throws BadConfigException { + if (jmin < 0) + throw new BadConfigException( + Section.INTERFACE, Location.JMIN, Reason.INVALID_VALUE, String.valueOf(jmin)); + this.jmin = Optional.of(jmin); + return this; + } + + public Builder setJmax(final int jmax) throws BadConfigException { + if (jmax < 0) + throw new BadConfigException( + Section.INTERFACE, Location.JMAX, Reason.INVALID_VALUE, String.valueOf(jmax)); + this.jmax = Optional.of(jmax); + return this; + } + + public Builder setS1(final int s1) throws BadConfigException { + if (s1 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.S1, Reason.INVALID_VALUE, String.valueOf(s1)); + this.s1 = Optional.of(s1); + return this; + } + + public Builder setS2(final int s2) throws BadConfigException { + if (s2 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.S2, Reason.INVALID_VALUE, String.valueOf(s2)); + this.s2 = Optional.of(s2); + return this; + } + + public Builder setH1(final long h1) throws BadConfigException { + if (h1 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H1, Reason.INVALID_VALUE, String.valueOf(h1)); + this.h1 = Optional.of(h1); + return this; + } + + public Builder setH2(final long h2) throws BadConfigException { + if (h2 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H2, Reason.INVALID_VALUE, String.valueOf(h2)); + this.h2 = Optional.of(h2); + return this; + } + + public Builder setH3(final long h3) throws BadConfigException { + if (h3 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H3, Reason.INVALID_VALUE, String.valueOf(h3)); + this.h3 = Optional.of(h3); + return this; + } + + public Builder setH4(final long h4) throws BadConfigException { + if (h4 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H4, Reason.INVALID_VALUE, String.valueOf(h4)); + this.h4 = Optional.of(h4); + return this; + } } -} +} diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index 082fe412..06f58980 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -380,7 +380,10 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { mNetworkState.bindNetworkListener() } "wireguard" -> { - startWireGuard() + startWireGuard("wireguard") + } + "awg" -> { + startWireGuard("awg") } "shadowsocks" -> { startShadowsocks() @@ -457,7 +460,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { fun turnOff() { Log.v(tag, "Aman: turnOff....................") when (mProtocol) { - "wireguard" -> { + "wireguard", + "awg" -> { GoBackend.wgTurnOff(currentTunnelHandle) } "cloak", @@ -559,14 +563,14 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { } return parseData } - + /** * Create a Wireguard [Config] from a [json] string - * The [json] will be created in AndroidVpnProtocol.cpp */ - private fun buildWireguardConfig(obj: JSONObject): Config { + private fun buildWireguardConfig(obj: JSONObject, type: String): Config { val confBuilder = Config.Builder() - val wireguardConfigData = obj.getJSONObject("wireguard_config_data") + val wireguardConfigData = obj.getJSONObject(type) val config = parseConfigData(wireguardConfigData.getString("config")) val peerBuilder = Peer.Builder() val peerConfig = config["Peer"]!! @@ -599,6 +603,30 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { ifaceConfig["DNS"]!!.split(",").forEach { ifaceBuilder.addDnsServer(InetNetwork.parse(it.trim()).address) } + + ifaceBuilder.parsePrivateKey(ifaceConfig["PrivateKey"]) + if (type == "awg_config_data") { + ifaceBuilder.parseJc(ifaceConfig["Jc"]) + ifaceBuilder.parseJmin(ifaceConfig["Jmin"]) + ifaceBuilder.parseJmax(ifaceConfig["Jmax"]) + ifaceBuilder.parseS1(ifaceConfig["S1"]) + ifaceBuilder.parseS2(ifaceConfig["S2"]) + ifaceBuilder.parseH1(ifaceConfig["H1"]) + ifaceBuilder.parseH2(ifaceConfig["H2"]) + ifaceBuilder.parseH3(ifaceConfig["H3"]) + ifaceBuilder.parseH4(ifaceConfig["H4"]) + } else { + ifaceBuilder.parseJc("0") + ifaceBuilder.parseJmin("0") + ifaceBuilder.parseJmax("0") + ifaceBuilder.parseS1("0") + ifaceBuilder.parseS2("0") + ifaceBuilder.parseH1("0") + ifaceBuilder.parseH2("0") + ifaceBuilder.parseH3("0") + ifaceBuilder.parseH4("0") + + } /*val jExcludedApplication = obj.getJSONArray("excludedApps") (0 until jExcludedApplication.length()).toList().forEach { val appName = jExcludedApplication.get(it).toString() @@ -716,8 +744,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { }).start() } - private fun startWireGuard() { - val wireguard_conf = buildWireguardConfig(mConfig!!) + private fun startWireGuard(type: String) { + val wireguard_conf = buildWireguardConfig(mConfig!!, type + "_config_data") Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf") if (currentTunnelHandle != -1) { Log.e(tag, "Tunnel already up") @@ -728,9 +756,15 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { val builder = Builder() setupBuilder(wireguard_conf, builder) builder.setSession("Amnezia") + + builder.establish().use { tun -> - if (tun == null) return - currentTunnelHandle = GoBackend.wgTurnOn("Amnezia", tun.detachFd(), wgConfig) + if (tun == null) return + if (type == "awg"){ + currentTunnelHandle = GoBackend.wgTurnOn("awg0", tun.detachFd(), wgConfig) + } else { + currentTunnelHandle = GoBackend.wgTurnOn("amn0", tun.detachFd(), wgConfig) + } } if (currentTunnelHandle < 0) { Log.e(tag, "Activation Error Code -> $currentTunnelHandle") diff --git a/client/cmake/android.cmake b/client/cmake/android.cmake index dd37e0a6..9440ad10 100644 --- a/client/cmake/android.cmake +++ b/client/cmake/android.cmake @@ -10,6 +10,7 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidvpnactivity.h + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h ) @@ -18,6 +19,7 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidvpnactivity.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp ) diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index 5dc1b2e7..7aa9f1a9 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -97,7 +97,7 @@ target_compile_options(${PROJECT} PRIVATE -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" ) -set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources) +set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/awg-apple/Sources) target_sources(${PROJECT} PRIVATE # ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp new file mode 100644 index 00000000..c3e42258 --- /dev/null +++ b/client/configurators/awg_configurator.cpp @@ -0,0 +1,47 @@ +#include "awg_configurator.h" + +#include +#include + +#include "core/servercontroller.h" + +AwgConfigurator::AwgConfigurator(std::shared_ptr settings, QObject *parent) + : WireguardConfigurator(settings, true, parent) +{ +} + +QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, + DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) +{ + QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode); + + QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); + QString awgConfig = jsonConfig.value(config_key::config).toString(); + + QMap configMap; + auto configLines = awgConfig.split("\n"); + for (auto &line : configLines) { + auto trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + configMap.insert(parts[0].trimmed(), parts[1].trimmed()); + } + } + } + + jsonConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount); + jsonConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize); + jsonConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize); + jsonConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize); + jsonConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize); + jsonConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader); + jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); + jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); + jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); + + return QJsonDocument(jsonConfig).toJson(); +} diff --git a/client/configurators/awg_configurator.h b/client/configurators/awg_configurator.h new file mode 100644 index 00000000..cf0f2cae --- /dev/null +++ b/client/configurators/awg_configurator.h @@ -0,0 +1,18 @@ +#ifndef AWGCONFIGURATOR_H +#define AWGCONFIGURATOR_H + +#include + +#include "wireguard_configurator.h" + +class AwgConfigurator : public WireguardConfigurator +{ + Q_OBJECT +public: + AwgConfigurator(std::shared_ptr settings, QObject *parent = nullptr); + + QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); +}; + +#endif // AWGCONFIGURATOR_H diff --git a/client/configurators/ikev2_configurator.cpp b/client/configurators/ikev2_configurator.cpp index 7ed83da1..4ca0e5da 100644 --- a/client/configurators/ikev2_configurator.cpp +++ b/client/configurators/ikev2_configurator.cpp @@ -1,28 +1,26 @@ #include "ikev2_configurator.h" -#include + +#include +#include #include #include #include -#include #include -#include #include #include "containers/containers_defs.h" -#include "core/server_defs.h" #include "core/scripts_registry.h" -#include "utilities.h" +#include "core/server_defs.h" #include "core/servercontroller.h" +#include "utilities.h" - -Ikev2Configurator::Ikev2Configurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +Ikev2Configurator::Ikev2Configurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { - } Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, - DockerContainer container, ErrorCode *errorCode) + DockerContainer container, ErrorCode *errorCode) { Ikev2Configurator::ConnectionData connData; connData.host = credentials.hostName; @@ -32,26 +30,27 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12"; - QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) "\ - "-S -c \"IKEv2 VPN CA\" -n \"%1\" "\ - "-s \"O=IKEv2 VPN,CN=%1\" "\ - "-k rsa -g 3072 -v 120 "\ - "-d sql:/etc/ipsec.d -t \",,\" "\ - "--keyUsage digitalSignature,keyEncipherment "\ - "--extKeyUsage serverAuth,clientAuth -8 \"%1\"") - .arg(connData.clientId); + QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) " + "-S -c \"IKEv2 VPN CA\" -n \"%1\" " + "-s \"O=IKEv2 VPN,CN=%1\" " + "-k rsa -g 3072 -v 120 " + "-d sql:/etc/ipsec.d -t \",,\" " + "--keyUsage digitalSignature,keyEncipherment " + "--extKeyUsage serverAuth,clientAuth -8 \"%1\"") + .arg(connData.clientId); ServerController serverController(m_settings); ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert); QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"") - .arg(connData.password) - .arg(connData.clientId) - .arg(certFileName); + .arg(connData.password) + .arg(connData.clientId) + .arg(certFileName); e = serverController.runContainerScript(credentials, container, scriptExportCert); connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e); - connData.caCert = serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e); + connData.caCert = + serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e); qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size(); qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size(); @@ -59,8 +58,8 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se return connData; } -QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) { Q_UNUSED(containerConfig) @@ -120,4 +119,3 @@ QString Ikev2Configurator::genStrongSwanConfig(const ConnectionData &connData) return config; } - diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 3bc6676a..a62bdd9c 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -1,82 +1,94 @@ #include "openvpn_configurator.h" -#include + +#include +#include +#include #include #include #include -#include #include -#include -#include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif #include "containers/containers_defs.h" +#include "core/scripts_registry.h" #include "core/server_defs.h" #include "core/servercontroller.h" -#include "core/scripts_registry.h" -#include "utilities.h" #include "settings.h" +#include "utilities.h" +#include #include #include -#include -OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { - } OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials, - DockerContainer container, ErrorCode *errorCode) + DockerContainer container, + ErrorCode *errorCode) { OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest(); connData.host = credentials.hostName; if (connData.privKey.isEmpty() || connData.request.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::OpenSslFailed; + if (errorCode) + *errorCode = ErrorCode::OpenSslFailed; return connData; } - QString reqFileName = QString("%1/%2.req"). - arg(amnezia::protocols::openvpn::clientsDirPath). - arg(connData.clientId); + QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId); ServerController serverController(m_settings); ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } e = signCert(container, credentials, connData.clientId); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - connData.caCert = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, &e); - connData.clientCert = serverController.getTextFileFromContainer(container, credentials, - QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e); + connData.caCert = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::openvpn::caCertPath, &e); + connData.clientCert = serverController.getTextFileFromContainer( + container, credentials, + QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - connData.taKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, &e); + connData.taKey = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::openvpn::taKeyPath, &e); if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::SshSftpFailureError; + if (errorCode) + *errorCode = ErrorCode::SshSftpFailureError; } return connData; } -QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) { ServerController serverController(m_settings); - QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), - serverController.genVarsForScript(credentials, container, containerConfig)); + QString config = + serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), + serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode); if (errorCode && *errorCode) { @@ -89,8 +101,7 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia if (config.contains("$OPENVPN_TA_KEY")) { config.replace("$OPENVPN_TA_KEY", connData.taKey); - } - else { + } else { config.replace("", ""); config.replace("", ""); } @@ -133,12 +144,11 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) config.replace("block-outside-dns", ""); #endif -#if (defined (MZ_MACOS) || defined(MZ_LINUX)) - QString dnsConf = QString( - "\nscript-security 2\n" - "up %1/update-resolv-conf.sh\n" - "down %1/update-resolv-conf.sh\n"). - arg(qApp->applicationDirPath()); +#if (defined(MZ_MACOS) || defined(MZ_LINUX)) + QString dnsConf = QString("\nscript-security 2\n" + "up %1/update-resolv-conf.sh\n" + "down %1/update-resolv-conf.sh\n") + .arg(qApp->applicationDirPath()); config.append(dnsConf); #endif @@ -168,23 +178,23 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig) return QJsonDocument(json).toJson(); } -ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, - const ServerCredentials &credentials, QString clientId) +ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId) { QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && " - "easyrsa import-req %2/%3.req %3\"") - .arg(ContainerProps::containerToString(container)) - .arg(amnezia::protocols::openvpn::clientsDirPath) - .arg(clientId); + "easyrsa import-req %2/%3.req %3\"") + .arg(ContainerProps::containerToString(container)) + .arg(amnezia::protocols::openvpn::clientsDirPath) + .arg(clientId); QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && " - "easyrsa sign-req client %2\"") - .arg(ContainerProps::containerToString(container)) - .arg(clientId); + "easyrsa sign-req client %2\"") + .arg(ContainerProps::containerToString(container)) + .arg(clientId); ServerController serverController(m_settings); - QStringList scriptList {script_import, script_sign}; - QString script = serverController.replaceVars(scriptList.join("\n"), serverController.genVarsForScript(credentials, container)); + QStringList scriptList { script_import, script_sign }; + QString script = serverController.replaceVars(scriptList.join("\n"), + serverController.genVarsForScript(credentials, container)); return serverController.runScript(credentials, script); } @@ -194,18 +204,17 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() ConnectionData connData; connData.clientId = Utils::getRandomString(32); - int ret = 0; - int nVersion = 1; + int ret = 0; + int nVersion = 1; QByteArray clientIdUtf8 = connData.clientId.toUtf8(); - EVP_PKEY * pKey = EVP_PKEY_new(); + EVP_PKEY *pKey = EVP_PKEY_new(); q_check_ptr(pKey); - RSA * rsa = RSA_generate_key(2048, RSA_F4, nullptr, nullptr); + RSA *rsa = RSA_generate_key(2048, RSA_F4, nullptr, nullptr); q_check_ptr(rsa); EVP_PKEY_assign_RSA(pKey, rsa); - // 2. set version of x509 req X509_REQ *x509_req = X509_REQ_new(); ret = X509_REQ_set_version(x509_req, nVersion); @@ -219,16 +228,14 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() // 3. set subject of x509 req X509_NAME *x509_name = X509_REQ_get_subject_name(x509_req); - X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, - (unsigned char *)"ORG", -1, -1, 0); - X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, - (unsigned char *)"", -1, -1, 0); + X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, (unsigned char *)"ORG", -1, -1, 0); + X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, (unsigned char *)"", -1, -1, 0); X509_NAME_add_entry_by_txt(x509_name, "CN", MBSTRING_ASC, reinterpret_cast(clientIdUtf8.data()), clientIdUtf8.size(), -1, 0); // 4. set public key of x509 req ret = X509_REQ_set_pubkey(x509_req, pKey); - if (ret != 1){ + if (ret != 1) { qWarning() << "Could not set pubkey!"; X509_REQ_free(x509_req); EVP_PKEY_free(pKey); @@ -236,8 +243,8 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() } // 5. set sign key of x509 req - ret = X509_REQ_sign(x509_req, pKey, EVP_sha256()); // return x509_req->signature->length - if (ret <= 0){ + ret = X509_REQ_sign(x509_req, pKey, EVP_sha256()); // return x509_req->signature->length + if (ret <= 0) { qWarning() << "Could not sign request!"; X509_REQ_free(x509_req); EVP_PKEY_free(pKey); @@ -245,10 +252,9 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() } // save private key - BIO * bp_private = BIO_new(BIO_s_mem()); + BIO *bp_private = BIO_new(BIO_s_mem()); q_check_ptr(bp_private); - if (PEM_write_bio_PrivateKey(bp_private, pKey, nullptr, nullptr, 0, nullptr, nullptr) != 1) - { + if (PEM_write_bio_PrivateKey(bp_private, pKey, nullptr, nullptr, 0, nullptr, nullptr) != 1) { qFatal("PEM_write_bio_PrivateKey"); EVP_PKEY_free(pKey); BIO_free_all(bp_private); @@ -256,7 +262,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() return connData; } - const char * buffer = nullptr; + const char *buffer = nullptr; size_t size = BIO_get_mem_data(bp_private, &buffer); q_check_ptr(buffer); connData.privKey = QByteArray(buffer, size); @@ -270,7 +276,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() BIO_free_all(bp_private); // save req - BIO * bio_req = BIO_new(BIO_s_mem()); + BIO *bio_req = BIO_new(BIO_s_mem()); PEM_write_bio_X509_REQ(bio_req, x509_req); BUF_MEM *bio_buf; @@ -278,7 +284,6 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() connData.request = QByteArray(bio_buf->data, bio_buf->length); BIO_free(bio_req); - EVP_PKEY_free(pKey); // this will also free the rsa key return connData; diff --git a/client/configurators/ssh_configurator.cpp b/client/configurators/ssh_configurator.cpp index e1435bc3..42e7eb47 100644 --- a/client/configurators/ssh_configurator.cpp +++ b/client/configurators/ssh_configurator.cpp @@ -1,24 +1,25 @@ #include "ssh_configurator.h" -#include + +#include +#include #include #include #include -#include #include #include -#include -#include -#include #include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif #include "core/server_defs.h" #include "utilities.h" - -SshConfigurator::SshConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +SshConfigurator::SshConfigurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { - } QString SshConfigurator::convertOpenSShKey(const QString &key) @@ -28,23 +29,30 @@ QString SshConfigurator::convertOpenSShKey(const QString &key) p.setProcessChannelMode(QProcess::MergedChannels); QTemporaryFile tmp; -#ifdef QT_DEBUG + #ifdef QT_DEBUG tmp.setAutoRemove(false); -#endif + #endif tmp.open(); tmp.write(key.toUtf8()); tmp.close(); // ssh-keygen -p -P "" -N "" -m pem -f id_ssh -#ifdef Q_OS_WIN + #ifdef Q_OS_WIN p.setProcessEnvironment(prepareEnv()); p.setProgram("cmd.exe"); p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName())); -#else + #else p.setProgram("ssh-keygen"); - p.setArguments(QStringList() << "-p" << "-P" << "" << "-N" << "" << "-m" << "pem" << "-f" << tmp.fileName()); -#endif + p.setArguments(QStringList() << "-p" + << "-P" + << "" + << "-N" + << "" + << "-m" + << "pem" + << "-f" << tmp.fileName()); + #endif p.start(); p.waitForFinished(); @@ -65,22 +73,21 @@ void SshConfigurator::openSshTerminal(const ServerCredentials &credentials) QProcess *p = new QProcess(); p->setProcessChannelMode(QProcess::SeparateChannels); -#ifdef Q_OS_WIN + #ifdef Q_OS_WIN p->setProcessEnvironment(prepareEnv()); p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe"); - if (credentials.password.contains("PRIVATE KEY")) { + if (credentials.secretData.contains("PRIVATE KEY")) { // todo: connect by key -// p->setNativeArguments(QString("%1@%2") -// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.password)); + // p->setNativeArguments(QString("%1@%2") + // .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); + } else { + p->setNativeArguments( + QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); } - else { - p->setNativeArguments(QString("%1@%2 -pw %3") - .arg(credentials.userName).arg(credentials.hostName).arg(credentials.password)); - } -#else + #else p->setProgram("/bin/bash"); -#endif + #endif p->startDetached(); #endif @@ -95,11 +102,11 @@ QProcessEnvironment SshConfigurator::prepareEnv() pathEnvVar.clear(); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;"); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;"); -#else +#elif defined(Q_OS_MACX) pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS"); #endif env.insert("PATH", pathEnvVar); - //qDebug().noquote() << "ENV PATH" << pathEnvVar; + // qDebug().noquote() << "ENV PATH" << pathEnvVar; return env; } diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp index ceb6a5a4..6c5286c2 100644 --- a/client/configurators/vpn_configurator.cpp +++ b/client/configurators/vpn_configurator.cpp @@ -1,32 +1,34 @@ #include "vpn_configurator.h" -#include "openvpn_configurator.h" #include "cloak_configurator.h" -#include "shadowsocks_configurator.h" -#include "wireguard_configurator.h" #include "ikev2_configurator.h" +#include "openvpn_configurator.h" +#include "shadowsocks_configurator.h" #include "ssh_configurator.h" +#include "wireguard_configurator.h" +#include "awg_configurator.h" #include -#include #include +#include #include "containers/containers_defs.h" -#include "utilities.h" #include "settings.h" +#include "utilities.h" -VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { openVpnConfigurator = std::shared_ptr(new OpenVpnConfigurator(settings, this)); shadowSocksConfigurator = std::shared_ptr(new ShadowSocksConfigurator(settings, this)); cloakConfigurator = std::shared_ptr(new CloakConfigurator(settings, this)); - wireguardConfigurator = std::shared_ptr(new WireguardConfigurator(settings, this)); + wireguardConfigurator = std::shared_ptr(new WireguardConfigurator(settings, false, this)); ikev2Configurator = std::shared_ptr(new Ikev2Configurator(settings, this)); sshConfigurator = std::shared_ptr(new SshConfigurator(settings, this)); + awgConfigurator = std::shared_ptr(new AwgConfigurator(settings, this)); } -QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode) +QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode) { switch (proto) { case Proto::OpenVpn: @@ -35,17 +37,17 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia case Proto::ShadowSocks: return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode); - case Proto::Cloak: - return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode); + case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode); case Proto::WireGuard: return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode); - case Proto::Ikev2: - return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode); + case Proto::Awg: + return awgConfigurator->genAwgConfig(credentials, container, containerConfig, errorCode); - default: - return ""; + case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode); + + default: return ""; } } @@ -62,8 +64,8 @@ QPair VpnConfigurator::getDnsForConfig(int serverIndex) if (dns.first.isEmpty() || !Utils::checkIPv4Format(dns.first)) { if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) { dns.first = protocols::dns::amneziaDnsIp; - } - else dns.first = m_settings->primaryDns(); + } else + dns.first = m_settings->primaryDns(); } if (dns.second.isEmpty() || !Utils::checkIPv4Format(dns.second)) { dns.second = m_settings->secondaryDns(); @@ -73,8 +75,8 @@ QPair VpnConfigurator::getDnsForConfig(int serverIndex) return dns; } -QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, - Proto proto, QString &config) +QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, + QString &config) { auto dns = getDnsForConfig(serverIndex); @@ -84,8 +86,8 @@ QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerCo return config; } -QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, - Proto proto, QString &config) +QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, + QString &config) { processConfigWithDnsSettings(serverIndex, container, proto, config); @@ -95,8 +97,8 @@ QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, Docker return config; } -QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, - Proto proto, QString &config) +QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, + QString &config) { processConfigWithDnsSettings(serverIndex, container, proto, config); @@ -107,7 +109,7 @@ QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, Docke } void VpnConfigurator::updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig, - const QString &stdOut) + const QString &stdOut) { Proto mainProto = ContainerProps::defaultProtocol(container); diff --git a/client/configurators/vpn_configurator.h b/client/configurators/vpn_configurator.h index 3b9c761b..ac89b0e4 100644 --- a/client/configurators/vpn_configurator.h +++ b/client/configurators/vpn_configurator.h @@ -13,13 +13,14 @@ class CloakConfigurator; class WireguardConfigurator; class Ikev2Configurator; class SshConfigurator; +class AwgConfigurator; // Retrieve connection settings from server class VpnConfigurator : ConfiguratorBase { Q_OBJECT public: - VpnConfigurator(std::shared_ptr settings, QObject *parent = nullptr); + explicit VpnConfigurator(std::shared_ptr settings, QObject *parent = nullptr); QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode = nullptr); @@ -40,6 +41,7 @@ public: std::shared_ptr wireguardConfigurator; std::shared_ptr ikev2Configurator; std::shared_ptr sshConfigurator; + std::shared_ptr awgConfigurator; }; #endif // VPN_CONFIGURATOR_H diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 54ee320c..e22c8282 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -1,30 +1,38 @@ #include "wireguard_configurator.h" -#include + +#include +#include #include #include #include -#include #include -#include - +#include #include #include #include -#include - #include "containers/containers_defs.h" -#include "core/server_defs.h" #include "core/scripts_registry.h" -#include "utilities.h" +#include "core/server_defs.h" #include "core/servercontroller.h" #include "settings.h" +#include "utilities.h" -WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, bool isAwg, QObject *parent) + : ConfiguratorBase(settings, parent), m_isAwg(isAwg) { + m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath + : amnezia::protocols::wireguard::serverConfigPath; + m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath + : amnezia::protocols::wireguard::serverPublicKeyPath; + m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath + : amnezia::protocols::wireguard::serverPskKeyPath; + m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template + : ProtocolScriptType::wireguard_template; + m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard; + m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort; } WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() @@ -36,37 +44,40 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() unsigned char buff[EDDSA_KEY_LENGTH]; int ret = RAND_priv_bytes(buff, EDDSA_KEY_LENGTH); - if (ret <=0) return connData; + if (ret <= 0) + return connData; - EVP_PKEY * pKey = EVP_PKEY_new(); + EVP_PKEY *pKey = EVP_PKEY_new(); q_check_ptr(pKey); pKey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, &buff[0], EDDSA_KEY_LENGTH); - size_t keySize = EDDSA_KEY_LENGTH; // save private key unsigned char priv[EDDSA_KEY_LENGTH]; EVP_PKEY_get_raw_private_key(pKey, priv, &keySize); - connData.clientPrivKey = QByteArray::fromRawData((char*)priv, keySize).toBase64(); + connData.clientPrivKey = QByteArray::fromRawData((char *)priv, keySize).toBase64(); // save public key unsigned char pub[EDDSA_KEY_LENGTH]; EVP_PKEY_get_raw_public_key(pKey, pub, &keySize); - connData.clientPubKey = QByteArray::fromRawData((char*)pub, keySize).toBase64(); + connData.clientPubKey = QByteArray::fromRawData((char *)pub, keySize).toBase64(); return connData; } WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) + DockerContainer container, + const QJsonObject &containerConfig, + ErrorCode *errorCode) { WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); connData.host = credentials.hostName; - connData.port = containerConfig.value(config_key::port).toString(protocols::wireguard::defaultPort); + connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort); if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::InternalError; + if (errorCode) + *errorCode = ErrorCode::InternalError; return connData; } @@ -76,7 +87,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon // Get list of already created clients (only IP addresses) QString nextIpNumber; { - QString script = QString("cat %1 | grep AllowedIPs").arg(amnezia::protocols::wireguard::serverConfigPath); + QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath); QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; @@ -96,22 +107,24 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon // Calc next IP address if (ips.isEmpty()) { nextIpNumber = "2"; - } - else { + } else { int next = ips.last().split(".").last().toInt() + 1; if (next > 254) { - if (errorCode) *errorCode = ErrorCode::AddressPoolError; + if (errorCode) + *errorCode = ErrorCode::AddressPoolError; return connData; } nextIpNumber = QString::number(next); } } - QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); + QString subnetIp = + containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); { QStringList l = subnetIp.split(".", Qt::SkipEmptyParts); if (l.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::AddressPoolError; + if (errorCode) + *errorCode = ErrorCode::AddressPoolError; return connData; } l.removeLast(); @@ -121,52 +134,55 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon } // Get keys - connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e); + connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, &e); connData.serverPubKey.replace("\n", ""); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - connData.pskKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPskKeyPath, &e); + connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, &e); connData.pskKey.replace("\n", ""); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } // Add client to config - QString configPart = QString( - "[Peer]\n" - "PublicKey = %1\n" - "PresharedKey = %2\n" - "AllowedIPs = %3/32\n\n"). - arg(connData.clientPubKey). - arg(connData.pskKey). - arg(connData.clientIP); + QString configPart = QString("[Peer]\n" + "PublicKey = %1\n" + "PresharedKey = %2\n" + "AllowedIPs = %3/32\n\n") + .arg(connData.clientPubKey, connData.pskKey, connData.clientIP); - e = serverController.uploadTextFileToContainer(container, credentials, configPart, - protocols::wireguard::serverConfigPath, libssh::SftpOverwriteMode::SftpAppendToExisting); + e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath, + libssh::SftpOverwriteMode::SftpAppendToExisting); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - e = serverController.runScript(credentials, - serverController.replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'", - serverController.genVarsForScript(credentials, container))); + QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'") + .arg(m_serverConfigPath); + + e = serverController.runScript( + credentials, serverController.replaceVars(script, serverController.genVarsForScript(credentials, container))); return connData; } -QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) { ServerController serverController(m_settings); - QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container), - serverController.genVarsForScript(credentials, container, containerConfig)); + QString scriptData = amnezia::scriptData(m_configTemplate, container); + QString config = serverController.replaceVars( + scriptData, serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); if (errorCode && *errorCode) { diff --git a/client/configurators/wireguard_configurator.h b/client/configurators/wireguard_configurator.h index 7674eb06..7f8e1587 100644 --- a/client/configurators/wireguard_configurator.h +++ b/client/configurators/wireguard_configurator.h @@ -6,35 +6,44 @@ #include "configurator_base.h" #include "core/defs.h" +#include "core/scripts_registry.h" -class WireguardConfigurator : ConfiguratorBase +class WireguardConfigurator : public ConfiguratorBase { Q_OBJECT public: - WireguardConfigurator(std::shared_ptr settings, QObject *parent = nullptr); + WireguardConfigurator(std::shared_ptr settings, bool isAwg, QObject *parent = nullptr); - struct ConnectionData { + struct ConnectionData + { QString clientPrivKey; // client private key - QString clientPubKey; // client public key - QString clientIP; // internal client IP address - QString serverPubKey; // tls-auth key - QString pskKey; // preshared key - QString host; // host ip + QString clientPubKey; // client public key + QString clientIP; // internal client IP address + QString serverPubKey; // tls-auth key + QString pskKey; // preshared key + QString host; // host ip QString port; }; QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); QString processConfigWithLocalSettings(QString config); QString processConfigWithExportSettings(QString config); - private: - ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); + ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); ConnectionData genClientKeys(); + + bool m_isAwg; + QString m_serverConfigPath; + QString m_serverPublicKeyPath; + QString m_serverPskKeyPath; + amnezia::ProtocolScriptType m_configTemplate; + QString m_protocolName; + QString m_defaultPort; }; #endif // WIREGUARD_CONFIGURATOR_H diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 92066dab..b6f1b111 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -8,18 +8,23 @@ QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c) return debug; } -amnezia::DockerContainer ContainerProps::containerFromString(const QString &container){ +amnezia::DockerContainer ContainerProps::containerFromString(const QString &container) +{ QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { DockerContainer c = static_cast(i); - if (container == containerToString(c)) return c; + if (container == containerToString(c)) + return c; } return DockerContainer::None; } -QString ContainerProps::containerToString(amnezia::DockerContainer c){ - if (c == DockerContainer::None) return "none"; - if (c == DockerContainer::Cloak) return "amnezia-openvpn-cloak"; +QString ContainerProps::containerToString(amnezia::DockerContainer c) +{ + if (c == DockerContainer::None) + return "none"; + if (c == DockerContainer::Cloak) + return "amnezia-openvpn-cloak"; QMetaEnum metaEnum = QMetaEnum::fromType(); QString containerKey = metaEnum.valueToKey(static_cast(c)); @@ -27,9 +32,12 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c){ return "amnezia-" + containerKey.toLower(); } -QString ContainerProps::containerTypeToString(amnezia::DockerContainer c){ - if (c == DockerContainer::None) return "none"; - if (c == DockerContainer::Ipsec) return "ikev2"; +QString ContainerProps::containerTypeToString(amnezia::DockerContainer c) +{ + if (c == DockerContainer::None) + return "none"; + if (c == DockerContainer::Ipsec) + return "ikev2"; QMetaEnum metaEnum = QMetaEnum::fromType(); QString containerKey = metaEnum.valueToKey(static_cast(c)); @@ -40,29 +48,21 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c){ QVector ContainerProps::protocolsForContainer(amnezia::DockerContainer container) { switch (container) { - case DockerContainer::None: - return { }; + case DockerContainer::None: return {}; - case DockerContainer::OpenVpn: - return { Proto::OpenVpn }; + case DockerContainer::OpenVpn: return { Proto::OpenVpn }; - case DockerContainer::ShadowSocks: - return { Proto::OpenVpn, Proto::ShadowSocks }; + case DockerContainer::ShadowSocks: return { Proto::OpenVpn, Proto::ShadowSocks }; - case DockerContainer::Cloak: - return { Proto::OpenVpn, Proto::ShadowSocks, Proto::Cloak }; + case DockerContainer::Cloak: return { Proto::OpenVpn, Proto::ShadowSocks, Proto::Cloak }; - case DockerContainer::Ipsec: - return { Proto::Ikev2 /*, Protocol::L2tp */}; + case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ }; - case DockerContainer::Dns: - return { }; + case DockerContainer::Dns: return {}; - case DockerContainer::Sftp: - return { Proto::Sftp}; + case DockerContainer::Sftp: return { Proto::Sftp }; - default: - return { defaultProtocol(container) }; + default: return { defaultProtocol(container) }; } } @@ -79,70 +79,164 @@ QList ContainerProps::allContainers() QMap ContainerProps::containerHumanNames() { - return { - {DockerContainer::None, "Not installed"}, - {DockerContainer::OpenVpn, "OpenVPN"}, - {DockerContainer::ShadowSocks, "OpenVpn over ShadowSocks"}, - {DockerContainer::Cloak, "OpenVpn over Cloak"}, - {DockerContainer::WireGuard, "WireGuard"}, - {DockerContainer::Ipsec, QObject::tr("IPsec")}, + return { { DockerContainer::None, "Not installed" }, + { DockerContainer::OpenVpn, "OpenVPN" }, + { DockerContainer::ShadowSocks, "ShadowSocks" }, + { DockerContainer::Cloak, "OpenVPN over Cloak" }, + { DockerContainer::WireGuard, "WireGuard" }, + { DockerContainer::Awg, "AmneziaWG" }, + { DockerContainer::Ipsec, QObject::tr("IPsec") }, - {DockerContainer::TorWebSite, QObject::tr("Web site in Tor network")}, - {DockerContainer::Dns, QObject::tr("DNS Service")}, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service")}, - {DockerContainer::Sftp, QObject::tr("Sftp file sharing service")} - }; + { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, + { DockerContainer::Dns, QObject::tr("Amnezia DNS") }, + { DockerContainer::Sftp, QObject::tr("Sftp file sharing service") } }; } QMap ContainerProps::containerDescriptions() { - return { - {DockerContainer::OpenVpn, QObject::tr("OpenVPN container")}, - {DockerContainer::ShadowSocks, QObject::tr("Container with OpenVpn and ShadowSocks")}, - {DockerContainer::Cloak, QObject::tr("Container with OpenVpn and ShadowSocks protocols " - "configured with traffic masking by Cloak plugin")}, - {DockerContainer::WireGuard, QObject::tr("WireGuard container")}, - {DockerContainer::Ipsec, QObject::tr("IPsec container")}, + 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("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is " + "recognised by analysis systems in some highly censored regions.") }, + { DockerContainer::Cloak, + QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against " + "active-probbing detection. Ideal for bypassing blocking in regions with the highest levels " + "of censorship.") }, + { DockerContainer::WireGuard, + QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " + "consumption. Recommended for regions with low levels of censorship.") }, + { DockerContainer::Awg, + QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, " + "but very resistant to blockages. " + "Recommended for regions with high levels of censorship.") }, + { DockerContainer::Ipsec, + QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after " + "signal loss. It has native support on the latest versions of Android and iOS.") }, - {DockerContainer::TorWebSite, QObject::tr("Web site in Tor network")}, - {DockerContainer::Dns, QObject::tr("DNS Service")}, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, - {DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service")} + { DockerContainer::TorWebSite, QObject::tr("Deploy a WordPress site on the Tor network in two clicks.") }, + { DockerContainer::Dns, + QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") }, + { DockerContainer::Sftp, + QObject::tr("Creates a file vault on your server to securely store and transfer files.") } }; +} + +QMap ContainerProps::containerDetailedDescriptions() +{ + return { + { DockerContainer::OpenVpn, + QObject::tr( + "OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n" + "It employs its unique security protocol, " + "leveraging the strength of SSL/TLS for encryption and key exchange. " + "Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, " + "catering to a wide range of devices and operating systems. " + "Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, " + "which continually reinforces its security. " + "With a strong balance of performance, security, and compatibility, " + "OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* Normal power consumption on mobile devices\n" + "* Flexible customisation to suit user needs to work with different operating systems and devices\n" + "* Recognised by DPI analysis systems and therefore susceptible to blocking\n" + "* Can operate over both TCP and UDP network protocols.") }, + { DockerContainer::ShadowSocks, + QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. " + "Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection." + "However, certain traffic analysis systems might still detect a Shadowsocks connection. " + "Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n" + "* Available in the AmneziaVPN only on desktop platforms\n" + "* Normal power consumption on mobile devices\n\n" + "* Configurable encryption protocol\n" + "* Detectable by some DPI systems\n" + "* Works over TCP network protocol.") }, + { DockerContainer::Cloak, + QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for " + "blocking protection.\n\n" + "OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client " + "and the server.\n\n" + "Cloak protects OpenVPN from detection and blocking. \n\n" + "Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, " + "and also protects the VPN from detection by Active Probing. This makes it very resistant to " + "being detected\n\n" + "Immediately after receiving the first data packet, Cloak authenticates the incoming connection. " + "If authentication fails, the plugin masks the server as a fake website and your VPN becomes " + "invisible to analysis systems.\n\n" + "If there is a extreme level of Internet censorship in your region, we advise you to use only " + "OpenVPN over Cloak from the first connection\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* High power consumption on mobile devices\n" + "* Flexible settings\n" + "* Not recognised by DPI analysis systems\n" + "* Works over TCP network protocol, 443 port.\n") }, + { DockerContainer::WireGuard, + QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n" + "Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption " + "settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n" + "WireGuard is very susceptible to blocking due to its distinct packet signatures. " + "Unlike some other VPN protocols that employ obfuscation techniques, " + "the consistent signature patterns of WireGuard packets can be more easily identified and " + "thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* Low power consumption\n" + "* Minimum number of settings\n" + "* Easily recognised by DPI analysis systems, susceptible to blocking\n" + "* Works over UDP network protocol.") }, + { DockerContainer::Awg, + QObject::tr("A modern iteration of the popular VPN protocol, " + "AmneziaWG builds upon the foundation set by WireGuard, " + "retaining its simplified architecture and high-performance capabilities across devices.\n" + "While WireGuard is known for its efficiency, " + "it had issues with being easily detected due to its distinct packet signatures. " + "AmneziaWG solves this problem by using better obfuscation methods, " + "making its traffic blend in with regular internet traffic.\n" + "This means that AmneziaWG keeps the fast performance of the original " + "while adding an extra layer of stealth, " + "making it a great choice for those wanting a fast and discreet VPN connection.\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* Low power consumption\n" + "* Minimum number of settings\n" + "* Not recognised by DPI analysis systems, resistant to blocking\n" + "* Works over UDP network protocol.") }, + { DockerContainer::Ipsec, + QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n" + "One of its distinguishing features is its ability to swiftly switch between networks and devices, " + "making it particularly adaptive in dynamic network environments. \n" + "While it offers a blend of security, stability, and speed, " + "it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n" + "* Available in the AmneziaVPN only on Windows\n" + "* Low power consumption, on mobile devices\n" + "* Minimal configuration\n" + "* Recognised by DPI analysis systems\n" + "* Works over UDP network protocol, ports 500 and 4500.") }, + + { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, + { DockerContainer::Dns, QObject::tr("DNS Service") }, + { DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") } }; } amnezia::ServiceType ContainerProps::containerService(DockerContainer c) { - switch (c) { - case DockerContainer::None : return ServiceType::None; - case DockerContainer::OpenVpn : return ServiceType::Vpn; - case DockerContainer::Cloak : return ServiceType::Vpn; - case DockerContainer::ShadowSocks : return ServiceType::Vpn; - case DockerContainer::WireGuard : return ServiceType::Vpn; - case DockerContainer::Ipsec : return ServiceType::Vpn; - case DockerContainer::TorWebSite : return ServiceType::Other; - case DockerContainer::Dns : return ServiceType::Other; - //case DockerContainer::FileShare : return ServiceType::Other; - case DockerContainer::Sftp : return ServiceType::Other; - default: return ServiceType::Other; - } + return ProtocolProps::protocolService(defaultProtocol(c)); } Proto ContainerProps::defaultProtocol(DockerContainer c) { switch (c) { - case DockerContainer::None : return Proto::Any; - case DockerContainer::OpenVpn : return Proto::OpenVpn; - case DockerContainer::Cloak : return Proto::Cloak; - case DockerContainer::ShadowSocks : return Proto::ShadowSocks; - case DockerContainer::WireGuard : return Proto::WireGuard; - case DockerContainer::Ipsec : return Proto::Ikev2; + case DockerContainer::None: return Proto::Any; + case DockerContainer::OpenVpn: return Proto::OpenVpn; + case DockerContainer::Cloak: return Proto::Cloak; + case DockerContainer::ShadowSocks: return Proto::ShadowSocks; + case DockerContainer::WireGuard: return Proto::WireGuard; + case DockerContainer::Awg: return Proto::Awg; + case DockerContainer::Ipsec: return Proto::Ikev2; - case DockerContainer::TorWebSite : return Proto::TorWebSite; - case DockerContainer::Dns : return Proto::Dns; - //case DockerContainer::FileShare : return Protocol::FileShare; - case DockerContainer::Sftp : return Proto::Sftp; - default: return Proto::Any; + case DockerContainer::TorWebSite: return Proto::TorWebSite; + case DockerContainer::Dns: return Proto::Dns; + case DockerContainer::Sftp: return Proto::Sftp; + default: return Proto::Any; } } @@ -151,31 +245,34 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) #ifdef Q_OS_WINDOWS return true; -#elif defined (Q_OS_IOS) +#elif defined(Q_OS_IOS) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; - case DockerContainer::Cloak: return true; -// case DockerContainer::ShadowSocks: return true; + case DockerContainer::Awg: return true; + case DockerContainer::Cloak: + return true; + // case DockerContainer::ShadowSocks: return true; default: return false; } -#elif defined (Q_OS_MAC) +#elif defined(Q_OS_MAC) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::Ipsec: return false; default: return true; } -#elif defined (Q_OS_ANDROID) +#elif defined(Q_OS_ANDROID) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; case DockerContainer::ShadowSocks: return true; + case DockerContainer::Awg: return true; case DockerContainer::Cloak: return true; default: return false; } -#elif defined (Q_OS_LINUX) +#elif defined(Q_OS_LINUX) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::Ipsec: return false; @@ -183,14 +280,65 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) } #else -return false; + return false; #endif } QStringList ContainerProps::fixedPortsForContainer(DockerContainer c) { switch (c) { - case DockerContainer::Ipsec : return QStringList{"500", "4500"}; - default: return {}; + case DockerContainer::Ipsec: return QStringList { "500", "4500" }; + default: return {}; + } +} + +bool ContainerProps::isEasySetupContainer(DockerContainer container) +{ + switch (container) { + case DockerContainer::WireGuard: return true; + case DockerContainer::Awg: return true; + case DockerContainer::Cloak: return true; + default: return false; + } +} + +QString ContainerProps::easySetupHeader(DockerContainer container) +{ + switch (container) { + case DockerContainer::WireGuard: return tr("Low"); + case DockerContainer::Awg: return tr("Medium or High"); + case DockerContainer::Cloak: return tr("Extreme"); + default: return ""; + } +} + +QString ContainerProps::easySetupDescription(DockerContainer container) +{ + switch (container) { + case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy."); + case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases."); + case DockerContainer::Cloak: + return tr("Most VPN protocols are blocked. Recommended if other options are not working."); + default: return ""; + } +} + +int ContainerProps::easySetupOrder(DockerContainer container) +{ + switch (container) { + case DockerContainer::WireGuard: return 3; + case DockerContainer::Awg: return 2; + case DockerContainer::Cloak: return 1; + default: return 0; + } +} + +bool ContainerProps::isShareable(DockerContainer container) +{ + switch (container) { + case DockerContainer::TorWebSite: return false; + case DockerContainer::Dns: return false; + case DockerContainer::Sftp: return false; + default: return true; } } diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index ff230c3e..92ca4f18 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -8,68 +8,72 @@ using namespace amnezia; -namespace amnezia { - -namespace ContainerEnumNS { -Q_NAMESPACE -enum DockerContainer { - None = 0, - OpenVpn, - ShadowSocks, - Cloak, - WireGuard, - Ipsec, - - //non-vpn - TorWebSite, - Dns, - //FileShare, - Sftp -}; -Q_ENUM_NS(DockerContainer) -} // namespace ContainerEnumNS - -using namespace ContainerEnumNS; -using namespace ProtocolEnumNS; - -class ContainerProps : public QObject +namespace amnezia { - Q_OBJECT -public: - Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container); - Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container); - Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c); + namespace ContainerEnumNS + { + Q_NAMESPACE + enum DockerContainer { + None = 0, + Awg, + WireGuard, + OpenVpn, + Cloak, + ShadowSocks, + Ipsec, - Q_INVOKABLE static QList allContainers(); + // non-vpn + TorWebSite, + Dns, + Sftp + }; + Q_ENUM_NS(DockerContainer) + } // namespace ContainerEnumNS - Q_INVOKABLE static QMap containerHumanNames(); - Q_INVOKABLE static QMap containerDescriptions(); + using namespace ContainerEnumNS; + using namespace ProtocolEnumNS; - // these protocols will be displayed in container settings - Q_INVOKABLE static QVector protocolsForContainer(amnezia::DockerContainer container); + class ContainerProps : public QObject + { + Q_OBJECT - Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c); + public: + Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container); + Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container); + Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c); - // binding between Docker container and main protocol of given container - // it may be changed fot future containers :) - Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c); + Q_INVOKABLE static QList allContainers(); - Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c); - Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c); -}; + Q_INVOKABLE static QMap containerHumanNames(); + Q_INVOKABLE static QMap containerDescriptions(); + Q_INVOKABLE static QMap containerDetailedDescriptions(); + // these protocols will be displayed in container settings + Q_INVOKABLE static QVector protocolsForContainer(amnezia::DockerContainer container); + Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c); -static void declareQmlContainerEnum() { - qmlRegisterUncreatableMetaObject( - ContainerEnumNS::staticMetaObject, - "ContainerEnum", - 1, 0, - "ContainerEnum", - "Error: only enums" - ); -} + // binding between Docker container and main protocol of given container + // it may be changed fot future containers :) + Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c); + + Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c); + Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c); + + static bool isEasySetupContainer(amnezia::DockerContainer container); + static QString easySetupHeader(amnezia::DockerContainer container); + static QString easySetupDescription(amnezia::DockerContainer container); + static int easySetupOrder(amnezia::DockerContainer container); + + static bool isShareable(amnezia::DockerContainer container); + }; + + static void declareQmlContainerEnum() + { + qmlRegisterUncreatableMetaObject(ContainerEnumNS::staticMetaObject, "ContainerEnum", 1, 0, "ContainerEnum", + "Error: only enums"); + } } // namespace amnezia diff --git a/client/core/defs.h b/client/core/defs.h index 4fb140e4..35515103 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -12,10 +12,10 @@ struct ServerCredentials { QString hostName; QString userName; - QString password; + QString secretData; int port = 22; - bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !password.isEmpty() && port > 0; } + bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; } }; enum ErrorCode @@ -37,7 +37,7 @@ enum ErrorCode // Ssh connection errors SshRequsetDeniedError, SshInterruptedError, SshInternalError, - SshPrivateKeyError, SshPrivateKeyFormatError, + SshPrivateKeyError, SshPrivateKeyFormatError, SshTimeoutError, // Ssh sftp errors SshSftpEofError, SshSftpNoSuchFileError, SshSftpPermissionDeniedError, @@ -69,7 +69,10 @@ enum ErrorCode OpenSslFailed, OpenVpnExecutableCrashed, ShadowSocksExecutableCrashed, - CloakExecutableCrashed + CloakExecutableCrashed, + + // import and install errors + ImportInvalidConfigError }; } // namespace amnezia diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 17b40b09..cd66186d 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -24,6 +24,7 @@ QString errorString(ErrorCode code){ case(SshInternalError): return QObject::tr("Ssh internal error"); case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered"); case(SshPrivateKeyFormatError): return QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); + case(SshTimeoutError): return QObject::tr("Timeout connecting to server"); // Libssh sftp errors case(SshSftpEofError): return QObject::tr("Sftp error: End-of-file encountered"); @@ -57,6 +58,8 @@ QString errorString(ErrorCode code){ case (OpenVpnTapAdapterError): return QObject::tr("Can't setup OpenVPN TAP network adapter"); case (AddressPoolError): return QObject::tr("VPN pool error: no available addresses"); + case (ImportInvalidConfigError): return QObject::tr("The config does not contain any containers and credentiaks for connecting to the server"); + case(InternalError): default: return QObject::tr("Internal error"); diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 1b379ea1..61ae8962 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -1,8 +1,8 @@ #include "scripts_registry.h" -#include #include #include +#include QString amnezia::scriptFolder(amnezia::DockerContainer container) { @@ -11,11 +11,11 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container) case DockerContainer::Cloak: return QLatin1String("openvpn_cloak"); case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks"); case DockerContainer::WireGuard: return QLatin1String("wireguard"); + case DockerContainer::Awg: return QLatin1String("awg"); case DockerContainer::Ipsec: return QLatin1String("ipsec"); case DockerContainer::TorWebSite: return QLatin1String("website_tor"); case DockerContainer::Dns: return QLatin1String("dns"); - //case DockerContainer::FileShare: return QLatin1String("file_share"); case DockerContainer::Sftp: return QLatin1String("sftp"); default: return ""; } @@ -45,6 +45,7 @@ QString amnezia::scriptName(ProtocolScriptType type) case ProtocolScriptType::container_startup: return QLatin1String("start.sh"); case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn"); case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf"); + case ProtocolScriptType::awg_template: return QLatin1String("template.conf"); } } @@ -52,7 +53,7 @@ QString amnezia::scriptData(amnezia::SharedScriptType type) { QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type)); QFile file(fileName); - if (! file.open(QIODevice::ReadOnly)) { + if (!file.open(QIODevice::ReadOnly)) { qDebug() << "Warning: script missing" << fileName; return ""; } @@ -67,7 +68,7 @@ QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer co { QString fileName = QString(":/server_scripts/%1/%2").arg(amnezia::scriptFolder(container), amnezia::scriptName(type)); QFile file(fileName); - if (! file.open(QIODevice::ReadOnly)) { + if (!file.open(QIODevice::ReadOnly)) { qDebug() << "Warning: script missing" << fileName; return ""; } diff --git a/client/core/scripts_registry.h b/client/core/scripts_registry.h index b30be2ff..02fc94fd 100644 --- a/client/core/scripts_registry.h +++ b/client/core/scripts_registry.h @@ -26,7 +26,8 @@ enum ProtocolScriptType { configure_container, container_startup, openvpn_template, - wireguard_template + wireguard_template, + awg_template }; diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index e5505d46..da76e1ff 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -2,22 +2,21 @@ #include #include -#include #include +#include +#include +#include +#include #include #include -#include -#include -#include -#include #include -#include #include +#include #include #include -#include #include +#include #include #include @@ -25,15 +24,14 @@ #include "containers/containers_defs.h" #include "logger.h" +#include "scripts_registry.h" #include "server_defs.h" #include "settings.h" -#include "scripts_registry.h" #include "utilities.h" #include -ServerController::ServerController(std::shared_ptr settings, QObject *parent) : - m_settings(settings) +ServerController::ServerController(std::shared_ptr settings, QObject *parent) : m_settings(settings) { } @@ -42,10 +40,10 @@ ServerController::~ServerController() m_sshClient.disconnectFromHost(); } - ErrorCode ServerController::runScript(const ServerCredentials &credentials, QString script, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr) { + const std::function &cbReadStdOut, + const std::function &cbReadStdErr) +{ auto error = m_sshClient.connectToHost(credentials); if (error != ErrorCode::NoError) { @@ -92,36 +90,36 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr return ErrorCode::NoError; } -ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, - DockerContainer container, QString script, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr) +ErrorCode +ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, + const std::function &cbReadStdOut, + const std::function &cbReadStdErr) { QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh"; Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script); ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName); - if (e) return e; + if (e) + return e; QString runner = QString("sudo docker exec -i $CONTAINER_NAME bash %1 ").arg(fileName); - e = runScript(credentials, - replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName); - runScript(credentials, - replaceVars(remover, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + runScript(credentials, replaceVars(remover, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); return e; } -ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, - const ServerCredentials &credentials, const QString &file, const QString &path, - libssh::SftpOverwriteMode overwriteMode) +ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, + const QString &file, const QString &path, + libssh::SftpOverwriteMode overwriteMode) { ErrorCode e = ErrorCode::NoError; QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16)); e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName); - if (e) return e; + if (e) + return e; QString stdOut; auto cbReadStd = [&](const QString &data, libssh::Client &) { @@ -130,61 +128,63 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, }; // mkdir - QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"") - .arg(path); - - e = runScript(credentials, - replaceVars(mkdir, genVarsForScript(credentials, container))); - if (e) return e; + QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path); + e = runScript(credentials, replaceVars(mkdir, genVarsForScript(credentials, container))); + if (e) + return e; if (overwriteMode == libssh::SftpOverwriteMode::SftpOverwriteExisting) { e = runScript(credentials, - replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), - genVarsForScript(credentials, container)), cbReadStd, cbReadStd); + replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), + genVarsForScript(credentials, container)), + cbReadStd, cbReadStd); - if (e) return e; - } - else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) { + if (e) + return e; + } else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) { e = runScript(credentials, - replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName), - genVarsForScript(credentials, container)), cbReadStd, cbReadStd); + replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName), + genVarsForScript(credentials, container)), + cbReadStd, cbReadStd); - if (e) return e; + if (e) + return e; - e = runScript(credentials, - replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path), - genVarsForScript(credentials, container)), cbReadStd, cbReadStd); - - if (e) return e; - } - else return ErrorCode::NotImplementedError; + e = runScript( + credentials, + replaceVars( + QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path), + genVarsForScript(credentials, container)), + cbReadStd, cbReadStd); + if (e) + return e; + } else + return ErrorCode::NotImplementedError; if (stdOut.contains("Error: No such container:")) { return ErrorCode::ServerContainerMissingError; } runScript(credentials, - replaceVars(QString("sudo shred %1").arg(tmpFileName), - genVarsForScript(credentials, container))); + replaceVars(QString("sudo shred %1").arg(tmpFileName), genVarsForScript(credentials, container))); - runScript(credentials, - replaceVars(QString("sudo rm %1").arg(tmpFileName), - genVarsForScript(credentials, container))); + runScript(credentials, replaceVars(QString("sudo rm %1").arg(tmpFileName), genVarsForScript(credentials, container))); return e; } -QByteArray ServerController::getTextFileFromContainer(DockerContainer container, - const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode) +QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, + const QString &path, ErrorCode *errorCode) { - if (errorCode) *errorCode = ErrorCode::NoError; - - QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\""). - arg(ContainerProps::containerToString(container)).arg(path); + if (errorCode) + *errorCode = ErrorCode::NoError; + QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"") + .arg(ContainerProps::containerToString(container)) + .arg(path); QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { @@ -196,8 +196,8 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container, return QByteArray::fromHex(stdOut.toUtf8()); } -ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, - libssh::SftpOverwriteMode overwriteMode) +ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, + const QString &remotePath, libssh::SftpOverwriteMode overwriteMode) { auto error = m_sshClient.connectToHost(credentials); if (error != ErrorCode::NoError) { @@ -209,7 +209,8 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential localFile.write(data); localFile.close(); - error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), "non_desc"); + error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), + "non_desc"); if (error != ErrorCode::NoError) { return error; } @@ -218,15 +219,14 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials) { - return runScript(credentials, - amnezia::scriptData(SharedScriptType::remove_all_containers)); + return runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers)); } ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container) { return runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::remove_container), - genVarsForScript(credentials, container))); + replaceVars(amnezia::scriptData(SharedScriptType::remove_container), + genVarsForScript(credentials, container))); } ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, @@ -236,22 +236,33 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, ErrorCode e = ErrorCode::NoError; e = isUserInSudo(credentials, container); - if (e) return e; + if (e) + return e; e = isServerDpkgBusy(credentials, container); - if (e) return e; + if (e) + return e; e = installDockerWorker(credentials, container); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer installDockerWorker finished"; if (!isUpdate) { e = isServerPortBusy(credentials, container, config); - if (e) return e; + if (e) + return e; + } + + if (!isUpdate) { + e = isServerPortBusy(credentials, container, config); + if (e) + return e; } e = prepareHostWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer prepareHostWorker finished"; removeContainer(credentials, container); @@ -259,15 +270,18 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, qDebug().noquote() << "buildContainerWorker start"; e = buildContainerWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer buildContainerWorker finished"; e = runContainerWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer runContainerWorker finished"; e = configureContainerWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer configureContainerWorker finished"; setupServerFirewall(credentials); @@ -277,46 +291,25 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, } ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &oldConfig, QJsonObject &newConfig) + const QJsonObject &oldConfig, QJsonObject &newConfig) { bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); - qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired; + qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" + << reinstallRequired; if (reinstallRequired) { return setupContainer(credentials, container, newConfig, true); - } - else { + } else { ErrorCode e = configureContainerWorker(credentials, container, newConfig); - if (e) return e; + if (e) + return e; return startupContainerWorker(credentials, container, newConfig); } } -QJsonObject ServerController::createContainerInitialConfig(DockerContainer container, int port, TransportProto tp) -{ - Proto mainProto = ContainerProps::defaultProtocol(container); - - QJsonObject config { - { config_key::container, ContainerProps::containerToString(container) } - }; - - QJsonObject protoConfig; - protoConfig.insert(config_key::port, QString::number(port)); - protoConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(tp, mainProto)); - - - if (container == DockerContainer::Sftp) { - protoConfig.insert(config_key::userName, protocols::sftp::defaultUserName); - protoConfig.insert(config_key::password, Utils::getRandomString(10)); - } - - config.insert(ProtocolProps::protoToString(mainProto), protoConfig); - - return config; -} - -bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig) +bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, + const QJsonObject &newConfig) { Proto mainProto = ContainerProps::defaultProtocol(container); @@ -324,25 +317,29 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); if (container == DockerContainer::OpenVpn) { - if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) != - newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto)) - return true; + if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) + != newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto)) + return true; - if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) != - newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)) - return true; + if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) + != newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)) + return true; } if (container == DockerContainer::Cloak) { - if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort) != - newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort)) - return true; + if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort) + != newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort)) + return true; } if (container == DockerContainer::ShadowSocks) { - if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) != - newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)) - return true; + if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) + != newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)) + return true; + } + + if (container == DockerContainer::Awg) { + return true; } return false; @@ -364,75 +361,86 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent return ErrorCode::NoError; }; - ErrorCode error = runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::install_docker), - genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); + ErrorCode error = + runScript(credentials, + replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)), + cbReadStdOut, cbReadStdErr); qDebug().noquote() << "ServerController::installDockerWorker" << stdOut; - if (stdOut.contains("lock")) return ErrorCode::ServerPacketManagerError; - if (stdOut.contains("command not found")) return ErrorCode::ServerDockerFailedError; + if (stdOut.contains("lock")) + return ErrorCode::ServerPacketManagerError; + if (stdOut.contains("command not found")) + return ErrorCode::ServerDockerFailedError; return error; } -ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { // create folder on host - return runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), - genVarsForScript(credentials, container))); + return runScript( + credentials, + replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container))); } -ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(), - amnezia::server::getDockerfileFolder(container) + "/Dockerfile"); + amnezia::server::getDockerfileFolder(container) + "/Dockerfile"); - if (e) return e; + if (e) + return e; QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; return ErrorCode::NoError; }; -// auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { -// stdOut += data + "\n"; -// }; + // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { + // stdOut += data + "\n"; + // }; e = runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::build_container), - genVarsForScript(credentials, container, config)), cbReadStdOut); - if (e) return e; + replaceVars(amnezia::scriptData(SharedScriptType::build_container), + genVarsForScript(credentials, container, config)), + cbReadStdOut); + if (e) + return e; return e; } -ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) +ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, + QJsonObject &config) { QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; return ErrorCode::NoError; }; - // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { - // stdOut += data + "\n"; - // }; + // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { + // stdOut += data + "\n"; + // }; ErrorCode e = runScript(credentials, - replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), - genVarsForScript(credentials, container, config)), cbReadStdOut); + replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), + genVarsForScript(credentials, container, config)), + cbReadStdOut); - - if (stdOut.contains("docker: Error response from daemon")) return ErrorCode::ServerDockerFailedError; - - if (stdOut.contains("address already in use")) return ErrorCode::ServerPortAlreadyAllocatedError; - if (stdOut.contains("is already in use by container")) return ErrorCode::ServerPortAlreadyAllocatedError; - if (stdOut.contains("invalid publish")) return ErrorCode::ServerDockerFailedError; + if (stdOut.contains("address already in use")) + return ErrorCode::ServerPortAlreadyAllocatedError; + if (stdOut.contains("is already in use by container")) + return ErrorCode::ServerPortAlreadyAllocatedError; + if (stdOut.contains("invalid publish")) + return ErrorCode::ServerDockerFailedError; return e; } -ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) +ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, + QJsonObject &config) { QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { @@ -444,19 +452,18 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr return ErrorCode::NoError; }; - ErrorCode e = runContainerScript(credentials, container, - replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), - genVarsForScript(credentials, container, config)), - cbReadStdOut, cbReadStdErr); - + replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), + genVarsForScript(credentials, container, config)), + cbReadStdOut, cbReadStdErr); m_configurator->updateContainerConfigAfterInstallation(container, config, stdOut); return e; } -ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container); @@ -465,104 +472,144 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred } ErrorCode e = uploadTextFileToContainer(container, credentials, - replaceVars(script, genVarsForScript(credentials, container, config)), - "/opt/amnezia/start.sh"); - if (e) return e; + replaceVars(script, genVarsForScript(credentials, container, config)), + "/opt/amnezia/start.sh"); + if (e) + return e; return runScript(credentials, - replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh\"", - genVarsForScript(credentials, container, config))); + replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && " + "/opt/amnezia/start.sh\"", + genVarsForScript(credentials, container, config))); } -ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &config) { const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject(); const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject(); const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject(); const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject(); + const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject(); const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject(); Vars vars; - vars.append({{"$REMOTE_HOST", credentials.hostName}}); + vars.append({ { "$REMOTE_HOST", credentials.hostName } }); // OpenVPN vars - vars.append({{"$OPENVPN_SUBNET_IP", openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) }}); - vars.append({{"$OPENVPN_SUBNET_CIDR", openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) }}); - vars.append({{"$OPENVPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) }}); + vars.append( + { { "$OPENVPN_SUBNET_IP", + openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } }); + vars.append({ { "$OPENVPN_SUBNET_CIDR", + openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } }); + vars.append({ { "$OPENVPN_SUBNET_MASK", + openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } }); - vars.append({{"$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) }}); - vars.append({{"$OPENVPN_TRANSPORT_PROTO", openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) }}); + vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } }); + vars.append( + { { "$OPENVPN_TRANSPORT_PROTO", + openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } }); bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); - vars.append({{"$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" }}); + vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } }); - vars.append({{"$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) }}); - vars.append({{"$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) }}); + vars.append({ { "$OPENVPN_CIPHER", + openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } }); + vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } }); bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); - vars.append({{"$OPENVPN_TLS_AUTH", isTlsAuth ? protocols::openvpn::tlsAuthString : "" }}); + vars.append({ { "$OPENVPN_TLS_AUTH", isTlsAuth ? protocols::openvpn::tlsAuthString : "" } }); if (!isTlsAuth) { // erase $OPENVPN_TA_KEY, so it will not set in OpenVpnConfigurator::genOpenVpnConfig - vars.append({{"$OPENVPN_TA_KEY", "" }}); + vars.append({ { "$OPENVPN_TA_KEY", "" } }); } - vars.append({{"$OPENVPN_ADDITIONAL_CLIENT_CONFIG", openvpnConfig.value(config_key::additional_client_config). - toString(protocols::openvpn::defaultAdditionalClientConfig) }}); - vars.append({{"$OPENVPN_ADDITIONAL_SERVER_CONFIG", openvpnConfig.value(config_key::additional_server_config). - toString(protocols::openvpn::defaultAdditionalServerConfig) }}); + vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG", + openvpnConfig.value(config_key::additional_client_config) + .toString(protocols::openvpn::defaultAdditionalClientConfig) } }); + vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG", + openvpnConfig.value(config_key::additional_server_config) + .toString(protocols::openvpn::defaultAdditionalServerConfig) } }); // ShadowSocks vars - vars.append({{"$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) }}); - vars.append({{"$SHADOWSOCKS_LOCAL_PORT", ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) }}); - vars.append({{"$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) }}); + vars.append({ { "$SHADOWSOCKS_SERVER_PORT", + ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } }); + vars.append({ { "$SHADOWSOCKS_LOCAL_PORT", + ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } }); + vars.append({ { "$SHADOWSOCKS_CIPHER", + ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } }); - vars.append({{"$CONTAINER_NAME", ContainerProps::containerToString(container)}}); - vars.append({{"$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container)}}); + vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } }); + vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } }); // Cloak vars - vars.append({{"$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) }}); - vars.append({{"$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) }}); + vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } }); + vars.append({ { "$FAKE_WEB_SITE_ADDRESS", + cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } }); // Wireguard vars - vars.append({{"$WIREGUARD_SUBNET_IP", wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) }}); - vars.append({{"$WIREGUARD_SUBNET_CIDR", wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) }}); - vars.append({{"$WIREGUARD_SUBNET_MASK", wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) }}); + vars.append( + { { "$WIREGUARD_SUBNET_IP", + wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } }); + vars.append({ { "$WIREGUARD_SUBNET_CIDR", + wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } }); + vars.append({ { "$WIREGUARD_SUBNET_MASK", + wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } }); - vars.append({{"$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) }}); + vars.append({ { "$WIREGUARD_SERVER_PORT", + wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } }); // IPsec vars - vars.append({{"$IPSEC_VPN_L2TP_NET", "192.168.42.0/24"}}); - vars.append({{"$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250"}}); - vars.append({{"$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1"}}); + vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } }); + vars.append({ { "$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250" } }); + vars.append({ { "$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1" } }); - vars.append({{"$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24"}}); - vars.append({{"$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250"}}); + vars.append({ { "$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24" } }); + vars.append({ { "$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250" } }); - vars.append({{"$IPSEC_VPN_SHA2_TRUNCBUG", "yes"}}); + vars.append({ { "$IPSEC_VPN_SHA2_TRUNCBUG", "yes" } }); - vars.append({{"$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes"}}); - vars.append({{"$IPSEC_VPN_DISABLE_IKEV2", "no"}}); - vars.append({{"$IPSEC_VPN_DISABLE_L2TP", "no"}}); - vars.append({{"$IPSEC_VPN_DISABLE_XAUTH", "no"}}); + vars.append({ { "$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_IKEV2", "no" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_L2TP", "no" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_XAUTH", "no" } }); - vars.append({{"$IPSEC_VPN_C2C_TRAFFIC", "no"}}); - - vars.append({{"$PRIMARY_SERVER_DNS", m_settings->primaryDns()}}); - vars.append({{"$SECONDARY_SERVER_DNS", m_settings->secondaryDns()}}); + vars.append({ { "$IPSEC_VPN_C2C_TRAFFIC", "no" } }); + vars.append({ { "$PRIMARY_SERVER_DNS", m_settings->primaryDns() } }); + vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } }); // Sftp vars - vars.append({{"$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) }}); - vars.append({{"$SFTP_USER", sftpConfig.value(config_key::userName).toString() }}); - vars.append({{"$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() }}); + vars.append( + { { "$SFTP_PORT", + sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } }); + vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } }); + vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } }); + // Amnezia wireguard vars + vars.append({ { "$AWG_SERVER_PORT", + amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } }); + + vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } }); + vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } }); + vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } }); + vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } }); + vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", + amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } }); + vars.append({ { "$INIT_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } }); + vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } }); + vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } }); + vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } }); QString serverIp = Utils::getIPAddress(credentials.hostName); if (!serverIp.isEmpty()) { - vars.append({{"$SERVER_IP_ADDRESS", serverIp}}); - } - else { + vars.append({ { "$SERVER_IP_ADDRESS", serverIp } }); + } else { qWarning() << "ServerController::genVarsForScript unable to resolve address for credentials.hostName"; } @@ -581,10 +628,11 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential return ErrorCode::NoError; }; - ErrorCode e = runScript(credentials, - amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); + ErrorCode e = + runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return stdOut; } @@ -596,9 +644,9 @@ void ServerController::setCancelInstallation(const bool cancel) ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials) { - return runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), - genVarsForScript(credentials))); + return runScript( + credentials, + replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials))); } QString ServerController::replaceVars(const QString &script, const Vars &vars) @@ -610,7 +658,8 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars) return s; } -ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { if (container == DockerContainer::Dns) { return ErrorCode::NoError; @@ -633,12 +682,15 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container); QString defaultPort("%1"); - QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol))); - QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol); + QString port = + containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol))); + QString defaultTransportProto = + ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol); QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto); // TODO reimplement with netstat - QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port); + QString script = + QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port); for (auto &port : fixedPorts) { script = script.append("|:%1").arg(port); } @@ -648,8 +700,8 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential script = script.append(" | grep LISTEN"); } - ErrorCode errorCode = runScript(credentials, - replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)), + cbReadStdOut, cbReadStdErr); if (errorCode != ErrorCode::NoError) { return errorCode; } @@ -677,9 +729,11 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D }; const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo); - ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); + ErrorCode error = + runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); - if (!stdOut.contains("sudo")) return ErrorCode::ServerUserNotInSudo; + if (!stdOut.contains("sudo")) + return ErrorCode::ServerUserNotInSudo; return error; } @@ -707,18 +761,20 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential stdOut.clear(); runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), - genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); + genVarsForScript(credentials)), + cbReadStdOut, cbReadStdErr); - if (stdOut.contains("Packet manager not found")) return ErrorCode::ServerPacketManagerError; - if (stdOut.contains("fuser not installed")) return ErrorCode::NoError; + if (stdOut.contains("Packet manager not found")) + return ErrorCode::ServerPacketManagerError; + if (stdOut.contains("fuser not installed")) + return ErrorCode::NoError; if (stdOut.isEmpty()) { return ErrorCode::NoError; - } - else { - #ifdef MZ_DEBUG + } else { +#ifdef MZ_DEBUG qDebug().noquote() << stdOut; - #endif +#endif emit serverIsBusy(true); QThread::msleep(10000); } @@ -737,7 +793,8 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential return future.result(); } -ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers) +ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials, + QMap &installedContainers) { QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { @@ -768,14 +825,47 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential QString port = containerAndPortMatch.captured(2); QString transportProto = containerAndPortMatch.captured(3); DockerContainer container = ContainerProps::containerFromString(name); + + QJsonObject config; Proto mainProto = ContainerProps::defaultProtocol(container); - QJsonObject config { - { config_key::container, name }, - { ProtocolProps::protoToString(mainProto), QJsonObject { - { config_key::port, port }, - { config_key::transport_proto, transportProto }} + for (auto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject containerConfig; + if (protocol == mainProto) { + containerConfig.insert(config_key::port, port); + containerConfig.insert(config_key::transport_proto, transportProto); + + if (protocol == Proto::Awg) { + QString serverConfig = getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, &errorCode); + + QMap serverConfigMap; + auto serverConfigLines = serverConfig.split("\n"); + for (auto &line : serverConfigLines) { + auto trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed()); + } + } + } + + containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount); + containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize); + containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize); + containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize); + containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize); + containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader); + containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader); + containerConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader); + containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader); + } + + config.insert(config_key::container, ContainerProps::containerToString(container)); } - }; + config.insert(ProtocolProps::protoToString(protocol), containerConfig); + } installedContainers.insert(container, config); } } @@ -783,7 +873,8 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential return ErrorCode::NoError; } -ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback) +ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, + const std::function &callback) { auto error = m_sshClient.getDecryptedPrivateKey(credentials, decryptedPrivateKey, callback); return error; diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index 70ac9cc2..3191386c 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -4,8 +4,8 @@ #include #include -#include "defs.h" #include "containers/containers_defs.h" +#include "defs.h" #include "sshclient.h" class Settings; @@ -24,52 +24,62 @@ public: ErrorCode removeAllContainers(const ServerCredentials &credentials); ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); - ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, - QJsonObject &config, bool isUpdate = false); + ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, + bool isUpdate = false); ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig, QJsonObject &newConfig); - ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); - // create initial config - generate passwords, etc - QJsonObject createContainerInitialConfig(DockerContainer container, int port, TransportProto tp); - ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, + QMap &installedContainers); - ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, - const QString &file, const QString &path, - libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); + ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config = QJsonObject()); + + ErrorCode uploadTextFileToContainer( + DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path, + libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr); QString replaceVars(const QString &script, const Vars &vars); - Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject()); + Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, + const QJsonObject &config = QJsonObject()); ErrorCode runScript(const ServerCredentials &credentials, QString script, - const std::function &cbReadStdOut = nullptr, - const std::function &cbReadStdErr = nullptr); + const std::function &cbReadStdOut = nullptr, + const std::function &cbReadStdErr = nullptr); - ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, - const std::function &cbReadStdOut = nullptr, - const std::function &cbReadStdErr = nullptr); + ErrorCode + runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, + const std::function &cbReadStdOut = nullptr, + const std::function &cbReadStdErr = nullptr); QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); void setCancelInstallation(const bool cancel); - ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback); + ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, + const std::function &callback); + private: ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); - ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); - ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config = QJsonObject()); + ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config = QJsonObject()); ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); - ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); + ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, + QJsonObject &config); - ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config); - bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig); + ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config); + bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, + const QJsonObject &newConfig); ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container); ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container); - - ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, - const QString &remotePath, libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); + + ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, + libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); ErrorCode setupServerFirewall(const ServerCredentials &credentials); diff --git a/client/core/sshclient.cpp b/client/core/sshclient.cpp index 4e557645..797bdc6f 100644 --- a/client/core/sshclient.cpp +++ b/client/core/sshclient.cpp @@ -10,6 +10,8 @@ const uint32_t S_IRWXU = 0644; #endif namespace libssh { + const QString libsshTimeoutError = "Timeout connecting to"; + std::function Client::m_passphraseCallback; Client::Client(QObject *parent) : QObject(parent) @@ -45,20 +47,29 @@ namespace libssh { ssh_options_set(m_session, SSH_OPTIONS_USER, hostUsername.c_str()); ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &logVerbosity); - int connectionResult = ssh_connect(m_session); + QFutureWatcher watcher; + QFuture future = QtConcurrent::run([this]() { + return ssh_connect(m_session); + }); + + QEventLoop wait; + connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); + watcher.setFuture(future); + wait.exec(); + + int connectionResult = watcher.result(); if (connectionResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } std::string authUsername = credentials.userName.toStdString(); int authResult = SSH_ERROR; - if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) { + if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) { ssh_key privateKey = nullptr; ssh_key publicKey = nullptr; - authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); + authResult = ssh_pki_import_privkey_base64(credentials.secretData.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); if (authResult == SSH_OK) { authResult = ssh_pki_export_privkey_to_pubkey(privateKey, &publicKey); } @@ -78,18 +89,17 @@ namespace libssh { ssh_key_free(privateKey); } if (authResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - ErrorCode errorCode = fromLibsshErrorCode(ssh_get_error_code(m_session)); + qCritical() << ssh_get_error(m_session); + ErrorCode errorCode = fromLibsshErrorCode(); if (errorCode == ErrorCode::NoError) { errorCode = ErrorCode::SshPrivateKeyFormatError; } return errorCode; } } else { - authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.password.toStdString().c_str()); + authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.secretData.toStdString().c_str()); if (authResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } } } @@ -186,16 +196,15 @@ namespace libssh { ErrorCode Client::writeResponse(const QString &data) { if (m_channel == nullptr) { - qDebug() << "ssh channel not initialized"; - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + qCritical() << "ssh channel not initialized"; + return fromLibsshErrorCode(); } int bytesWritten = ssh_channel_write(m_channel, data.toUtf8(), (uint32_t)data.size()); if (bytesWritten == data.size() && ssh_channel_write(m_channel, "\n", 1)) { - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } ErrorCode Client::closeChannel() @@ -210,8 +219,7 @@ namespace libssh { ssh_channel_free(m_channel); m_channel = nullptr; } - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const std::string& localPath, const std::string& remotePath, const std::string& fileDesc) @@ -308,12 +316,21 @@ namespace libssh { sftp_free(m_sftpSession); m_sftpSession = nullptr; } - qDebug() << ssh_get_error(m_session); + qCritical() << ssh_get_error(m_session); return errorCode; } - ErrorCode Client::fromLibsshErrorCode(int errorCode) + ErrorCode Client::fromLibsshErrorCode() { + int errorCode = ssh_get_error_code(m_session); + if (errorCode != SSH_NO_ERROR) { + QString errorMessage = ssh_get_error(m_session); + qCritical() << errorMessage; + if (errorMessage.contains(libsshTimeoutError)) { + return ErrorCode::SshTimeoutError; + } + } + switch (errorCode) { case(SSH_NO_ERROR): return ErrorCode::NoError; case(SSH_REQUEST_DENIED): return ErrorCode::SshRequsetDeniedError; @@ -350,7 +367,7 @@ namespace libssh { ssh_key privateKey = nullptr; m_passphraseCallback = passphraseCallback; - authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); + authResult = ssh_pki_import_privkey_base64(credentials.secretData.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); if (authResult == SSH_OK) { char *b64 = nullptr; diff --git a/client/core/sshclient.h b/client/core/sshclient.h index db22d0dd..4e08faaa 100644 --- a/client/core/sshclient.h +++ b/client/core/sshclient.h @@ -40,7 +40,7 @@ namespace libssh { private: ErrorCode closeChannel(); ErrorCode closeSftpSession(); - ErrorCode fromLibsshErrorCode(int errorCode); + ErrorCode fromLibsshErrorCode(); ErrorCode fromLibsshSftpErrorCode(int errorCode); static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata); diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 3a0dc4d9..b85b2c33 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -359,6 +359,23 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) { return false; } + + if (!obj.value("Jc").isNull() && !obj.value("Jmin").isNull() + && !obj.value("Jmax").isNull() && !obj.value("S1").isNull() + && !obj.value("S2").isNull() && !obj.value("H1").isNull() + && !obj.value("H2").isNull() && !obj.value("H3").isNull() + && !obj.value("H4").isNull()) { + config.m_junkPacketCount = obj.value("Jc").toString(); + config.m_junkPacketMinSize = obj.value("Jmin").toString(); + config.m_junkPacketMaxSize = obj.value("Jmax").toString(); + config.m_initPacketJunkSize = obj.value("S1").toString(); + config.m_responsePacketJunkSize = obj.value("S2").toString(); + config.m_initPacketMagicHeader = obj.value("H1").toString(); + config.m_responsePacketMagicHeader = obj.value("H2").toString(); + config.m_underloadPacketMagicHeader = obj.value("H3").toString(); + config.m_transportPacketMagicHeader = obj.value("H4").toString(); + } + return true; } diff --git a/client/daemon/daemonlocalserverconnection.cpp b/client/daemon/daemonlocalserverconnection.cpp index 43c67f16..1a49b7e5 100644 --- a/client/daemon/daemonlocalserverconnection.cpp +++ b/client/daemon/daemonlocalserverconnection.cpp @@ -108,7 +108,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) { } if (type == "deactivate") { - Daemon::instance()->deactivate(); + Daemon::instance()->deactivate(true); return; } diff --git a/client/daemon/interfaceconfig.cpp b/client/daemon/interfaceconfig.cpp index 68bebca0..8aa06b9b 100644 --- a/client/daemon/interfaceconfig.cpp +++ b/client/daemon/interfaceconfig.cpp @@ -97,6 +97,34 @@ QString InterfaceConfig::toWgConf(const QMap& extra) const { out << "DNS = " << dnsServers.join(", ") << "\n"; } + if (!m_junkPacketCount.isNull()) { + out << "Jc = " << m_junkPacketCount << "\n"; + } + if (!m_junkPacketMinSize.isNull()) { + out << "JMin = " << m_junkPacketMinSize << "\n"; + } + if (!m_junkPacketMaxSize.isNull()) { + out << "JMax = " << m_junkPacketMaxSize << "\n"; + } + if (!m_initPacketJunkSize.isNull()) { + out << "S1 = " << m_initPacketJunkSize << "\n"; + } + if (!m_responsePacketJunkSize.isNull()) { + out << "S2 = " << m_responsePacketJunkSize << "\n"; + } + if (!m_initPacketMagicHeader.isNull()) { + out << "H1 = " << m_initPacketMagicHeader << "\n"; + } + if (!m_responsePacketMagicHeader.isNull()) { + out << "H2 = " << m_responsePacketMagicHeader << "\n"; + } + if (!m_underloadPacketMagicHeader.isNull()) { + out << "H3 = " << m_underloadPacketMagicHeader << "\n"; + } + if (!m_transportPacketMagicHeader.isNull()) { + out << "H4 = " << m_transportPacketMagicHeader << "\n"; + } + // If any extra config was provided, append it now. for (const QString& key : extra.keys()) { out << key << " = " << extra[key] << "\n"; diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h index 61ffdd83..29aef085 100644 --- a/client/daemon/interfaceconfig.h +++ b/client/daemon/interfaceconfig.h @@ -40,6 +40,16 @@ class InterfaceConfig { QString m_installationId; #endif + QString m_junkPacketCount; + QString m_junkPacketMinSize; + QString m_junkPacketMaxSize; + QString m_initPacketJunkSize; + QString m_responsePacketJunkSize; + QString m_initPacketMagicHeader; + QString m_responsePacketMagicHeader; + QString m_underloadPacketMagicHeader; + QString m_transportPacketMagicHeader; + QJsonObject toJson() const; QString toWgConf( const QMap& extra = QMap()) const; diff --git a/client/fonts/pt-root-ui_vf.ttf b/client/fonts/pt-root-ui_vf.ttf new file mode 100644 index 00000000..34e6f1f9 Binary files /dev/null and b/client/fonts/pt-root-ui_vf.ttf differ diff --git a/client/images/amneziaBigLogo.png b/client/images/amneziaBigLogo.png new file mode 100644 index 00000000..35a45f3b Binary files /dev/null and b/client/images/amneziaBigLogo.png differ diff --git a/client/images/amneziaBigLogo.svg b/client/images/amneziaBigLogo.svg new file mode 100644 index 00000000..c50c7743 --- /dev/null +++ b/client/images/amneziaBigLogo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/images/connectionOff.svg b/client/images/connectionOff.svg new file mode 100644 index 00000000..27905ff9 --- /dev/null +++ b/client/images/connectionOff.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/client/images/connectionOn.svg b/client/images/connectionOn.svg new file mode 100644 index 00000000..ef317622 --- /dev/null +++ b/client/images/connectionOn.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/client/images/connectionProgress.svg b/client/images/connectionProgress.svg new file mode 100644 index 00000000..8c4024c9 --- /dev/null +++ b/client/images/connectionProgress.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/images/controls/amnezia.svg b/client/images/controls/amnezia.svg new file mode 100644 index 00000000..0a6017dd --- /dev/null +++ b/client/images/controls/amnezia.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/app.svg b/client/images/controls/app.svg new file mode 100644 index 00000000..87775cd1 --- /dev/null +++ b/client/images/controls/app.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/images/controls/arrow-left.svg b/client/images/controls/arrow-left.svg new file mode 100644 index 00000000..98c9950b --- /dev/null +++ b/client/images/controls/arrow-left.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/arrow-right.svg b/client/images/controls/arrow-right.svg new file mode 100644 index 00000000..69b21b89 --- /dev/null +++ b/client/images/controls/arrow-right.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/check.svg b/client/images/controls/check.svg new file mode 100644 index 00000000..16b4c0da --- /dev/null +++ b/client/images/controls/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/check_off.png b/client/images/controls/check_off.png deleted file mode 100644 index 0a7fbf70..00000000 Binary files a/client/images/controls/check_off.png and /dev/null differ diff --git a/client/images/controls/check_on.png b/client/images/controls/check_on.png deleted file mode 100644 index 8b3b683b..00000000 Binary files a/client/images/controls/check_on.png and /dev/null differ diff --git a/client/images/controls/chevron-down.svg b/client/images/controls/chevron-down.svg new file mode 100644 index 00000000..3f453815 --- /dev/null +++ b/client/images/controls/chevron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/chevron-right.svg b/client/images/controls/chevron-right.svg new file mode 100644 index 00000000..726eabac --- /dev/null +++ b/client/images/controls/chevron-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/chevron-up.svg b/client/images/controls/chevron-up.svg new file mode 100644 index 00000000..b51628d2 --- /dev/null +++ b/client/images/controls/chevron-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/copy.svg b/client/images/controls/copy.svg new file mode 100644 index 00000000..787e71db --- /dev/null +++ b/client/images/controls/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/delete.svg b/client/images/controls/delete.svg new file mode 100644 index 00000000..78ef3eea --- /dev/null +++ b/client/images/controls/delete.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/download.svg b/client/images/controls/download.svg new file mode 100644 index 00000000..1e592887 --- /dev/null +++ b/client/images/controls/download.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/edit-3.svg b/client/images/controls/edit-3.svg new file mode 100644 index 00000000..4e1dc071 --- /dev/null +++ b/client/images/controls/edit-3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/eye-off.svg b/client/images/controls/eye-off.svg new file mode 100644 index 00000000..d05e0b85 --- /dev/null +++ b/client/images/controls/eye-off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/images/controls/eye.svg b/client/images/controls/eye.svg new file mode 100644 index 00000000..a01452af --- /dev/null +++ b/client/images/controls/eye.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/file-cog-2.svg b/client/images/controls/file-cog-2.svg new file mode 100644 index 00000000..20815319 --- /dev/null +++ b/client/images/controls/file-cog-2.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/client/images/controls/folder-open.svg b/client/images/controls/folder-open.svg new file mode 100644 index 00000000..f126e5c5 --- /dev/null +++ b/client/images/controls/folder-open.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/github.svg b/client/images/controls/github.svg new file mode 100644 index 00000000..7b1f250a --- /dev/null +++ b/client/images/controls/github.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/home.svg b/client/images/controls/home.svg new file mode 100644 index 00000000..5ade1d33 --- /dev/null +++ b/client/images/controls/home.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/mail.svg b/client/images/controls/mail.svg new file mode 100644 index 00000000..1debe4f1 --- /dev/null +++ b/client/images/controls/mail.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/more-vertical.svg b/client/images/controls/more-vertical.svg new file mode 100644 index 00000000..2110125d --- /dev/null +++ b/client/images/controls/more-vertical.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/plus.svg b/client/images/controls/plus.svg new file mode 100644 index 00000000..96d0b71d --- /dev/null +++ b/client/images/controls/plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/qr-code.svg b/client/images/controls/qr-code.svg new file mode 100644 index 00000000..a8f760fe --- /dev/null +++ b/client/images/controls/qr-code.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/client/images/controls/radio-button-inner-circle-pressed.png b/client/images/controls/radio-button-inner-circle-pressed.png new file mode 100644 index 00000000..efcd6f1f Binary files /dev/null and b/client/images/controls/radio-button-inner-circle-pressed.png differ diff --git a/client/images/controls/radio-button-inner-circle.png b/client/images/controls/radio-button-inner-circle.png new file mode 100644 index 00000000..da29d54a Binary files /dev/null and b/client/images/controls/radio-button-inner-circle.png differ diff --git a/client/images/controls/radio-button-pressed.svg b/client/images/controls/radio-button-pressed.svg new file mode 100644 index 00000000..cf302db0 --- /dev/null +++ b/client/images/controls/radio-button-pressed.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/radio-button.svg b/client/images/controls/radio-button.svg new file mode 100644 index 00000000..75b1b5b4 --- /dev/null +++ b/client/images/controls/radio-button.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/radio.svg b/client/images/controls/radio.svg new file mode 100644 index 00000000..27731814 --- /dev/null +++ b/client/images/controls/radio.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/client/images/controls/radio_off.png b/client/images/controls/radio_off.png deleted file mode 100644 index 685980bd..00000000 Binary files a/client/images/controls/radio_off.png and /dev/null differ diff --git a/client/images/controls/radio_on.png b/client/images/controls/radio_on.png deleted file mode 100644 index 48560e53..00000000 Binary files a/client/images/controls/radio_on.png and /dev/null differ diff --git a/client/images/controls/save.svg b/client/images/controls/save.svg new file mode 100644 index 00000000..442ff72c --- /dev/null +++ b/client/images/controls/save.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/server.svg b/client/images/controls/server.svg new file mode 100644 index 00000000..52aad656 --- /dev/null +++ b/client/images/controls/server.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/images/controls/settings-2.svg b/client/images/controls/settings-2.svg new file mode 100644 index 00000000..9f1cf974 --- /dev/null +++ b/client/images/controls/settings-2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/images/controls/settings.svg b/client/images/controls/settings.svg new file mode 100644 index 00000000..0693fb4e --- /dev/null +++ b/client/images/controls/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/share-2.svg b/client/images/controls/share-2.svg new file mode 100644 index 00000000..b0aa6331 --- /dev/null +++ b/client/images/controls/share-2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/client/images/controls/telegram.svg b/client/images/controls/telegram.svg new file mode 100644 index 00000000..92bb6a79 --- /dev/null +++ b/client/images/controls/telegram.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/text-cursor.svg b/client/images/controls/text-cursor.svg new file mode 100644 index 00000000..17876d77 --- /dev/null +++ b/client/images/controls/text-cursor.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/trash.svg b/client/images/controls/trash.svg new file mode 100644 index 00000000..5f2f08bf --- /dev/null +++ b/client/images/controls/trash.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/x-circle.svg b/client/images/controls/x-circle.svg new file mode 100644 index 00000000..2d3f5b26 --- /dev/null +++ b/client/images/controls/x-circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/ios/networkextension/CMakeLists.txt b/client/ios/networkextension/CMakeLists.txt index 29dc0bbe..16769ea3 100644 --- a/client/ios/networkextension/CMakeLists.txt +++ b/client/ios/networkextension/CMakeLists.txt @@ -58,7 +58,7 @@ target_link_libraries(networkextension PRIVATE ${FW_UI_KIT}) target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\") target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1) -set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources) +set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/awg-apple/Sources) target_sources(networkextension PRIVATE ${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift diff --git a/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h b/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h index 03a987ad..44d0b6b0 100644 --- a/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h +++ b/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h @@ -1,6 +1,6 @@ #include "wireguard-go-version.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitGo/wireguard.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitGo/wireguard.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include diff --git a/client/macos/app/WireGuard-Bridging-Header.h b/client/macos/app/WireGuard-Bridging-Header.h index 40b6c89d..da71002d 100644 --- a/client/macos/app/WireGuard-Bridging-Header.h +++ b/client/macos/app/WireGuard-Bridging-Header.h @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "wireguard-go-version.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include diff --git a/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h b/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h index 8a437ce0..ea5c8e38 100644 --- a/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h +++ b/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h @@ -4,7 +4,7 @@ #include "macos/gobridge/wireguard.h" #include "wireguard-go-version.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include "3rd/ShadowSocks/ShadowSocks/ShadowSocks.h" #include "platforms/ios/ssconnectivity.h" #include "platforms/ios/iosopenvpn2ssadapter.h" diff --git a/client/main.cpp b/client/main.cpp index 72ed6018..bf861dc2 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -2,20 +2,19 @@ #include #include "amnezia_application.h" -#include "version.h" #include "migrations.h" +#include "version.h" #include #ifdef Q_OS_WIN -#include "Windows.h" + #include "Windows.h" #endif #if defined(Q_OS_IOS) -#include "platforms/ios/QtAppDelegate-C-Interface.h" + #include "platforms/ios/QtAppDelegate-C-Interface.h" #endif - int main(int argc, char *argv[]) { Migrations migrationsManager; @@ -27,16 +26,19 @@ int main(int argc, char *argv[]) AllowSetForegroundWindow(ASFW_ANY); #endif +// QTBUG-95974 QTBUG-95764 QTBUG-102168 +#ifdef Q_OS_ANDROID + qputenv("QT_ANDROID_DISABLE_ACCESSIBILITY", "1"); +#endif #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) AmneziaApplication app(argc, argv); #else - AmneziaApplication app(argc, argv, true, SingleApplication::Mode::User | SingleApplication::Mode::SecondaryNotification); + AmneziaApplication app(argc, argv, true, + SingleApplication::Mode::User | SingleApplication::Mode::SecondaryNotification); if (!app.isPrimary()) { - QTimer::singleShot(1000, &app, [&](){ - app.quit(); - }); + QTimer::singleShot(1000, &app, [&]() { app.quit(); }); return app.exec(); } #endif @@ -56,13 +58,16 @@ int main(int argc, char *argv[]) app.setOrganizationName(ORGANIZATION_NAME); app.setApplicationDisplayName(APPLICATION_NAME); - app.loadTranslator(); app.loadFonts(); bool doExec = app.parseCommands(); if (doExec) { app.init(); + + qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME, APP_VERSION); + qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); + return app.exec(); } return 0; diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 40bc0bba..2f6fe371 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -115,7 +115,9 @@ void LocalSocketController::daemonConnected() { } void LocalSocketController::activate(const QJsonObject &rawConfig) { - QJsonObject wgConfig = rawConfig.value("wireguard_config_data").toObject(); + QString protocolName = rawConfig.value("protocol").toString(); + + QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject(); QJsonObject json; json.insert("type", "activate"); @@ -160,6 +162,19 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { // splitTunnelApps.append(QJsonValue(uri)); // } // json.insert("vpnDisabledApps", splitTunnelApps); + + if (protocolName == amnezia::config_key::awg) { + json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); + json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize)); + json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); + json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize)); + json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize)); + json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader)); + json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); + json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader)); + json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); + } + write(json); } @@ -175,6 +190,7 @@ void LocalSocketController::deactivate() { QJsonObject json; json.insert("type", "deactivate"); write(json); + emit disconnected(); } void LocalSocketController::checkStatus() { diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index 18955532..a56edcbf 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -13,17 +13,16 @@ #include "android_controller.h" #include "private/qandroidextras_p.h" -#include "ui/pages_logic/StartPageLogic.h" -#include "androidvpnactivity.h" #include "androidutils.h" +#include "androidvpnactivity.h" -namespace { -AndroidController* s_instance = nullptr; +namespace +{ + AndroidController *s_instance = nullptr; -constexpr auto PERMISSIONHELPER_CLASS = - "org/amnezia/vpn/qt/VPNPermissionHelper"; -} // namespace + constexpr auto PERMISSIONHELPER_CLASS = "org/amnezia/vpn/qt/VPNPermissionHelper"; +} // namespace AndroidController::AndroidController() : QObject() { @@ -33,109 +32,127 @@ AndroidController::AndroidController() : QObject() auto activity = AndroidVPNActivity::instance(); - connect(activity, &AndroidVPNActivity::serviceConnected, this, []() { - qDebug() << "Transact: service connected"; - AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, ""); - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::serviceConnected, this, + []() { + qDebug() << "Transact: service connected"; + AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, ""); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventInitialized, this, - [this](const QString& parcelBody) { - // We might get multiple Init events as widgets, or fragments - // might query this. - if (m_init) { - return; - } + connect( + activity, &AndroidVPNActivity::eventInitialized, this, + [this](const QString &parcelBody) { + // We might get multiple Init events as widgets, or fragments + // might query this. + if (m_init) { + return; + } - qDebug() << "Transact: init"; + qDebug() << "Transact: init"; - m_init = true; + m_init = true; - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - qlonglong time = doc.object()["time"].toVariant().toLongLong(); + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + qlonglong time = doc.object()["time"].toVariant().toLongLong(); - isConnected = doc.object()["connected"].toBool(); + isConnected = doc.object()["connected"].toBool(); - if (isConnected) { - emit scheduleStatusCheckSignal(); - } + if (isConnected) { + emit scheduleStatusCheckSignal(); + } - emit initialized( - true, isConnected, - time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime()); + emit initialized(true, isConnected, time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime()); - setFallbackConnectedNotification(); - }, Qt::QueuedConnection); + setFallbackConnectedNotification(); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventConnected, this, - [this](const QString& parcelBody) { - Q_UNUSED(parcelBody); - qDebug() << "Transact: connected"; + connect( + activity, &AndroidVPNActivity::eventConnected, this, + [this](const QString &parcelBody) { + Q_UNUSED(parcelBody); + qDebug() << "Transact: connected"; - if (!isConnected) { - emit scheduleStatusCheckSignal(); - } + if (!isConnected) { + emit scheduleStatusCheckSignal(); + } - isConnected = true; + isConnected = true; - emit connectionStateChanged(VpnProtocol::Connected); - }, Qt::QueuedConnection); + emit connectionStateChanged(Vpn::ConnectionState::Connected); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventDisconnected, this, - [this]() { - qDebug() << "Transact: disconnected"; + connect( + activity, &AndroidVPNActivity::eventDisconnected, this, + [this]() { + qDebug() << "Transact: disconnected"; - isConnected = false; + isConnected = false; - emit connectionStateChanged(VpnProtocol::Disconnected); - }, Qt::QueuedConnection); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventStatisticUpdate, this, - [this](const QString& parcelBody) { - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + connect( + activity, &AndroidVPNActivity::eventStatisticUpdate, this, + [this](const QString &parcelBody) { + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - QString rx = doc.object()["rx_bytes"].toString(); - QString tx = doc.object()["tx_bytes"].toString(); - QString endpoint = doc.object()["endpoint"].toString(); - QString deviceIPv4 = doc.object()["deviceIpv4"].toString(); + QString rx = doc.object()["rx_bytes"].toString(); + QString tx = doc.object()["tx_bytes"].toString(); + QString endpoint = doc.object()["endpoint"].toString(); + QString deviceIPv4 = doc.object()["deviceIpv4"].toString(); - emit statusUpdated(rx, tx, endpoint, deviceIPv4); - }, Qt::QueuedConnection); + emit statusUpdated(rx, tx, endpoint, deviceIPv4); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventBackendLogs, this, - [this](const QString& parcelBody) { - qDebug() << "Transact: backend logs"; + connect( + activity, &AndroidVPNActivity::eventBackendLogs, this, + [this](const QString &parcelBody) { + qDebug() << "Transact: backend logs"; - QString buffer = parcelBody.toUtf8(); - if (m_logCallback) { - m_logCallback(buffer); - } - }, Qt::QueuedConnection); + QString buffer = parcelBody.toUtf8(); + if (m_logCallback) { + m_logCallback(buffer); + } + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventActivationError, this, - [this](const QString& parcelBody) { - Q_UNUSED(parcelBody) - qDebug() << "Transact: error"; - emit connectionStateChanged(VpnProtocol::Error); - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::eventActivationError, this, + [this](const QString &parcelBody) { + Q_UNUSED(parcelBody) + qDebug() << "Transact: error"; + emit connectionStateChanged(Vpn::ConnectionState::Error); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventConfigImport, this, - [this](const QString& parcelBody) { - qDebug() << "Transact: config import"; - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + connect( + activity, &AndroidVPNActivity::eventConfigImport, this, + [this](const QString &parcelBody) { + qDebug() << "Transact: config import"; + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - QString buffer = doc.object()["config"].toString(); - qDebug() << "Transact: config string" << buffer; - importConfig(buffer); - }, Qt::QueuedConnection); + QString buffer = doc.object()["config"].toString(); + qDebug() << "Transact: config string" << buffer; + importConfigFromOutside(buffer); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::serviceDisconnected, this, - [this]() { - qDebug() << "Transact: service disconnected"; - m_serviceConnected = false; - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::serviceDisconnected, this, + [this]() { + qDebug() << "Transact: service disconnected"; + m_serviceConnected = false; + }, + Qt::QueuedConnection); } -AndroidController* AndroidController::instance() { +AndroidController *AndroidController::instance() +{ if (!s_instance) { s_instance = new AndroidController(); } @@ -143,16 +160,13 @@ AndroidController* AndroidController::instance() { return s_instance; } -bool AndroidController::initialize(StartPageLogic *startPageLogic) +bool AndroidController::initialize() { qDebug() << "Initializing"; - m_startPageLogic = startPageLogic; - // Hook in the native implementation for startActivityForResult into the JNI - JNINativeMethod methods[]{{"startActivityForResult", - "(Landroid/content/Intent;)V", - reinterpret_cast(startActivityForResult)}}; + JNINativeMethod methods[] { { "startActivityForResult", "(Landroid/content/Intent;)V", + reinterpret_cast(startActivityForResult) } }; QJniObject javaClass(PERMISSIONHELPER_CLASS); QJniEnvironment env; jclass objectClass = env->GetObjectClass(javaClass.object()); @@ -168,11 +182,9 @@ ErrorCode AndroidController::start() { qDebug() << "Prompting for VPN permission"; QJniObject activity = AndroidUtils::getActivity(); - auto appContext = activity.callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); - QJniObject::callStaticMethod( - PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V", - appContext.object()); + auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); + QJniObject::callStaticMethod(PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V", + appContext.object()); QJsonDocument doc(m_vpnConfig); AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE, doc.toJson()); @@ -180,7 +192,8 @@ ErrorCode AndroidController::start() return NoError; } -void AndroidController::stop() { +void AndroidController::stop() +{ qDebug() << "AndroidController::stop"; AndroidVPNActivity::sendToService(ServiceAction::ACTION_DEACTIVATE, QString()); @@ -188,16 +201,16 @@ void AndroidController::stop() { // Activates the tunnel that is currently set // in the VPN Service -void AndroidController::resumeStart() { +void AndroidController::resumeStart() +{ AndroidVPNActivity::sendToService(ServiceAction::ACTION_RESUME_ACTIVATE, QString()); } /* * Sets the current notification text that is shown */ -void AndroidController::setNotificationText(const QString& title, - const QString& message, - int timerSec) { +void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec) +{ QJsonObject args; args["title"] = title; args["message"] = message; @@ -207,7 +220,8 @@ void AndroidController::setNotificationText(const QString& title, AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_TEXT, doc.toJson()); } -void AndroidController::shareConfig(const QString& configContent, const QString& suggestedName) { +void AndroidController::shareConfig(const QString &configContent, const QString &suggestedName) +{ AndroidVPNActivity::saveFileAs(configContent, suggestedName); } @@ -216,7 +230,8 @@ void AndroidController::shareConfig(const QString& configContent, const QString& * switches into the Connected state without the app open * e.g via always-on vpn */ -void AndroidController::setFallbackConnectedNotification() { +void AndroidController::setFallbackConnectedNotification() +{ QJsonObject args; args["title"] = tr("AmneziaVPN"); //% "Ready for you to connect" @@ -227,11 +242,13 @@ void AndroidController::setFallbackConnectedNotification() { AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_FALLBACK, doc.toJson()); } -void AndroidController::checkStatus() { +void AndroidController::checkStatus() +{ AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, QString()); } -void AndroidController::getBackendLogs(std::function&& a_callback) { +void AndroidController::getBackendLogs(std::function &&a_callback) +{ qDebug() << "get logs"; m_logCallback = std::move(a_callback); @@ -239,16 +256,13 @@ void AndroidController::getBackendLogs(std::function&& a_c AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_GET_LOG, QString()); } -void AndroidController::cleanupBackendLogs() { +void AndroidController::cleanupBackendLogs() +{ qDebug() << "cleanup logs"; AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_CLEANUP_LOG, QString()); } -void AndroidController::importConfig(const QString& data){ - m_startPageLogic->importAnyFile(data); -} - const QJsonObject &AndroidController::vpnConfig() const { return m_vpnConfig; @@ -285,31 +299,29 @@ void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject int qDebug() << "start vpnPermissionHelper"; Q_UNUSED(env); - QtAndroidPrivate::startActivity(intent, 1337, - [](int receiverRequestCode, int resultCode, - const QJniObject& data) { - // Currently this function just used in - // VPNService.kt::checkPermissions. So the result - // we're getting is if the User gave us the - // Vpn.bind permission. In case of NO we should - // abort. - Q_UNUSED(receiverRequestCode); - Q_UNUSED(data); + QtAndroidPrivate::startActivity(intent, 1337, [](int receiverRequestCode, int resultCode, const QJniObject &data) { + // Currently this function just used in + // VPNService.kt::checkPermissions. So the result + // we're getting is if the User gave us the + // Vpn.bind permission. In case of NO we should + // abort. + Q_UNUSED(receiverRequestCode); + Q_UNUSED(data); - AndroidController* controller = AndroidController::instance(); - if (!controller) { - return; - } + AndroidController *controller = AndroidController::instance(); + if (!controller) { + return; + } - if (resultCode == ACTIVITY_RESULT_OK) { - qDebug() << "VPN PROMPT RESULT - Accepted"; - controller->resumeStart(); - return; - } - // If the request got rejected abort the current - // connection. - qWarning() << "VPN PROMPT RESULT - Rejected"; - emit controller->connectionStateChanged(VpnProtocol::Disconnected); - }); + if (resultCode == ACTIVITY_RESULT_OK) { + qDebug() << "VPN PROMPT RESULT - Accepted"; + controller->resumeStart(); + return; + } + // If the request got rejected abort the current + // connection. + qWarning() << "VPN PROMPT RESULT - Rejected"; + emit controller->connectionStateChanged(Vpn::ConnectionState::Disconnected); + }); return; } diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 7e5b52c8..baa0bc80 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -8,36 +8,32 @@ #include #include -#include "ui/pages_logic/StartPageLogic.h" - #include "protocols/vpnprotocol.h" using namespace amnezia; - class AndroidController : public QObject { Q_OBJECT public: explicit AndroidController(); - static AndroidController* instance(); + static AndroidController *instance(); virtual ~AndroidController() override = default; - bool initialize(StartPageLogic *startPageLogic); + bool initialize(); ErrorCode start(); void stop(); void resumeStart(); void checkStatus(); - void setNotificationText(const QString& title, const QString& message, int timerSec); - void shareConfig(const QString& data, const QString& suggestedName); + void setNotificationText(const QString &title, const QString &message, int timerSec); + void shareConfig(const QString &data, const QString &suggestedName); void setFallbackConnectedNotification(); - void getBackendLogs(std::function&& callback); + void getBackendLogs(std::function &&callback); void cleanupBackendLogs(); - void importConfig(const QString& data); const QJsonObject &vpnConfig() const; void setVpnConfig(const QJsonObject &newVpnConfig); @@ -45,18 +41,20 @@ public: void startQrReaderActivity(); signals: - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); // This signal is emitted when the controller is initialized. Note that the // VPN tunnel can be already active. In this case, "connected" should be set // to true and the "connectionDate" should be set to the activation date if // known. // If "status" is set to false, the backend service is considered unavailable. - void initialized(bool status, bool connected, const QDateTime& connectionDate); + void initialized(bool status, bool connected, const QDateTime &connectionDate); void statusUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4); void scheduleStatusCheckSignal(); + void importConfigFromOutside(QString &data); + protected slots: void scheduleStatusCheckSlot(); @@ -65,12 +63,10 @@ private: QJsonObject m_vpnConfig; - StartPageLogic *m_startPageLogic; - bool m_serviceConnected = false; - std::function m_logCallback; + std::function m_logCallback; - static void startActivityForResult(JNIEnv* env, jobject /*thiz*/, jobject intent); + static void startActivityForResult(JNIEnv *env, jobject /*thiz*/, jobject intent); bool isConnected = false; diff --git a/client/platforms/android/androidutils.cpp b/client/platforms/android/androidutils.cpp index 5e9f094c..7cc39824 100644 --- a/client/platforms/android/androidutils.cpp +++ b/client/platforms/android/androidutils.cpp @@ -4,23 +4,25 @@ #include "androidutils.h" -#include +#include #include #include #include #include #include -#include #include +#include #include "jni.h" -namespace { - AndroidUtils* s_instance = nullptr; -} // namespace +namespace +{ + AndroidUtils *s_instance = nullptr; +} // namespace // static -QString AndroidUtils::GetDeviceName() { +QString AndroidUtils::GetDeviceName() +{ QJniEnvironment env; jclass BUILD = env->FindClass("android/os/Build"); jfieldID model = env->GetStaticFieldID(BUILD, "MODEL", "Ljava/lang/String;"); @@ -30,7 +32,7 @@ QString AndroidUtils::GetDeviceName() { return QString("Android Device"); } - const char* buffer = env->GetStringUTFChars(value, nullptr); + const char *buffer = env->GetStringUTFChars(value, nullptr); if (!buffer) { return QString("Android Device"); } @@ -42,7 +44,8 @@ QString AndroidUtils::GetDeviceName() { }; // static -AndroidUtils* AndroidUtils::instance() { +AndroidUtils *AndroidUtils::instance() +{ if (!s_instance) { Q_ASSERT(qApp); s_instance = new AndroidUtils(qApp); @@ -51,19 +54,22 @@ AndroidUtils* AndroidUtils::instance() { return s_instance; } -AndroidUtils::AndroidUtils(QObject* parent) : QObject(parent) { +AndroidUtils::AndroidUtils(QObject *parent) : QObject(parent) +{ Q_ASSERT(!s_instance); s_instance = this; } -AndroidUtils::~AndroidUtils() { +AndroidUtils::~AndroidUtils() +{ Q_ASSERT(s_instance == this); s_instance = nullptr; } // static -void AndroidUtils::dispatchToMainThread(std::function callback) { - QTimer* timer = new QTimer(); +void AndroidUtils::dispatchToMainThread(std::function callback) +{ + QTimer *timer = new QTimer(); timer->moveToThread(qApp->thread()); timer->setSingleShot(true); QObject::connect(timer, &QTimer::timeout, [=]() { @@ -74,8 +80,9 @@ void AndroidUtils::dispatchToMainThread(std::function callback) { } // static -QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv* env, jstring data) { - const char* buffer = env->GetStringUTFChars(data, nullptr); +QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv *env, jstring data) +{ + const char *buffer = env->GetStringUTFChars(data, nullptr); if (!buffer) { qDebug() << "getQByteArrayFromJString - failed to parse data."; return QByteArray(); @@ -87,8 +94,9 @@ QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv* env, jstring data) { } // static -QString AndroidUtils::getQStringFromJString(JNIEnv* env, jstring data) { - const char* buffer = env->GetStringUTFChars(data, nullptr); +QString AndroidUtils::getQStringFromJString(JNIEnv *env, jstring data) +{ + const char *buffer = env->GetStringUTFChars(data, nullptr); if (!buffer) { qDebug() << "getQStringFromJString - failed to parse data."; return QString(); @@ -100,15 +108,14 @@ QString AndroidUtils::getQStringFromJString(JNIEnv* env, jstring data) { } // static -QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv* env, jstring data) { +QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv *env, jstring data) +{ QByteArray raw(getQByteArrayFromJString(env, data)); QJsonParseError jsonError; QJsonDocument json = QJsonDocument::fromJson(raw, &jsonError); if (QJsonParseError::NoError != jsonError.error) { - qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: " - << jsonError.error << "Offset: " << jsonError.offset - << "Message: " << jsonError.errorString() - << "Data: " << raw; + qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: " << jsonError.error + << "Offset: " << jsonError.offset << "Message: " << jsonError.errorString() << "Data: " << raw; return QJsonObject(); } @@ -120,11 +127,13 @@ QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv* env, jstring data) { return json.object(); } -QJniObject AndroidUtils::getActivity() { +QJniObject AndroidUtils::getActivity() +{ return QNativeInterface::QAndroidApplication::context(); } -int AndroidUtils::GetSDKVersion() { +int AndroidUtils::GetSDKVersion() +{ QJniEnvironment env; jclass versionClass = env->FindClass("android/os/Build$VERSION"); jfieldID sdkIntFieldID = env->GetStaticFieldID(versionClass, "SDK_INT", "I"); @@ -133,15 +142,14 @@ int AndroidUtils::GetSDKVersion() { return sdk; } -QString AndroidUtils::GetManufacturer() { +QString AndroidUtils::GetManufacturer() +{ QJniEnvironment env; jclass buildClass = env->FindClass("android/os/Build"); - jfieldID manuFacturerField = - env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;"); - jstring value = - (jstring)env->GetStaticObjectField(buildClass, manuFacturerField); + jfieldID manuFacturerField = env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;"); + jstring value = (jstring)env->GetStaticObjectField(buildClass, manuFacturerField); - const char* buffer = env->GetStringUTFChars(value, nullptr); + const char *buffer = env->GetStringUTFChars(value, nullptr); if (!buffer) { qDebug() << "Failed to fetch MANUFACTURER"; @@ -154,21 +162,22 @@ QString AndroidUtils::GetManufacturer() { return res; } -void AndroidUtils::runOnAndroidThreadSync(const std::function runnable) { - QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable) - .waitForFinished(); +void AndroidUtils::runOnAndroidThreadSync(const std::function runnable) +{ + QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable).waitForFinished(); } -void AndroidUtils::runOnAndroidThreadAsync(const std::function runnable) { +void AndroidUtils::runOnAndroidThreadAsync(const std::function runnable) +{ QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable); } // Static // Creates a copy of the passed QByteArray in the JVM and passes back a ref -jbyteArray AndroidUtils::tojByteArray(const QByteArray& data) { +jbyteArray AndroidUtils::tojByteArray(const QByteArray &data) +{ QJniEnvironment env; jbyteArray out = env->NewByteArray(data.size()); - env->SetByteArrayRegion(out, 0, data.size(), - reinterpret_cast(data.constData())); + env->SetByteArrayRegion(out, 0, data.size(), reinterpret_cast(data.constData())); return out; } diff --git a/client/platforms/android/androidvpnactivity.cpp b/client/platforms/android/androidvpnactivity.cpp index 2076280d..e81e694b 100644 --- a/client/platforms/android/androidvpnactivity.cpp +++ b/client/platforms/android/androidvpnactivity.cpp @@ -4,7 +4,6 @@ #include "androidvpnactivity.h" -#include #include #include #include @@ -13,19 +12,21 @@ #include "androidutils.h" #include "jni.h" -namespace { - AndroidVPNActivity* s_instance = nullptr; +namespace +{ + AndroidVPNActivity *s_instance = nullptr; constexpr auto CLASSNAME = "org.amnezia.vpn.qt.VPNActivity"; } -AndroidVPNActivity::AndroidVPNActivity() { +AndroidVPNActivity::AndroidVPNActivity() +{ AndroidUtils::runOnAndroidThreadAsync([]() { - JNINativeMethod methods[]{ - {"handleBackButton", "()Z", reinterpret_cast(handleBackButton)}, - {"onServiceMessage", "(ILjava/lang/String;)V", reinterpret_cast(onServiceMessage)}, - {"qtOnServiceConnected", "()V", reinterpret_cast(onServiceConnected)}, - {"qtOnServiceDisconnected", "()V", reinterpret_cast(onServiceDisconnected)}, - {"onActivityMessage", "(ILjava/lang/String;)V", reinterpret_cast(onAndroidVpnActivityMessage)} + JNINativeMethod methods[] { + { "handleBackButton", "()Z", reinterpret_cast(handleBackButton) }, + { "onServiceMessage", "(ILjava/lang/String;)V", reinterpret_cast(onServiceMessage) }, + { "qtOnServiceConnected", "()V", reinterpret_cast(onServiceConnected) }, + { "qtOnServiceDisconnected", "()V", reinterpret_cast(onServiceDisconnected) }, + { "onActivityMessage", "(ILjava/lang/String;)V", reinterpret_cast(onAndroidVpnActivityMessage) } }; QJniObject javaClass(CLASSNAME); @@ -36,19 +37,22 @@ AndroidVPNActivity::AndroidVPNActivity() { }); } -void AndroidVPNActivity::maybeInit() { +void AndroidVPNActivity::maybeInit() +{ if (s_instance == nullptr) { s_instance = new AndroidVPNActivity(); } } // static -bool AndroidVPNActivity::handleBackButton(JNIEnv* env, jobject thiz) { +bool AndroidVPNActivity::handleBackButton(JNIEnv *env, jobject thiz) +{ Q_UNUSED(env); Q_UNUSED(thiz); } -void AndroidVPNActivity::connectService() { +void AndroidVPNActivity::connectService() +{ QJniObject::callStaticMethod(CLASSNAME, "connectService", "()V"); } @@ -57,16 +61,16 @@ void AndroidVPNActivity::startQrCodeReader() QJniObject::callStaticMethod(CLASSNAME, "startQrCodeReader", "()V"); } -void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename) { - QJniObject::callStaticMethod( - CLASSNAME, - "saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V", - QJniObject::fromString(fileContent).object(), - QJniObject::fromString(suggestedFilename).object()); +void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename) +{ + QJniObject::callStaticMethod(CLASSNAME, "saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V", + QJniObject::fromString(fileContent).object(), + QJniObject::fromString(suggestedFilename).object()); } // static -AndroidVPNActivity* AndroidVPNActivity::instance() { +AndroidVPNActivity *AndroidVPNActivity::instance() +{ if (s_instance == nullptr) { AndroidVPNActivity::maybeInit(); } @@ -75,21 +79,19 @@ AndroidVPNActivity* AndroidVPNActivity::instance() { } // static -void AndroidVPNActivity::sendToService(ServiceAction type, const QString& data) { +void AndroidVPNActivity::sendToService(ServiceAction type, const QString &data) +{ int messageType = (int)type; - QJniObject::callStaticMethod( - CLASSNAME, - "sendToService", "(ILjava/lang/String;)V", - static_cast(messageType), - QJniObject::fromString(data).object()); + QJniObject::callStaticMethod(CLASSNAME, "sendToService", "(ILjava/lang/String;)V", + static_cast(messageType), QJniObject::fromString(data).object()); } // static -void AndroidVPNActivity::onServiceMessage(JNIEnv* env, jobject thiz, - jint messageType, jstring body) { +void AndroidVPNActivity::onServiceMessage(JNIEnv *env, jobject thiz, jint messageType, jstring body) +{ Q_UNUSED(thiz); - const char* buffer = env->GetStringUTFChars(body, nullptr); + const char *buffer = env->GetStringUTFChars(body, nullptr); if (!buffer) { return; } @@ -97,38 +99,23 @@ void AndroidVPNActivity::onServiceMessage(JNIEnv* env, jobject thiz, QString parcelBody(buffer); env->ReleaseStringUTFChars(body, buffer); AndroidUtils::dispatchToMainThread([messageType, parcelBody] { - AndroidVPNActivity::instance()->handleServiceMessage(messageType, - parcelBody); + AndroidVPNActivity::instance()->handleServiceMessage(messageType, parcelBody); }); } -void AndroidVPNActivity::handleServiceMessage(int code, const QString& data) { +void AndroidVPNActivity::handleServiceMessage(int code, const QString &data) +{ auto mode = (ServiceEvents)code; switch (mode) { - case ServiceEvents::EVENT_INIT: - emit eventInitialized(data); - break; - case ServiceEvents::EVENT_CONNECTED: - emit eventConnected(data); - break; - case ServiceEvents::EVENT_DISCONNECTED: - emit eventDisconnected(data); - break; - case ServiceEvents::EVENT_STATISTIC_UPDATE: - emit eventStatisticUpdate(data); - break; - case ServiceEvents::EVENT_BACKEND_LOGS: - emit eventBackendLogs(data); - break; - case ServiceEvents::EVENT_ACTIVATION_ERROR: - emit eventActivationError(data); - break; - case ServiceEvents::EVENT_CONFIG_IMPORT: - emit eventConfigImport(data); - break; - default: - Q_ASSERT(false); + case ServiceEvents::EVENT_INIT: emit eventInitialized(data); break; + case ServiceEvents::EVENT_CONNECTED: emit eventConnected(data); break; + case ServiceEvents::EVENT_DISCONNECTED: emit eventDisconnected(data); break; + case ServiceEvents::EVENT_STATISTIC_UPDATE: emit eventStatisticUpdate(data); break; + case ServiceEvents::EVENT_BACKEND_LOGS: emit eventBackendLogs(data); break; + case ServiceEvents::EVENT_ACTIVATION_ERROR: emit eventActivationError(data); break; + case ServiceEvents::EVENT_CONFIG_IMPORT: emit eventConfigImport(data); break; + default: Q_ASSERT(false); } } @@ -137,22 +124,21 @@ void AndroidVPNActivity::handleActivityMessage(int code, const QString &data) auto mode = (UIEvents)code; switch (mode) { - case UIEvents::QR_CODED_DECODED: - emit eventQrCodeReceived(data); - break; - default: - Q_ASSERT(false); + case UIEvents::QR_CODED_DECODED: emit eventQrCodeReceived(data); break; + default: Q_ASSERT(false); } } -void AndroidVPNActivity::onServiceConnected(JNIEnv* env, jobject thiz) { +void AndroidVPNActivity::onServiceConnected(JNIEnv *env, jobject thiz) +{ Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidVPNActivity::instance()->serviceConnected(); } -void AndroidVPNActivity::onServiceDisconnected(JNIEnv* env, jobject thiz) { +void AndroidVPNActivity::onServiceDisconnected(JNIEnv *env, jobject thiz) +{ Q_UNUSED(env); Q_UNUSED(thiz); @@ -162,7 +148,7 @@ void AndroidVPNActivity::onServiceDisconnected(JNIEnv* env, jobject thiz) { void AndroidVPNActivity::onAndroidVpnActivityMessage(JNIEnv *env, jobject thiz, jint messageType, jstring message) { Q_UNUSED(thiz); - const char* buffer = env->GetStringUTFChars(message, nullptr); + const char *buffer = env->GetStringUTFChars(message, nullptr); if (!buffer) { return; } diff --git a/client/platforms/android/authResultReceiver.cpp b/client/platforms/android/authResultReceiver.cpp new file mode 100644 index 00000000..21e838a2 --- /dev/null +++ b/client/platforms/android/authResultReceiver.cpp @@ -0,0 +1,16 @@ +#include "authResultReceiver.h" + +AuthResultReceiver::AuthResultReceiver(QSharedPointer ¬ifier) : m_notifier(notifier) +{ +} + +void AuthResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) +{ + qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode; + + if (resultCode == -1) { // ResultOK + emit m_notifier->authSuccessful(); + } else { + emit m_notifier->authFailed(); + } +} diff --git a/client/platforms/android/authResultReceiver.h b/client/platforms/android/authResultReceiver.h new file mode 100644 index 00000000..9a88dcf5 --- /dev/null +++ b/client/platforms/android/authResultReceiver.h @@ -0,0 +1,32 @@ +#ifndef AUTHRESULTRECEIVER_H +#define AUTHRESULTRECEIVER_H + +#include + +#include + +class AuthResultNotifier : public QObject +{ + Q_OBJECT + +public: + AuthResultNotifier(QObject *parent = nullptr) : QObject(parent) {}; + +signals: + void authFailed(); + void authSuccessful(); +}; + +/* Auth result handler for Android */ +class AuthResultReceiver final : public QAndroidActivityResultReceiver +{ +public: + AuthResultReceiver(QSharedPointer ¬ifier); + + void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override; + +private: + QSharedPointer m_notifier; +}; + +#endif // AUTHRESULTRECEIVER_H diff --git a/client/platforms/ios/MobileUtils.cpp b/client/platforms/ios/MobileUtils.cpp index 3923d291..14c0854a 100644 --- a/client/platforms/ios/MobileUtils.cpp +++ b/client/platforms/ios/MobileUtils.cpp @@ -1,5 +1,15 @@ #include "MobileUtils.h" -void MobileUtils::shareText(const QStringList&) {} +MobileUtils::MobileUtils(QObject *parent) : QObject(parent) +{ +} +bool MobileUtils::shareText(const QStringList &) +{ + return false; +} +QString MobileUtils::openFile() +{ + return QString(); +} diff --git a/client/platforms/ios/MobileUtils.h b/client/platforms/ios/MobileUtils.h index a7967fdf..04a12c51 100644 --- a/client/platforms/ios/MobileUtils.h +++ b/client/platforms/ios/MobileUtils.h @@ -4,15 +4,19 @@ #include #include -class MobileUtils : public QObject { +class MobileUtils : public QObject +{ Q_OBJECT - + public: - MobileUtils() = delete; + explicit MobileUtils(QObject *parent = nullptr); public slots: - static void shareText(const QStringList& filesToSend); - + bool shareText(const QStringList &filesToSend); + QString openFile(); + +signals: + void finished(); }; #endif // MOBILEUTILS_H diff --git a/client/platforms/ios/MobileUtils.mm b/client/platforms/ios/MobileUtils.mm index a9ad52b5..fbf26ffd 100644 --- a/client/platforms/ios/MobileUtils.mm +++ b/client/platforms/ios/MobileUtils.mm @@ -3,7 +3,7 @@ #include #include -#include +#include static UIViewController* getViewController() { NSArray *windows = [[UIApplication sharedApplication]windows]; @@ -15,7 +15,11 @@ static UIViewController* getViewController() { return nil; } -void MobileUtils::shareText(const QStringList& filesToSend) { +MobileUtils::MobileUtils(QObject *parent) : QObject(parent) { + +} + +bool MobileUtils::shareText(const QStringList& filesToSend) { NSMutableArray *sharingItems = [NSMutableArray new]; for (int i = 0; i < filesToSend.size(); i++) { @@ -27,11 +31,78 @@ void MobileUtils::shareText(const QStringList& filesToSend) { if (!qtController) return; UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil]; + + __block bool isAccepted = false; + + [activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { + isAccepted = completed; + emit finished(); + }]; [qtController presentViewController:activityController animated:YES completion:nil]; UIPopoverPresentationController *popController = activityController.popoverPresentationController; if (popController) { popController.sourceView = qtController.view; } + + QEventLoop wait; + QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit); + wait.exec(); + + return isAccepted; } +typedef void (^DocumentPickerClosedCallback)(NSString *path); + +@interface DocumentPickerDelegate : NSObject + +@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback; + +@end + +@implementation DocumentPickerDelegate + +- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { + for (NSURL *url in urls) { + if (self.documentPickerClosedCallback) { + self.documentPickerClosedCallback([url path]); + } + } +} + +- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { + if (self.documentPickerClosedCallback) { + self.documentPickerClosedCallback(nil); + } +} + +@end + +QString MobileUtils::openFile() { + UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen]; + + DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init]; + documentPicker.delegate = documentPickerDelegate; + + UIViewController *qtController = getViewController(); + if (!qtController) return; + + [qtController presentViewController:documentPicker animated:YES completion:nil]; + + __block QString filePath; + + documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) { + if (path) { + filePath = QString::fromUtf8(path.UTF8String); + } else { + filePath = QString(); + } + emit finished(); + }; + + QEventLoop wait; + QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit); + wait.exec(); + + return filePath; +} diff --git a/client/platforms/ios/QRCodeReaderBase.mm b/client/platforms/ios/QRCodeReaderBase.mm index bd0dbac3..af879e2f 100644 --- a/client/platforms/ios/QRCodeReaderBase.mm +++ b/client/platforms/ios/QRCodeReaderBase.mm @@ -49,14 +49,15 @@ _videoPreviewPlayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession: _captureSession]; - CGFloat tabBarHeight = 20.0; + CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height; + QRect cameraRect = _qrCodeReader->cameraSize(); CGRect cameraCGRect = CGRectMake(cameraRect.x(), - cameraRect.y() + tabBarHeight, + cameraRect.y() + statusBarHeight, cameraRect.width(), cameraRect.height()); - [_videoPreviewPlayer setVideoGravity: AVLayerVideoGravityResizeAspect]; + [_videoPreviewPlayer setVideoGravity: AVLayerVideoGravityResizeAspectFill]; [_videoPreviewPlayer setFrame: cameraCGRect]; CALayer* layer = [UIApplication sharedApplication].keyWindow.layer; diff --git a/client/platforms/ios/QtAppDelegate-C-Interface.h b/client/platforms/ios/QtAppDelegate-C-Interface.h index afd31e7d..dd358097 100644 --- a/client/platforms/ios/QtAppDelegate-C-Interface.h +++ b/client/platforms/ios/QtAppDelegate-C-Interface.h @@ -1,9 +1,6 @@ #ifndef QTAPPDELEGATECINTERFACE_H #define QTAPPDELEGATECINTERFACE_H -#include "ui/pages_logic/StartPageLogic.h" - void QtAppDelegateInitialize(); -void setStartPageLogic(StartPageLogic*); #endif // QTAPPDELEGATECINTERFACE_H diff --git a/client/platforms/ios/QtAppDelegate.h b/client/platforms/ios/QtAppDelegate.h index 1e0dc412..5148c2ca 100644 --- a/client/platforms/ios/QtAppDelegate.h +++ b/client/platforms/ios/QtAppDelegate.h @@ -1,9 +1,4 @@ #import -#import "QtAppDelegate-C-Interface.h" - -#include "ui/pages_logic/StartPageLogic.h" @interface QtAppDelegate : UIResponder -+(QtAppDelegate *)sharedQtAppDelegate; -@property (nonatomic) StartPageLogic* startPageLogic; @end diff --git a/client/platforms/ios/QtAppDelegate.mm b/client/platforms/ios/QtAppDelegate.mm index f65856d9..48cef914 100644 --- a/client/platforms/ios/QtAppDelegate.mm +++ b/client/platforms/ios/QtAppDelegate.mm @@ -1,4 +1,5 @@ #import "QtAppDelegate.h" +#import "ios_controller.h" #include @@ -75,11 +76,15 @@ QString filePath(url.path.UTF8String); if (filePath.isEmpty()) return NO; - QFile file(filePath); - bool isOpenFile = file.open(QIODevice::ReadOnly); - QByteArray data = file.readAll(); - - [QtAppDelegate sharedQtAppDelegate].startPageLogic->importAnyFile(QString(data)); + if (filePath.contains("backup")) { + IosController::Instance()->importBackupFromOutside(filePath); + } else { + QFile file(filePath); + bool isOpenFile = file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); + + IosController::Instance()->importConfigFromOutside(QString(data)); + } return YES; } return NO; @@ -92,8 +97,4 @@ void QtAppDelegateInitialize() NSLog(@"Created a new AppDelegate"); } -void setStartPageLogic(StartPageLogic* startPage) { - [QtAppDelegate sharedQtAppDelegate].startPageLogic = startPage; -} - @end diff --git a/client/platforms/ios/WireGuard-Bridging-Header.h b/client/platforms/ios/WireGuard-Bridging-Header.h index e5dfa39f..fbccb2d4 100644 --- a/client/platforms/ios/WireGuard-Bridging-Header.h +++ b/client/platforms/ios/WireGuard-Bridging-Header.h @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index cc409f71..68f30ce8 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -4,28 +4,30 @@ #include "protocols/vpnprotocol.h" #ifdef __OBJC__ -#import + #import @class NETunnelProviderManager; #endif using namespace amnezia; -struct Action { - static const char* start; - static const char* restart; - static const char* stop; - static const char* getTunnelId; - static const char* getStatus; +struct Action +{ + static const char *start; + static const char *restart; + static const char *stop; + static const char *getTunnelId; + static const char *getStatus; }; -struct MessageKey { - static const char* action; - static const char* tunnelId; - static const char* config; - static const char* errorCode; - static const char* host; - static const char* port; - static const char* isOnDemand; +struct MessageKey +{ + static const char *action; + static const char *tunnelId; + static const char *config; + static const char *errorCode; + static const char *host; + static const char *port; + static const char *isOnDemand; }; class IosController : public QObject @@ -33,32 +35,34 @@ class IosController : public QObject Q_OBJECT public: - static IosController* Instance(); + static IosController *Instance(); virtual ~IosController() override = default; bool initialize(); - bool connectVpn(amnezia::Proto proto, const QJsonObject& configuration); + bool connectVpn(amnezia::Proto proto, const QJsonObject &configuration); void disconnectVpn(); void vpnStatusDidChange(void *pNotification); void vpnConfigurationDidChange(void *pNotification); - void getBackendLogs(std::function &&callback); + void getBackendLogs(std::function &&callback); void checkStatus(); signals: - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); void bytesChanged(quint64 receivedBytes, quint64 sentBytes); + void importConfigFromOutside(const QString); + void importBackupFromOutside(const QString); protected slots: - private: explicit IosController(); bool setupOpenVPN(); bool setupCloak(); bool setupWireGuard(); + bool setupAwg(); bool startOpenVPN(const QString &config); bool startWireGuard(const QString &jsonConfig); @@ -66,12 +70,12 @@ private: void startTunnel(); private: - void *m_iosControllerWrapper{}; + void *m_iosControllerWrapper {}; #ifdef __OBJC__ - NETunnelProviderManager *m_currentTunnel{}; - NSString *m_serverAddress{}; - bool isOurManager(NETunnelProviderManager* manager); - void sendVpnExtensionMessage(NSDictionary* message, std::function callback = nullptr); + NETunnelProviderManager *m_currentTunnel {}; + NSString *m_serverAddress {}; + bool isOurManager(NETunnelProviderManager *manager); + void sendVpnExtensionMessage(NSDictionary *message, std::function callback = nullptr); #endif amnezia::Proto m_proto; diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 23bd75e0..5665ff1d 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -30,22 +30,22 @@ const char* MessageKey::host = "host"; const char* MessageKey::port = "port"; const char* MessageKey::isOnDemand = "is-on-demand"; -VpnProtocol::VpnConnectionState iosStatusToState(NEVPNStatus status) { +Vpn::ConnectionState iosStatusToState(NEVPNStatus status) { switch (status) { case NEVPNStatusInvalid: - return VpnProtocol::VpnConnectionState::Unknown; + return Vpn::ConnectionState::Unknown; case NEVPNStatusDisconnected: - return VpnProtocol::VpnConnectionState::Disconnected; + return Vpn::ConnectionState::Disconnected; case NEVPNStatusConnecting: - return VpnProtocol::VpnConnectionState::Connecting; + return Vpn::ConnectionState::Connecting; case NEVPNStatusConnected: - return VpnProtocol::VpnConnectionState::Connected; + return Vpn::ConnectionState::Connected; case NEVPNStatusReasserting: - return VpnProtocol::VpnConnectionState::Connecting; + return Vpn::ConnectionState::Connecting; case NEVPNStatusDisconnecting: - return VpnProtocol::VpnConnectionState::Disconnecting; + return Vpn::ConnectionState::Disconnecting; default: - return VpnProtocol::VpnConnectionState::Unknown; + return Vpn::ConnectionState::Unknown; } } @@ -82,7 +82,7 @@ bool IosController::initialize() @try { if (error) { qDebug() << "IosController::initialize : Error:" << [error.localizedDescription UTF8String]; - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); ok = false; return; } @@ -95,6 +95,7 @@ bool IosController::initialize() if (manager.connection.status == NEVPNStatusConnected) { m_currentTunnel = manager; qDebug() << "IosController::initialize : VPN already connected"; + emit connectionStateChanged(Vpn::ConnectionState::Connected); break; // TODO: show connected state @@ -141,7 +142,7 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur @try { if (error) { qDebug() << "IosController::connectVpn : Error:" << [error.localizedDescription UTF8String]; - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); ok = false; return; } @@ -155,7 +156,7 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur m_currentTunnel = manager; qDebug() << "IosController::connectVpn : Using existing tunnel"; if (manager.connection.status == NEVPNStatusConnected) { - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); return; } @@ -203,6 +204,9 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur if (proto == amnezia::Proto::WireGuard) { return setupWireGuard(); } + if (proto == amnezia::Proto::Awg) { + return setupAwg(); + } return false; } @@ -306,6 +310,15 @@ bool IosController::setupWireGuard() return startWireGuard(wgConfig); } +bool IosController::setupAwg() +{ + QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject(); + + QString wgConfig = config[config_key::config].toString(); + + return startWireGuard(wgConfig); +} + bool IosController::startOpenVPN(const QString &config) { qDebug() << "IosController::startOpenVPN"; @@ -344,14 +357,14 @@ void IosController::startTunnel() dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if (saveError) { - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } [m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) { if (loadError) { qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Load Error" << loadError.localizedDescription.UTF8String; - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } @@ -373,7 +386,7 @@ void IosController::startTunnel() if (!started || startError) { qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Start Error" << (startError ? startError.localizedDescription.UTF8String : ""); - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); } else { qDebug() << "IosController::startOpenVPN : Starting the tunnel succeeded"; } diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index a8b7b04a..792120a7 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -100,6 +100,19 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { QTextStream out(&message); out << "private_key=" << QString(privateKey.toHex()) << "\n"; out << "replace_peers=true\n"; + + if (config.m_junkPacketCount != "") { + out << "jc=" << config.m_junkPacketCount << "\n"; + out << "jmin=" << config.m_junkPacketMinSize << "\n"; + out << "jmax=" << config.m_junkPacketMaxSize << "\n"; + out << "s1=" << config.m_initPacketJunkSize << "\n"; + out << "s2=" << config.m_responsePacketJunkSize << "\n"; + out << "h1=" << config.m_initPacketMagicHeader << "\n"; + out << "h2=" << config.m_responsePacketMagicHeader << "\n"; + out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; + out << "h4=" << config.m_transportPacketMagicHeader << "\n"; + } + int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 1f422462..ef13f4c7 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -100,6 +100,19 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { QTextStream out(&message); out << "private_key=" << QString(privateKey.toHex()) << "\n"; out << "replace_peers=true\n"; + + if (config.m_junkPacketCount != "") { + out << "jc=" << config.m_junkPacketCount << "\n"; + out << "jmin=" << config.m_junkPacketMinSize << "\n"; + out << "jmax=" << config.m_junkPacketMaxSize << "\n"; + out << "s1=" << config.m_initPacketJunkSize << "\n"; + out << "s2=" << config.m_responsePacketJunkSize << "\n"; + out << "h1=" << config.m_initPacketMagicHeader << "\n"; + out << "h2=" << config.m_responsePacketMagicHeader << "\n"; + out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; + out << "h4=" << config.m_transportPacketMagicHeader << "\n"; + } + int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); diff --git a/client/protocols/awgprotocol.cpp b/client/protocols/awgprotocol.cpp new file mode 100644 index 00000000..64415dbe --- /dev/null +++ b/client/protocols/awgprotocol.cpp @@ -0,0 +1,10 @@ +#include "awgprotocol.h" + +Awg::Awg(const QJsonObject &configuration, QObject *parent) + : WireguardProtocol(configuration, parent) +{ +} + +Awg::~Awg() +{ +} diff --git a/client/protocols/awgprotocol.h b/client/protocols/awgprotocol.h new file mode 100644 index 00000000..d7fc9c92 --- /dev/null +++ b/client/protocols/awgprotocol.h @@ -0,0 +1,17 @@ +#ifndef AWGPROTOCOL_H +#define AWGPROTOCOL_H + +#include + +#include "wireguardprotocol.h" + +class Awg : public WireguardProtocol +{ + Q_OBJECT + +public: + explicit Awg(const QJsonObject &configuration, QObject *parent = nullptr); + virtual ~Awg() override; +}; + +#endif // AWGPROTOCOL_H diff --git a/client/protocols/ikev2_vpn_protocol_windows.cpp b/client/protocols/ikev2_vpn_protocol_windows.cpp index 7564f969..5c471e22 100644 --- a/client/protocols/ikev2_vpn_protocol_windows.cpp +++ b/client/protocols/ikev2_vpn_protocol_windows.cpp @@ -35,14 +35,14 @@ Ikev2Protocol::~Ikev2Protocol() void Ikev2Protocol::stop() { - setConnectionState(VpnProtocol::Disconnecting); + setConnectionState(Vpn::ConnectionState::Disconnecting); { if (! disconnect_vpn() ){ qDebug()<<"We don't disconnect"; - setConnectionState(VpnProtocol::Error); + setConnectionState(Vpn::ConnectionState::Error); } else { - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); } } } @@ -55,40 +55,40 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE { case RASCS_OpenPort: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_PortOpened: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_ConnectDevice: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_DeviceConnected: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_AllDevicesConnected: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_Authenticate: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_AuthNotify: //qDebug()<<__FUNCTION__ << __LINE__; if (dwError != 0) { //qDebug() << "have error" << dwError; - setConnectionState(Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); } else { //qDebug() << "RASCS_AuthNotify but no error" << dwError; } break; case RASCS_AuthRetry: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_AuthCallback: qDebug()<<__FUNCTION__ << __LINE__; @@ -151,16 +151,16 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE qDebug()<<__FUNCTION__ << __LINE__; break; case RASCS_PasswordExpired: - setConnectionState(Error); + setConnectionState(Vpn::ConnectionState::Error); qDebug()<<__FUNCTION__ << __LINE__; break; case RASCS_Connected: // = RASCS_DONE: - setConnectionState(Connected); + setConnectionState(Vpn::ConnectionState::Connected); //qDebug()<<__FUNCTION__ << __LINE__; break; case RASCS_Disconnected: - setConnectionState(Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); //qDebug()<<__FUNCTION__ << __LINE__; break; default: @@ -177,7 +177,7 @@ void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) ErrorCode Ikev2Protocol::start() { QByteArray cert = QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()); - setConnectionState(Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); QTemporaryFile certFile; certFile.setAutoRemove(false); diff --git a/client/protocols/openvpnovercloakprotocol.cpp b/client/protocols/openvpnovercloakprotocol.cpp index 538f555c..7000e5ef 100644 --- a/client/protocols/openvpnovercloakprotocol.cpp +++ b/client/protocols/openvpnovercloakprotocol.cpp @@ -61,7 +61,7 @@ ErrorCode OpenVpnOverCloakProtocol::start() m_errorHandlerConnection = connect(&m_ckProcess, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){ qDebug().noquote() << "OpenVpnOverCloakProtocol finished, exitCode, exiStatus" << exitCode << exitStatus; - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); if (exitStatus != QProcess::NormalExit){ emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed); stop(); @@ -76,7 +76,7 @@ ErrorCode OpenVpnOverCloakProtocol::start() m_ckProcess.waitForStarted(); if (m_ckProcess.state() == QProcess::ProcessState::Running) { - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); return OpenVpnProtocol::start(); } diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index ed6b63d0..c38c6eea 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -1,21 +1,20 @@ #include #include #include -#include -#include #include +#include +#include #include "logger.h" -#include "version.h" -#include "utilities.h" #include "openvpnprotocol.h" +#include "utilities.h" +#include "version.h" - -OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject* parent) : - VpnProtocol(configuration, parent) +OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent) { readOpenVpnConfiguration(configuration); - connect(&m_managementServer, &ManagementServer::readyRead, this, &OpenVpnProtocol::onReadyReadDataFromManagementServer); + connect(&m_managementServer, &ManagementServer::readyRead, this, + &OpenVpnProtocol::onReadyReadDataFromManagementServer); } OpenVpnProtocol::~OpenVpnProtocol() @@ -26,7 +25,7 @@ OpenVpnProtocol::~OpenVpnProtocol() QString OpenVpnProtocol::defaultConfigFileName() { - //qDebug() << "OpenVpnProtocol::defaultConfigFileName" << defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME); + // qDebug() << "OpenVpnProtocol::defaultConfigFileName" << defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME); return defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME); } @@ -41,21 +40,20 @@ QString OpenVpnProtocol::defaultConfigPath() void OpenVpnProtocol::stop() { qDebug() << "OpenVpnProtocol::stop()"; - setConnectionState(VpnProtocol::Disconnecting); + setConnectionState(Vpn::ConnectionState::Disconnecting); // TODO: need refactoring // sendTermSignal() will even return true while server connected ??? - if ((m_connectionState == VpnProtocol::Preparing) || - (m_connectionState == VpnProtocol::Connecting) || - (m_connectionState == VpnProtocol::Connected) || - (m_connectionState == VpnProtocol::Reconnecting)) { + 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(); } - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); } ErrorCode OpenVpnProtocol::prepare() @@ -67,18 +65,19 @@ ErrorCode OpenVpnProtocol::prepare() QRemoteObjectPendingReply resultCheck = IpcClient::Interface()->getTapList(); resultCheck.waitForFinished(); - if (resultCheck.returnValue().isEmpty()){ + if (resultCheck.returnValue().isEmpty()) { QRemoteObjectPendingReply resultInstall = IpcClient::Interface()->checkAndInstallDriver(); resultInstall.waitForFinished(); - if (!resultInstall.returnValue()) return ErrorCode::OpenVpnTapAdapterError; + if (!resultInstall.returnValue()) + return ErrorCode::OpenVpnTapAdapterError; } return ErrorCode::NoError; } void OpenVpnProtocol::killOpenVpnProcess() { - if (m_openVpnProcess){ + if (m_openVpnProcess) { m_openVpnProcess->close(); } } @@ -112,9 +111,9 @@ QString OpenVpnProtocol::configPath() const return m_configFileName; } -void OpenVpnProtocol::sendManagementCommand(const QString& command) +void OpenVpnProtocol::sendManagementCommand(const QString &command) { - QIODevice *device = dynamic_cast(m_managementServer.socket().data()); + QIODevice *device = dynamic_cast(m_managementServer.socket().data()); if (device) { QTextStream stream(device); stream << command << Qt::endl; @@ -126,11 +125,12 @@ uint OpenVpnProtocol::selectMgmtPort() for (int i = 0; i < 100; ++i) { quint32 port = QRandomGenerator::global()->generate(); - port = (double)(65000-15001) * port / UINT32_MAX + 15001; + port = (double)(65000 - 15001) * port / UINT32_MAX + 15001; QTcpServer s; bool ok = s.listen(QHostAddress::LocalHost, port); - if (ok) return port; + if (ok) + return port; } return m_managementPort; @@ -140,7 +140,8 @@ void OpenVpnProtocol::updateRouteGateway(QString line) { // TODO: fix for macos line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); - if (!line.contains("/")) return; + if (!line.contains("/")) + return; m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); m_routeGateway.replace(" ", ""); qDebug() << "Set VPN route gateway" << m_routeGateway; @@ -148,7 +149,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line) ErrorCode OpenVpnProtocol::start() { - //qDebug() << "Start OpenVPN connection"; + // qDebug() << "Start OpenVPN connection"; OpenVpnProtocol::stop(); if (!QFileInfo::exists(Utils::openVpnExecPath())) { @@ -166,24 +167,25 @@ ErrorCode OpenVpnProtocol::start() QProcess p; p.setProcessChannelMode(QProcess::MergedChannels); - p.start("route", QStringList() << "-n" << "get" << "default"); + p.start("route", + QStringList() << "-n" + << "get" + << "default"); p.waitForFinished(); - QString s = p.readAll(); + QString s = p.readAll(); QRegularExpression rx(R"(gateway:\s*(\d+\.\d+\.\d+\.\d+))"); QRegularExpressionMatch match = rx.match(s); if (match.hasMatch()) { m_routeGateway = match.captured(1); qDebug() << "Set VPN route gateway" << m_routeGateway; - } - else { + } else { qWarning() << "Unable to set VPN route gateway, output:\n" << s; } #endif - -// QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log"; -// Utils::createEmptyFile(vpnLogFileNamePath); + // QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log"; + // Utils::createEmptyFile(vpnLogFileNamePath); uint mgmtPort = selectMgmtPort(); qDebug() << "OpenVpnProtocol::start mgmt port selected:" << mgmtPort; @@ -193,12 +195,12 @@ ErrorCode OpenVpnProtocol::start() return lastError(); } - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); m_openVpnProcess = IpcClient::CreatePrivilegedProcess(); if (!m_openVpnProcess) { - //qWarning() << "IpcProcess replica is not created!"; + // qWarning() << "IpcProcess replica is not created!"; setLastError(ErrorCode::AmneziaServiceConnectionFailed); return ErrorCode::AmneziaServiceConnectionFailed; } @@ -210,28 +212,25 @@ ErrorCode OpenVpnProtocol::start() return ErrorCode::AmneziaServiceConnectionFailed; } m_openVpnProcess->setProgram(PermittedProcess::OpenVPN); - QStringList arguments({"--config" , configPath(), - "--management", m_managementHost, QString::number(mgmtPort), - "--management-client"/*, "--log", vpnLogFileNamePath */ - }); + QStringList arguments({ + "--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort), + "--management-client" /*, "--log", vpnLogFileNamePath */ + }); m_openVpnProcess->setArguments(arguments); qDebug() << arguments.join(" "); - connect(m_openVpnProcess.data(), &PrivilegedProcess::errorOccurred, [&](QProcess::ProcessError error) { - qDebug() << "PrivilegedProcess errorOccurred" << error; - }); + connect(m_openVpnProcess.data(), &PrivilegedProcess::errorOccurred, + [&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; }); - connect(m_openVpnProcess.data(), &PrivilegedProcess::stateChanged, [&](QProcess::ProcessState newState) { - qDebug() << "PrivilegedProcess stateChanged" << newState; - }); + connect(m_openVpnProcess.data(), &PrivilegedProcess::stateChanged, + [&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; }); - connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, [&]() { - setConnectionState(VpnConnectionState::Disconnected); - }); + connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, + [&]() { setConnectionState(Vpn::ConnectionState::Disconnected); }); m_openVpnProcess->start(); - //startTimeoutTimer(); + // startTimeoutTimer(); return ErrorCode::NoError; } @@ -254,7 +253,7 @@ void OpenVpnProtocol::sendInitialData() void OpenVpnProtocol::onReadyReadDataFromManagementServer() { - for (;;) { + for (;;) { QString line = m_managementServer.readLine().simplified(); if (line.isEmpty()) { @@ -267,18 +266,18 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() if (line.contains(">INFO:OpenVPN Management Interface")) { sendInitialData(); - } else if (line.startsWith(">STATE")) { + } else if (line.startsWith(">STATE")) { if (line.contains("CONNECTED,SUCCESS")) { sendByteCount(); stopTimeoutTimer(); - setConnectionState(VpnProtocol::Connected); + setConnectionState(Vpn::ConnectionState::Connected); continue; } else if (line.contains("EXITING,SIGTER")) { - //openVpnStateSigTermHandler(); - setConnectionState(VpnProtocol::Disconnecting); + // openVpnStateSigTermHandler(); + setConnectionState(Vpn::ConnectionState::Disconnecting); continue; } else if (line.contains("RECONNECTING")) { - setConnectionState(VpnProtocol::Reconnecting); + setConnectionState(Vpn::ConnectionState::Reconnecting); continue; } } @@ -294,8 +293,7 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() if (line.contains("FATAL")) { if (line.contains("tap-windows6 adapters on this system are currently in use or disabled")) { emit protocolError(ErrorCode::OpenVpnAdaptersInUseError); - } - else { + } else { emit protocolError(ErrorCode::OpenVpnUnknownError); } return; @@ -320,7 +318,8 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() void OpenVpnProtocol::updateVpnGateway(const QString &line) { // line looks like - // PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM' + // PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart + // 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM' QStringList params = line.split(","); for (const QString &l : params) { diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index 63351311..a451014c 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -1,5 +1,7 @@ #include "protocols_defs.h" +#include + using namespace amnezia; QDebug operator<<(QDebug debug, const amnezia::ProtocolEnumNS::Proto &p) @@ -10,17 +12,21 @@ QDebug operator<<(QDebug debug, const amnezia::ProtocolEnumNS::Proto &p) return debug; } -amnezia::Proto ProtocolProps::protoFromString(QString proto){ +amnezia::Proto ProtocolProps::protoFromString(QString proto) +{ QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { Proto p = static_cast(i); - if (proto == protoToString(p)) return p; + if (proto == protoToString(p)) + return p; } return Proto::Any; } -QString ProtocolProps::protoToString(amnezia::Proto p){ - if (p == Proto::Any) return ""; +QString ProtocolProps::protoToString(amnezia::Proto p) +{ + if (p == Proto::Any) + return ""; QMetaEnum metaEnum = QMetaEnum::fromType(); QString protoKey = metaEnum.valueToKey(static_cast(p)); @@ -43,7 +49,8 @@ TransportProto ProtocolProps::transportProtoFromString(QString p) QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { TransportProto tp = static_cast(i); - if (p.toLower() == transportProtoToString(tp).toLower()) return tp; + if (p.toLower() == transportProtoToString(tp).toLower()) + return tp; } return TransportProto::Udp; } @@ -55,22 +62,19 @@ QString ProtocolProps::transportProtoToString(TransportProto proto, Proto p) return protoKey.toLower(); } - QMap ProtocolProps::protocolHumanNames() { - return { - {Proto::OpenVpn, "OpenVPN"}, - {Proto::ShadowSocks, "ShadowSocks"}, - {Proto::Cloak, "Cloak"}, - {Proto::WireGuard, "WireGuard"}, - {Proto::Ikev2, "IKEv2"}, - {Proto::L2tp, "L2TP"}, + return { { Proto::OpenVpn, "OpenVPN" }, + { Proto::ShadowSocks, "ShadowSocks" }, + { Proto::Cloak, "Cloak" }, + { Proto::WireGuard, "WireGuard" }, + { Proto::Awg, "AmneziaWG" }, + { Proto::Ikev2, "IKEv2" }, + { Proto::L2tp, "L2TP" }, - {Proto::TorWebSite, "Web site in Tor network"}, - {Proto::Dns, "DNS Service"}, - {Proto::FileShare, "File Sharing Service"}, - {Proto::Sftp, QObject::tr("Sftp service")} - }; + { Proto::TorWebSite, "Website in Tor network" }, + { Proto::Dns, "DNS Service" }, + { Proto::Sftp, QObject::tr("Sftp service") } }; } QMap ProtocolProps::protocolDescriptions() @@ -81,90 +85,106 @@ QMap ProtocolProps::protocolDescriptions() amnezia::ServiceType ProtocolProps::protocolService(Proto p) { switch (p) { - case Proto::Any : return ServiceType::None; - case Proto::OpenVpn : return ServiceType::Vpn; - case Proto::Cloak : return ServiceType::Vpn; - case Proto::ShadowSocks : return ServiceType::Vpn; - case Proto::WireGuard : return ServiceType::Vpn; - case Proto::TorWebSite : return ServiceType::Other; - case Proto::Dns : return ServiceType::Other; - case Proto::FileShare : return ServiceType::Other; - default: return ServiceType::Other; + case Proto::Any: return ServiceType::None; + case Proto::OpenVpn: return ServiceType::Vpn; + case Proto::Cloak: return ServiceType::Vpn; + case Proto::ShadowSocks: return ServiceType::Vpn; + case Proto::WireGuard: return ServiceType::Vpn; + case Proto::Awg: return ServiceType::Vpn; + case Proto::Ikev2: return ServiceType::Vpn; + + case Proto::TorWebSite: return ServiceType::Other; + case Proto::Dns: return ServiceType::Other; + case Proto::Sftp: return ServiceType::Other; + default: return ServiceType::Other; + } +} + +int ProtocolProps::getPortForInstall(Proto p) +{ + switch (p) { + case Awg: + case WireGuard: + case ShadowSocks: + case OpenVpn: + return QRandomGenerator::global()->bounded(30000, 50000); + default: + return defaultPort(p); } } int ProtocolProps::defaultPort(Proto p) { switch (p) { - case Proto::Any : return -1; - case Proto::OpenVpn : return 1194; - case Proto::Cloak : return 443; - case Proto::ShadowSocks : return 6789; - case Proto::WireGuard : return 51820; - case Proto::Ikev2 : return -1; - case Proto::L2tp : return -1; + case Proto::Any: return -1; + case Proto::OpenVpn: return QString(protocols::openvpn::defaultPort).toInt(); + case Proto::Cloak: return QString(protocols::cloak::defaultPort).toInt(); + case Proto::ShadowSocks: return QString(protocols::shadowsocks::defaultPort).toInt(); + case Proto::WireGuard: return QString(protocols::wireguard::defaultPort).toInt(); + case Proto::Awg: return QString(protocols::awg::defaultPort).toInt(); + case Proto::Ikev2: return -1; + case Proto::L2tp: return -1; - case Proto::TorWebSite : return -1; - case Proto::Dns : return 53; - case Proto::FileShare : return 139; - case Proto::Sftp : return 222; - default: return -1; + case Proto::TorWebSite: return -1; + case Proto::Dns: return 53; + case Proto::Sftp: return 222; + default: return -1; } } bool ProtocolProps::defaultPortChangeable(Proto p) { switch (p) { - case Proto::Any : return false; - case Proto::OpenVpn : return true; - case Proto::Cloak : return true; - case Proto::ShadowSocks : return true; - case Proto::WireGuard : return true; - case Proto::Ikev2 : return false; - case Proto::L2tp : return false; + case Proto::Any: return false; + case Proto::OpenVpn: return true; + case Proto::Cloak: return true; + case Proto::ShadowSocks: return true; + case Proto::WireGuard: return true; + case Proto::Awg: return true; + case Proto::Ikev2: return false; + case Proto::L2tp: return false; - - case Proto::TorWebSite : return true; - case Proto::Dns : return false; - case Proto::FileShare : return false; - default: return -1; + case Proto::TorWebSite: return false; + case Proto::Dns: return false; + case Proto::Sftp: return true; + default: return false; } } TransportProto ProtocolProps::defaultTransportProto(Proto p) { switch (p) { - case Proto::Any : return TransportProto::Udp; - case Proto::OpenVpn : return TransportProto::Udp; - case Proto::Cloak : return TransportProto::Tcp; - case Proto::ShadowSocks : return TransportProto::Tcp; - case Proto::WireGuard : return TransportProto::Udp; - case Proto::Ikev2 : return TransportProto::Udp; - case Proto::L2tp : return TransportProto::Udp; + case Proto::Any: return TransportProto::Udp; + case Proto::OpenVpn: return TransportProto::Udp; + case Proto::Cloak: return TransportProto::Tcp; + case Proto::ShadowSocks: return TransportProto::Tcp; + case Proto::WireGuard: return TransportProto::Udp; + case Proto::Awg: return TransportProto::Udp; + case Proto::Ikev2: return TransportProto::Udp; + case Proto::L2tp: return TransportProto::Udp; // non-vpn - case Proto::TorWebSite : return TransportProto::Tcp; - case Proto::Dns : return TransportProto::Udp; - case Proto::FileShare : return TransportProto::Udp; - case Proto::Sftp : return TransportProto::Tcp; + case Proto::TorWebSite: return TransportProto::Tcp; + case Proto::Dns: return TransportProto::Udp; + case Proto::Sftp: return TransportProto::Tcp; } } bool ProtocolProps::defaultTransportProtoChangeable(Proto p) { switch (p) { - case Proto::Any : return false; - case Proto::OpenVpn : return true; - case Proto::Cloak : return false; - case Proto::ShadowSocks : return false; - case Proto::WireGuard : return false; - case Proto::Ikev2 : return false; - case Proto::L2tp : return false; + case Proto::Any: return false; + case Proto::OpenVpn: return true; + case Proto::Cloak: return false; + case Proto::ShadowSocks: return false; + case Proto::WireGuard: return false; + case Proto::Awg: return false; + case Proto::Ikev2: return false; + case Proto::L2tp: return false; // non-vpn - case Proto::TorWebSite : return false; - case Proto::Dns : return false; - case Proto::FileShare : return false; - case Proto::Sftp : return false; - default: return false; + case Proto::TorWebSite: return false; + case Proto::Dns: return false; + case Proto::Sftp: return false; + default: return false; } } diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index db794100..ab9cac1b 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -1,206 +1,244 @@ #ifndef PROTOCOLS_DEFS_H #define PROTOCOLS_DEFS_H +#include #include #include -#include - -namespace amnezia { -namespace config_key { - -// Json config strings -constexpr char hostName[] = "hostName"; -constexpr char userName[] = "userName"; -constexpr char password[] = "password"; -constexpr char port[] = "port"; -constexpr char local_port[] = "local_port"; - -constexpr char dns1[] = "dns1"; -constexpr char dns2[] = "dns2"; - - -constexpr char description[] = "description"; -constexpr char cert[] = "cert"; -constexpr char config[] = "config"; - - -constexpr char containers[] = "containers"; -constexpr char container[] = "container"; -constexpr char defaultContainer[] = "defaultContainer"; - -constexpr char vpnproto[] = "protocol"; -constexpr char protocols[] = "protocols"; - -constexpr char remote[] = "remote"; -constexpr char transport_proto[] = "transport_proto"; -constexpr char cipher[] = "cipher"; -constexpr char hash[] = "hash"; -constexpr char ncp_disable[] = "ncp_disable"; -constexpr char tls_auth[] = "tls_auth"; - -constexpr char client_priv_key[] = "client_priv_key"; -constexpr char client_pub_key[] = "client_pub_key"; -constexpr char server_priv_key[] = "server_priv_key"; -constexpr char server_pub_key[] = "server_pub_key"; -constexpr char psk_key[] = "psk_key"; - -constexpr char client_ip[] = "client_ip"; // internal ip address - -constexpr char site[] = "site"; -constexpr char block_outside_dns[] = "block_outside_dns"; - -constexpr char subnet_address[] = "subnet_address"; -constexpr char subnet_mask[] = "subnet_mask"; -constexpr char subnet_cidr[] = "subnet_cidr"; - -constexpr char additional_client_config[] = "additional_client_config"; -constexpr char additional_server_config[] = "additional_server_config"; - -// proto config keys -constexpr char last_config[] = "last_config"; - -constexpr char isThirdPartyConfig[] = "isThirdPartyConfig"; - -constexpr char openvpn[] = "openvpn"; -constexpr char cloak[] = "cloak"; -constexpr char wireguard[] = "wireguard"; - -} - -namespace protocols { - -namespace dns { -constexpr char amneziaDnsIp[] = "172.29.172.254"; -} - -namespace openvpn { -constexpr char defaultSubnetAddress[] = "10.8.0.0"; -constexpr char defaultSubnetMask[] = "255.255.255.0"; -constexpr char defaultSubnetCidr[] = "24"; - -constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf"; -constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt"; -constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued"; -constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key"; -constexpr char clientsDirPath[] = "/opt/amnezia/openvpn/clients"; -constexpr char defaultPort[] = "1194"; -constexpr char defaultTransportProto[] = "udp"; -constexpr char defaultCipher[] = "AES-256-GCM"; -constexpr char defaultHash[] = "SHA512"; -constexpr bool defaultBlockOutsideDns = true; -constexpr bool defaultNcpDisable = false; -constexpr bool defaultTlsAuth = true; -constexpr char ncpDisableString[] = "ncp-disable"; -constexpr char tlsAuthString[] = "tls-auth /opt/amnezia/openvpn/ta.key 0"; - -constexpr char defaultAdditionalClientConfig[] = ""; -constexpr char defaultAdditionalServerConfig[] = ""; -} - -namespace shadowsocks { -constexpr char ssKeyPath[] = "/opt/amnezia/shadowsocks/shadowsocks.key"; -constexpr char defaultPort[] = "6789"; -constexpr char defaultLocalProxyPort[] = "8585"; -constexpr char defaultCipher[] = "chacha20-ietf-poly1305"; -} - -namespace cloak { -constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key"; -constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key"; -constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key"; -constexpr char defaultPort[] = "443"; -constexpr char defaultRedirSite[] = "tile.openstreetmap.org"; -constexpr char defaultCipher[] = "chacha20-poly1305"; - -} - -namespace wireguard { -constexpr char defaultSubnetAddress[] = "10.8.1.0"; -constexpr char defaultSubnetMask[] = "255.255.255.0"; -constexpr char defaultSubnetCidr[] = "24"; - -constexpr char defaultPort[] = "51820"; -constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf"; -constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key"; -constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key"; - -} - -namespace sftp { -constexpr char defaultUserName[] = "sftp_user"; - -} // namespace sftp - -} // namespace protocols - -namespace ProtocolEnumNS { -Q_NAMESPACE - -enum TransportProto { - Udp, - Tcp -}; -Q_ENUM_NS(TransportProto) - -enum Proto { - Any = 0, - OpenVpn, - ShadowSocks, - Cloak, - WireGuard, - Ikev2, - L2tp, - - // non-vpn - TorWebSite, - Dns, - FileShare, - Sftp -}; -Q_ENUM_NS(Proto) - -enum ServiceType { - None = 0, - Vpn, - Other -}; -Q_ENUM_NS(ServiceType) -} // namespace ProtocolEnumNS - -using namespace ProtocolEnumNS; - -class ProtocolProps : public QObject +namespace amnezia { - Q_OBJECT + namespace config_key + { -public: - Q_INVOKABLE static QList allProtocols(); + // Json config strings + constexpr char hostName[] = "hostName"; + constexpr char userName[] = "userName"; + constexpr char password[] = "password"; + constexpr char port[] = "port"; + constexpr char local_port[] = "local_port"; - // spelling may differ for various protocols - TCP for OpenVPN, tcp for others - Q_INVOKABLE static TransportProto transportProtoFromString(QString p); - Q_INVOKABLE static QString transportProtoToString(TransportProto proto, Proto p = Proto::Any); + constexpr char dns1[] = "dns1"; + constexpr char dns2[] = "dns2"; - Q_INVOKABLE static Proto protoFromString(QString p); - Q_INVOKABLE static QString protoToString(Proto p); + constexpr char description[] = "description"; + constexpr char cert[] = "cert"; + constexpr char config[] = "config"; - Q_INVOKABLE static QMap protocolHumanNames(); - Q_INVOKABLE static QMap protocolDescriptions(); + constexpr char containers[] = "containers"; + constexpr char container[] = "container"; + constexpr char defaultContainer[] = "defaultContainer"; - Q_INVOKABLE static ServiceType protocolService(Proto p); + constexpr char vpnproto[] = "protocol"; + constexpr char protocols[] = "protocols"; - Q_INVOKABLE static int defaultPort(Proto p); - Q_INVOKABLE static bool defaultPortChangeable(Proto p); + constexpr char remote[] = "remote"; + constexpr char transport_proto[] = "transport_proto"; + constexpr char cipher[] = "cipher"; + constexpr char hash[] = "hash"; + constexpr char ncp_disable[] = "ncp_disable"; + constexpr char tls_auth[] = "tls_auth"; - Q_INVOKABLE static TransportProto defaultTransportProto(Proto p); - Q_INVOKABLE static bool defaultTransportProtoChangeable(Proto p); + constexpr char client_priv_key[] = "client_priv_key"; + constexpr char client_pub_key[] = "client_pub_key"; + constexpr char server_priv_key[] = "server_priv_key"; + constexpr char server_pub_key[] = "server_pub_key"; + constexpr char psk_key[] = "psk_key"; + constexpr char client_ip[] = "client_ip"; // internal ip address - Q_INVOKABLE static QString key_proto_config_data(Proto p); - Q_INVOKABLE static QString key_proto_config_path(Proto p); + constexpr char site[] = "site"; + constexpr char block_outside_dns[] = "block_outside_dns"; -}; + constexpr char subnet_address[] = "subnet_address"; + constexpr char subnet_mask[] = "subnet_mask"; + constexpr char subnet_cidr[] = "subnet_cidr"; + constexpr char additional_client_config[] = "additional_client_config"; + constexpr char additional_server_config[] = "additional_server_config"; + + // proto config keys + constexpr char last_config[] = "last_config"; + + constexpr char isThirdPartyConfig[] = "isThirdPartyConfig"; + + constexpr char junkPacketCount[] = "Jc"; + constexpr char junkPacketMinSize[] = "Jmin"; + constexpr char junkPacketMaxSize[] = "Jmax"; + constexpr char initPacketJunkSize[] = "S1"; + constexpr char responsePacketJunkSize[] = "S2"; + constexpr char initPacketMagicHeader[] = "H1"; + constexpr char responsePacketMagicHeader[] = "H2"; + constexpr char underloadPacketMagicHeader[] = "H3"; + constexpr char transportPacketMagicHeader[] = "H4"; + + constexpr char openvpn[] = "openvpn"; + constexpr char wireguard[] = "wireguard"; + constexpr char shadowsocks[] = "shadowsocks"; + constexpr char cloak[] = "cloak"; + constexpr char sftp[] = "sftp"; + constexpr char awg[] = "awg"; + + } + + namespace protocols + { + + namespace dns + { + constexpr char amneziaDnsIp[] = "172.29.172.254"; + } + + namespace openvpn + { + constexpr char defaultSubnetAddress[] = "10.8.0.0"; + constexpr char defaultSubnetMask[] = "255.255.255.0"; + constexpr char defaultSubnetCidr[] = "24"; + + constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf"; + constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt"; + constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued"; + constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key"; + constexpr char clientsDirPath[] = "/opt/amnezia/openvpn/clients"; + constexpr char defaultPort[] = "1194"; + constexpr char defaultTransportProto[] = "udp"; + constexpr char defaultCipher[] = "AES-256-GCM"; + constexpr char defaultHash[] = "SHA512"; + constexpr bool defaultBlockOutsideDns = true; + constexpr bool defaultNcpDisable = false; + constexpr bool defaultTlsAuth = true; + constexpr char ncpDisableString[] = "ncp-disable"; + constexpr char tlsAuthString[] = "tls-auth /opt/amnezia/openvpn/ta.key 0"; + + constexpr char defaultAdditionalClientConfig[] = ""; + constexpr char defaultAdditionalServerConfig[] = ""; + } + + namespace shadowsocks + { + constexpr char ssKeyPath[] = "/opt/amnezia/shadowsocks/shadowsocks.key"; + constexpr char defaultPort[] = "6789"; + constexpr char defaultLocalProxyPort[] = "8585"; + constexpr char defaultCipher[] = "chacha20-ietf-poly1305"; + } + + namespace cloak + { + constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key"; + constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key"; + constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key"; + constexpr char defaultPort[] = "443"; + constexpr char defaultRedirSite[] = "tile.openstreetmap.org"; + constexpr char defaultCipher[] = "chacha20-poly1305"; + + } + + namespace wireguard + { + constexpr char defaultSubnetAddress[] = "10.8.1.0"; + constexpr char defaultSubnetMask[] = "255.255.255.0"; + constexpr char defaultSubnetCidr[] = "24"; + + constexpr char defaultPort[] = "51820"; + constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf"; + constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key"; + constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key"; + + } + + namespace sftp + { + constexpr char defaultUserName[] = "sftp_user"; + + } // namespace sftp + + namespace awg + { + constexpr char defaultPort[] = "55424"; + + constexpr char serverConfigPath[] = "/opt/amnezia/awg/wg0.conf"; + constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key"; + constexpr char serverPskKeyPath[] = "/opt/amnezia/awg/wireguard_psk.key"; + + constexpr char defaultJunkPacketCount[] = "3"; + constexpr char defaultJunkPacketMinSize[] = "10"; + constexpr char defaultJunkPacketMaxSize[] = "30"; + constexpr char defaultInitPacketJunkSize[] = "15"; + constexpr char defaultResponsePacketJunkSize[] = "18"; + constexpr char defaultInitPacketMagicHeader[] = "1020325451"; + constexpr char defaultResponsePacketMagicHeader[] = "3288052141"; + constexpr char defaultTransportPacketMagicHeader[] = "2528465083"; + constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858"; + } + + } // namespace protocols + + namespace ProtocolEnumNS + { + Q_NAMESPACE + + enum TransportProto { + Udp, + Tcp + }; + Q_ENUM_NS(TransportProto) + + enum Proto { + Any = 0, + OpenVpn, + ShadowSocks, + Cloak, + WireGuard, + Awg, + Ikev2, + L2tp, + + // non-vpn + TorWebSite, + Dns, + Sftp + }; + Q_ENUM_NS(Proto) + + enum ServiceType { + None = 0, + Vpn, + Other + }; + Q_ENUM_NS(ServiceType) + } // namespace ProtocolEnumNS + + using namespace ProtocolEnumNS; + + class ProtocolProps : public QObject + { + Q_OBJECT + + public: + Q_INVOKABLE static QList allProtocols(); + + // spelling may differ for various protocols - TCP for OpenVPN, tcp for others + Q_INVOKABLE static TransportProto transportProtoFromString(QString p); + Q_INVOKABLE static QString transportProtoToString(TransportProto proto, Proto p = Proto::Any); + + Q_INVOKABLE static Proto protoFromString(QString p); + Q_INVOKABLE static QString protoToString(Proto p); + + Q_INVOKABLE static QMap protocolHumanNames(); + Q_INVOKABLE static QMap protocolDescriptions(); + + Q_INVOKABLE static ServiceType protocolService(Proto p); + + Q_INVOKABLE static int getPortForInstall(Proto p); + + Q_INVOKABLE static int defaultPort(Proto p); + Q_INVOKABLE static bool defaultPortChangeable(Proto p); + + Q_INVOKABLE static TransportProto defaultTransportProto(Proto p); + Q_INVOKABLE static bool defaultTransportProtoChangeable(Proto p); + + Q_INVOKABLE static QString key_proto_config_data(Proto p); + Q_INVOKABLE static QString key_proto_config_path(Proto p); + }; } // namespace amnezia QDebug operator<<(QDebug debug, const amnezia::Proto &p); diff --git a/client/protocols/shadowsocksvpnprotocol.cpp b/client/protocols/shadowsocksvpnprotocol.cpp index 82ae08b8..0ffc2768 100644 --- a/client/protocols/shadowsocksvpnprotocol.cpp +++ b/client/protocols/shadowsocksvpnprotocol.cpp @@ -66,7 +66,7 @@ ErrorCode ShadowSocksVpnProtocol::start() connect(&m_ssProcess, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){ qDebug().noquote() << "ShadowSocksVpnProtocol finished, exitCode, exiStatus" << exitCode << exitStatus; - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); if (exitStatus != QProcess::NormalExit){ emit protocolError(amnezia::ErrorCode::ShadowSocksExecutableCrashed); stop(); @@ -81,7 +81,7 @@ ErrorCode ShadowSocksVpnProtocol::start() m_ssProcess.waitForStarted(); if (m_ssProcess.state() == QProcess::ProcessState::Running) { - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); return OpenVpnProtocol::start(); } diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index a8f392e9..2ddc0684 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -1,24 +1,23 @@ #include #include -#include "vpnprotocol.h" #include "core/errorstrings.h" +#include "vpnprotocol.h" #if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) -#include "openvpnprotocol.h" -#include "shadowsocksvpnprotocol.h" -#include "openvpnovercloakprotocol.h" -#include "wireguardprotocol.h" + #include "openvpnovercloakprotocol.h" + #include "openvpnprotocol.h" + #include "shadowsocksvpnprotocol.h" + #include "wireguardprotocol.h" #endif #ifdef Q_OS_WINDOWS -#include "ikev2_vpn_protocol_windows.h" + #include "ikev2_vpn_protocol_windows.h" #endif - -VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent) +VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent) : QObject(parent), - m_connectionState(VpnConnectionState::Unknown), + m_connectionState(Vpn::ConnectionState::Unknown), m_rawConfig(configuration), m_timeoutTimer(new QTimer(this)), m_receivedBytes(0), @@ -31,8 +30,8 @@ VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent) void VpnProtocol::setLastError(ErrorCode lastError) { m_lastError = lastError; - if (lastError){ - setConnectionState(VpnConnectionState::Error); + if (lastError) { + setConnectionState(Vpn::ConnectionState::Error); } qCritical().noquote() << "VpnProtocol error, code" << m_lastError << errorString(m_lastError); } @@ -60,7 +59,7 @@ void VpnProtocol::stopTimeoutTimer() m_timeoutTimer->stop(); } -VpnProtocol::VpnConnectionState VpnProtocol::connectionState() const +Vpn::ConnectionState VpnProtocol::connectionState() const { return m_connectionState; } @@ -76,19 +75,19 @@ void VpnProtocol::setBytesChanged(quint64 receivedBytes, quint64 sentBytes) m_sentBytes = sentBytes; } -void VpnProtocol::setConnectionState(VpnProtocol::VpnConnectionState state) +void VpnProtocol::setConnectionState(Vpn::ConnectionState state) { qDebug() << "VpnProtocol::setConnectionState" << textConnectionState(state); if (m_connectionState == state) { return; } - if (m_connectionState == VpnConnectionState::Disconnected && state == VpnConnectionState::Disconnecting) { + if (m_connectionState == Vpn::ConnectionState::Disconnected && state == Vpn::ConnectionState::Disconnecting) { return; } m_connectionState = state; - if (m_connectionState == VpnConnectionState::Disconnected) { + if (m_connectionState == Vpn::ConnectionState::Disconnected) { m_receivedBytes = 0; m_sentBytes = 0; } @@ -103,7 +102,7 @@ QString VpnProtocol::vpnGateway() const return m_vpnGateway; } -VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject& configuration) +VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration) { switch (container) { #if defined(Q_OS_WINDOWS) @@ -114,6 +113,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject& case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration); case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration); case DockerContainer::WireGuard: return new WireguardProtocol(configuration); + case DockerContainer::Awg: return new WireguardProtocol(configuration); #endif default: return nullptr; } @@ -124,19 +124,18 @@ QString VpnProtocol::routeGateway() const return m_routeGateway; } -QString VpnProtocol::textConnectionState(VpnConnectionState connectionState) +QString VpnProtocol::textConnectionState(Vpn::ConnectionState connectionState) { switch (connectionState) { - case VpnConnectionState::Unknown: return tr("Unknown"); - case VpnConnectionState::Disconnected: return tr("Disconnected"); - case VpnConnectionState::Preparing: return tr("Preparing"); - case VpnConnectionState::Connecting: return tr("Connecting..."); - case VpnConnectionState::Connected: return tr("Connected"); - case VpnConnectionState::Disconnecting: return tr("Disconnecting..."); - case VpnConnectionState::Reconnecting: return tr("Reconnecting..."); - case VpnConnectionState::Error: return tr("Error"); - default: - ; + case Vpn::ConnectionState::Unknown: return tr("Unknown"); + case Vpn::ConnectionState::Disconnected: return tr("Disconnected"); + case Vpn::ConnectionState::Preparing: return tr("Preparing"); + case Vpn::ConnectionState::Connecting: return tr("Connecting..."); + case Vpn::ConnectionState::Connected: return tr("Connected"); + case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting..."); + case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting..."); + case Vpn::ConnectionState::Error: return tr("Error"); + default:; } return QString(); @@ -149,10 +148,10 @@ QString VpnProtocol::textConnectionState() const bool VpnProtocol::isConnected() const { - return m_connectionState == VpnConnectionState::Connected; + return m_connectionState == Vpn::ConnectionState::Connected; } bool VpnProtocol::isDisconnected() const { - return m_connectionState == VpnConnectionState::Disconnected; + return m_connectionState == Vpn::ConnectionState::Disconnected; } diff --git a/client/protocols/vpnprotocol.h b/client/protocols/vpnprotocol.h index 4b6876d5..bb71a5de 100644 --- a/client/protocols/vpnprotocol.h +++ b/client/protocols/vpnprotocol.h @@ -12,6 +12,33 @@ using namespace amnezia; class QTimer; +//todo change name +namespace Vpn +{ + Q_NAMESPACE + enum ConnectionState { + Unknown, + Disconnected, + Preparing, + Connecting, + Connected, + Disconnecting, + Reconnecting, + Error + }; + Q_ENUM_NS(ConnectionState) + + static void declareQmlVpnConnectionStateEnum() { + qmlRegisterUncreatableMetaObject( + Vpn::staticMetaObject, + "ConnectionState", + 1, 0, + "ConnectionState", + "Error: only enums" + ); + } +} + class VpnProtocol : public QObject { Q_OBJECT @@ -20,10 +47,7 @@ public: explicit VpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr); virtual ~VpnProtocol() override = default; - enum VpnConnectionState {Unknown, Disconnected, Preparing, Connecting, Connected, Disconnecting, Reconnecting, Error}; - Q_ENUM(VpnConnectionState) - - static QString textConnectionState(VpnConnectionState connectionState); + static QString textConnectionState(Vpn::ConnectionState connectionState); virtual ErrorCode prepare() { return ErrorCode::NoError; } @@ -32,7 +56,7 @@ public: virtual ErrorCode start() = 0; virtual void stop() = 0; - VpnConnectionState connectionState() const; + Vpn::ConnectionState connectionState() const; ErrorCode lastError() const; QString textConnectionState() const; void setLastError(ErrorCode lastError); @@ -44,7 +68,7 @@ public: signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); void timeoutTimerEvent(); void protocolError(amnezia::ErrorCode e); @@ -52,13 +76,13 @@ public slots: virtual void onTimeout(); // todo: remove? void setBytesChanged(quint64 receivedBytes, quint64 sentBytes); - void setConnectionState(VpnProtocol::VpnConnectionState state); + void setConnectionState(Vpn::ConnectionState state); protected: void startTimeoutTimer(); void stopTimeoutTimer(); - VpnConnectionState m_connectionState; + Vpn::ConnectionState m_connectionState; QString m_routeGateway; QString m_vpnLocalAddress; diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index 91903b2b..d675cd02 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -5,12 +5,13 @@ #include #include "logger.h" -#include "wireguardprotocol.h" #include "utilities.h" +#include "wireguardprotocol.h" #include "mozilla/localsocketcontroller.h" -WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject* parent) : VpnProtocol(configuration, parent) +WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent) + : VpnProtocol(configuration, parent) { m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf"); writeWireguardConfiguration(configuration); @@ -18,12 +19,12 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject* // MZ #if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX) m_impl.reset(new LocalSocketController()); - connect(m_impl.get(), &ControllerImpl::connected, this, [this](const QString& pubkey, const QDateTime& connectionTimestamp) { - emit connectionStateChanged(VpnProtocol::Connected); - }); - connect(m_impl.get(), &ControllerImpl::disconnected, this, [this](){ - emit connectionStateChanged(VpnProtocol::Disconnected); - }); + connect(m_impl.get(), &ControllerImpl::connected, this, + [this](const QString &pubkey, const QDateTime &connectionTimestamp) { + emit connectionStateChanged(Vpn::ConnectionState::Connected); + }); + connect(m_impl.get(), &ControllerImpl::disconnected, this, + [this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }); m_impl->initialize(nullptr, nullptr); #endif } @@ -69,23 +70,24 @@ void WireguardProtocol::stop() connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error; - setConnectionState(VpnConnectionState::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); }); - connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { - qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState; - }); + connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, + [this](QProcess::ProcessState newState) { + qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState; + }); #ifdef Q_OS_LINUX if (IpcClient::Interface()) { QRemoteObjectPendingReply result = IpcClient::Interface()->isWireguardRunning(); if (result.returnValue()) { - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); return; } } else { qCritical() << "IPC client not initialized"; - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); return; } #endif @@ -93,14 +95,12 @@ void WireguardProtocol::stop() m_wireguardStopProcess->start(); m_wireguardStopProcess->waitForFinished(10000); - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); } #if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX) ErrorCode WireguardProtocol::startMzImpl() { - - qDebug() << "WireguardProtocol::startMzImpl():" << m_rawConfig; m_impl->activate(m_rawConfig); return ErrorCode::NoError; } @@ -143,8 +143,8 @@ void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configura m_isConfigLoaded = true; qDebug().noquote() << QString("Set config data") << configPath(); - qDebug().noquote() << QString("Set config data") << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8(); - + qDebug().noquote() << QString("Set config data") + << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8(); } QString WireguardProtocol::configPath() const @@ -156,7 +156,8 @@ void WireguardProtocol::updateRouteGateway(QString line) { // TODO: fix for macos line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); - if (!line.contains("/")) return; + if (!line.contains("/")) + return; m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); m_routeGateway.replace(" ", ""); qDebug() << "Set VPN route gateway" << m_routeGateway; @@ -190,7 +191,7 @@ ErrorCode WireguardProtocol::start() return lastError(); } - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); m_wireguardStartProcess = IpcClient::CreatePrivilegedProcess(); @@ -213,16 +214,16 @@ ErrorCode WireguardProtocol::start() connect(m_wireguardStartProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol errorOccurred" << error; - setConnectionState(VpnConnectionState::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); }); - connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { - qDebug() << "WireguardProtocol::WireguardProtocol stateChanged" << newState; - }); + connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, + [this](QProcess::ProcessState newState) { + qDebug() << "WireguardProtocol::WireguardProtocol stateChanged" << newState; + }); - connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, [this]() { - setConnectionState(VpnConnectionState::Connected); - }); + connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, + [this]() { setConnectionState(Vpn::ConnectionState::Connected); }); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() { QRemoteObjectPendingReply reply = m_wireguardStartProcess->readAll(); @@ -250,7 +251,6 @@ ErrorCode WireguardProtocol::start() void WireguardProtocol::updateVpnGateway(const QString &line) { - } QString WireguardProtocol::serviceName() const @@ -261,9 +261,9 @@ QString WireguardProtocol::serviceName() const QStringList WireguardProtocol::stopArgs() { #ifdef Q_OS_WIN - return {"--remove", configPath()}; + return { "--remove", configPath() }; #elif defined Q_OS_LINUX - return {"down", "wg99"}; + return { "down", "wg99" }; #else return {}; #endif @@ -272,11 +272,10 @@ QStringList WireguardProtocol::stopArgs() QStringList WireguardProtocol::startArgs() { #ifdef Q_OS_WIN - return {"--add", configPath()}; + return { "--add", configPath() }; #elif defined Q_OS_LINUX - return {"up", "wg99"}; + return { "up", "wg99" }; #else return {}; #endif } - diff --git a/client/resources.qrc b/client/resources.qrc index 91830679..4c63383c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -1,15 +1,10 @@ - translations/amneziavpn_ru.qm images/close.png images/settings.png images/favorites_disabled.png images/favorites_enabled.png images/favorites_hover.png - images/controls/check_off.png - images/controls/check_on.png - images/controls/radio_off.png - images/controls/radio_on.png images/download.png images/upload.png images/tray/active.png @@ -61,75 +56,6 @@ server_scripts/wireguard/template.conf server_scripts/website_tor/configure_container.sh server_scripts/website_tor/run_container.sh - ui/qml/main.qml - ui/qml/Pages/PageBase.qml - ui/qml/Pages/PageAppSetting.qml - ui/qml/Pages/PageGeneralSettings.qml - ui/qml/Pages/PageNetworkSetting.qml - ui/qml/Pages/PageNewServer.qml - ui/qml/Pages/PageServerConfiguringProgress.qml - ui/qml/Pages/PageNewServerProtocols.qml - ui/qml/Pages/PageServerList.qml - ui/qml/Pages/PageServerContainers.qml - ui/qml/Pages/PageServerSettings.qml - ui/qml/Pages/PageSetupWizard.qml - ui/qml/Pages/PageSetupWizardHighLevel.qml - ui/qml/Pages/PageSetupWizardLowLevel.qml - ui/qml/Pages/PageSetupWizardMediumLevel.qml - ui/qml/Pages/PageSetupWizardVPNMode.qml - ui/qml/Pages/PageShareConnection.qml - ui/qml/Pages/PageSites.qml - ui/qml/Pages/PageStart.qml - ui/qml/Pages/PageVPN.qml - ui/qml/Pages/PageAbout.qml - ui/qml/Pages/PageQrDecoderIos.qml - ui/qml/Pages/PageViewConfig.qml - ui/qml/Pages/PageClientManagement.qml - ui/qml/Pages/ClientInfo/PageClientInfoBase.qml - ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml - ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml - ui/qml/Pages/Protocols/PageProtoCloak.qml - ui/qml/Pages/Protocols/PageProtoOpenVPN.qml - ui/qml/Pages/Protocols/PageProtoShadowSocks.qml - ui/qml/Pages/Protocols/PageProtoSftp.qml - ui/qml/Pages/Protocols/PageProtoTorWebSite.qml - ui/qml/Pages/Protocols/PageProtocolBase.qml - ui/qml/Pages/Protocols/PageProtoWireGuard.qml - ui/qml/Pages/InstallSettings/InstallSettingsBase.qml - ui/qml/Pages/InstallSettings/SelectContainer.qml - ui/qml/Pages/Share/PageShareProtoCloak.qml - ui/qml/Pages/Share/PageShareProtocolBase.qml - ui/qml/Pages/Share/PageShareProtoOpenVPN.qml - ui/qml/Pages/Share/PageShareProtoSftp.qml - ui/qml/Pages/Share/PageShareProtoShadowSocks.qml - ui/qml/Pages/Share/PageShareProtoTorWebSite.qml - ui/qml/Pages/Share/PageShareProtoAmnezia.qml - ui/qml/Pages/Share/PageShareProtoWireGuard.qml - ui/qml/Pages/Share/PageShareProtoIkev2.qml - ui/qml/Controls/BasicButtonType.qml - ui/qml/Controls/BlueButtonType.qml - ui/qml/Controls/CheckBoxType.qml - ui/qml/Controls/ComboBoxType.qml - ui/qml/Controls/ImageButtonType.qml - ui/qml/Controls/LabelType.qml - ui/qml/Controls/RadioButtonType.qml - ui/qml/Controls/SettingButtonType.qml - ui/qml/Controls/ShareConnectionButtonType.qml - ui/qml/Controls/ShareConnectionContent.qml - ui/qml/Controls/TextFieldType.qml - ui/qml/Controls/RichLabelType.qml - ui/qml/Controls/SvgImageType.qml - ui/qml/Controls/FlickableType.qml - ui/qml/Controls/UrlButtonType.qml - ui/qml/Controls/TextAreaType.qml - ui/qml/Controls/ContextMenu.qml - ui/qml/Controls/FadeBehavior.qml - ui/qml/Controls/VisibleBehavior.qml - ui/qml/Controls/Caption.qml - ui/qml/Controls/Logo.qml - ui/qml/Controls/BackButton.qml - ui/qml/Controls/ShareConnectionButtonCopyType.qml - ui/qml/Controls/SvgButtonType.qml ui/qml/Config/GlobalConfig.qml ui/qml/Config/qmldir server_scripts/check_server_is_busy.sh @@ -166,10 +92,135 @@ images/svg/control_point_black_24dp.svg images/svg/settings_suggest_black_24dp.svg server_scripts/website_tor/Dockerfile - ui/qml/Controls/PopupWithQuestion.qml - ui/qml/Pages/PageAdvancedServerSettings.qml - ui/qml/Controls/PopupWarning.qml - ui/qml/Controls/PopupWithTextField.qml server_scripts/check_user_in_sudo.sh + ui/qml/Controls2/BasicButtonType.qml + ui/qml/Controls2/TextFieldWithHeaderType.qml + fonts/pt-root-ui_vf.ttf + ui/qml/Controls2/LabelWithButtonType.qml + images/controls/arrow-right.svg + images/controls/chevron-right.svg + ui/qml/Controls2/ImageButtonType.qml + ui/qml/Controls2/CardType.qml + ui/qml/Controls2/CheckBoxType.qml + images/controls/check.svg + ui/qml/Controls2/DropDownType.qml + ui/qml/Pages2/PageSetupWizardStart.qml + ui/qml/main2.qml + images/amneziaBigLogo.png + images/amneziaBigLogo.svg + ui/qml/Controls2/FlickableType.qml + ui/qml/Pages2/PageSetupWizardCredentials.qml + ui/qml/Controls2/HeaderType.qml + images/controls/arrow-left.svg + ui/qml/Pages2/PageSetupWizardProtocols.qml + ui/qml/Pages2/PageSetupWizardEasy.qml + images/controls/chevron-down.svg + images/controls/chevron-up.svg + ui/qml/Controls2/TextTypes/ParagraphTextType.qml + ui/qml/Controls2/TextTypes/Header2TextType.qml + ui/qml/Controls2/HorizontalRadioButton.qml + ui/qml/Controls2/VerticalRadioButton.qml + ui/qml/Controls2/SwitcherType.qml + ui/qml/Controls2/TabButtonType.qml + ui/qml/Pages2/PageSetupWizardProtocolSettings.qml + ui/qml/Pages2/PageSetupWizardInstalling.qml + ui/qml/Pages2/PageSetupWizardConfigSource.qml + images/controls/folder-open.svg + images/controls/qr-code.svg + images/controls/text-cursor.svg + ui/qml/Pages2/PageSetupWizardTextKey.qml + ui/qml/Pages2/PageStart.qml + ui/qml/Controls2/TabImageButtonType.qml + images/controls/home.svg + images/controls/settings-2.svg + images/controls/share-2.svg + ui/qml/Pages2/PageHome.qml + ui/qml/Pages2/PageSettingsServersList.qml + ui/qml/Pages2/PageShare.qml + ui/qml/Controls2/TextTypes/Header1TextType.qml + ui/qml/Controls2/TextTypes/LabelTextType.qml + ui/qml/Controls2/TextTypes/ButtonTextType.qml + ui/qml/Controls2/Header2Type.qml + images/controls/plus.svg + ui/qml/Components/ConnectButton.qml + images/connectionProgress.svg + images/connectionOff.svg + images/connectionOn.svg + images/controls/download.svg + ui/qml/Controls2/ProgressBarType.qml + ui/qml/Components/ConnectionTypeSelectionDrawer.qml + ui/qml/Components/HomeContainersListView.qml + ui/qml/Controls2/TextTypes/CaptionTextType.qml + images/controls/settings.svg + ui/qml/Pages2/PageSettingsServerInfo.qml + ui/qml/Controls2/PageType.qml + ui/qml/Controls2/PopupType.qml + images/controls/edit-3.svg + ui/qml/Pages2/PageSettingsServerData.qml + ui/qml/Components/SettingsContainersListView.qml + ui/qml/Controls2/TextTypes/ListItemTitleType.qml + ui/qml/Controls2/DividerType.qml + ui/qml/Controls2/DrawerType.qml + ui/qml/Controls2/StackViewType.qml + ui/qml/Pages2/PageSettings.qml + images/controls/amnezia.svg + images/controls/app.svg + images/controls/radio.svg + images/controls/save.svg + images/controls/server.svg + ui/qml/Pages2/PageSettingsServerProtocols.qml + ui/qml/Pages2/PageSettingsServerServices.qml + ui/qml/Pages2/PageSetupWizardViewConfig.qml + images/controls/file-cog-2.svg + ui/qml/Components/QuestionDrawer.qml + ui/qml/Pages2/PageDeinstalling.qml + ui/qml/Controls2/BackButtonType.qml + ui/qml/Pages2/PageSettingsServerProtocol.qml + ui/qml/Components/TransportProtoSelector.qml + ui/qml/Controls2/ListViewWithRadioButtonType.qml + images/controls/radio-button.svg + images/controls/radio-button-inner-circle.png + images/controls/radio-button-pressed.svg + images/controls/radio-button-inner-circle-pressed.png + ui/qml/Components/ShareConnectionDrawer.qml + ui/qml/Pages2/PageSettingsConnection.qml + ui/qml/Pages2/PageSettingsDns.qml + ui/qml/Pages2/PageSettingsApplication.qml + ui/qml/Pages2/PageSettingsBackup.qml + images/controls/delete.svg + ui/qml/Pages2/PageSettingsAbout.qml + images/controls/github.svg + images/controls/mail.svg + images/controls/telegram.svg + ui/qml/Controls2/TextTypes/SmallTextType.qml + ui/qml/Filters/ContainersModelFilters.qml + ui/qml/Components/SelectLanguageDrawer.qml + ui/qml/Controls2/BusyIndicatorType.qml + ui/qml/Pages2/PageProtocolOpenVpnSettings.qml + ui/qml/Pages2/PageProtocolShadowSocksSettings.qml + ui/qml/Pages2/PageProtocolCloakSettings.qml + ui/qml/Pages2/PageProtocolRaw.qml + ui/qml/Pages2/PageSettingsLogging.qml + ui/qml/Pages2/PageServiceSftpSettings.qml + images/controls/copy.svg + ui/qml/Pages2/PageServiceTorWebsiteSettings.qml + ui/qml/Pages2/PageSetupWizardQrReader.qml + images/controls/eye.svg + images/controls/eye-off.svg + ui/qml/Pages2/PageSettingsSplitTunneling.qml + ui/qml/Controls2/ContextMenuType.qml + ui/qml/Controls2/TextAreaType.qml + images/controls/trash.svg + images/controls/more-vertical.svg + ui/qml/Controls2/ListViewWithLabelsType.qml + ui/qml/Pages2/PageServiceDnsSettings.qml + ui/qml/Controls2/TopCloseButtonType.qml + images/controls/x-circle.svg + ui/qml/Pages2/PageProtocolAwgSettings.qml + server_scripts/awg/template.conf + server_scripts/awg/start.sh + server_scripts/awg/configure_container.sh + server_scripts/awg/run_container.sh + server_scripts/awg/Dockerfile diff --git a/client/secure_qsettings.cpp b/client/secure_qsettings.cpp index e71eff48..1df10168 100644 --- a/client/secure_qsettings.cpp +++ b/client/secure_qsettings.cpp @@ -1,30 +1,28 @@ #include "secure_qsettings.h" #include "platforms/ios/MobileUtils.h" +#include "QAead.h" +#include "QBlockCipher.h" +#include "utilities.h" #include #include #include #include #include #include +#include #include #include -#include "utilities.h" -#include -#include "QAead.h" -#include "QBlockCipher.h" using namespace QKeychain; SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent) - : QObject{parent}, - m_settings(organization, application, parent), - encryptedKeys({"Servers/serversList"}) + : QObject { parent }, m_settings(organization, application, parent), encryptedKeys({ "Servers/serversList" }) { bool encrypted = m_settings.value("Conf/encrypted").toBool(); // convert settings to encrypted for if updated to >= 2.1.0 - if (encryptionRequired() && ! encrypted) { + if (encryptionRequired() && !encrypted) { for (const QString &key : m_settings.allKeys()) { if (encryptedKeys.contains(key)) { const QVariant &val = value(key); @@ -44,15 +42,15 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue return m_cache.value(key); } - if (!m_settings.contains(key)) return defaultValue; + if (!m_settings.contains(key)) + return defaultValue; QVariant retVal; // check if value is not encrypted, v. < 2.0.x retVal = m_settings.value(key); if (retVal.isValid()) { - if (retVal.userType() == QVariant::ByteArray && - retVal.toByteArray().mid(0, magicString.size()) == magicString) { + if (retVal.userType() == QVariant::ByteArray && retVal.toByteArray().mid(0, magicString.size()) == magicString) { if (getEncKey().isEmpty() || getEncIv().isEmpty()) { qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty"; @@ -71,8 +69,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue retVal = QVariant(); } } - } - else { + } else { qWarning() << "SecureQSettings::value invalid QVariant value"; retVal = QVariant(); } @@ -95,14 +92,12 @@ void SecureQSettings::setValue(const QString &key, const QVariant &value) QByteArray encryptedValue = encryptText(decryptedValue); m_settings.setValue(key, magicString + encryptedValue); - } - else { + } else { qCritical() << "SecureQSettings::setValue Encryption required, but key is empty"; return; } - } - else { + } else { m_settings.setValue(key, value); } @@ -139,7 +134,8 @@ QByteArray SecureQSettings::backupAppConfig() const bool SecureQSettings::restoreAppConfig(const QByteArray &json) { QJsonObject cfg = QJsonDocument::fromJson(json).object(); - if (cfg.isEmpty()) return false; + if (cfg.isEmpty()) + return false; for (const QString &key : cfg.keys()) { setValue(key, cfg.value(key).toVariant()); @@ -149,14 +145,13 @@ bool SecureQSettings::restoreAppConfig(const QByteArray &json) return true; } - -QByteArray SecureQSettings::encryptText(const QByteArray& value) const +QByteArray SecureQSettings::encryptText(const QByteArray &value) const { QSimpleCrypto::QBlockCipher cipher; return cipher.encryptAesBlockCipher(value, getEncKey(), getEncIv()); } -QByteArray SecureQSettings::decryptText(const QByteArray& ba) const +QByteArray SecureQSettings::decryptText(const QByteArray &ba) const { QSimpleCrypto::QBlockCipher cipher; return cipher.decryptAesBlockCipher(ba, getEncKey(), getEncIv()); @@ -228,13 +223,11 @@ QByteArray SecureQSettings::getSecTag(const QString &tag) job->setAutoDelete(false); job->setKey(tag); QEventLoop loop; - job->connect(job.data(), &ReadPasswordJob::finished, job.data(), [&loop](){ - loop.quit(); - }); + job->connect(job.data(), &ReadPasswordJob::finished, job.data(), [&loop]() { loop.quit(); }); job->start(); loop.exec(); - if ( job->error() ) { + if (job->error()) { qCritical() << "SecureQSettings::getSecTag Error:" << job->errorString(); } @@ -249,9 +242,7 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data) job->setBinaryData(data); QEventLoop loop; QTimer::singleShot(1000, &loop, SLOT(quit())); - job->connect(job.data(), &WritePasswordJob::finished, job.data(), [&loop](){ - loop.quit(); - }); + job->connect(job.data(), &WritePasswordJob::finished, job.data(), [&loop]() { loop.quit(); }); job->start(); loop.exec(); @@ -260,4 +251,10 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data) } } - +void SecureQSettings::clearSettings() +{ + QMutexLocker locker(&mutex); + m_settings.clear(); + m_cache.clear(); + sync(); +} diff --git a/client/secure_qsettings.h b/client/secure_qsettings.h index 9b1f6167..7421ce01 100644 --- a/client/secure_qsettings.h +++ b/client/secure_qsettings.h @@ -1,23 +1,22 @@ #ifndef SECUREQSETTINGS_H #define SECUREQSETTINGS_H -#include -#include #include #include +#include +#include #include "keychain.h" - -constexpr const char* settingsKeyTag = "settingsKeyTag"; -constexpr const char* settingsIvTag = "settingsIvTag"; -constexpr const char* keyChainName = "AmneziaVPN-Keychain"; - +constexpr const char *settingsKeyTag = "settingsKeyTag"; +constexpr const char *settingsIvTag = "settingsIvTag"; +constexpr const char *keyChainName = "AmneziaVPN-Keychain"; class SecureQSettings : public QObject { public: - explicit SecureQSettings(const QString &organization, const QString &application = QString(), QObject *parent = nullptr); + explicit SecureQSettings(const QString &organization, const QString &application = QString(), + QObject *parent = nullptr); QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; void setValue(const QString &key, const QVariant &value); @@ -28,7 +27,7 @@ public: bool restoreAppConfig(const QByteArray &json); QByteArray encryptText(const QByteArray &value) const; - QByteArray decryptText(const QByteArray& ba) const; + QByteArray decryptText(const QByteArray &ba) const; bool encryptionRequired() const; @@ -38,6 +37,8 @@ public: static QByteArray getSecTag(const QString &tag); static void setSecTag(const QString &tag, const QByteArray &data); + void clearSettings(); + private: QSettings m_settings; diff --git a/client/server_scripts/awg/Dockerfile b/client/server_scripts/awg/Dockerfile new file mode 100644 index 00000000..8c536fc7 --- /dev/null +++ b/client/server_scripts/awg/Dockerfile @@ -0,0 +1,46 @@ +FROM amneziavpn/amnezia-wg:latest + +LABEL maintainer="AmneziaVPN" + +#Install required packages +RUN apk add --no-cache bash curl dumb-init +RUN apk --update upgrade --no-cache + +RUN mkdir -p /opt/amnezia +RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh +RUN chmod a+x /opt/amnezia/start.sh + +# Tune network +RUN echo -e " \n\ + fs.file-max = 51200 \n\ + \n\ + net.core.rmem_max = 67108864 \n\ + net.core.wmem_max = 67108864 \n\ + net.core.netdev_max_backlog = 250000 \n\ + net.core.somaxconn = 4096 \n\ + \n\ + net.ipv4.tcp_syncookies = 1 \n\ + net.ipv4.tcp_tw_reuse = 1 \n\ + net.ipv4.tcp_tw_recycle = 0 \n\ + net.ipv4.tcp_fin_timeout = 30 \n\ + net.ipv4.tcp_keepalive_time = 1200 \n\ + net.ipv4.ip_local_port_range = 10000 65000 \n\ + net.ipv4.tcp_max_syn_backlog = 8192 \n\ + net.ipv4.tcp_max_tw_buckets = 5000 \n\ + net.ipv4.tcp_fastopen = 3 \n\ + net.ipv4.tcp_mem = 25600 51200 102400 \n\ + net.ipv4.tcp_rmem = 4096 87380 67108864 \n\ + net.ipv4.tcp_wmem = 4096 65536 67108864 \n\ + net.ipv4.tcp_mtu_probing = 1 \n\ + net.ipv4.tcp_congestion_control = hybla \n\ + # for low-latency network, use cubic instead \n\ + # net.ipv4.tcp_congestion_control = cubic \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \ + mkdir -p /etc/security && \ + echo -e " \n\ + * soft nofile 51200 \n\ + * hard nofile 51200 \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf + +ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ] +CMD [ "" ] diff --git a/client/server_scripts/awg/configure_container.sh b/client/server_scripts/awg/configure_container.sh new file mode 100644 index 00000000..322cc38f --- /dev/null +++ b/client/server_scripts/awg/configure_container.sh @@ -0,0 +1,26 @@ +mkdir -p /opt/amnezia/awg +cd /opt/amnezia/awg +WIREGUARD_SERVER_PRIVATE_KEY=$(wg genkey) +echo $WIREGUARD_SERVER_PRIVATE_KEY > /opt/amnezia/awg/wireguard_server_private_key.key + +WIREGUARD_SERVER_PUBLIC_KEY=$(echo $WIREGUARD_SERVER_PRIVATE_KEY | wg pubkey) +echo $WIREGUARD_SERVER_PUBLIC_KEY > /opt/amnezia/awg/wireguard_server_public_key.key + +WIREGUARD_PSK=$(wg genpsk) +echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key + +cat > /opt/amnezia/awg/wg0.conf < /dev/null 2>&1; then $pm update -yq; $pm install -yq sudo; fi;\ -if ! command -v fuser > /dev/null 2>&1; then $pm install -yq psmisc; fi;\ -if ! command -v lsof > /dev/null 2>&1; then $pm install -yq lsof; fi;\ -if ! command -v docker > /dev/null 2>&1; then $pm update -yq; $pm install -yq $docker_pkg;\ - if [ "$dist" = "fedora" ] || [ "$dist" = "debian" ]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\ +if ! command -v fuser > /dev/null 2>&1; then sudo $pm install -yq psmisc; fi;\ +if ! command -v lsof > /dev/null 2>&1; then sudo $pm install -yq lsof; fi;\ +if ! command -v docker > /dev/null 2>&1; then sudo $pm update -yq; sudo $pm install -yq $docker_pkg;\ + if [ "$dist" = "fedora" ] || [ "$dist" = "centos" ] || [ "$dist" = "debian" ]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\ fi;\ if [ "$dist" = "debian" ]; then \ docker_service=$(systemctl list-units --full --all | grep docker.service | grep -v inactive | grep -v dead | grep -v failed);\ @@ -17,4 +17,3 @@ if [ "$dist" = "debian" ]; then \ fi;\ if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install Docker";exit 1;fi;\ docker --version - diff --git a/client/settings.cpp b/client/settings.cpp index 53377f07..93871834 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -1,6 +1,6 @@ -#include "version.h" #include "settings.h" #include "utilities.h" +#include "version.h" #include "containers/containers_defs.h" #include "logger.h" @@ -8,10 +8,7 @@ const char Settings::cloudFlareNs1[] = "1.1.1.1"; const char Settings::cloudFlareNs2[] = "1.0.0.1"; - -Settings::Settings(QObject* parent) : - QObject(parent), - m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this) +Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this) { // Import old settings if (serversCount() == 0) { @@ -20,7 +17,7 @@ Settings::Settings(QObject* parent) : QString serverName = m_settings.value("Server/serverName").toString(); int port = m_settings.value("Server/serverPort").toInt(); - if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()){ + if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) { QJsonObject server; server.insert(config_key::userName, user); server.insert(config_key::password, password); @@ -46,7 +43,8 @@ int Settings::serversCount() const QJsonObject Settings::server(int index) const { const QJsonArray &servers = serversArray(); - if (index >= servers.size()) return QJsonObject(); + if (index >= servers.size()) + return QJsonObject(); return servers.at(index).toObject(); } @@ -61,7 +59,8 @@ void Settings::addServer(const QJsonObject &server) void Settings::removeServer(int index) { QJsonArray servers = serversArray(); - if (index >= servers.size()) return; + if (index >= servers.size()) + return; servers.removeAt(index); setServersArray(servers); @@ -70,7 +69,8 @@ void Settings::removeServer(int index) bool Settings::editServer(int index, const QJsonObject &server) { QJsonArray servers = serversArray(); - if (index >= servers.size()) return false; + if (index >= servers.size()) + return false; servers.replace(index, server); setServersArray(servers); @@ -94,8 +94,8 @@ QString Settings::defaultContainerName(int serverIndex) const QString name = server(serverIndex).value(config_key::defaultContainer).toString(); if (name.isEmpty()) { return ContainerProps::containerToString(DockerContainer::None); - } - else return name; + } else + return name; } QMap Settings::containers(int serverIndex) const @@ -104,7 +104,8 @@ QMap Settings::containers(int serverIndex) const QMap containersMap; for (const QJsonValue &val : containers) { - containersMap.insert(ContainerProps::containerFromString(val.toObject().value(config_key::container).toString()), val.toObject()); + containersMap.insert(ContainerProps::containerFromString(val.toObject().value(config_key::container).toString()), + val.toObject()); } return containersMap; @@ -114,17 +115,17 @@ void Settings::setContainers(int serverIndex, const QMap(m_settings.value("Conf/routeMode", 0).toInt()); } -void Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) +bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) { QVariantMap sites = vpnSites(mode); - if (sites.contains(site) && ip.isEmpty()) return; + if (sites.contains(site) && ip.isEmpty()) + return false; sites.insert(site, ip); setVpnSites(mode, sites); + return true; } void Settings::addVpnSites(RouteMode mode, const QMap &sites) @@ -257,7 +258,8 @@ void Settings::addVpnSites(RouteMode mode, const QMap &sites) const QString &site = i.key(); const QString &ip = i.value(); - if (allSites.contains(site) && allSites.value(site) == ip) continue; + if (allSites.contains(site) && allSites.value(site) == ip) + continue; allSites.insert(site, ip); } @@ -272,8 +274,7 @@ QStringList Settings::getVpnIps(RouteMode mode) const for (auto i = m.constBegin(); i != m.constEnd(); ++i) { if (Utils::checkIpSubnetFormat(i.key())) { ips.append(i.key()); - } - else if (Utils::checkIpSubnetFormat(i.value().toString())) { + } else if (Utils::checkIpSubnetFormat(i.value().toString())) { ips.append(i.value().toString()); } } @@ -284,7 +285,8 @@ QStringList Settings::getVpnIps(RouteMode mode) const void Settings::removeVpnSite(RouteMode mode, const QString &site) { QVariantMap sites = vpnSites(mode); - if (!sites.contains(site)) return; + if (!sites.contains(site)) + return; sites.remove(site); setVpnSites(mode, sites); @@ -294,7 +296,8 @@ void Settings::addVpnIps(RouteMode mode, const QStringList &ips) { QVariantMap sites = vpnSites(mode); for (const QString &ip : ips) { - if (ip.isEmpty()) continue; + if (ip.isEmpty()) + continue; sites.insert(ip, ""); } @@ -306,7 +309,8 @@ void Settings::removeVpnSites(RouteMode mode, const QStringList &sites) { QVariantMap sitesMap = vpnSites(mode); for (const QString &site : sites) { - if (site.isEmpty()) continue; + if (site.isEmpty()) + continue; sitesMap.remove(site); } @@ -314,9 +318,25 @@ void Settings::removeVpnSites(RouteMode mode, const QStringList &sites) setVpnSites(mode, sitesMap); } -QString Settings::primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); } +void Settings::removeAllVpnSites(RouteMode mode) +{ + setVpnSites(mode, QVariantMap()); +} -QString Settings::secondaryDns() const { return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); } +QString Settings::primaryDns() const +{ + return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); +} + +QString Settings::secondaryDns() const +{ + return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); +} + +void Settings::clearSettings() +{ + m_settings.clearSettings(); +} ServerCredentials Settings::defaultServerCredentials() const { @@ -330,7 +350,7 @@ ServerCredentials Settings::serverCredentials(int index) const ServerCredentials credentials; credentials.hostName = s.value(config_key::hostName).toString(); credentials.userName = s.value(config_key::userName).toString(); - credentials.password = s.value(config_key::password).toString(); + credentials.secretData = s.value(config_key::password).toString(); credentials.port = s.value(config_key::port).toInt(); return credentials; diff --git a/client/settings.h b/client/settings.h index ec3484d7..f530f6c5 100644 --- a/client/settings.h +++ b/client/settings.h @@ -2,15 +2,15 @@ #define SETTINGS_H #include -#include #include +#include -#include #include +#include #include -#include "core/defs.h" #include "containers/containers_defs.h" +#include "core/defs.h" #include "secure_qsettings.h" using namespace amnezia; @@ -22,13 +22,19 @@ class Settings : public QObject Q_OBJECT public: - explicit Settings(QObject* parent = nullptr); + explicit Settings(QObject *parent = nullptr); ServerCredentials defaultServerCredentials() const; ServerCredentials serverCredentials(int index) const; - QJsonArray serversArray() const { return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array(); } - void setServersArray(const QJsonArray &servers) { m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson()); } + QJsonArray serversArray() const + { + return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array(); + } + void setServersArray(const QJsonArray &servers) + { + m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson()); + } // Servers section int serversCount() const; @@ -37,9 +43,18 @@ public: void removeServer(int index); bool editServer(int index, const QJsonObject &server); - int defaultServerIndex() const { return m_settings.value("Servers/defaultServerIndex", 0).toInt(); } - void setDefaultServer(int index) { m_settings.setValue("Servers/defaultServerIndex", index); } - QJsonObject defaultServer() const { return server(defaultServerIndex()); } + int defaultServerIndex() const + { + return m_settings.value("Servers/defaultServerIndex", 0).toInt(); + } + void setDefaultServer(int index) + { + m_settings.setValue("Servers/defaultServerIndex", index); + } + QJsonObject defaultServer() const + { + return server(defaultServerIndex()); + } void setDefaultContainer(int serverIndex, DockerContainer container); DockerContainer defaultContainer(int serverIndex) const; @@ -61,13 +76,28 @@ public: QString nextAvailableServerName() const; // App settings section - bool isAutoConnect() const { return m_settings.value("Conf/autoConnect", false).toBool(); } - void setAutoConnect(bool enabled) { m_settings.setValue("Conf/autoConnect", enabled); } + bool isAutoConnect() const + { + return m_settings.value("Conf/autoConnect", false).toBool(); + } + void setAutoConnect(bool enabled) + { + m_settings.setValue("Conf/autoConnect", enabled); + } - bool isStartMinimized() const { return m_settings.value("Conf/startMinimized", false).toBool(); } - void setStartMinimized(bool enabled) { m_settings.setValue("Conf/startMinimized", enabled); } + bool isStartMinimized() const + { + return m_settings.value("Conf/startMinimized", false).toBool(); + } + void setStartMinimized(bool enabled) + { + m_settings.setValue("Conf/startMinimized", enabled); + } - bool isSaveLogs() const { return m_settings.value("Conf/saveLogs", false).toBool(); } + bool isSaveLogs() const + { + return m_settings.value("Conf/saveLogs", false).toBool(); + } void setSaveLogs(bool enabled); enum RouteMode { @@ -75,50 +105,95 @@ public: VpnOnlyForwardSites, VpnAllExceptSites }; - Q_ENUM (RouteMode) + Q_ENUM(RouteMode) QString routeModeString(RouteMode mode) const; RouteMode routeMode() const; void setRouteMode(RouteMode mode) { m_settings.setValue("Conf/routeMode", mode); } - QVariantMap vpnSites(RouteMode mode) const { return m_settings.value("Conf/" + routeModeString(mode)).toMap(); } - void setVpnSites(RouteMode mode, const QVariantMap &sites) { m_settings.setValue("Conf/"+ routeModeString(mode), sites); m_settings.sync(); } - void addVpnSite(RouteMode mode, const QString &site, const QString &ip= ""); + QVariantMap vpnSites(RouteMode mode) const + { + return m_settings.value("Conf/" + routeModeString(mode)).toMap(); + } + void setVpnSites(RouteMode mode, const QVariantMap &sites) + { + m_settings.setValue("Conf/" + routeModeString(mode), sites); + m_settings.sync(); + } + bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); void addVpnSites(RouteMode mode, const QMap &sites); // map QStringList getVpnIps(RouteMode mode) const; void removeVpnSite(RouteMode mode, const QString &site); void addVpnIps(RouteMode mode, const QStringList &ip); void removeVpnSites(RouteMode mode, const QStringList &sites); + void removeAllVpnSites(RouteMode mode); - bool useAmneziaDns() const { return m_settings.value("Conf/useAmneziaDns", true).toBool(); } - void setUseAmneziaDns(bool enabled) { m_settings.setValue("Conf/useAmneziaDns", enabled); } + bool useAmneziaDns() const + { + return m_settings.value("Conf/useAmneziaDns", true).toBool(); + } + void setUseAmneziaDns(bool enabled) + { + m_settings.setValue("Conf/useAmneziaDns", enabled); + } QString primaryDns() const; QString secondaryDns() const; - //QString primaryDns() const { return m_primaryDns; } - void setPrimaryDns(const QString &primaryDns) { m_settings.setValue("Conf/primaryDns", primaryDns); } + // QString primaryDns() const { return m_primaryDns; } + void setPrimaryDns(const QString &primaryDns) + { + m_settings.setValue("Conf/primaryDns", primaryDns); + } - //QString secondaryDns() const { return m_secondaryDns; } - void setSecondaryDns(const QString &secondaryDns) { m_settings.setValue("Conf/secondaryDns", secondaryDns); } + // QString secondaryDns() const { return m_secondaryDns; } + void setSecondaryDns(const QString &secondaryDns) + { + m_settings.setValue("Conf/secondaryDns", secondaryDns); + } static const char cloudFlareNs1[]; static const char cloudFlareNs2[]; -// static constexpr char openNicNs5[] = "94.103.153.176"; -// static constexpr char openNicNs13[] = "144.76.103.143"; + // static constexpr char openNicNs5[] = "94.103.153.176"; + // static constexpr char openNicNs13[] = "144.76.103.143"; - QByteArray backupAppConfig() const { return m_settings.backupAppConfig(); } - bool restoreAppConfig(const QByteArray &cfg) { return m_settings.restoreAppConfig(cfg); } + QByteArray backupAppConfig() const + { + return m_settings.backupAppConfig(); + } + bool restoreAppConfig(const QByteArray &cfg) + { + return m_settings.restoreAppConfig(cfg); + } + + QLocale getAppLanguage() + { + return m_settings.value("Conf/appLanguage", QLocale()).toLocale(); + }; + void setAppLanguage(QLocale locale) + { + m_settings.setValue("Conf/appLanguage", locale); + }; + + bool isScreenshotsEnabled() const + { + return m_settings.value("Conf/screenshotsEnabled", false).toBool(); + } + void setScreenshotsEnabled(bool enabled) + { + m_settings.setValue("Conf/screenshotsEnabled", enabled); + } + + void clearSettings(); signals: void saveLogsChanged(); private: SecureQSettings m_settings; - }; #endif // SETTINGS_H diff --git a/client/translations/amneziavpn_ru.qm b/client/translations/amneziavpn_ru.qm deleted file mode 100644 index f0e3aaea..00000000 Binary files a/client/translations/amneziavpn_ru.qm and /dev/null differ diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index befce5d5..cfa33040 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -2,1820 +2,3025 @@ - MainWindow + AmneziaApplication - - Connect to the already created VPN server - Подключиться к уже созданному серверу VPN + Split tunneling for WireGuard is not implemented, the option was disabled + Раздельное туннелирование для "Wireguard" не реализовано,опция отключена - - Connection code - Код для подключения + + Split tunneling for %1 is not implemented, the option was disabled + Раздельное туннелирование для %1 не реализовано, опция отключена + + + + AndroidController + + + AmneziaVPN + AmneziaVPN - - Connecting... + + VPN Connected + Refers to the app - which is currently running the background and waiting + VPN Подключен + + + + ConnectionController + + + VPN Protocols is not installed. + Please install VPN container at first + VPN протоколы не установлены. + Пожалуйста, установите протокол + + + + Connection... Подключение... - - - - - - Connect - Подключиться - - - - - Set up your own server - Настроить собственный сервер - - - Connect your server to use VPN - Подключите ваш сервер, чтобы использовать VPN - - - - Server IP address - IP адрес сервера - - - - Login to connect via SSH - Логин для подключения по SSH - - - - - - Password - Пароль - - - - Where to get connection data → - Где взять логин для подключения → - - - - vpn://... - - - - - - - - - Please wait, configuring process may take up to 5 minutes - Пожалуйста подождите, настройка может занять до 5 минут - - - - - Setup your server to use VPN - Настроить ваш сервер для VPN - - - - root - - - - - - Connect using SSH key - Использовать SSH ключ - - - Select VPN protocols to install - Выберите VPN протоколы для установки - - - - OpenVPN and ShadowSocks - with masking using Cloak plugin - OpenVPN и ShadowSocks - с маскировкой плагином Cloak - - - - Port (TCP) - Порт (TCP) - - - - 443 - - - - - - Fake Web Site - Сайт маскировки - - - - - mail.ru - - - - - ShadowSocks - - - - - Port(TCP) - Порт (TCP) - - - - 6789 - - - - - Encryption - Шифрование - - - - chacha20-ietf-poly1305 - - - - - xchacha20-ietf-poly1305 - - - - - - - aes-256-gcm - - - - - - aes-192-gcm - - - - - - - aes-128-gcm - - - - - OpenVPN - - - - - - - - Port - Порт - - - - Protocol - Протокол - - - - DNS settings - Настройки DNS - - - - UDP - - - - - TCP - - - - - AmneziaVPN will install OpenVPN protocol with public/private key pairs generated on server and client sides. You can also configure connection on your mobile device by copying exported ".ovpn" file to your device and setting up official OpenVPN client. We recommend do not use messengers for sending connection profile - it contains VPN private keys. - AmneziaVPN установит протокол OpenVPN и сгенерирует публичные/приваные пары ключей для серверной и клиентской стороны. Вы сможете так же настроить подключение для вашего мобильного устройства, экспортировав конфиг ".ovpn" на устройство и установив официальный клиент OpenVPN. Мы рекоммендуем не передавать конфиг через мессенджеры - он содержит приватный ключ для VPN. - - - - - - - - - - Configuring... - Настройка... - - - - Setup server - Установить сервер - - - - - 0 Mbps - 0 Мбит/сек - - - Add site - Добавить сайт - - - + Connected Подключено - - How to use VPN - Как использовать VPN + + Settings updated successfully, Reconnnection... + Настройки успешно обновлены. Подключение... - - For all connections - Для всех соединений + + Reconnection... + Переподключение... - - For selected sites - Для выбранных сайтов + + + + + Connect + Подключиться - - Error text - + + Disconnection... + Отключение... + + + + ConnectionTypeSelectionDrawer + + + Add new connection + Добавить новое соединение - - List of the most popular prohibited sites - Список самых популярных запрещенных сайтов + + Configure your server + Настроить ваш сервер - For example, rutor.org or 17.21.111.8 - Например, rutor.org или 17.21.111.8 + + Open config file, key or QR code + Открыть файл конфига, ключ или QR код + + + + ContextMenuType + + + C&ut + &Вырезать - Anyone who logs in with this code will have the same rights to use the VPN as you. To create a new code, change your login and / or password for connection in your server settings. - Тот, кто зайдёт с этим кодом, будет иметь те же права на использование VPN, что и вы. Чтобы создать новый код смените логин и/или пароль для подлючения в настройках вашего сервера. + + &Copy + &Копировать - These sites will open via VPN - These sites will open via VPN + + &Paste + &Вставить - Delete selected item - Удалить выбранный элемент + + &SelectAll + &ВыбратьВсе + + + + ExportController + + + Access error! + Ошибка доступа! + + + + HomeContainersListView + + + Unable change protocol while there is an active connection + Невозможно изменить протокол при активном соединении - Hostname or IP address - Имя хоста или IP адрес + + The selected protocol is not supported on the current platform + Выбранный протокол не поддерживается на данном устройстве - - + - + + Reconnect via VPN Procotol: + Переподключение через VPN протокол: + + + + ImportController + + + Scanned %1 of %2. + Отсканировано %1 из%2. + + + + InstallController + + + + %1 installed successfully. + %1 успешно установлен. - - Server settings - Настройки сервера + + + %1 is already installed on the server. + %1 уже установлен на сервер. - - Share connection - Поделиться подключением + + +Added containers that were already installed on the server + +В приложение добавлены обнаруженные на сервере протоклы и сервисы - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Lato'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - + + +Already installed containers were found on the server. All installed containers have been added to the application + +На сервере обнаружены установленные протоколы и сервисы, все они добавлены в приложение - - Configure VPN protocols manually - Выбрать протоколы + + Settings updated successfully + Настройки успешно обновлены - - Run Setup Wizard - Мастер настройки + + Server '%1' was removed + Сервер '%1' был удален - - If you want easily configure your server just run Wizard - Для облегченной настройки сервера запустите мастер настройки + + All containers from server '%1' have been removed + Все протоклы и сервисы были удалены с сервера '%1' - - Press configure manually to choose VPN protocols you want to install - Выбрать протоколы для установки самостоятельно + + %1 has been removed from the server '%2' + %1 был удален с сервера '%2' - - - - - - Setup Wizard - Мастер настройки + + Please login as the user + Пожалуйста, войдите в систему от имени пользователя - - High censorship level - Высокий уровень цензуры + + Server added successfully + Сервер успешно добавлен + + + + KeyChainClass + + + Read key failed: %1 + Не удалось считать ключ: %1 - - Medium censorship level - Средний уровень цензуры + + Write key failed: %1 + Не удалось записать ключ: %1 - - Low censorship level - Низкий уровень цензуры + + Delete key failed: %1 + Не удалось удалить ключ: %1 + + + + NotificationHandler + + + + AmneziaVPN + AmneziaVPN - - I'm living in country with high censorship level. Many of foreign web sites and VPNs blocked by my government. I want to setup reliable VPN, which is invisible for government. - Я живу в стране с высоким уровнем цензуры. Многие зарубежные сайты и VPN сервисы заблокированы. Я хочу установить надёжный VPN, невидимый для надзорных органов. + + VPN Connected + VPN Подключен - - I'm living in country with medium censorship level. Some web sites blocked by my government, but VPNs are not blocked at all. I want to setup flexible solution. - Я живу в стране со средним уровнем цензуры. Некоторые зарубежные сайты заблокированы, но VPN сервисы в целом работают. Я хочу установить гибкое решение. + + VPN Disconnected + VPN Выключен - - I just want to improve my privacy in internet. - Я просто хочу повысить уровень моей приватности в интернете. + + AmneziaVPN notification + Уведомление AmneziaVPN - - - - Next - Далее + + Unsecured network detected: + Обнаружена незащищенная сеть: + + + + PageDeinstalling + + + Removing services from %1 + Удаление сервисов c %1 - - AmneziaVPN will install VPN protocol which is not visible for your internet provider and government firewall. Your VPN connection will be detected by your provider as regular web traffic to particular web site. - -You SHOULD set this web site address to some foreign web site which is not blocked by your internet provider. Other words you need to type below some foreign web site address which is accessible without VPN. - -Please note, this protocol still does not support export connection profile to mobile devices. Keep for updates. - AmneziaVPN установит VPN протокол невидимый для вашего провайдера и гос. фаервола. Ваше VPN соединение будет определяться как обычные запросы к определнному web сайту. -Вы ДОЛЖНЫ установить адрес этого сайта таким, который не заблокирован вашим провайдером, и находится за границей. Другими словами, вы должны ввести адрес какого-либо зарубежного сайта, который доступен и без VPN. - -Заметьте, этот протокол пока не имеет функции экспорта настроек подключения для мобильных устаройств. Следите за обновлениями. + + Usually it takes no more than 5 minutes + Обычно это занимает не более 5 минут + + + + PageHome + + + VPN protocol + VPN протокол - - OpenVPN over Cloak (VPN obfuscation) profile will be installed - Будет установлен профиль OpenVPN over Cloak (VPN маскировка) - - - - Type web site address for mask - Адрес сайта для маскировки - - - - Optional. - -We recommend to enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later. - -Please note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address. - Опционально. - -Мы рекомендуем включить режим VPN "Для выбранных сайтов" и добавить вручную заблокированные сайты, которые вы хотите посещать. Если вы выберите эту опцию, вы должны будете добавить каждый заблокированный сайт, который вы хотите посетить в список. Позже вы сможете переключаться между режимами работы VPN. - -Мы рекомендуем добавлять сайты в список только после того, как соединение VPN будет подключено. Вы сможете добавить любые домены, URL или IP адреса. - - - - - Start configuring - Начать настройку - - - - Turn on mode "VPN for selected sites" - Включить режим -"VPN для выбранных сайтов" - - - - AmneziaVPN will install VPN protocol which is difficult to detect by your internet provider and government firewall (but possible). In most cases, this is the most suitable protocol. This protocol is faster compared to the VPN protocols with "web traffic masking". - -This protocol support export connection profile to mobile devices using QR code (you should launch 3rd party opensource VPN client - ShadowSocks VPN). - AmneziaVPN установит VPN протокол, который трудно детектировать интернет провайдерам (но возможно). В большинстве случаев, это наиболее подходящий выбор. Этот протокол быстрее, по сравнению с VPN протоколами с полной маскировкой трафика. - -Этот протокол поддерживает экспорт профиля подключения с помощью QR кода для настройки на мобильных устройствах (вам нужно будет установить сторонний клиент VPN - ShadowSocks VPN). - - - - OpenVPN over ShadowSocks profile will be installed - Будет установлен профиль -OpenVPN over ShadowSocks - - - - OpenVPN profile will be installed - Будет установлен профиль OpenVPN - - - - Please wait. - Пожалуйста, ждите. - - - - Select VPN protocols - Выберите протоколы - - - - udp - - - - - tcp - - - - - + Add site - + Добавить сайт - - - - These sites will be opened using VPN - Эти сайты будут открываться через VPN - - - For example, yousite.com or 17.21.111.8 - Например, yousite.com или 17.21.111.8 - - - Web site or hostname or IP address - Web-сайт, имя хоста или IP-адрес - - - - - Reinstall server, clear server - Переустановить сервер, очистить сервер - - - - Server management - Управление сервером - - - - - Exit - Выход из приложения - - - - Auto start, Auto connect - Авто-запуск, авто-соединение - - - - App settings - Настройки приложения - - - - Network settings - Настройки сети - - - + Servers - Список серверов + Серверы - - Add or import new server - Добавить или импортировать новый сервер + + Unable change server while there is an active connection + Невозможно изменить сервер при активном соединении + + + + PageProtocolAwgSettings + + + AmneziaWG settings + AmneziaWG настройки - - Add server - Добавить сервер + + Port + Порт - - Servers list - Список серверов + + Junk packet count + Junk packet count - - Auto start - Авто старт + + Junk packet minimum size + Junk packet minimum size - - Application Settings - Настройки приложения + + Junk packet maximum size + Junk packet maximum size - - Auto connect - Авто соединение + + Init packet junk size + Init packet junk size - - Check for updates - Проверить обновления + + Response packet junk size + Response packet junk size - - Start minimized - Запускать свёрнутым + + Init packet magic header + Init packet magic header - - Open logs folder - Открыть папки с логами + + Response packet magic header + Response packet magic header - - DNS Servers - DNS сервера + + Transport packet magic header + Transport packet magic header - - - Reset to default value - Сбросить по умолчанию + + Underload packet magic header + Underload packet magic header - - Primary DNS server - Первичный DSN сервер + + Remove AmneziaWG + Удалить AmneziaWG - - Secondary DNS server - Вторичный DNS сервер + + Remove AmneziaWG from server? + Удалить AmneziaWG с сервера? - - - Clear client cached profile - Удалить закешировнный профиль + + All users with whom you shared a connection will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. - - - Clear server from Amnezia software - Очистить сервер от Amnezia + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. - - Forget this server - Забыть этот сервер + + Continue + Продолжить - - root@yourserver.org - + + Cancel + Отменить - - VPN protocols - VPN протоколы + + Save and Restart Amnezia + Сохранить и пререзагрузить Amnezia + + + PageProtocolCloakSettings - - VPN Protocol: - VPN протокол: - - - - Protocols - Протоколы - - - - Cloak container - Cloak контейнер - - - - - - OpenVPN settings - Настройки OpenVPN - - - - - ShadowSocks settings - Настройки ShadowSocks - - - + Cloak settings Настройки Cloak - - ShadowSocks container - ShadowSocks контейнер + + Disguised as traffic from + Замаскировать трафик под - - OpenVPN container - OpenVPN контейнер + + Port + Порт - - Full access - Полный доступ + + + Cipher + Шифрование - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Consolas'; font-size:20px; font-weight:600; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:20pt;">vpn:\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</span></p></body></html> - + + Save and Restart Amnezia + Сохранить и перезагрузить Amnezia + + + PageProtocolOpenVpnSettings - - Anyone who logs in with this code will have the same permissions to use VPN and your server as you. -This code includes your server credentials! -Provide this code only to TRUSTED users. - Любой, кто получит этот код, получит полный доступ к серверу и использованию VPN. Этот код содержит пароль от сервера. -Предоставляйте этот код только доверенным пользователям. - - - - - - - Share for Amnezia client - Расшарить для Amnezia - - - - Anyone who logs in with this code will be able to connect to this VPN server. -This code does not include server credentials. - Любой, кто получит этот код, получит возможность подключаться к этому VPN серверу -Этот код не содержи пароль от сервера. - - - - - - - Generate config - Сгенерировать конфиг - - - - - Share for OpenVPN client - Расшарить для OpenVPN - - - - - Save file - Сохранить файл - - - - AmneziaVPN - - - - - Except selected sites - Кроме выбранных сайтов - - - - yousite.com or IP address - - - - - Web site/Hostname/IP address/Subnet - Web сайт/хост/IP адрес/подсеть - - - - Delete selected - Удалить выбранные - - - - Software version: X.X.X (01.06.2021) - - - - - Share Server (FULL ACCESS) - Расшарить сервер (FULL ACCESS) - - - - - Share for ShadowSocks client - Расшарить для ShadowSocks - - - - - Server: - Сервер: - - - - - Encryption: - Шифрование: - - - - - Port: - Порт: - - - - Password: - Пароль: - - - - Connection string - Строка подключения - - - - Share for Cloak client - Расшарить для Cloak - - - - OpenVPN Settings + + OpenVPN settings Настройки OpenVPN - - Hash - Хеш - - - - - - Cipher - Шифр - - - - Network protocol - Сетевой протокол - - - - AES-256-GCM - - - - - AES-192-GCM - - - - - AES-128-GCM - - - - - AES-256-CBC - - - - - AES-192-CBC - - - - - AES-128-CBC - - - - - ChaCha20-Poly1305 - - - - - ARIA-256-CBC - - - - - CAMELLIA-256-CBC - - - - - none - - - - + VPN Addresses Subnet Подсеть для VPN - - - - Save and restart VPN - Сохранить и перезапустить VPN + + Network protocol + Сетевой протокол - + + Port + Порт + + + Auto-negotiate encryption - Авто-согласование шифрования + Шифрование с автоматическим согласованием - + + + Hash + Хэш + + + SHA512 - + SHA512 - + SHA384 - + SHA384 - + SHA256 - + SHA256 - + SHA3-512 - + SHA3-512 - + SHA3-384 - + SHA3-384 - + SHA3-256 - + SHA3-256 - + whirlpool - + whirlpool - + BLAKE2b512 - + BLAKE2b512 - + BLAKE2s256 - + BLAKE2s256 - + SHA1 - + SHA1 - + + + Cipher + Шифрование + + + + AES-256-GCM + AES-256-GCM + + + + AES-192-GCM + AES-192-GCM + + + + AES-128-GCM + AES-128-GCM + + + + AES-256-CBC + AES-256-CBC + + + + AES-192-CBC + AES-192-CBC + + + + AES-128-CBC + AES-128-CBC + + + + ChaCha20-Poly1305 + ChaCha20-Poly1305 + + + + ARIA-256-CBC + ARIA-256-CBC + + + + CAMELLIA-256-CBC + CAMELLIA-256-CBC + + + + none + none + + + + TLS auth + TLS авторизация + + + Block DNS requests outside of VPN - Блокировать запросы DNS мимо VPN + Блокировать DNS запросы за пределами VPN - - Enable TLS auth - Включить TLS auth + + Additional client configuration commands + Дополнительные команды конфигурации клиента - - ShadowSocks Settings + + + Commands: + Commands: + + + + Additional server configuration commands + Дополнительные команды конфигурации сервера + + + + Remove OpenVPN + Удалить OpenVPN + + + + Remove OpenVpn from server? + Удалить OpenVpn с сервера? + + + + All users with whom you shared a connection will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + Save and Restart Amnezia + Сохранить и перезагрузить + + + + PageProtocolRaw + + + settings + настройки + + + + Show connection options + Показать параметры подключения + + + + Connection options %1 + Параметры подключения %1 + + + + Remove + Удалить + + + + Remove %1 from server? + Удалить %1 с сервера? + + + + All users with whom you shared a connection will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + PageProtocolShadowSocksSettings + + + ShadowSocks settings Настройки ShadowSocks - - - chacha20-poly1305 - + + Port + Порт - - Cloak Settings - Настройки Cloak + + + Cipher + Шифрование - - plain - + + Save and Restart Amnezia + Сохранить и перезагрузить Amnezia + + + + PageServerContainers + + Continue + Продолжить + + + + PageServiceDnsSettings + + + A DNS service is installed on your server, and it is only accessible via VPN. + + На вашем сервере устанавливается DNS-сервис, доступ к нему возможен только через VPN. + - - - - - - - - - Copy - Копировать + + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. + Адрес DNS совпадает с адресом вашего сервера. Настроить DNS можно во вкладке "Соединения" настроек приложения - - Cannot open logs folder! - Невозможно открыть папку с логами! + + Remove + Удалить - - - Please fill in all fields - Пожалуйста, заполните все поля + + Remove %1 from server? + Удалить %1 с сервера? - - It's public key. Private key required - Это публичный ключ. Требуется приватный + + Continue + Продолжить - - of - из + + Cancel + Отменить + + + + PageServiceSftpSettings + + + Settings updated successfully + Настройки успешно обновлены - - - - Error occurred while configuring server. - Ошибка во время настройки сервера + + SFTP settings + Настройки SFTP - - Amnezia server installed - Amnezia сервер установлен + + Host + Хост - - Operation finished - Операция завершена - - - - Uninstalling Amnezia software... - Удаление Amnezia... - - - - See logs for details. - Смотрите логи для подробных деталей - - - - Amnezia server successfully uninstalled - Amnezia сервер удален - - - - Show - Показать окно - - - - - Disconnect - Отключиться - - - - Visit Website - Посетить сайт - - - - Quit - Выход - - - - Do you really want to quit? - Вы действительно хотите выйти? - - - - Import IP addresses - Импортовать IP адреса - - - - Import connection - Импортировать соединение - - - - Private key - Приватный ключ - - - - Connect using SSH password - Соединиться с паролем SSH - - - - Cache cleared - Кеш очищен - - - - - - + + + + Copied Скопировано - - Save AmneziaVPN config - Сохранить конфиг AmneziaVPN + + Port + Порт - - - Generating... - Генерация... + + Login + Логин - - Error while generating connection profile - Ошибка во время генерации профиля + + Password + Пароль - + + Mount folder on device + Смонтировать папку на вашем устройстве + + + + In order to mount remote SFTP folder as local drive, perform following steps: <br> + Чтобы смонтировать SFTP-папку как локальный диск на вашем устройстве, выполните следующие действия + + + + + <br>1. Install the latest version of + <br>1. Установите последнюю версию + + + + + <br>2. Install the latest version of + <br>2. Установите последнюю версию + + + + Detailed instructions + Подробные инструкции + + + + Remove SFTP and all data stored there + Удалить SFTP-хранилище со всеми данными + + + + Remove SFTP and all data stored there? + Удалить SFTP-хранилище и все хранящиеся на нем данные? + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + PageServiceTorWebsiteSettings + + + Settings updated successfully + Настройки успешно обновлены + + + + Tor website settings + Настройки сайта в сети Тоr + + + + Website address + Адрес сайта + + + + Copied + Скопировано + + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. + Используйте <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> для открытия этой ссылки. + + + + After installation it takes several minutes while your onion site will become available in the Tor Network. + Через несколько минут после установки ваш Onion сайт станет доступен в сети Tor. + + + + When configuring WordPress set the this onion address as domain. + При настройке WordPress укажите этот onion адрес в качестве домена. + + + When configuring WordPress set the this address as domain. + При настройке WordPress укажите этот onion адрес в качестве домена. + + + + Remove website + Удалить сайт + + + + The site with all data will be removed from the tor network. + Сайт со всеми данными будет удален из сети Tor. + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + PageSettings + + + Settings + Настройки + + + + Servers + Серверы + + + + Connection + Соединение + + + + Application + Приложение + + + + Backup + Резервное копирование + + + + About AmneziaVPN + Об AmneziaVPN + + + + Close application + Закрыть приложение + + + + PageSettingsAbout + + + Support the project with a donation + Поддержите проект пожертвованием + + + + This is a free and open source application. If you like it, support the developers with a donation. + Это бесплатное приложение с открытым исходным кодом. Если, оно вам нравится - поддержите разработчиков пожертвованием. + + + + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. + А, если оно вам не нравится, тем более поддержите-пожертвование пойдет на улучшение приложения. + + + + Card on Patreon + Картой на Patreon + + + + https://www.patreon.com/amneziavpn + https://www.patreon.com/amneziavpn + + + + Show other methods on Github + Показать другие способы на Github + + + + Contacts + Контакты + + + + Telegram group + Группа в Telegram + + + + To discuss features + Для обсуждений + + + + https://t.me/amnezia_vpn_en + https://t.me/amnezia_vpn + + + + Mail + Почта + + + + For reviews and bug reports + Для отзывов и сообщений об ошибках + + + + Github + Github + + + + https://github.com/amnezia-vpn/amnezia-client + https://github.com/amnezia-vpn/amnezia-client + + + + Website + Веб-сайт + + + + https://amnezia.org + https://amnezia.org + + + + Check for updates + Проверить обновления + + + + PageSettingsApplication + + + Application + Приложение + + + + Allow application screenshots + Разрешить скриншоты + + + + Auto start + Авто-запуск + + + + Launch the application every time the device is starts + Запускать приложение при каждом включении + + + + Start minimized + Запускать в свернутом виде + + + + Launch application minimized + Запускать приложение в свернутом виде + + + + Language + Язык + + + + Logging + Логирование + + + + Enabled + Включено + + + + Disabled + Отключено + + + + Reset settings and remove all data from the application + Сбросить настройки и удалить все данные из приложения + + + + Reset settings and remove all data from the application? + Сбросить настройки и удалить все данные из приложения? + + + + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. + Все данные из приложения будут удалены, все установленные сервисы AmneziaVPN останутся на сервере. + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + PageSettingsBackup + + + Backup + Резервное копирование + + + + Settings restored from backup file + Восстановление настроек из бэкап файла + + + + Configuration backup + Бэкап конфигурация + + + + You can save your settings to a backup file to restore them the next time you install the application. + Поможет мгновенно восстановить настройки соединений при следующей установке. + + + + Make a backup + Сделать бэкап + + + + Save backup file + Сохранить бэкап файл + + + + + Backup files (*.backup) + Файлы резервного копирования (*.backup) + + + + Backup file saved + Бэкап файл сохранен + + + + Restore from backup + Восстановить из бэкапа + + + + Open backup file + Открыть бэкап файл + + + + Import settings from a backup file? + Импортировать настройки из бэкап файла? + + + + All current settings will be reset + Все текущие настройки будут сброшены + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + PageSettingsConnection + + + Connection + Соединение + + + + Auto connect + Автоподключение + + + + Connect to VPN on app start + Подключение к VPN при запуске приложения + + + + Use AmneziaDNS + Использовать Amnezia DNS + + + + If AmneziaDNS is installed on the server + Если он уставновлен на сервере + + + + DNS servers + DNS сервер + + + + If AmneziaDNS is not used or installed + Эти серверы будут использоваться, если не включен AmneziaDNS + + + + Site-based split tunneling + Раздельное туннелирование сайтов + + + + Allows you to select which sites you want to access through the VPN + Позволяет подключаться к одним сайтам через VPN, а к другим в обход него + + + + App-based split tunneling + Раздельное VPN-туннелирование приложений + + + + Allows you to use the VPN only for certain applications + Позволяет использовать VPN только для определённых приложений + + + + PageSettingsDns + + + DNS servers + DNS сервер + + + + If AmneziaDNS is not used or installed + Эти адреса будут использоваться, если не включен или не установлен AmneziaDNS + + + + Primary DNS + Первичный DNS + + + + Secondary DNS + Вторичный DNS + + + + Restore default + Восстановить по умолчанию + + + + Restore default DNS settings? + Восстановить настройки DNS по умолчанию? + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + Settings have been reset + Настройки сброшены + + + + Save + Сохранить + + + + Settings saved + Сохранить настройки + + + + PageSettingsLogging + + + Logging + Логирование + + + + Save logs + Сохранять логи + + + + Open folder with logs + Открыть папку с логами + + + + Save + Сохранить + + + + Logs files (*.log) + Logs files (*.log) + + + + Logs file saved + Файл с логами сохранен + + + + Save logs to file + Сохранить логи в файл + + + + Clear logs? + Очистить логи? + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + Logs have been cleaned up + Логи удалены + + + + Clear logs + Удалить логи + + + + PageSettingsServerData + + + All installed containers have been added to the application + Все установленные протоколы и сервисы были добавлены в приложение + + + + Clear Amnezia cache + Очистить кэш Amnezia + + + + May be needed when changing other settings + Может понадобиться при изменении других настроек + + + + Clear cached profiles? + Удалить кэш Amnezia с сервера? + + + + No new installed containers found + Новые установленные протоколы и сервисы не обнаружены + + + + + + + + + + + Continue + Продолжить + + + + + + Cancel + Отменить + + + + Check the server for previously installed Amnezia services + Проверить сервер на наличие ранее установленных сервисов Amnezia + + + + Add them to the application if they were not displayed + Добавить их в приложение, если они не были отображены + + + + Remove server from application + Удалить сервер из приложения + + + + Remove server? + Удалить сервер? + + + + All installed AmneziaVPN services will still remain on the server. + Все установленные сервисы и протоколы Amnezia всё ещё останутся на сервере. + + + + Clear server from Amnezia software + Очистить сервер от протоколов и сервисов Amnezia + + + + Clear server from Amnezia software? + Удалить все сервисы и протоколы Amnezia с сервера? + + + + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. + На сервере будут удалены все данные, связанные с Amnezia: протоколы, сервисы, конфигурационные файлы, ключи и сертификаты. + + + + PageSettingsServerInfo + + + Server name + Имя сервера + + + + Save + Сохранить + + + + Protocols + Протоколы + + + + Services + Сервисы + + + + Data + Данные + + + + PageSettingsServerProtocol + + + settings + настройки + + + + Remove + Удалить + + + + Remove %1 from server? + Удалить %1 с сервера? + + + + All users with whom you shared a connection will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, которым вы поделились VPN, больше не смогут к нему подключаться. + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + PageSettingsServersList + + + Servers + Серверы + + + + PageSettingsSplitTunneling + + + Addresses from the list should be accessed via VPN + Только адреса из списка должны открываться через VPN + + + + Addresses from the list should not be accessed via VPN + Адреса из списка не должны открываться через VPN + + + + Split tunneling + Раздельное VPN-туннелирование + + + + Mode + Режим + + + + Remove + Удалить + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + Site or IP + Сайт или IP + + + + Import/Export Sites + Импорт/экспорт Сайтов + + + + Import + Импорт + + + + Save site list + Сохранить список сайтов + + + + Save sites + Сохранить + + + + + + Sites files (*.json) + Sites files (*.json) + + + + Import a list of sites + Импортировать список с сайтами + + + + Replace site list + Заменить список сайтов + + + + + Open sites file + Открыть список с сайтами + + + + Add imported sites to existing ones + Добавить импортированные сайты к существующим + + + + PageSetupWizardConfigSource + + + Server connection + Подключение к серверу + + + + Do not use connection code from public sources. It may have been created to intercept your data. + +It's okay as long as it's from someone you trust. + Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.. + +Всё в порядке, если кодом поделился пользователь, которому вы доверяете. + + + + What do you have? + Выберете что у вас есть + + + + File with connection settings + Файл с настройками подключения + + + + File with connection settings or backup + Файл с настройками подключения или бэкап + + + + Open config file + Открыть файл с конфигурацией + + + + QR-code + QR-код + + + + Key as text + Ключ в виде текста + + + + PageSetupWizardCredentials + + Server connection + Подключение к серверу + + + + Server IP address [:port] + Server IP address [:port] + + + + 255.255.255.255:88 + 255.255.255.255:88 + + + + Password / SSH private key + Password / SSH private key + + + + Continue + Продолжить + + + + All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties + Все данные, которые вы вводите, останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или каким-либо третьим сторонам + + + + Enter the address in the format 255.255.255.255:88 + Введите адрес в формате 255.255.255.255:88 + + + + Login to connect via SSH + Login to connect via SSH + + + + Configure your server + Настроить ваш сервер + + + + Ip address cannot be empty + Поле Ip address не может быть пустым + + + + Login cannot be empty + Поле Login не может быть пустым + + + + Password/private key cannot be empty + Поле Password/private key не может быть пустым + + + + PageSetupWizardEasy + + + What is the level of internet control in your region? + Какой уровень контроля интеренета в вашем регионе? + + + + Set up a VPN yourself + Настроить VPN самостоятельно + + + + I want to choose a VPN protocol + Выбрать VPN-протокол + + + + Continue + Продолжить + + + + Set up later + Настроить позднее + + + + PageSetupWizardInstalling + + + The server has already been added to the application + Сервер уже был добавлен в приложение + + + Amnesia has detected that your server is currently + Amnesia обнаружила, что ваш сервер в настоящее время + + + busy installing other software. Amnesia installation + занят установкой других протоколов или сервисов. Установка Amnesia + + + + Amnezia has detected that your server is currently + Amnezia обнаружила, что ваш сервер в настоящее время + + + + busy installing other software. Amnezia installation + занят установкой другого программного обеспечения. Установка Amnezia + + + + will pause until the server finishes installing other software + будет приостановлена до тех пор, пока сервер не завершит установку + + + + Installing + Установка + + + + + Usually it takes no more than 5 minutes + Обычно это занимает не более 5 минут + + + + PageSetupWizardProtocolSettings + + + Installing %1 + Установить %1 + + + + More detailed + Подробнее + + + + Close + Закрыть + + + + Network protocol + Сетевой протокол + + + + Port + Порт + + + + Install + Установить + + + + PageSetupWizardProtocols + + + VPN protocol + VPN протокол + + + + Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. + Выберите протокол, который вам больше подходит. В дальнейшем можно установить другие протоколы и дополнительные сервисы, такие как DNS-прокси, TOR-сайт и SFTP. + + + + PageSetupWizardQrReader + + + Point the camera at the QR code and hold for a couple of seconds. + Наведите камеру на QR-код и удерживайте ее в течение нескольких секунд. + + + + PageSetupWizardStart + + + Settings restored from backup file + Восстановление настроек из бэкап файла + + + + Free service for creating a personal VPN on your server. + Простое и бесплатное приложение для запуска self-hosted VPN с высокими требованиями к приватности. + + + + Helps you access blocked content without revealing your privacy, even to VPN providers. + Помогает получить доступ к заблокированному контенту, не раскрывая вашу конфиденциальность даже провайдерам VPN. + + + + I have the data to connect + У меня есть данные для подключения + + + + I have nothing + У меня ничего нет + + + + PageSetupWizardTextKey + + + Connection key + Ключ для подключения + + + + A line that starts with vpn://... + Строка, которая начинается с vpn://... + + + + Key + Ключ + + + + Insert + Вставить + + + + Continue + Продолжить + + + + PageSetupWizardViewConfig + + + New connection + Новое соединение + + + + Do not use connection code from public sources. It could be created to intercept your data. + Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные. + + + + Collapse content + Свернуть + + + + Show content + Показать содержимое ключа + + + + Connect + Подключиться + + + + PageShare + + + OpenVpn native format + OpenVpn нативный формат + + + + WireGuard native format + WireGuard нативный формат + + + VPN Access + VPN-Доступ + + + + Connection + Соединение + + + VPN access without the ability to manage the server + Доступ к VPN, без возможности управления сервером + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. + Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки. + + + + + Server + Сервер + + + + Accessing + Доступ + + + + File with accessing settings to + Файл с настройками доступа к + + + + Connection to + Подключение к + + + + File with connection settings to + Файл с настройками доступа к + + + Save OpenVPN config - Сохранить OpenVPN конфиг + Сохранить OpenVPN config - - VPN Protocols is not installed. - Please install VPN container at first - VPN протоколы ещё не установлены. Установите VPN контейнеры + + Save WireGuard config + Сохранить WireGuard config - - VPN Protocol not chosen - VPN протокол не выбран + + For the AmneziaVPN app + Для AmneziaVPN - - Software version - Версия программы + + Share VPN Access + Поделиться VPN - - Protocol: - Протокол: + + Full access + Полный доступ - - Press Generate config - Нажмите Сгенерировать конфиг + + Share VPN access without the ability to manage the server + Поделиться доступом к VPN, без возможности управления сервером - - Share server full access - Расшарить полный доступ + + Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings. + Поделиться доступом к управлению сервером. Пользователь, с которым вы делитесь полным доступом к серверу, сможет добавлять и удалять любые протоколы и службы на сервере, а также изменять настройки. + + + + + Protocol + Протокол + + + + + Connection format + Формат подключения + + + + Share + Поделиться + + + + PopupType + + + Close + Закрыть + + + + QKeychain::DeletePasswordJobPrivate + + + Password entry not found + Password entry not found + + + + Could not decrypt data + Could not decrypt data + + + + + Unknown error + Unknown error + + + + Could not open wallet: %1; %2 + Could not open wallet: %1; %2 + + + + Password not found + Password not found + + + + Could not open keystore + Could not open keystore + + + + Could not remove private key from keystore + Could not remove private key from keystore + + + + QKeychain::JobPrivate + + + Unknown error + Unknown error + + + + Access to keychain denied + Access to keychain denied + + + + QKeychain::PlainTextStore + + + Could not store data in settings: access error + Could not store data in settings: access error + + + + Could not store data in settings: format error + Could not store data in settings: format error + + + + Could not delete data from settings: access error + Could not delete data from settings: access error + + + + Could not delete data from settings: format error + Could not delete data from settings: format error + + + + Entry not found + Entry not found + + + + QKeychain::ReadPasswordJobPrivate + + + Password entry not found + Password entry not found + + + + + Could not decrypt data + Could not decrypt data + + + + D-Bus is not running + D-Bus is not running + + + + + Unknown error + Unknown error + + + + No keychain service available + No keychain service available + + + + Could not open wallet: %1; %2 + Could not open wallet: %1; %2 + + + + Access to keychain denied + Access to keychain denied + + + + Could not determine data type: %1; %2 + Could not determine data type: %1; %2 + + + + + Entry not found + Entry not found + + + + Unsupported entry type 'Map' + Unsupported entry type 'Map' + + + + Unknown kwallet entry type '%1' + Unknown kwallet entry type '%1' + + + + Password not found + Password not found + + + + Could not open keystore + Could not open keystore + + + + Could not retrieve private key from keystore + Could not retrieve private key from keystore + + + + Could not create decryption cipher + Could not create decryption cipher + + + + QKeychain::WritePasswordJobPrivate + + + Credential size exceeds maximum size of %1 + Credential size exceeds maximum size of %1 + + + + Credential key exceeds maximum size of %1 + Credential key exceeds maximum size of %1 + + + + Writing credentials failed: Win32 error code %1 + Writing credentials failed: Win32 error code %1 + + + + Encryption failed + Encryption failed + + + + D-Bus is not running + D-Bus is not running + + + + + Unknown error + Unknown error + + + + Could not open wallet: %1; %2 + Could not open wallet: %1; %2 + + + + Password not found + Password not found + + + + Could not open keystore + Could not open keystore + + + + Could not create private key generator + Could not create private key generator + + + + Could not generate new private key + Could not generate new private key + + + + Could not retrieve private key from keystore + Could not retrieve private key from keystore + + + + Could not create encryption cipher + Could not create encryption cipher + + + + Could not encrypt data + Could not encrypt data QObject - - AmneziaVPN is already running. - Приложение AmneziaVPN уже запущено. - No error - + No error Unknown Error - + Unknown Error Function not implemented - + Function not implemented Server check failed - + Server check failed Server port already used. Check for another software - + Server port already used. Check for another software + + + + Server error: Docker container missing + Server error: Docker container missing + + + + Server error: Docker failed + Server error: Docker failed - Ssh connection error - + Installation canceled by user + Installation canceled by user - Ssh connection timeout - - - - - Ssh protocol error - - - - - Ssh server ket check failed - + The user does not have permission to use sudo + The user does not have permission to use sudo - Ssh key file error - + Ssh request was denied + Ssh request was denied - Ssh authentication error - + Ssh request was interrupted + Ssh request was interrupted - Ssh session closed - + Ssh internal error + Ssh internal error - Ssh internal error - + Invalid private key or invalid passphrase entered + Invalid private key or invalid passphrase entered - - Failed to create remote process on server - + + The selected private key format is not supported, use openssh ED25519 key types or PEM key types + The selected private key format is not supported, use openssh ED25519 key types or PEM key types - - Failed to start remote process on server - + + Timeout connecting to server + Timeout connecting to server - Remote process on server crashed - + Sftp error: End-of-file encountered + Sftp error: End-of-file encountered + + + + Sftp error: File does not exist + Sftp error: File does not exist + + + + Sftp error: Permission denied + Sftp error: Permission denied - Failed to save config to disk - + Sftp error: Generic failure + Sftp error: Generic failure - OpenVPN config missing - + Sftp error: Garbage received from server + Sftp error: Garbage received from server - OpenVPN management server error - + Sftp error: No connection has been set up + Sftp error: No connection has been set up - EasyRSA runtime error - + Sftp error: There was a connection, but we lost it + Sftp error: There was a connection, but we lost it + + + + Sftp error: Operation not supported by libssh yet + Sftp error: Operation not supported by libssh yet + + + + Sftp error: Invalid file handle + Sftp error: Invalid file handle - OpenVPN executable missing - + Sftp error: No such file or directory path exists + Sftp error: No such file or directory path exists - EasyRsa executable missing - + Sftp error: An attempt to create an already existing file or directory has been made + Sftp error: An attempt to create an already existing file or directory has been made + Sftp error: Write-protected filesystem + Sftp error: Write-protected filesystem + + + + Sftp error: No media was in remote drive + Sftp error: No media was in remote drive + + + + Failed to save config to disk + Failed to save config to disk + + + + OpenVPN config missing + OpenVPN config missing + + + + OpenVPN management server error + OpenVPN management server error + + + + OpenVPN executable missing + OpenVPN executable missing + + + + ShadowSocks (ss-local) executable missing + ShadowSocks (ss-local) executable missing + + + + Cloak (ck-client) executable missing + Cloak (ck-client) executable missing + + + Amnezia helper service error - Ошибка локального сервиса Amnezia + Amnezia helper service error - + + OpenSSL failed + OpenSSL failed + + + Can't connect: another VPN connection is active - + Can't connect: another VPN connection is active - + + Can't setup OpenVPN TAP network adapter + Can't setup OpenVPN TAP network adapter + + + + VPN pool error: no available addresses + VPN pool error: no available addresses + + + + The config does not contain any containers and credentiaks for connecting to the server + The config does not contain any containers and credentiaks for connecting to the server + + + Internal error + Internal error + + + + IPsec + IPsec + + + + IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol. +One of its distinguishing features is its ability to swiftly switch between networks and devices, making it particularly adaptive in dynamic network environments. +While it offers a blend of security, stability, and speed, it's essential to note that IKEv2 can be easily detected and is susceptible to blocking. + +* Available in the AmneziaVPN only on Windows +* Low power consumption, on mobile devices +* Minimal configuration +* Recognised by DPI analysis systems +* Works over UDP network protocol, ports 500 and 4500. + IKEv2 в сочетании с уровнем шифрования IPSec это современный и стабильный протокол VPN. +Он может быстро переключаться между сетями и устройствами, что делает его особенно адаптивным в динамичных сетевых средах. +Несмотря на сочетание безопасности, стабильности и скорости, необходимо отметить, что IKEv2 легко обнаруживается и подвержен блокировке. + +* Доступно в AmneziaVPN только для Windows. +* Низкое энергопотребление, на мобильных устройствах +* Минимальная конфигурация +* Распознается системами DPI-анализа +* Работает по сетевому протоколу UDP, порты 500 и 4500. + + + + DNS Service + DNS Сервис + + + + Sftp file sharing service + Сервис обмена файлами Sftp + + + + + Website in Tor network + Веб-сайт в сети Tor + + + + Amnezia DNS + Amnezia DNS + + + + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. + OpenVPN - популярный VPN-протокол, с гибкой настройкой. Имеет собственный протокол безопасности с SSL/TLS для обмена ключами. + + + + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. + ShadowSocks - маскирует VPN-трафик под обычный веб-трафик, но распознается системами анализа в некоторых регионах с высоким уровнем цензуры. + + + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + OpenVPN over Cloak - OpenVPN с маскировкой VPN под web-трафик и защитой от обнаружения active-probbing. Подходит для регионов с самым высоким уровнем цензуры. + + + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + WireGuard - Популярный VPN-протокол с высокой производительностью, высокой скоростью и низким энергопотреблением. Для регионов с низким уровнем цензуры. + + + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. + AmneziaWG - Специальный протокол от Amnezia, основанный на протоколе WireGuard. Он такой же быстрый, как WireGuard, но очень устойчив к блокировкам. Рекомендуется для регионов с высоким уровнем цензуры. + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. + IKEv2 Современный стабильный протокол, немного быстрее других восстанавливает соединение после потери сигнала. Имеет нативную поддержку последних версиий Android и iOS. + + + + Deploy a WordPress site on the Tor network in two clicks. + Разверните сайт на WordPress в сети Tor в два клика. + + + + Replace the current DNS server with your own. This will increase your privacy level. + Замените DNS-сервер на Amnezia DNS. Это повысит уровень конфиденциальности. + + + + Creates a file vault on your server to securely store and transfer files. + Создайте на сервере файловое хранилище для безопасного хранения и передачи файлов. + + + + OpenVPN stands as one of the most popular and time-tested VPN protocols available. +It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. + +* Available in the AmneziaVPN across all platforms +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices +* Recognised by DPI analysis systems and therefore susceptible to blocking +* Can operate over both TCP and UDP network protocols. + OpenVPN однин из самых популярных и проверенных временем VPN-протоколов. +В нем используется уникальный протокол безопасности, опирающийся на протокол SSL/TLS для шифрования и обмена ключами. Кроме того, поддержка OpenVPN множества методов аутентификации делает его универсальным и адаптируемым к широкому спектру устройств и операционных систем. Благодаря открытому исходному коду OpenVPN подвергается тщательному анализу со стороны мирового сообщества, что постоянно повышает его безопасность. Благодаря оптимальному соотношению производительности, безопасности и совместимости OpenVPN остается лучшим выбором как для частных лиц, так и для компаний, заботящихся о конфиденциальности. + +* Доступность AmneziaVPN для всех платформ +* Нормальное энергопотребление на мобильных устройствах +* Гибкая настройка под нужды пользователя для работы с различными операционными системами и устройствами +* Распознается системами DPI-анализа и поэтому подвержен блокировке +* Может работать по сетевым протоколам TCP и UDP. + + + + Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. + +* Available in the AmneziaVPN only on desktop platforms +* Normal power consumption on mobile devices + +* Configurable encryption protocol +* Detectable by some DPI systems +* Works over TCP network protocol. + Shadowsocks, создан на основе протокола SOCKS5, защищает соединение с помощью шифра AEAD. Несмотря на то, что протокол Shadowsocks разработан таким образом, чтобы быть незаметным и сложным для идентификации, он не идентичен стандартному HTTPS-соединению. Однако некоторые системы анализа трафика все же могут обнаружить соединение Shadowsocks. В связи с ограниченной поддержкой в Amnezia рекомендуется использовать протокол AmneziaWG, или OpenVPN over Cloak. + +* Доступен в AmneziaVPN только на ПК ноутбуках. +* Настраиваемый протокол шифрования +* Обнаруживается некоторыми DPI-системами +* Работает по сетевому протоколу TCP. + + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. + +OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works over TCP network protocol, 443 port. + + OpenVPN over Cloak - это комбинация протокола OpenVPN и плагина Cloak, разработанного специально для защиты от блокировок. + +OpenVPN обеспечивает безопасное VPN-соединение за счет шифрования всего интернет-трафика между клиентом и сервером. + +Cloak защищает OpenVPN от обнаружения и блокировок. + +Cloak может изменять метаданные пакетов. Он полностью маскирует VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью Active Probing. Это делает его очень устойчивым к обнаружению + +Сразу же после получения первого пакета данных Cloak проверяет подлинность входящего соединения. Если аутентификация не проходит, плагин маскирует сервер под поддельный сайт, и ваш VPN становится невидимым для аналитических систем. + +Если в вашем регионе существует экстремальный уровень цензуры в Интернете, мы советуем вам при первом подключении использовать только OpenVPN через Cloak + +* Доступность AmneziaVPN на всех платформах +* Высокое энергопотребление на мобильных устройствах +* Гибкие настройки +* Не распознается системами DPI-анализа +* Работает по сетевому протоколу TCP, 443 порт. + + + + + A relatively new popular VPN protocol with a simplified architecture. +Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. + WireGuard - относительно новый популярный VPN-протокол с упрощенной архитектурой. +Обеспечивает стабильное VPN-соединение, высокую производительность на всех устройствах. Использует жестко заданные настройки шифрования. WireGuard по сравнению с OpenVPN имеет меньшую задержку и лучшую пропускную способность при передаче данных. +WireGuard очень восприимчив к блокированию из-за особенностей сигнатур пакетов. В отличие от некоторых других VPN-протоколов, использующих методы обфускации, последовательные сигнатуры пакетов WireGuard легче выявляются и, соответственно, блокируются современными системами глубокой проверки пакетов (DPI) и другими средствами сетевого мониторинга. + +* Доступность AmneziaVPN для всех платформ +* Низкое энергопотребление +* Минимальное количество настроек +* Легко распознается системами DPI-анализа, подвержен блокировке +* Работает по сетевому протоколу UDP. + + + + A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. +While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. +This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Not recognised by DPI analysis systems, resistant to blocking +* Works over UDP network protocol. + AmneziaWG - усовершенствованная версия популярного VPN-протокола Wireguard. AmneziaWG опирается на фундамент, заложенный WireGuard, сохраняя упрощенную архитектуру и высокопроизводительные возможности работы на разных устройствах. +Хотя WireGuard известен своей эффективностью, у него были проблемы с обнаружением из-за характерных сигнатур пакетов. AmneziaWG решает эту проблему за счет использования более совершенных методов обфускации, благодаря чему его трафик сливается с обычным интернет-трафиком. +Таким образом, AmneziaWG сохраняет высокую производительность оригинала, добавляя при этом дополнительный уровень скрытности, что делает его отличным выбором для тех, кому нужно быстрое и незаметное VPN-соединение. + +* Доступность AmneziaVPN на всех платформах +* Низкое энергопотребление +* Минимальное количество настроек +* Не распознается системами DPI-анализа, устойчив к блокировке +* Работает по сетевому протоколу UDP. + + + AmneziaWG container + AmneziaWG протокол + + + + Sftp file sharing service - is secure FTP service + Сервис обмена файлами Sftp - безопасный FTP-сервис + + + + Sftp service + Сервис SFTP + + + + Entry not found + Entry not found + + + + Access to keychain denied + Access to keychain denied + + + + No keyring daemon + No keyring daemon + + + + Already unlocked + Already unlocked + + + + No such keyring + No such keyring + + + + Bad arguments + Bad arguments + + + + I/O error + I/O error + + + + Cancelled + Cancelled + + + + Keyring already exists + Keyring already exists + + + + No match + No match + + + + Unknown error + Unknown error + + + + error 0x%1: %2 + error 0x%1: %2 + + + + WireGuard Configuration Highlighter + + + + + &Randomize colors - QSsh::Internal::SftpChannelPrivate + SelectLanguageDrawer - - Server could not start SFTP subsystem. - - - - - The SFTP server finished unexpectedly with exit code %1. - - - - - The SFTP server crashed: %1. - - - - - Unexpected packet of type %1. - - - - - Protocol version mismatch: Expected %1, got %2 - - - - - Unknown error. - - - - - Created remote directory "%1". - - - - - Remote directory "%1" already exists. - - - - - Error creating directory "%1": %2 - - - - - Could not open local file "%1": %2 - - - - - Remote directory could not be opened for reading. - - - - - Failed to list remote directory contents. - - - - - Failed to close remote directory. - - - - - Failed to open remote file for reading. - - - - - Failed to retrieve information on the remote file ('stat' failed). - - - - - Failed to read remote file. - - - - - - Failed to close remote file. - - - - - Failed to open remote file for writing. - - - - - Failed to write remote file. - - - - - Cannot append to remote file: Server does not support the file size attribute. - - - - - SFTP channel closed unexpectedly. - - - - - Server could not start session: %1 - - - - - Error reading local file: %1 - - - - - QSsh::Internal::SshChannelManager - - - Unexpected request success packet. - - - - - Unexpected request failure packet. - - - - - Invalid channel id %1 - - - - - QSsh::Internal::SshConnectionPrivate - - - SSH Protocol error: %1 - - - - - Botan library exception: %1 - - - - - Server identification string is %n characters long, but the maximum allowed length is 255. - - - - - - - - - Server identification string contains illegal NUL character. - - - - - Server Identification string "%1" is invalid. - - - - - Server protocol version is "%1", but needs to be 2.0 or 1.99. - - - - - Server identification string is invalid (missing carriage return). - - - - - Server reports protocol version 1.99, but sends data before the identification string, which is not allowed. - - - - - - - - Unexpected packet of type %1. - - - - - Password expired. - - - - - Server rejected key. - - - - - Server rejected password. - - - - - The server sent an unexpected SSH packet of type SSH_MSG_UNIMPLEMENTED. - - - - - Server closed connection: %1 - - - - - Connection closed unexpectedly. - - - - - Timeout waiting for reply from server. - - - - - No private key file given. - - - - - QSsh::Internal::SshRemoteProcessPrivate - - - Process killed by signal - - - - - Server sent invalid signal "%1" - - - - - QSsh::SftpFileSystemModel - - - File Type - - - - - File Name - - - - - Error getting "stat" info about "%1": %2 - - - - - Error listing contents of directory "%1": %2 - - - - - QSsh::Ssh - - - Failed to open key file "%1" for reading: %2 - - - - - Failed to open key file "%1" for writing: %2 - - - - - Password Required - - - - - Please enter the password for your private key. - - - - - QSsh::SshKeyCreationDialog - - - SSH Key Configuration - - - - - Options - - - - - Key algorithm: - - - - - &RSA - - - - - &DSA - - - - - ECDSA - - - - - Key &size: - - - - - Private key file: - - - - - - Browse... - - - - - Public key file: - - - - - &Generate And Save Key Pair - - - - - &Cancel - - - - - Choose... - - - - - Key Generation Failed - - - - - Choose Private Key File Name - - - - - Cannot Save Key File - - - - - Failed to create directory: "%1". - - - - - Cannot Save Private Key File - - - - - The private key file could not be saved: %1 - - - - - Cannot Save Public Key File - - - - - The public key file could not be saved: %1 - - - - - File Exists - - - - - There already is a file of that name. Do you want to overwrite it? - - - - - ServerWidget - - - Form - - - - - Description - - - - - Address - - - - - Set as default - - - - - Share connection - Поделиться подключением - - - - Connection - - - - - Server settings - Настройки сервера + + Choose language + Выберите язык Settings - + Server #1 - + Server #1 - - + + Server - + Server - SshConnection + SettingsController - - Server and client capabilities don't match. Client list was: %1. -Server list was %2. - + + Software version + Версия ПО + + + + All settings have been reset to default values + Все настройки были сброшены к значению "По умолчанию" + + + + Cached profiles cleared + Кэш профиля очищен + + + + Backup file is corrupted + Backup файл поврежден - SshKeyGenerator + ShareConnectionDrawer - - Error generating key: %1 - + + + Save AmneziaVPN config + Сохранить config AmneziaVPN - - Password for Private Key - + + Share + Поделиться - - It is recommended that you secure your private key -with a password, which you can enter below. - + + Copy + Скопировать - - Encrypt Key File - + + Copied + Скопировано - - Do Not Encrypt Key File - + + Show connection settings + Показать настройки подключения + + + + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" + Для считывания QR-кода в приложении Amnezia выберите "Добавить сервер" → "У меня есть данные для подключения" → "QR-код, ключ или файл настроек" + + + + SitesController + + + Hostname not look like ip adress or domain name + Имя хоста не похоже на ip-адрес или доменное имя + + + + New site added: %1 + Добавлен новый сайт %1 + + + + Site removed: %1 + Сайт удален %1 + + + + Can't open file: %1 + Невозможно открыть файл: %1 + + + + Failed to parse JSON data from file: %1 + Не удалось разобрать JSON-данные из файла: %1 + + + + The JSON data is not an array in file: %1 + Данные JSON не являются массивом в файле: %1 + + + + Import completed + Импорт завершен + + + + Export completed + Экспорт завершен + + + + SystemTrayNotificationHandler + + + + Show + Показать + + + + + Connect + Подключиться + + + + + Disconnect + Отключиться + + + + + Visit Website + Посетить сайт + + + + + Quit + Закрыть + + + + TextFieldWithHeaderType + + + The field can't be empty + Поле не может быть пустым VpnConnection - + Mbps - + Mbps VpnProtocol - + Unknown - Неизвестно + Неизвестный - + Disconnected - Отключено + Отключен - + Preparing Подготовка - + Connecting... Подключение... - + Connected Подключено - + Disconnecting... Отключение... - + Reconnecting... Переподключение... - + Error Ошибка + + amnezia::ContainerProps + + + Low + Низкий + + + + Medium or High + Средний или Высокий + + + + Extreme + Экстремальный + + + + I just want to increase the level of my privacy. + Я просто хочу повысить уровень своей приватности. + + + + I want to bypass censorship. This option recommended in most cases. + Я хочу обойти блокировки. Этот вариант рекомендуется в большинстве случаев. + + + + Most VPN protocols are blocked. Recommended if other options are not working. + Большинство VPN протоколов заблокированы. Рекомендуется, если другие варианты не работают. + + + High + Высокий + + + Medium + Средний + + + Many foreign websites and VPN providers are blocked + Многие иностранные сайты и VPN-провайдеры заблокированы + + + Some foreign sites are blocked, but VPN providers are not blocked + Некоторые иностранные сайты заблокированы, но VPN-провайдеры не блокируются + + + I just want to increase the level of privacy + Хочу просто повысить уровень приватности + + + + main2 + + + Private key passphrase + Кодовая фраза для закрытого ключа + + + + Save + Сохранить + + diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts new file mode 100644 index 00000000..8833d5c6 --- /dev/null +++ b/client/translations/amneziavpn_zh_CN.ts @@ -0,0 +1,3169 @@ + + + + + AmneziaApplication + + Split tunneling for WireGuard is not implemented, the option was disabled + 未启用选项,还未实现基于WireGuard协议的VPN分离 + + + + Split tunneling for %1 is not implemented, the option was disabled + + + + + AndroidController + + + AmneziaVPN + + + + + VPN Connected + Refers to the app - which is currently running the background and waiting + VPN已连接 + + + + ConnectionController + + + + + + Connect + 连接 + + + + VPN Protocols is not installed. + Please install VPN container at first + 请先安装VPN协议 + + + + Connection... + 连接中 + + + + Connected + 已连接 + + + + Reconnection... + 重连中 + + + + Disconnection... + 断开中 + + + + Settings updated successfully, Reconnnection... + 配置已更新,重连中 + + + + ConnectionTypeSelectionDrawer + + Connection data + 连接方式 + + + + Add new connection + 添加新连接 + + + + Configure your server + 配置您的服务器 + + + + Open config file, key or QR code + 配置文件,授权码或二维码 + + + Server IP, login and password + 服务器IP,用户名和密码 + + + QR code, key or configuration file + 二维码,授权码或者配置文件 + + + + ContextMenuType + + + C&ut + 剪切 + + + + &Copy + 拷贝 + + + + &Paste + 粘贴 + + + + &SelectAll + 全选 + + + + ExportController + + + Access error! + 访问错误 + + + + HomeContainersListView + + + Unable change protocol while there is an active connection + 已建立连接时无法更改服务器配置 + + + + The selected protocol is not supported on the current platform + 当前平台不支持所选协议 + + + Reconnect via VPN Procotol: + 重连VPN基于协议: + + + + ImportController + + + Scanned %1 of %2. + 扫描 %1 of %2. + + + + InstallController + + installed successfully. + 安装成功 + + + is already installed on the server. + 已安装在服务器上 + + + + + %1 installed successfully. + %1 安装成功。 + + + + + %1 is already installed on the server. + 服务器上已经安装 %1。 + + + + +Added containers that were already installed on the server + 添加已安装在服务器上的容器 + + + + +Already installed containers were found on the server. All installed containers have been added to the application + +在服务上发现已经安装协议并添加至应用 + + + + Settings updated successfully + 配置更新成功 + + + + Server '%1' was removed + 已移除服务器 '%1' + + + + All containers from server '%1' have been removed + 服务器 '%1' 的所有容器已移除 + + + + %1 has been removed from the server '%2' + %1 已从服务器 '%2' 上移除 + + + 1% has been removed from the server '%2' + %1 已从服务器 '%2' 上移除 + + + Server ' + 服务器 + + + ' was removed + 已经移除 + + + has been removed from the server ' + 协议已从 + + + + Please login as the user + 请以用户身份登录 + + + + Server added successfully + 增加服务器成功 + + + + KeyChainClass + + + Read key failed: %1 + 获取授权码失败: %1 + + + + Write key failed: %1 + 写入授权码失败: %1 + + + + Delete key failed: %1 + 删除授权码失败: %1 + + + + NotificationHandler + + + + AmneziaVPN + + + + + VPN Connected + 已连接到VPN + + + + VPN Disconnected + 已从VPN断开 + + + + AmneziaVPN notification + AmneziaVPN 提示 + + + + Unsecured network detected: + 发现不安全网络 + + + + PageDeinstalling + + + Removing services from %1 + 正从 %1 移除服务 + + + + Usually it takes no more than 5 minutes + 大约5分钟之内完成 + + + + PageHome + + + VPN protocol + VPN协议 + + + + Servers + 服务器 + + + + Unable change server while there is an active connection + 已建立连接时无法更改服务器配置 + + + + PageProtocolAwgSettings + + + AmneziaWG settings + AmneziaWG 配置 + + + + Port + 端口 + + + + Junk packet count + 垃圾包数量 + + + + Junk packet minimum size + 垃圾包最小值 + + + + Junk packet maximum size + 垃圾包最大值 + + + + Init packet junk size + 初始化垃圾包大小 + + + + Response packet junk size + 响应垃圾包大小 + + + + Init packet magic header + 初始化数据包魔数头 + + + + Response packet magic header + 响应包魔数头 + + + + Transport packet magic header + 传输包魔数头 + + + + Underload packet magic header + 低负载数据包魔数头 + + + + Remove AmneziaWG + 移除AmneziaWG + + + + Remove AmneziaWG from server? + 从服务上移除AmneziaWG? + + + + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + Save and Restart Amnezia + 保存并重启Amnezia + + + + PageProtocolCloakSettings + + + Cloak settings + Cloak 配置 + + + + Disguised as traffic from + 伪装流量为 + + + + Port + 端口 + + + + + Cipher + 加密算法 + + + + Save and Restart Amnezia + 保存并重启Amnezia + + + + PageProtocolOpenVpnSettings + + + OpenVPN settings + OpenVPN 配置 + + + + VPN Addresses Subnet + VPN子网掩码 + + + + Network protocol + 网络协议 + + + + Port + 端口 + + + + Auto-negotiate encryption + 自定义加密方式 + + + + + Hash + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + + Cipher + + + + + AES-256-GCM + + + + + AES-192-GCM + + + + + AES-128-GCM + + + + + AES-256-CBC + + + + + AES-192-CBC + + + + + AES-128-CBC + + + + + ChaCha20-Poly1305 + + + + + ARIA-256-CBC + + + + + CAMELLIA-256-CBC + + + + + none + + + + + TLS auth + TLS认证 + + + + Block DNS requests outside of VPN + 阻止VPN外的DNS请求 + + + + Additional client configuration commands + 附加客户端配置命令 + + + + + Commands: + 命令: + + + + Additional server configuration commands + 附加服务器端配置命令 + + + + Remove OpenVPN + 移除OpenVPN + + + + Remove OpenVpn from server? + 从服务器移除OpenVPN吗? + + + + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 + + + All users with whom you shared a connection will no longer be able to connect to it + 与您共享连接的所有用户将无法再连接到此链接 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + Save and Restart Amnezia + 保存并重启Amnezia + + + + PageProtocolRaw + + + settings + 配置 + + + + Show connection options + 显示连接选项 + + + Connection options + 连接选项 + + + + Connection options %1 + %1 连接选项 + + + + Remove + 移除 + + + + Remove %1 from server? + 从服务器移除 %1 ? + + + + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 + + + from server? + 从服务器 + + + All users with whom you shared a connection will no longer be able to connect to it + 与您共享连接的所有用户将无法再连接到此链接 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageProtocolShadowSocksSettings + + + ShadowSocks settings + ShadowSocks 配置 + + + + Port + 端口 + + + + + Cipher + 加密算法 + + + + Save and Restart Amnezia + 保存并重启Amnezia + + + + PageServiceDnsSettings + + + A DNS service is installed on your server, and it is only accessible via VPN. + + 您的服务器已安装DNS服务,仅能通过VPN访问。 + + + + + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. + 其地址与您的服务器地址相同。您可以在 设置 连接 中进行配置。 + + + + Remove + 移除 + + + + Remove %1 from server? + 从服务器移除 %1 ? + + + from server? + 从服务器 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageServiceSftpSettings + + + Settings updated successfully + 配置更新成功 + + + + SFTP settings + SFTP 配置 + + + + Host + 主机 + + + + + + + Copied + 拷贝 + + + + Port + 端口 + + + + Login + 用户 + + + + Password + 密码 + + + + Mount folder on device + 挂载文件夹 + + + + In order to mount remote SFTP folder as local drive, perform following steps: <br> + 为将远程 SFTP 文件夹挂载到本地,请执行以下步骤: <br> + + + + + <br>1. Install the latest version of + <br>1. 安装最新版的 + + + + + <br>2. Install the latest version of + <br>2. 安装最新版的 + + + + Detailed instructions + 详细说明 + + + + Remove SFTP and all data stored there + 移除SFTP和其本地所有数据 + + + + Remove SFTP and all data stored there? + 移除SFTP和其本地所有数据? + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageServiceTorWebsiteSettings + + + Settings updated successfully + 配置更新成功 + + + + Tor website settings + Tor网站配置 + + + + Website address + 网址 + + + + Copied + 已拷贝 + + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. + 用 <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> 打开上面网址 + + + + After installation it takes several minutes while your onion site will become available in the Tor Network. + 完成安装几分钟后,洋葱站点才会在 Tor 网络中生效。 + + + + When configuring WordPress set the this onion address as domain. + 配置 WordPress 时,将此洋葱地址设置为域。 + + + When configuring WordPress set the domain as this onion address. + 配置 WordPress 时,将域设置为此洋葱地址。 + + + + Remove website + 移除网站 + + + + The site with all data will be removed from the tor network. + 网站及其所有数据将从 Tor 网络中删除 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageSettings + + + Settings + 设置 + + + + Servers + 服务器 + + + + Connection + 连接 + + + + Application + 应用 + + + + Backup + 备份 + + + + About AmneziaVPN + 关于 + + + + Close application + 关闭应用 + + + + PageSettingsAbout + + + Support the project with a donation + 捐款 + + + This is a free and open source application. If you like it, support the developers with a donation. +And if you don't like the app, all the more support it - the donation will be used to improve the app. + 这是一个免费且开源的应用软件。如果您喜欢它,请捐助支持我们继续研发。 +如果您不喜欢,请捐助支持我们改进它。 + + + + This is a free and open source application. If you like it, support the developers with a donation. + 这是一个免费且开源的软件。如果您喜欢它,请捐助开发者们。 + + + + + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. + 如果您不喜欢,请捐助支持我们改进它。 + + + + Card on Patreon + Patreon订阅 + + + + https://www.patreon.com/amneziavpn + + + + + Show other methods on Github + 其他捐款途径 + + + + Contacts + 联系方式 + + + + Telegram group + 电报群 + + + + To discuss features + 用于功能讨论 + + + + https://t.me/amnezia_vpn_en + + + + + Mail + 邮件 + + + + For reviews and bug reports + 用于评论和提交软件的缺陷 + + + + Github + + + + + https://github.com/amnezia-vpn/amnezia-client + + + + + Website + 官网 + + + + https://amnezia.org + + + + + Check for updates + 检查更新 + + + + PageSettingsApplication + + + Application + 应用 + + + + Allow application screenshots + 允许截屏 + + + + Auto start + 自动运行 + + + Launch the application every time + 总是在系统 + + + starts + 启动时自动运行运用程序 + + + Launch the application every time %1 starts + 运行应用软件在%1系统启动时 + + + + Launch the application every time the device is starts + 每次设备启动时启动应用程序 + + + + Start minimized + 最小化 + + + + Launch application minimized + 开启应用软件时窗口最小化 + + + + Language + 语言 + + + + Logging + 日志 + + + + Enabled + 开启 + + + + Disabled + 禁用 + + + + Reset settings and remove all data from the application + 重置并清理应用的所有数据 + + + + Reset settings and remove all data from the application? + 重置并清理应用的所有数据? + + + + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. + 所有配置恢复为默认值。服务器已安装的AmneziaVPN服务将被保留。 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageSettingsBackup + + + Settings restored from backup file + 从备份文件还原配置 + + + + Backup + 备份 + + + + Configuration backup + 备份设置 + + + It will help you instantly restore connection settings at the next installation + 帮助您在下次安装时立即恢复连接设置 + + + + You can save your settings to a backup file to restore them the next time you install the application. + 您可以将配置信息备份到文件中,以便在下次安装应用软件时恢复配置 + + + + Make a backup + 进行备份 + + + + Save backup file + 保存备份 + + + + + Backup files (*.backup) + + + + + Backup file saved + 备份文件已保存 + + + + Restore from backup + 从备份还原 + + + + Open backup file + 打开备份文件 + + + + Import settings from a backup file? + 从备份文件导入设置? + + + + All current settings will be reset + 当前所有设置将重置 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageSettingsConnection + + + Connection + 连接 + + + + Auto connect + 自动连接 + + + + Connect to VPN on app start + 应用开启时连接VPN + + + Use AmneziaDNS if installed on the server + 使用AmneziaDNS,如其已安装在服务器上 + + + + Use AmneziaDNS + 使用AmneziaDNS + + + + If AmneziaDNS is installed on the server + 如果已在服务器安装AmneziaDNS + + + + DNS servers + DNS服务器 + + + + If AmneziaDNS is not used or installed + 如果未使用或未安装AmneziaDNS + + + + Site-based split tunneling + 基于网站的隧道分离 + + + + Allows you to select which sites you want to access through the VPN + 配置想要通过VPN访问网站 + + + + App-based split tunneling + 基于应用的隧道分离 + + + Split site tunneling + 网站级VPN分流 + + + Allows you to connect to some sites through a secure connection, and to others bypassing it + 使用VPN访问指定网站,其他的则绕过 + + + Separate application tunneling + 应用级VPN分流 + + + + Allows you to use the VPN only for certain applications + 仅指定应用使用VPN + + + + PageSettingsDns + + + DNS servers + DNS服务器 + + + + If AmneziaDNS is not used or installed + 如果未使用或未安装AmneziaDNS + + + + Primary DNS + 首选 DNS + + + + Secondary DNS + 备用 DNS + + + + Restore default + 恢复默认配置 + + + + Restore default DNS settings? + 是否恢复默认DNS配置? + + + + Continue + 继续 + + + + Cancel + 取消 + + + + Settings have been reset + 已重置 + + + + Save + 保存 + + + + Settings saved + 配置已保存 + + + + PageSettingsLogging + + + Logging + 日志 + + + + Save logs + 记录日志 + + + + Open folder with logs + 打开日志文件夹 + + + + Save + 保存 + + + + Logs files (*.log) + + + + + Logs file saved + 日志文件已保存 + + + + Save logs to file + 保存日志到文件 + + + + Clear logs? + 清理日志? + + + + Continue + 继续 + + + + Cancel + 取消 + + + + Logs have been cleaned up + 日志已清理 + + + + Clear logs + 清理日志 + + + + PageSettingsServerData + + + All installed containers have been added to the application + 所有已安装的容器,已被添加到应用软件 + + + + No new installed containers found + 未发现新安装的容器 + + + + Clear Amnezia cache + 清除 Amnezia 缓存 + + + + May be needed when changing other settings + 更改其他设置时可能需要缓存 + + + + Clear cached profiles? + 清除缓存? + + + + + + + + + + + Continue + 继续 + + + + + + Cancel + 取消 + + + + Check the server for previously installed Amnezia services + 检查服务器上,是否存在之前安装的 Amnezia 服务 + + + + Add them to the application if they were not displayed + 如果存在且未显示,则添加到应用软件 + + + + Remove server from application + 移除本地服务器信息 + + + + Remove server? + 移除本地服务器信息? + + + + All installed AmneziaVPN services will still remain on the server. + 所有已安装的 AmneziaVPN 服务仍将保留在服务器上。 + + + + Clear server from Amnezia software + 清理Amnezia中服务器信息 + + + + Clear server from Amnezia software? + 清理Amnezia中服务器信息 + + + + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. + 服务器上的所有容器都将被删除。配置文件、密钥和证书也将被删除。 + + + + PageSettingsServerInfo + + + Server name + 服务器名 + + + + Save + 保存 + + + + Protocols + 协议 + + + + Services + 服务 + + + + Data + 数据 + + + + PageSettingsServerProtocol + + + settings + 配置 + + + + Remove + 移除 + + + + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 + + + from server? + 从服务器 + + + + Remove %1 from server? + 从服务器移除 %1 ? + + + All users with whom you shared a connection will no longer be able to connect to it + 与您共享连接的所有用户将无法再连接到此链接 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageSettingsServersList + + + Servers + 服务器 + + + + PageSettingsSplitTunneling + + Only the addresses in the list must be opened via VPN + 仅列表中的地址须通过VPN访问 + + + Addresses from the list should never be opened via VPN + 勿通过VPN访问列表中的地址 + + + Split site tunneling + 网站级VPN分流 + + + + Addresses from the list should be accessed via VPN + 仅使用VPN访问 + + + + Addresses from the list should not be accessed via VPN + 不使用VPN访问 + + + + Split tunneling + 隧道分离 + + + + Mode + 规则 + + + + Remove + 移除 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + Site or IP + 网站或IP地址 + + + + Import/Export Sites + 导入/导出网站 + + + + Import + 导入 + + + + Save site list + 保存网址 + + + + Save sites + 保存网址 + + + + + + Sites files (*.json) + + + + + Import a list of sites + 导入网址列表 + + + + Replace site list + 替换网址列表 + + + + + Open sites file + 打开网址文件 + + + + Add imported sites to existing ones + 将导入的网址添加到现有网址中 + + + + PageSetupWizardConfigSource + + + Server connection + 服务器连接 + + + + Do not use connection code from public sources. It may have been created to intercept your data. + +It's okay as long as it's from someone you trust. + 请勿使用公共来源的连接码。它可能是为了拦截您的数据而创建的。 +请确保连接码来源可信。 + + + + What do you have? + 你用什么方式创建连接? + + + + File with connection settings or backup + 包含连接配置或备份的文件 + + + + File with connection settings + 包含连接配置的文件 + + + + Open config file + 打开配置文件 + + + + QR-code + 二维码 + + + + Key as text + 授权码文本 + + + + PageSetupWizardCredentials + + Server connection + 连接服务器 + + + + Configure your server + 配置服务器 + + + + Server IP address [:port] + 服务器IP [:端口] + + + + 255.255.255.255:88 + + + + + Login to connect via SSH + 用户 + + + + Password / SSH private key + 密码 或 私钥 + + + + Continue + 继续 + + + + All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties + 您输入的所有数据将严格保密 +不会向 Amnezia 或任何第三方分享或披露 + + + + Ip address cannot be empty + IP不能为空 + + + + Enter the address in the format 255.255.255.255:88 + 按照这种格式输入 255.255.255.255:88 + + + + Login cannot be empty + 账号不能为空 + + + + Password/private key cannot be empty + 密码或私钥不能为空 + + + + PageSetupWizardEasy + + + What is the level of internet control in your region? + 您所在地区的互联网管控力度如何? + + + + Set up a VPN yourself + 自己架设VPN + + + + I want to choose a VPN protocol + 我想选择VPN协议 + + + + Continue + 继续 + + + + Set up later + 稍后设置 + + + + PageSetupWizardInstalling + + + + Usually it takes no more than 5 minutes + 通常不超过5分钟 + + + + The server has already been added to the application + 服务器已添加到应用软件中 + + + + Amnezia has detected that your server is currently + Amnezia 检测到您的服务器当前 + + + + busy installing other software. Amnezia installation + 正安装其他软件。Amnezia安装 + + + Amnesia has detected that your server is currently + Amnezia 检测到您的服务器当前 + + + busy installing other software. Amnesia installation + 正安装其他软件。Amnezia安装 + + + + will pause until the server finishes installing other software + 将暂停,直到其他软件安装完成。 + + + + Installing + 安装中 + + + + PageSetupWizardProtocolSettings + + + Installing %1 + 正在安装 %1 + + + + More detailed + 更多细节 + + + + Close + 关闭 + + + + Network protocol + 网络协议 + + + + Port + 端口 + + + + Install + 安装 + + + + PageSetupWizardProtocols + + + VPN protocol + VPN 协议 + + + + Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. + 选择你认为优先级最高的一项。稍后,您可以安装其他协议和附加服务,例如 DNS 代理和 SFTP。 + + + + PageSetupWizardQrReader + + + Point the camera at the QR code and hold for a couple of seconds. + 将相机对准二维码并按住几秒钟 + + + + PageSetupWizardStart + + + Settings restored from backup file + 从备份文件还原配置 + + + + Free service for creating a personal VPN on your server. + 在您的服务器上架设私人免费VPN服务。 + + + + Helps you access blocked content without revealing your privacy, even to VPN providers. + 帮助您访问受限内容,保护您的隐私,即使是VPN提供商也无法获取。 + + + + I have the data to connect + 我有连接配置 + + + + I have nothing + 我没有 + + + + PageSetupWizardTextKey + + + Connection key + 连接授权码 + + + + A line that starts with vpn://... + 以 vpn://... 开始的行 + + + + Key + 授权码 + + + + Insert + 插入 + + + + Continue + 继续 + + + + PageSetupWizardViewConfig + + + New connection + 新连接 + + + + Do not use connection code from public sources. It could be created to intercept your data. + 请勿使用公共来源的连接码。它可以被创建来拦截您的数据。 + + + + Collapse content + 折叠内容 + + + + Show content + 显示内容 + + + + Connect + 连接 + + + + PageShare + + + Save OpenVPN config + 保存OpenVPN配置 + + + + Save WireGuard config + 保存WireGuard配置 + + + + For the AmneziaVPN app + AmneziaVPN 应用 + + + + OpenVpn native format + OpenVPN原生格式 + + + + WireGuard native format + WireGuard原生格式 + + + + Share VPN Access + 共享 VPN 访问 + + + + Share VPN access without the ability to manage the server + 共享 VPN 访问,无需管理服务器 + + + + Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings. + 共享服务器管理访问权限。与您共享服务器全部访问权限的用户将可以添加和删除服务器上的任何协议和服务,以及更改设置。 + + + VPN Access + 访问VPN + + + + Connection + 连接 + + + + Full access + 完全访问 + + + VPN access without the ability to manage the server + 访问VPN,但没有权限管理服务。 + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. + 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. + 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 + + + Full access to server + 获得服务器完整授权 + + + Servers + 服务器 + + + + + Server + 服务器 + + + + Accessing + 访问 + + + + File with accessing settings to + 访问配置文件的内容为: + + + + File with connection settings to + 连接配置文件的内容为: + + + Protocols + 协议 + + + + + Protocol + 协议 + + + + Connection to + 连接到 + + + + + Connection format + 连接格式 + + + + Share + 共享 + + + + PopupType + + + Close + 关闭 + + + + QKeychain::DeletePasswordJobPrivate + + + Password entry not found + 未发现秘密 + + + + Could not decrypt data + 数据无法加密 + + + + + Unknown error + 未知错误 + + + + Could not open wallet: %1; %2 + 无法打开钱包: %1; %2 + + + + Password not found + 未发现密码 + + + + Could not open keystore + 无法打开密钥库 + + + + Could not remove private key from keystore + 无法从密钥库中删除私钥 + + + + QKeychain::JobPrivate + + + Unknown error + 未知错误 + + + + Access to keychain denied + 访问钥匙串被拒绝 + + + + QKeychain::PlainTextStore + + + Could not store data in settings: access error + 无法在配置中存储数据:访问错误 + + + + Could not store data in settings: format error + 无法在陪置中存储数据:格式错误 + + + + Could not delete data from settings: access error + 无法在配置中删除数据:访问错误 + + + + Could not delete data from settings: format error + 无法在配置中删除数据:格式错误 + + + + Entry not found + 未找到条目 + + + + QKeychain::ReadPasswordJobPrivate + + + Password entry not found + 未发现密码 + + + + + Could not decrypt data + 数据无法加密 + + + + D-Bus is not running + D-Bus未运行 + + + + + Unknown error + 未知错误 + + + + No keychain service available + 没有有效的钥匙串服务 + + + + Could not open wallet: %1; %2 + 无法打开钱包: %1; %2 + + + + Access to keychain denied + 访问钥匙串被拒绝 + + + + Could not determine data type: %1; %2 + 无法确定数据类型: %1; %2 + + + + + Entry not found + 未找到记录 + + + + Unsupported entry type 'Map' + 不支持的记录类型 'Map' + + + + Unknown kwallet entry type '%1' + 未知钱包类型 '%1' + + + + Password not found + 未发现密码 + + + + Could not open keystore + 无法打开密钥库 + + + + Could not retrieve private key from keystore + 无法从密钥存储库中检索私钥 + + + + Could not create decryption cipher + 无法创建解密算法 + + + + QKeychain::WritePasswordJobPrivate + + + Credential size exceeds maximum size of %1 + 证书大小超过上限,最大为: %1 + + + + Credential key exceeds maximum size of %1 + 凭证密钥大小超过上限,最大为: %1 + + + + Writing credentials failed: Win32 error code %1 + 写入凭证失败,Win32错误码: %1 + + + + Encryption failed + 加密失败 + + + + D-Bus is not running + D-Bus未运行 + + + + + Unknown error + 未知错误 + + + + Could not open wallet: %1; %2 + 无法打开钱包: %1; %2 + + + + Password not found + 未发现密码 + + + + Could not open keystore + 无法打开密钥库 + + + + Could not create private key generator + 无法创建私钥生成器 + + + + Could not generate new private key + 无法生成新的私钥 + + + + Could not retrieve private key from keystore + 无法从密钥库检索私钥 + + + + Could not create encryption cipher + 无法创建加密密码 + + + + Could not encrypt data + 无法加密数据 + + + + QObject + + + Sftp service + Sftp 服务 + + + + No error + 没有错误 + + + + Unknown Error + 未知错误 + + + + Function not implemented + 功能未实现 + + + + Server check failed + 服务器检测失败 + + + + Server port already used. Check for another software + 检测服务器该端口是否被其他软件被占用 + + + + Server error: Docker container missing + 服务器错误: Docker容器丢失 + + + + Server error: Docker failed + 服务器错误: Docker失败 + + + + Installation canceled by user + 用户取消安装 + + + + The user does not have permission to use sudo + 用户没有root权限 + + + + Ssh request was denied + ssh请求被拒绝 + + + + Ssh request was interrupted + ssh请求中断 + + + + Ssh internal error + ssh内部错误 + + + + Invalid private key or invalid passphrase entered + 输入的私钥或密码无效 + + + + The selected private key format is not supported, use openssh ED25519 key types or PEM key types + 不支持所选私钥格式,请使用 openssh ED25519 密钥类型或 PEM 密钥类型 + + + + Timeout connecting to server + 连接服务器超时 + + + + Sftp error: End-of-file encountered + Sftp错误: End-of-file encountered + + + + Sftp error: File does not exist + Sftp错误: 文件不存在 + + + + Sftp error: Permission denied + Sftp错误: 权限不足 + + + + Sftp error: Generic failure + Sftp错误: 一般失败 + + + + Sftp error: Garbage received from server + Sftp错误: 从服务器收到垃圾信息 + + + + Sftp error: No connection has been set up + Sftp 错误: 未建立连接 + + + + Sftp error: There was a connection, but we lost it + Sftp 错误: 已有连接丢失 + + + + Sftp error: Operation not supported by libssh yet + Sftp error: libssh不支持该操作 + + + + Sftp error: Invalid file handle + Sftp error: 无效的文件句柄 + + + + Sftp error: No such file or directory path exists + Sftp 错误: 文件夹或文件不存在 + + + + Sftp error: An attempt to create an already existing file or directory has been made + Sftp 错误: 文件或目录已存在 + + + + Sftp error: Write-protected filesystem + Sftp 错误: 文件系统写保护 + + + + Sftp error: No media was in remote drive + Sftp 错误: 远程驱动器中没有媒介 + + + + Failed to save config to disk + 配置保存到磁盘失败 + + + + OpenVPN config missing + OpenVPN配置丢失 + + + + OpenVPN management server error + OpenVPN 管理服务器错误 + + + + OpenVPN executable missing + OpenVPN 可执行文件丢失 + + + + ShadowSocks (ss-local) executable missing + ShadowSocks (ss-local) 执行文件丢失 + + + + Cloak (ck-client) executable missing + Cloak (ck-client) 执行文件丢失 + + + + Amnezia helper service error + Amnezia 服务连接失败 + + + + OpenSSL failed + OpenSSL错误 + + + + Can't connect: another VPN connection is active + 无法连接:另一个VPN连接处于活跃状态 + + + + Can't setup OpenVPN TAP network adapter + 无法设置 OpenVPN TAP 网络适配器 + + + + VPN pool error: no available addresses + VPN 池错误:没有可用地址 + + + + The config does not contain any containers and credentiaks for connecting to the server + 该配置不包含任何用于连接到服务器的容器和凭据。 + + + + Internal error + 内部错误 + + + + IPsec + + + + + + Website in Tor network + 在 Tor 网络中架设网站 + + + + Amnezia DNS + + + + + Sftp file sharing service + SFTP文件共享服务 + + + + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. + OpenVPN 是最流行的 VPN 协议,具有灵活的配置选项。它使用自己的安全协议与 SSL/TLS 进行密钥交换。 + + + + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. + ShadowSocks - 混淆 VPN 流量,使其与正常的 Web 流量相似,但在一些审查力度高的地区可以被分析系统识别。 + + + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + OpenVPN over Cloak - OpenVPN 与 VPN 具有伪装成网络流量和防止主动探测检测的保护。非常适合绕过审查力度特别强的地区的封锁。 + + + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + WireGuard - 新型流行的VPN协议,具有高性能、高速度和低功耗。建议用于审查力度较低的地区 + + + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. + AmneziaWG - Amnezia 的特殊协议,基于 WireGuard。它的速度像 WireGuard 一样快,但非常抗堵塞。推荐用于审查较严的地区。 + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. + IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 + + + + Deploy a WordPress site on the Tor network in two clicks. + 只需点击两次即可架设 WordPress 网站到 Tor 网络 + + + + Replace the current DNS server with your own. This will increase your privacy level. + 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私保护级别。 + + + + Creates a file vault on your server to securely store and transfer files. + 在您的服务器上创建文件仓库,以便安全地存储和传输文件 + + + + OpenVPN stands as one of the most popular and time-tested VPN protocols available. +It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. + +* Available in the AmneziaVPN across all platforms +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices +* Recognised by DPI analysis systems and therefore susceptible to blocking +* Can operate over both TCP and UDP network protocols. + OpenVPN 是最流行且经过时间考验的 VPN 协议之一。 +它采用其独特的安全协议,利用 SSL/TLS 的优势进行加密和密钥交换。此外,OpenVPN 支持多种身份验证方法,使其具有多功能性和适应性,可适应各种设备和操作系统。由于其开源性质,OpenVPN 受益于全球社区的广泛审查,这不断增强了其安全性。凭借性能、安全性和兼容性的强大平衡,OpenVPN 仍然是注重隐私的个人和企业的首选。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 移动设备的正常功耗 +* 灵活定制,满足用户使用不同操作系统和设备的需求 +* 被DPI分析系统识别,因此容易被阻塞 +* 可以通过 TCP 和 UDP 网络协议运行 + + + + Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. + +* Available in the AmneziaVPN only on desktop platforms +* Normal power consumption on mobile devices + +* Configurable encryption protocol +* Detectable by some DPI systems +* Works over TCP network protocol. + Shadowsocks 受到 SOCKS5 协议的启发,使用 AEAD 密码保护连接。尽管 Shadowsocks 设计得谨慎且难以识别,但它与标准 HTTPS 连接并不相同。但是,某些流量分析系统可能仍会检测到 Shadowsocks 连接。由于Amnezia支持有限,建议使用AmneziaWG协议。 + +* 仅在桌面平台上的 AmneziaVPN 中可用 +* 移动设备的正常功耗 + +* 可配置的加密协议 +* 可以被某些 DPI 系统检测到 +* 通过 TCP 网络协议工作。 + + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. + +OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works over TCP network protocol, 443 port. + + 这是 OpenVPN 协议和专门用于阻止保护的 Cloak 插件的组合。 + +OpenVPN 通过加密客户端和服务器之间的所有 Internet 流量来提供安全的 VPN 连接。 + +Cloak 可保护 OpenVPN 免遭检测和阻止。 + +Cloak 可以修改数据包元数据,以便将 VPN 流量完全屏蔽为正常 Web 流量,并且还可以保护 VPN 免受主动探测的检测。这使得它非常难以被发现 + +收到第一个数据包后,Cloak 立即对传入连接进行身份验证。如果身份验证失败,该插件会将服务器伪装成虚假网站,并且您的 VPN 对分析系统来说将变得不可见。 + +如果您所在地区的互联网审查非常严格,我们建议您在第一次连接时仅使用 OpenVPN over Cloak + +* 可在所有平台的 AmneziaVPN 中使用 +* 移动设备功耗高 +* 配置灵活 +* 不被 DPI 分析系统识别 +* 通过 TCP 网络协议、443 端口工作。 + + + + A relatively new popular VPN protocol with a simplified architecture. +Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. + 一种相对较新的流行 VPN 协议,具有简化的架构。 +在所有设备上提供稳定的 VPN 连接和高性能。使用硬编码的加密设置。 WireGuard 与 OpenVPN 相比具有更低的延迟和更好的数据传输吞吐量。 +由于其独特的数据包签名,WireGuard 非常容易受到阻塞。与其他一些采用混淆技术的 VPN 协议不同,WireGuard 数据包的一致签名模式可以更容易地被高级深度数据包检测 (DPI) 系统和其他网络监控工具识别并阻止。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 低功耗 +* 配置简单 +* 容易被DPI分析系统识别,容易被阻塞 +* 通过 UDP 网络协议工作。 + + + + A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. +While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. +This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Not recognised by DPI analysis systems, resistant to blocking +* Works over UDP network protocol. + AmneziaWG 是流行 VPN 协议的现代迭代,它建立在 WireGuard 的基础上,保留了其简化的架构和跨设备的高性能功能。 +虽然 WireGuard 以其高效而闻名,但由于其独特的数据包签名,它存在容易被检测到的问题。 AmneziaWG 通过使用更好的混淆方法解决了这个问题,使其流量与常规互联网流量融合在一起。 +这意味着 AmneziaWG 保留了原始版本的快速性能,同时添加了额外的隐秘层,使其成为那些想要快速且谨慎的 VPN 连接的人的绝佳选择。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 低功耗 +* 配置简单 +* 不被DPI分析系统识别,抗阻塞 +* 通过 UDP 网络协议工作。 + + + + IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol. +One of its distinguishing features is its ability to swiftly switch between networks and devices, making it particularly adaptive in dynamic network environments. +While it offers a blend of security, stability, and speed, it's essential to note that IKEv2 can be easily detected and is susceptible to blocking. + +* Available in the AmneziaVPN only on Windows +* Low power consumption, on mobile devices +* Minimal configuration +* Recognised by DPI analysis systems +* Works over UDP network protocol, ports 500 and 4500. + IKEv2 与 IPSec 加密层配合使用,是一种现代且稳定的 VPN 协议。 +其显着特征之一是能够在网络和设备之间快速切换,使其特别适应动态网络环境。 +虽然 IKEv2 兼具安全性、稳定性和速度,但必须注意的是,IKEv2 很容易被检测到,并且容易受到阻止。 + +* 仅在 Windows 上的 AmneziaVPN 中可用 +* 低功耗,在移动设备上 +* 最低配置 +* 获得DPI分析系统认可 +* 通过 UDP 网络协议、端口 500 和 4500 工作。 + + + OpenVPN container + OpenVPN容器 + + + Container with OpenVpn and ShadowSocks + 含 OpenVpn 和 ShadowSocks 的容器 + + + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin + 含 OpenVpn 和 ShadowSocks 协议的容器,通过 Cloak 插件配置混淆流量 + + + WireGuard container + WireGuard 容器 + + + IPsec container + IPsec 容器 + + + + DNS Service + DNS 服务 + + + + Sftp file sharing service - is secure FTP service + Sftp 文件共享服务 - 安全的 FTP 服务 + + + + Entry not found + 未找到记录 + + + + Access to keychain denied + 访问钥匙串被拒绝 + + + + No keyring daemon + 没有密钥环守护进程 + + + + Already unlocked + 已经解锁 + + + + No such keyring + 没有这样的密钥环 + + + + Bad arguments + 错误参数 + + + + I/O error + I/O错误 + + + + Cancelled + 已取消 + + + + Keyring already exists + 密匙环已经存在 + + + + No match + 不匹配 + + + + Unknown error + 未知错误 + + + + error 0x%1: %2 + 错误 0x%1: %2 + + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + + + + SelectLanguageDrawer + + + Choose language + 选择语言 + + + + Settings + + + Server #1 + + + + + + Server + 服务器 + + + + SettingsController + + + Software version + 软件版本 + + + + Backup file is corrupted + 备份文件已损坏 + + + + All settings have been reset to default values + 所配置恢复为默认值 + + + + Cached profiles cleared + 缓存的配置文件已清除 + + + + ShareConnectionDrawer + + + + Save AmneziaVPN config + 保存配置 + + + + Share + 共享 + + + + Copy + 拷贝 + + + + Copied + 已拷贝 + + + + Show connection settings + 显示连接配置 + + + Show content + 展示内容 + + + + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" + 要应用二维码到 Amnezia,请底部工具栏点击“+”→“连接方式”→“二维码、授权码或配置文件” + + + + SitesController + + + Hostname not look like ip adress or domain name + 请输入有效的域名或IP地址 + + + + New site added: %1 + 已经添加新网站: %1 + + + + Site removed: %1 + 已移除网站: %1 + + + + Can't open file: %1 + 无法打开文件: %1 + + + + Failed to parse JSON data from file: %1 + JSON解析失败,文件: %1 + + + + The JSON data is not an array in file: %1 + 文件中的JSON数据不是一个数组,文件: %1 + + + + Import completed + 完成导入 + + + + Export completed + 完成导出 + + + + SystemTrayNotificationHandler + + + + Show + 显示 + + + + + Connect + 连接 + + + + + Disconnect + 断开 + + + + + Visit Website + 官网 + + + + + Quit + 退出 + + + + TextFieldWithHeaderType + + + The field can't be empty + 输入不能为空 + + + + VpnConnection + + + Mbps + + + + + VpnProtocol + + + Unknown + 未知 + + + + Disconnected + 连接已断开 + + + + Preparing + 准备中 + + + + Connecting... + 连接中 + + + + Connected + 已连接 + + + + Disconnecting... + 断开中 + + + + Reconnecting... + 重连中 + + + + Error + 错误 + + + + amnezia::ContainerProps + + + Low + + + + + Medium or High + 中或高 + + + + Extreme + 极度 + + + + I just want to increase the level of my privacy. + 只是想提高隐私保护级别。 + + + + I want to bypass censorship. This option recommended in most cases. + 想要绕过审查制度。大多数情况下推荐使用此选项。 + + + + Most VPN protocols are blocked. Recommended if other options are not working. + 大多数 VPN 协议都被阻止。如果其他选项不起作用,推荐此选项。 + + + High + + + + Medium + + + + I just want to increase the level of privacy + 我只是想提高隐私保护级别 + + + Many foreign websites and VPN providers are blocked + 大多国外网站和VPN提供商被屏蔽 + + + Some foreign sites are blocked, but VPN providers are not blocked + 一些国外网站被屏蔽,但VPN提供商未被屏蔽 + + + + main2 + + + Private key passphrase + 私钥密码 + + + + Save + 保存 + + + diff --git a/client/translations/translations.qrc.in b/client/translations/translations.qrc.in new file mode 100644 index 00000000..f49df661 --- /dev/null +++ b/client/translations/translations.qrc.in @@ -0,0 +1,5 @@ + + + @QM_FILE_LIST@ + + diff --git a/client/ui/Controls2 b/client/ui/Controls2 new file mode 100644 index 00000000..13f01bb7 --- /dev/null +++ b/client/ui/Controls2 @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +TextArea { + id: root + + width: parent.width + + topPadding: 16 + leftPadding: 16 + + color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + wrapMode: Text.Wrap + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: contextMenu.open() + } + + ContextMenuType { + id: contextMenu + textObj: textField + } +} diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp new file mode 100644 index 00000000..74438dcc --- /dev/null +++ b/client/ui/controllers/connectionController.cpp @@ -0,0 +1,140 @@ +#include "connectionController.h" + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif + +#include "core/errorstrings.h" + +ConnectionController::ConnectionController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const QSharedPointer &vpnConnection, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_vpnConnection(vpnConnection) +{ + connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, this, + &ConnectionController::onConnectionStateChanged); + connect(this, &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, + Qt::QueuedConnection); + connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, + Qt::QueuedConnection); + + m_state = Vpn::ConnectionState::Disconnected; +} + +void ConnectionController::openConnection() +{ + int serverIndex = m_serversModel->getDefaultServerIndex(); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + DockerContainer container = m_containersModel->getDefaultContainer(); + QModelIndex containerModelIndex = m_containersModel->index(container); + const QJsonObject &containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); + + if (container == DockerContainer::None) { + emit connectionErrorOccurred(tr("VPN Protocols is not installed.\n Please install VPN container at first")); + return; + } + + qApp->processEvents(); + emit connectToVpn(serverIndex, credentials, container, containerConfig); +} + +void ConnectionController::closeConnection() +{ + emit disconnectFromVpn(); +} + +QString ConnectionController::getLastConnectionError() +{ + return errorString(m_vpnConnection->lastError()); +} + +void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) +{ + m_state = state; + + m_isConnected = false; + m_connectionStateText = tr("Connection..."); + switch (state) { + case Vpn::ConnectionState::Connected: { + m_isConnectionInProgress = false; + m_isConnected = true; + m_connectionStateText = tr("Connected"); + break; + } + case Vpn::ConnectionState::Connecting: { + m_isConnectionInProgress = true; + break; + } + case Vpn::ConnectionState::Reconnecting: { + m_isConnectionInProgress = true; + m_connectionStateText = tr("Reconnection..."); + break; + } + case Vpn::ConnectionState::Disconnected: { + m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); + break; + } + case Vpn::ConnectionState::Disconnecting: { + m_isConnectionInProgress = true; + m_connectionStateText = tr("Disconnection..."); + break; + } + case Vpn::ConnectionState::Preparing: { + m_isConnectionInProgress = true; + break; + } + case Vpn::ConnectionState::Error: { + m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); + emit connectionErrorOccurred(getLastConnectionError()); + break; + } + case Vpn::ConnectionState::Unknown: { + m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); + emit connectionErrorOccurred(getLastConnectionError()); + break; + } + } + emit connectionStateChanged(); +} + +void ConnectionController::onCurrentContainerUpdated() +{ + if (m_isConnected || m_isConnectionInProgress) { + emit reconnectWithUpdatedContainer(tr("Settings updated successfully, Reconnnection...")); + openConnection(); + } +} + +void ConnectionController::onTranslationsUpdated() +{ + // get translated text of current state + onConnectionStateChanged(getCurrentConnectionState()); +} + +Vpn::ConnectionState ConnectionController::getCurrentConnectionState() +{ + return m_state; +} + +QString ConnectionController::connectionStateText() const +{ + return m_connectionStateText; +} + +bool ConnectionController::isConnectionInProgress() const +{ + return m_isConnectionInProgress; +} + +bool ConnectionController::isConnected() const +{ + return m_isConnected; +} diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h new file mode 100644 index 00000000..74a3f600 --- /dev/null +++ b/client/ui/controllers/connectionController.h @@ -0,0 +1,63 @@ +#ifndef CONNECTIONCONTROLLER_H +#define CONNECTIONCONTROLLER_H + +#include "protocols/vpnprotocol.h" +#include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" +#include "vpnconnection.h" + +class ConnectionController : public QObject +{ + Q_OBJECT + +public: + Q_PROPERTY(bool isConnected READ isConnected NOTIFY connectionStateChanged) + Q_PROPERTY(bool isConnectionInProgress READ isConnectionInProgress NOTIFY connectionStateChanged) + Q_PROPERTY(QString connectionStateText READ connectionStateText NOTIFY connectionStateChanged) + + explicit ConnectionController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const QSharedPointer &vpnConnection, QObject *parent = nullptr); + + ~ConnectionController() = default; + + bool isConnected() const; + bool isConnectionInProgress() const; + QString connectionStateText() const; + +public slots: + void openConnection(); + void closeConnection(); + + QString getLastConnectionError(); + void onConnectionStateChanged(Vpn::ConnectionState state); + + void onCurrentContainerUpdated(); + + void onTranslationsUpdated(); + +signals: + void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig); + void disconnectFromVpn(); + void connectionStateChanged(); + + void connectionErrorOccurred(const QString &errorMessage); + void reconnectWithUpdatedContainer(const QString &message); + +private: + Vpn::ConnectionState getCurrentConnectionState(); + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + + QSharedPointer m_vpnConnection; + + bool m_isConnected = false; + bool m_isConnectionInProgress = false; + QString m_connectionStateText = tr("Connect"); + + Vpn::ConnectionState m_state; +}; + +#endif // CONNECTIONCONTROLLER_H diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp new file mode 100644 index 00000000..ef5cc4e3 --- /dev/null +++ b/client/ui/controllers/exportController.cpp @@ -0,0 +1,243 @@ +#include "exportController.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "configurators/openvpn_configurator.h" +#include "configurators/wireguard_configurator.h" +#include "core/errorstrings.h" +#include "systemController.h" +#ifdef Q_OS_ANDROID + #include "platforms/android/androidutils.h" +#endif +#include "qrcodegen.hpp" + +ExportController::ExportController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + const std::shared_ptr &configurator, QObject *parent) + : QObject(parent), + m_serversModel(serversModel), + m_containersModel(containersModel), + m_settings(settings), + m_configurator(configurator) +{ +#ifdef Q_OS_ANDROID + m_authResultNotifier.reset(new AuthResultNotifier); + m_authResultReceiver.reset(new AuthResultReceiver(m_authResultNotifier)); + connect(m_authResultNotifier.get(), &AuthResultNotifier::authFailed, this, + [this]() { emit exportErrorOccurred(tr("Access error!")); }); + connect(m_authResultNotifier.get(), &AuthResultNotifier::authSuccessful, this, + &ExportController::generateFullAccessConfig); +#endif +} + +void ExportController::generateFullAccessConfig() +{ + clearPreviousConfig(); + + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + QJsonObject config = m_settings->server(serverIndex); + + QByteArray compressedConfig = QJsonDocument(config).toJson(); + compressedConfig = qCompress(compressedConfig, 8); + m_config = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); + + m_qrCodes = generateQrCodeImageSeries(compressedConfig); + emit exportConfigChanged(); +} + +#if defined(Q_OS_ANDROID) +void ExportController::generateFullAccessConfigAndroid() +{ + /* We use builtin keyguard for ssh key export protection on Android */ + QJniObject activity = AndroidUtils::getActivity(); + auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); + if (appContext.isValid()) { + auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent", + "(Landroid/content/Context;)Landroid/content/Intent;", + appContext.object()); + if (intent.isValid()) { + if (intent.object() != nullptr) { + QtAndroidPrivate::startActivity(intent.object(), 1, m_authResultReceiver.get()); + } + } else { + generateFullAccessConfig(); + } + } +} +#endif + +void ExportController::generateConnectionConfig() +{ + clearPreviousConfig(); + + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); + QModelIndex containerModelIndex = m_containersModel->index(container); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); + containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); + + ErrorCode errorCode = ErrorCode::NoError; + for (Proto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject protocolConfig = m_settings->protocolConfig(serverIndex, container, protocol); + + QString vpnConfig = + m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, protocol, &errorCode); + if (errorCode) { + emit exportErrorOccurred(errorString(errorCode)); + return; + } + protocolConfig.insert(config_key::last_config, vpnConfig); + containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig); + } + + QJsonObject config = m_settings->server(serverIndex); + if (!errorCode) { + config.remove(config_key::userName); + config.remove(config_key::password); + config.remove(config_key::port); + config.insert(config_key::containers, QJsonArray { containerConfig }); + config.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); + + auto dns = m_configurator->getDnsForConfig(serverIndex); + config.insert(config_key::dns1, dns.first); + config.insert(config_key::dns2, dns.second); + } + + QByteArray compressedConfig = QJsonDocument(config).toJson(); + compressedConfig = qCompress(compressedConfig, 8); + m_config = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); + + m_qrCodes = generateQrCodeImageSeries(compressedConfig); + emit exportConfigChanged(); +} + +void ExportController::generateOpenVpnConfig() +{ + clearPreviousConfig(); + + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); + QModelIndex containerModelIndex = m_containersModel->index(container); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); + containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); + + ErrorCode errorCode = ErrorCode::NoError; + QString config = + m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, &errorCode); + if (errorCode) { + emit exportErrorOccurred(errorString(errorCode)); + return; + } + config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, config); + + auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); + QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); + for (const QString &line : lines) { + m_config.append(line + "\n"); + } + + emit exportConfigChanged(); +} + +void ExportController::generateWireGuardConfig() +{ + clearPreviousConfig(); + + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); + QModelIndex containerModelIndex = m_containersModel->index(container); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); + containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); + + ErrorCode errorCode = ErrorCode::NoError; + QString config = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, + &errorCode); + if (errorCode) { + emit exportErrorOccurred(errorString(errorCode)); + return; + } + config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, config); + + auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); + QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); + for (const QString &line : lines) { + m_config.append(line + "\n"); + } + + emit exportConfigChanged(); +} + +QString ExportController::getConfig() +{ + return m_config; +} + +QList ExportController::getQrCodes() +{ + return m_qrCodes; +} + +void ExportController::exportConfig(const QString &fileName) +{ + SystemController::saveFile(fileName, m_config); +} + +QList ExportController::generateQrCodeImageSeries(const QByteArray &data) +{ + double k = 850; + + quint8 chunksCount = std::ceil(data.size() / k); + QList chunks; + for (int i = 0; i < data.size(); i = i + k) { + QByteArray chunk; + QDataStream s(&chunk, QIODevice::WriteOnly); + s << amnezia::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k); + + QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW); + QString svg = QString::fromStdString(toSvgString(qr, 0)); + chunks.append(svgToBase64(svg)); + } + + return chunks; +} + +QString ExportController::svgToBase64(const QString &image) +{ + return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data()); +} + +int ExportController::getQrCodesCount() +{ + return m_qrCodes.size(); +} + +void ExportController::clearPreviousConfig() +{ + m_config.clear(); + m_qrCodes.clear(); +} diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h new file mode 100644 index 00000000..24eaa5c8 --- /dev/null +++ b/client/ui/controllers/exportController.h @@ -0,0 +1,70 @@ +#ifndef EXPORTCONTROLLER_H +#define EXPORTCONTROLLER_H + +#include + +#include "configurators/vpn_configurator.h" +#include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" +#ifdef Q_OS_ANDROID + #include "platforms/android/authResultReceiver.h" +#endif + +class ExportController : public QObject +{ + Q_OBJECT +public: + explicit ExportController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + const std::shared_ptr &configurator, QObject *parent = nullptr); + + Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY exportConfigChanged) + Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged) + Q_PROPERTY(QString config READ getConfig NOTIFY exportConfigChanged) + +public slots: + void generateFullAccessConfig(); +#if defined(Q_OS_ANDROID) + void generateFullAccessConfigAndroid(); +#endif + void generateConnectionConfig(); + void generateOpenVpnConfig(); + void generateWireGuardConfig(); + + QString getConfig(); + QList getQrCodes(); + + void exportConfig(const QString &fileName); + +signals: + void generateConfig(int type); + void exportErrorOccurred(const QString &errorMessage); + + void exportConfigChanged(); + + void saveFile(const QString &fileName, const QString &data); + +private: + QList generateQrCodeImageSeries(const QByteArray &data); + QString svgToBase64(const QString &image); + + int getQrCodesCount(); + + void clearPreviousConfig(); + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; + std::shared_ptr m_configurator; + + QString m_config; + QList m_qrCodes; + +#ifdef Q_OS_ANDROID + QSharedPointer m_authResultNotifier; + QSharedPointer m_authResultReceiver; +#endif +}; + +#endif // EXPORTCONTROLLER_H diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp new file mode 100644 index 00000000..bd924087 --- /dev/null +++ b/client/ui/controllers/importController.cpp @@ -0,0 +1,429 @@ +#include "importController.h" + +#include +#include +#include +#include + +#include "core/errorstrings.h" +#ifdef Q_OS_ANDROID + #include "../../platforms/android/android_controller.h" + #include "../../platforms/android/androidutils.h" + #include +#endif +#ifdef Q_OS_IOS + #include +#endif + +namespace +{ + enum class ConfigTypes { + Amnezia, + OpenVpn, + WireGuard + }; + + ConfigTypes checkConfigFormat(const QString &config) + { + const QString openVpnConfigPatternCli = "client"; + const QString openVpnConfigPatternProto1 = "proto tcp"; + const QString openVpnConfigPatternProto2 = "proto udp"; + const QString openVpnConfigPatternDriver1 = "dev tun"; + const QString openVpnConfigPatternDriver2 = "dev tap"; + + const QString wireguardConfigPatternSectionInterface = "[Interface]"; + const QString wireguardConfigPatternSectionPeer = "[Peer]"; + + if (config.contains(openVpnConfigPatternCli) + && (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) + && (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { + return ConfigTypes::OpenVpn; + } else if (config.contains(wireguardConfigPatternSectionInterface) + && config.contains(wireguardConfigPatternSectionPeer)) { + return ConfigTypes::WireGuard; + } + return ConfigTypes::Amnezia; + } + +#if defined Q_OS_ANDROID + ImportController *mInstance = nullptr; +#endif + +#ifdef Q_OS_ANDROID + constexpr auto AndroidCameraActivity = "org.amnezia.vpn.qt.CameraActivity"; +#endif +} // namespace + +ImportController::ImportController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) +{ +#ifdef Q_OS_ANDROID + mInstance = this; + + AndroidUtils::runOnAndroidThreadAsync([]() { + JNINativeMethod methods[] { + { "passDataToDecoder", "(Ljava/lang/String;)V", reinterpret_cast(onNewQrCodeDataChunk) }, + }; + + QJniObject javaClass(AndroidCameraActivity); + QJniEnvironment env; + jclass objectClass = env->GetObjectClass(javaClass.object()); + env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); + env->DeleteLocalRef(objectClass); + }); +#endif +} + +void ImportController::extractConfigFromFile(const QString &fileName) +{ + QFile file(fileName); + + if (file.open(QIODevice::ReadOnly)) { + QString data = file.readAll(); + + extractConfigFromData(data); + m_configFileName = QFileInfo(file.fileName()).fileName(); + } +} + +void ImportController::extractConfigFromData(QString data) +{ + auto configFormat = checkConfigFormat(data); + if (configFormat == ConfigTypes::OpenVpn) { + m_config = extractOpenVpnConfig(data); + } else if (configFormat == ConfigTypes::WireGuard) { + m_config = extractWireGuardConfig(data); + } else { + m_config = extractAmneziaConfig(data); + } +} + +void ImportController::extractConfigFromCode(QString code) +{ + m_config = extractAmneziaConfig(code); + m_configFileName = ""; +} + +bool ImportController::extractConfigFromQr(const QByteArray &data) +{ + QJsonObject dataObj = QJsonDocument::fromJson(data).object(); + if (!dataObj.isEmpty()) { + m_config = dataObj; + return true; + } + + QByteArray ba_uncompressed = qUncompress(data); + if (!ba_uncompressed.isEmpty()) { + m_config = QJsonDocument::fromJson(ba_uncompressed).object(); + return true; + } + + return false; +} + +QString ImportController::getConfig() +{ + return QJsonDocument(m_config).toJson(QJsonDocument::Indented); +} + +QString ImportController::getConfigFileName() +{ + return m_configFileName; +} + +void ImportController::importConfig() +{ + ServerCredentials credentials; + credentials.hostName = m_config.value(config_key::hostName).toString(); + credentials.port = m_config.value(config_key::port).toInt(); + credentials.userName = m_config.value(config_key::userName).toString(); + credentials.secretData = m_config.value(config_key::password).toString(); + + if (credentials.isValid() || m_config.contains(config_key::containers)) { + m_serversModel->addServer(m_config); + + emit importFinished(); + } else { + qDebug() << "Failed to import profile"; + qDebug().noquote() << QJsonDocument(m_config).toJson(); + emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); + } + + m_config = {}; + m_configFileName.clear(); +} + +QJsonObject ImportController::extractAmneziaConfig(QString &data) +{ + data.replace("vpn://", ""); + QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + QByteArray ba_uncompressed = qUncompress(ba); + if (!ba_uncompressed.isEmpty()) { + ba = ba_uncompressed; + } + + return QJsonDocument::fromJson(ba).object(); +} + +QJsonObject ImportController::extractOpenVpnConfig(const QString &data) +{ + QJsonObject openVpnConfig; + openVpnConfig[config_key::config] = data; + + QJsonObject lastConfig; + lastConfig[config_key::last_config] = QString(QJsonDocument(openVpnConfig).toJson()); + lastConfig[config_key::isThirdPartyConfig] = true; + + QJsonObject containers; + containers.insert(config_key::container, QJsonValue("amnezia-openvpn")); + containers.insert(config_key::openvpn, QJsonValue(lastConfig)); + + QJsonArray arr; + arr.push_back(containers); + + QString hostName; + const static QRegularExpression hostNameRegExp("remote (.*) [0-9]*"); + QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data); + if (hostNameMatch.hasMatch()) { + hostName = hostNameMatch.captured(1); + } + + QJsonObject config; + config[config_key::containers] = arr; + config[config_key::defaultContainer] = "amnezia-openvpn"; + config[config_key::description] = m_settings->nextAvailableServerName(); + + const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data); + if (dnsMatch.hasNext()) { + config[config_key::dns1] = dnsMatch.next().captured(1); + } + if (dnsMatch.hasNext()) { + config[config_key::dns2] = dnsMatch.next().captured(1); + } + + config[config_key::hostName] = hostName; + + return config; +} + +QJsonObject ImportController::extractWireGuardConfig(const QString &data) +{ + QMap configMap; + auto configByLines = data.split("\n"); + for (const QString &line : configByLines) { + QString trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + configMap[parts.at(0).trimmed()] = parts.at(1).trimmed(); + } + } + } + + QJsonObject lastConfig; + lastConfig[config_key::config] = data; + + const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*):([0-9]*)"); + QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(data); + QString hostName; + QString port; + if (hostNameAndPortMatch.hasCaptured(1)) { + hostName = hostNameAndPortMatch.captured(1); + } else { + qDebug() << "Failed to import profile"; + emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); + } + + if (hostNameAndPortMatch.hasCaptured(2)) { + port = hostNameAndPortMatch.captured(2); + } else { + port = protocols::wireguard::defaultPort; + } + + lastConfig[config_key::hostName] = hostName; + lastConfig[config_key::port] = port.toInt(); + +// if (!configMap.value("PrivateKey").isEmpty() && !configMap.value("Address").isEmpty() +// && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) { + lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey"); + lastConfig[config_key::client_ip] = configMap.value("Address"); + lastConfig[config_key::psk_key] = configMap.value("PresharedKey"); + lastConfig[config_key::server_pub_key] = configMap.value("PublicKey"); +// } else { +// qDebug() << "Failed to import profile"; +// emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); +// return QJsonObject(); +// } + + QString protocolName = "wireguard"; + if (!configMap.value(config_key::junkPacketCount).isEmpty() + && !configMap.value(config_key::junkPacketMinSize).isEmpty() + && !configMap.value(config_key::junkPacketMaxSize).isEmpty() + && !configMap.value(config_key::initPacketJunkSize).isEmpty() + && !configMap.value(config_key::responsePacketJunkSize).isEmpty() + && !configMap.value(config_key::initPacketMagicHeader).isEmpty() + && !configMap.value(config_key::responsePacketMagicHeader).isEmpty() + && !configMap.value(config_key::underloadPacketMagicHeader).isEmpty() + && !configMap.value(config_key::transportPacketMagicHeader).isEmpty()) { + lastConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount); + lastConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize); + lastConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize); + lastConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize); + lastConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize); + lastConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader); + lastConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); + lastConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); + lastConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); + protocolName = "awg"; + } + + QJsonObject wireguardConfig; + wireguardConfig[config_key::last_config] = QString(QJsonDocument(lastConfig).toJson()); + wireguardConfig[config_key::isThirdPartyConfig] = true; + wireguardConfig[config_key::port] = port; + wireguardConfig[config_key::transport_proto] = "udp"; + + QJsonObject containers; + containers.insert(config_key::container, QJsonValue("amnezia-" + protocolName)); + containers.insert(protocolName, QJsonValue(wireguardConfig)); + + QJsonArray arr; + arr.push_back(containers); + + QJsonObject config; + config[config_key::containers] = arr; + config[config_key::defaultContainer] = "amnezia-" + protocolName; + config[config_key::description] = m_settings->nextAvailableServerName(); + + const static QRegularExpression dnsRegExp( + "DNS = " + "(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + QRegularExpressionMatch dnsMatch = dnsRegExp.match(data); + if (dnsMatch.hasMatch()) { + config[config_key::dns1] = dnsMatch.captured(1); + config[config_key::dns2] = dnsMatch.captured(2); + } + + config[config_key::hostName] = hostName; + + return config; +} + +#ifdef Q_OS_ANDROID +void ImportController::onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data) +{ + Q_UNUSED(thiz); + const char *buffer = env->GetStringUTFChars(data, nullptr); + if (!buffer) { + return; + } + + QString parcelBody(buffer); + env->ReleaseStringUTFChars(data, buffer); + + if (mInstance != nullptr) { + if (!mInstance->m_isQrCodeProcessed) { + mInstance->m_qrCodeChunks.clear(); + mInstance->m_isQrCodeProcessed = true; + mInstance->m_totalQrCodeChunksCount = 0; + mInstance->m_receivedQrCodeChunksCount = 0; + } + mInstance->parseQrCodeChunk(parcelBody); + } +} +#endif + +#if defined Q_OS_ANDROID || defined Q_OS_IOS +void ImportController::startDecodingQr() +{ + m_qrCodeChunks.clear(); + m_totalQrCodeChunksCount = 0; + m_receivedQrCodeChunksCount = 0; + + #if defined Q_OS_IOS + m_isQrCodeProcessed = true; + #endif + #if defined Q_OS_ANDROID + AndroidController::instance()->startQrReaderActivity(); + #endif +} + +void ImportController::stopDecodingQr() +{ + #if defined Q_OS_ANDROID + QJniObject::callStaticMethod(AndroidCameraActivity, "stopQrCodeReader", "()V"); + #endif + emit qrDecodingFinished(); +} + +void ImportController::parseQrCodeChunk(const QString &code) +{ + // qDebug() << code; + if (!m_isQrCodeProcessed) + return; + + // check if chunk received + QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QDataStream s(&ba, QIODevice::ReadOnly); + qint16 magic; + s >> magic; + + if (magic == amnezia::qrMagicCode) { + quint8 chunksCount; + s >> chunksCount; + if (m_totalQrCodeChunksCount != chunksCount) { + m_qrCodeChunks.clear(); + } + + m_totalQrCodeChunksCount = chunksCount; + + quint8 chunkId; + s >> chunkId; + s >> m_qrCodeChunks[chunkId]; + m_receivedQrCodeChunksCount = m_qrCodeChunks.size(); + + if (m_qrCodeChunks.size() == m_totalQrCodeChunksCount) { + QByteArray data; + + for (int i = 0; i < m_totalQrCodeChunksCount; ++i) { + data.append(m_qrCodeChunks.value(i)); + } + + bool ok = extractConfigFromQr(data); + if (ok) { + m_isQrCodeProcessed = false; + qDebug() << "stopDecodingQr"; + stopDecodingQr(); + } else { + qDebug() << "error while extracting data from qr"; + m_qrCodeChunks.clear(); + m_totalQrCodeChunksCount = 0; + m_receivedQrCodeChunksCount = 0; + } + } + } else { + bool ok = extractConfigFromQr(ba); + if (ok) { + m_isQrCodeProcessed = false; + qDebug() << "stopDecodingQr"; + stopDecodingQr(); + } + } +} + +double ImportController::getQrCodeScanProgressBarValue() +{ + return (1.0 / m_totalQrCodeChunksCount) * m_receivedQrCodeChunksCount; +} + +QString ImportController::getQrCodeScanProgressString() +{ + return tr("Scanned %1 of %2.").arg(m_receivedQrCodeChunksCount).arg(m_totalQrCodeChunksCount); +} +#endif diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h new file mode 100644 index 00000000..1f8f8bbb --- /dev/null +++ b/client/ui/controllers/importController.h @@ -0,0 +1,72 @@ +#ifndef IMPORTCONTROLLER_H +#define IMPORTCONTROLLER_H + +#include + +#include "containers/containers_defs.h" +#include "core/defs.h" +#include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" +#ifdef Q_OS_ANDROID + #include "jni.h" +#endif + +class ImportController : public QObject +{ + Q_OBJECT +public: + explicit ImportController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, QObject *parent = nullptr); + +public slots: + void importConfig(); + void extractConfigFromFile(const QString &fileName); + void extractConfigFromData(QString data); + void extractConfigFromCode(QString code); + bool extractConfigFromQr(const QByteArray &data); + QString getConfig(); + QString getConfigFileName(); + +#if defined Q_OS_ANDROID || defined Q_OS_IOS + void startDecodingQr(); + void parseQrCodeChunk(const QString &code); + + double getQrCodeScanProgressBarValue(); + QString getQrCodeScanProgressString(); +#endif + +signals: + void importFinished(); + void importErrorOccurred(const QString &errorMessage); + + void qrDecodingFinished(); + +private: + QJsonObject extractAmneziaConfig(QString &data); + QJsonObject extractOpenVpnConfig(const QString &data); + QJsonObject extractWireGuardConfig(const QString &data); + +#if defined Q_OS_ANDROID || defined Q_OS_IOS + void stopDecodingQr(); +#endif +#if defined Q_OS_ANDROID + static void onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data); +#endif + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; + + QJsonObject m_config; + QString m_configFileName; + +#if defined Q_OS_ANDROID || defined Q_OS_IOS + QMap m_qrCodeChunks; + bool m_isQrCodeProcessed; + int m_totalQrCodeChunksCount; + int m_receivedQrCodeChunksCount; +#endif +}; + +#endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp new file mode 100644 index 00000000..80e74764 --- /dev/null +++ b/client/ui/controllers/installController.cpp @@ -0,0 +1,512 @@ +#include "installController.h" + +#include +#include +#include +#include +#include +#include + +#include "core/errorstrings.h" +#include "core/servercontroller.h" +#include "utilities.h" + +namespace +{ +#ifdef Q_OS_WINDOWS + QString getNextDriverLetter() + { + QProcess drivesProc; + drivesProc.start("wmic logicaldisk get caption"); + drivesProc.waitForFinished(); + QString drives = drivesProc.readAll(); + qDebug() << drives; + + QString letters = "CFGHIJKLMNOPQRSTUVWXYZ"; + QString letter; + for (int i = letters.size() - 1; i > 0; i--) { + letter = letters.at(i); + if (!drives.contains(letter + ":")) + break; + } + if (letter == "C:") { + // set err info + qDebug() << "Can't find free drive letter"; + return ""; + } + return letter; + } +#endif +} + +InstallController::InstallController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const QSharedPointer &protocolsModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), + m_serversModel(serversModel), + m_containersModel(containersModel), + m_protocolModel(protocolsModel), + m_settings(settings) +{ +} + +InstallController::~InstallController() +{ +#ifdef Q_OS_WINDOWS + for (QSharedPointer process : m_sftpMountProcesses) { + Utils::signalCtrl(process->processId(), CTRL_C_EVENT); + process->kill(); + process->waitForFinished(); + } +#endif +} + +void InstallController::install(DockerContainer container, int port, TransportProto transportProto) +{ + QJsonObject config; + auto mainProto = ContainerProps::defaultProtocol(container); + for (auto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject containerConfig; + + if (protocol == mainProto) { + containerConfig.insert(config_key::port, QString::number(port)); + containerConfig.insert(config_key::transport_proto, + ProtocolProps::transportProtoToString(transportProto, protocol)); + + if (container == DockerContainer::Awg) { + QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(3, 10)); + QString junkPacketMinSize = QString::number(50); + QString junkPacketMaxSize = QString::number(1000); + QString initPacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150)); + QString responsePacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150)); + + QSet headersValue; + while (headersValue.size() != 4) { + + auto max = (std::numeric_limits::max)(); + headersValue.insert(QString::number(QRandomGenerator::global()->bounded(1, max))); + } + + auto headersValueList = headersValue.values(); + + QString initPacketMagicHeader = headersValueList.at(0); + QString responsePacketMagicHeader = headersValueList.at(1); + QString underloadPacketMagicHeader = headersValueList.at(2); + QString transportPacketMagicHeader = headersValueList.at(3); + + containerConfig[config_key::junkPacketCount] = junkPacketCount; + containerConfig[config_key::junkPacketMinSize] = junkPacketMinSize; + containerConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize; + containerConfig[config_key::initPacketJunkSize] = initPacketJunkSize; + containerConfig[config_key::responsePacketJunkSize] = responsePacketJunkSize; + containerConfig[config_key::initPacketMagicHeader] = initPacketMagicHeader; + containerConfig[config_key::responsePacketMagicHeader] = responsePacketMagicHeader; + containerConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader; + containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; + } + + if (container == DockerContainer::Sftp) { + containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); + containerConfig.insert(config_key::password, Utils::getRandomString(10)); + } + + config.insert(config_key::container, ContainerProps::containerToString(container)); + } + config.insert(ProtocolProps::protoToString(protocol), containerConfig); + } + + if (m_shouldCreateServer) { + if (isServerAlreadyExists()) { + return; + } + installServer(container, config); + } else { + installContainer(container, config); + } +} + +void InstallController::installServer(DockerContainer container, QJsonObject &config) +{ + ServerController serverController(m_settings); + connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); + + QMap installedContainers; + ErrorCode errorCode = + serverController.getAlreadyInstalledContainers(m_currentlyInstalledServerCredentials, installedContainers); + + QString finishMessage = ""; + + if (!installedContainers.contains(container)) { + errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); + installedContainers.insert(container, config); + finishMessage = tr("%1 installed successfully. ").arg(ContainerProps::containerHumanNames().value(container)); + } else { + finishMessage = tr("%1 is already installed on the server. ").arg(ContainerProps::containerHumanNames().value(container)); + } + if (installedContainers.size() > 1) { + finishMessage += tr("\nAdded containers that were already installed on the server"); + } + + if (errorCode == ErrorCode::NoError) { + QJsonObject server; + server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); + server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName); + server.insert(config_key::password, m_currentlyInstalledServerCredentials.secretData); + server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); + server.insert(config_key::description, m_settings->nextAvailableServerName()); + + QJsonArray containerConfigs; + for (const QJsonObject &containerConfig : qAsConst(installedContainers)) { + containerConfigs.append(containerConfig); + } + + server.insert(config_key::containers, containerConfigs); + server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); + + m_serversModel->addServer(server); + + emit installServerFinished(finishMessage); + return; + } + + emit installationErrorOccurred(errorString(errorCode)); +} + +void InstallController::installContainer(DockerContainer container, QJsonObject &config) +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + ServerController serverController(m_settings); + connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); + + QMap installedContainers; + ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); + + QString finishMessage = ""; + + if (!installedContainers.contains(container)) { + errorCode = serverController.setupContainer(serverCredentials, container, config); + installedContainers.insert(container, config); + finishMessage = tr("%1 installed successfully. ").arg(ContainerProps::containerHumanNames().value(container)); + } else { + finishMessage = tr("%1 is already installed on the server. ").arg(ContainerProps::containerHumanNames().value(container)); + } + + bool isInstalledContainerAddedToGui = false; + + if (errorCode == ErrorCode::NoError) { + for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { + auto modelIndex = m_containersModel->index(iterator.key()); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(modelIndex, ContainersModel::Roles::ConfigRole)); + if (containerConfig.isEmpty()) { + m_containersModel->setData(m_containersModel->index(iterator.key()), iterator.value(), + ContainersModel::Roles::ConfigRole); + if (container != iterator.key()) { // skip the newly installed container + isInstalledContainerAddedToGui = true; + } + } + } + if (isInstalledContainerAddedToGui) { + finishMessage += tr("\nAlready installed containers were found on the server. " + "All installed containers have been added to the application"); + } + + emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other); + return; + } + + emit installationErrorOccurred(errorString(errorCode)); +} + +bool InstallController::isServerAlreadyExists() +{ + for (int i = 0; i < m_serversModel->getServersCount(); i++) { + auto modelIndex = m_serversModel->index(i); + const ServerCredentials credentials = + qvariant_cast(m_serversModel->data(modelIndex, ServersModel::Roles::CredentialsRole)); + if (m_currentlyInstalledServerCredentials.hostName == credentials.hostName + && m_currentlyInstalledServerCredentials.port == credentials.port) { + emit serverAlreadyExists(i); + return true; + } + } + return false; +} + +void InstallController::scanServerForInstalledContainers() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + ServerController serverController(m_settings); + + QMap installedContainers; + ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); + + if (errorCode == ErrorCode::NoError) { + bool isInstalledContainerAddedToGui = false; + + for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { + auto modelIndex = m_containersModel->index(iterator.key()); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(modelIndex, ContainersModel::Roles::ConfigRole)); + if (containerConfig.isEmpty()) { + m_containersModel->setData(m_containersModel->index(iterator.key()), iterator.value(), + ContainersModel::Roles::ConfigRole); + isInstalledContainerAddedToGui = true; + } + } + + emit scanServerFinished(isInstalledContainerAddedToGui); + return; + } + + emit installationErrorOccurred(errorString(errorCode)); +} + +void InstallController::updateContainer(QJsonObject config) +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + const DockerContainer container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + auto modelIndex = m_containersModel->index(container); + QJsonObject oldContainerConfig = + qvariant_cast(m_containersModel->data(modelIndex, ContainersModel::Roles::ConfigRole)); + + ServerController serverController(m_settings); + connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); + + auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); + if (errorCode == ErrorCode::NoError) { + m_containersModel->setData(modelIndex, config, ContainersModel::Roles::ConfigRole); + m_protocolModel->updateModel(config); + + if ((serverIndex == m_serversModel->getDefaultServerIndex()) + && (container == m_containersModel->getDefaultContainer())) { + emit currentContainerUpdated(); + } else { + emit updateContainerFinished(tr("Settings updated successfully")); + } + + return; + } + + emit installationErrorOccurred(errorString(errorCode)); +} + +void InstallController::removeCurrentlyProcessedServer() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); + + m_serversModel->removeServer(); + emit removeCurrentlyProcessedServerFinished(tr("Server '%1' was removed").arg(serverName)); +} + +void InstallController::removeAllContainers() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); + + ErrorCode errorCode = m_containersModel->removeAllContainers(); + if (errorCode == ErrorCode::NoError) { + emit removeAllContainersFinished(tr("All containers from server '%1' have been removed").arg(serverName)); + return; + } + emit installationErrorOccurred(errorString(errorCode)); +} + +void InstallController::removeCurrentlyProcessedContainer() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); + + int container = m_containersModel->getCurrentlyProcessedContainerIndex(); + QString containerName = m_containersModel->data(container, ContainersModel::Roles::NameRole).toString(); + + ErrorCode errorCode = m_containersModel->removeCurrentlyProcessedContainer(); + if (errorCode == ErrorCode::NoError) { + + emit removeCurrentlyProcessedContainerFinished(tr("%1 has been removed from the server '%2'").arg(containerName).arg(serverName)); + return; + } + emit installationErrorOccurred(errorString(errorCode)); +} + +QRegularExpression InstallController::ipAddressPortRegExp() +{ + return Utils::ipAddressPortRegExp(); +} + +QRegularExpression InstallController::ipAddressRegExp() +{ + return Utils::ipAddressRegExp(); +} + +void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, + const QString &secretData) +{ + m_currentlyInstalledServerCredentials.hostName = hostName; + if (m_currentlyInstalledServerCredentials.hostName.contains(":")) { + m_currentlyInstalledServerCredentials.port = + m_currentlyInstalledServerCredentials.hostName.split(":").at(1).toInt(); + m_currentlyInstalledServerCredentials.hostName = m_currentlyInstalledServerCredentials.hostName.split(":").at(0); + } + m_currentlyInstalledServerCredentials.userName = userName; + m_currentlyInstalledServerCredentials.secretData = secretData; +} + +void InstallController::setShouldCreateServer(bool shouldCreateServer) +{ + m_shouldCreateServer = shouldCreateServer; +} + +void InstallController::mountSftpDrive(const QString &port, const QString &password, const QString &username) +{ + QString mountPath; + QString cmd; + + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + QString hostname = serverCredentials.hostName; + +#ifdef Q_OS_WINDOWS + mountPath = getNextDriverLetter() + ":"; + // QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3") + // .arg(labelTftpUserNameText()) + // .arg(labelTftpPortText()) + // .arg(labelTftpPasswordText()); + + cmd = "C:\\Program Files\\SSHFS-Win\\bin\\sshfs.exe"; +#elif defined AMNEZIA_DESKTOP + mountPath = + QString("%1/sftp:%2:%3").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), hostname, port); + QDir dir(mountPath); + if (!dir.exists()) { + dir.mkpath(mountPath); + } + + cmd = "/usr/local/bin/sshfs"; +#endif + +#ifdef AMNEZIA_DESKTOP + QSharedPointer process; + process.reset(new QProcess()); + m_sftpMountProcesses.append(process); + process->setProcessChannelMode(QProcess::MergedChannels); + + connect(process.get(), &QProcess::readyRead, this, [this, process, mountPath]() { + QString s = process->readAll(); + if (s.contains("The service sshfs has been started")) { + QDesktopServices::openUrl(QUrl("file:///" + mountPath)); + } + qDebug() << s; + }); + + process->setProgram(cmd); + + QString args = QString("%1@%2:/ %3 " + "-o port=%4 " + "-f " + "-o reconnect " + "-o rellinks " + "-o fstypename=SSHFS " + "-o ssh_command=/usr/bin/ssh.exe " + "-o UserKnownHostsFile=/dev/null " + "-o StrictHostKeyChecking=no " + "-o password_stdin") + .arg(username, hostname, mountPath, port); + + // args.replace("\n", " "); + // args.replace("\r", " "); + // #ifndef Q_OS_WIN + // args.replace("reconnect-orellinks", ""); + // #endif + process->setArguments(args.split(" ", Qt::SkipEmptyParts)); + process->start(); + process->waitForStarted(50); + if (process->state() != QProcess::Running) { + qDebug() << "onPushButtonSftpMountDriveClicked process not started"; + qDebug() << args; + } else { + process->write((password + "\n").toUtf8()); + } + + // qDebug().noquote() << "onPushButtonSftpMountDriveClicked" << args; + +#endif +} + +bool InstallController::checkSshConnection() +{ + ServerController serverController(m_settings); + ErrorCode errorCode = ErrorCode::NoError; + m_privateKeyPassphrase = ""; + + if (m_currentlyInstalledServerCredentials.secretData.contains("BEGIN") + && m_currentlyInstalledServerCredentials.secretData.contains("PRIVATE KEY")) { + auto passphraseCallback = [this]() { + emit passphraseRequestStarted(); + QEventLoop loop; + QObject::connect(this, &InstallController::passphraseRequestFinished, &loop, &QEventLoop::quit); + loop.exec(); + + return m_privateKeyPassphrase; + }; + + QString decryptedPrivateKey; + errorCode = serverController.getDecryptedPrivateKey(m_currentlyInstalledServerCredentials, decryptedPrivateKey, + passphraseCallback); + if (errorCode == ErrorCode::NoError) { + m_currentlyInstalledServerCredentials.secretData = decryptedPrivateKey; + } else { + emit installationErrorOccurred(errorString(errorCode)); + return false; + } + } + + QString output; + output = serverController.checkSshConnection(m_currentlyInstalledServerCredentials, &errorCode); + + if (errorCode != ErrorCode::NoError) { + emit installationErrorOccurred(errorString(errorCode)); + return false; + } else { + if (output.contains(tr("Please login as the user"))) { + output.replace("\n", ""); + emit installationErrorOccurred(output); + return false; + } + } + return true; +} + +void InstallController::setEncryptedPassphrase(QString passphrase) +{ + m_privateKeyPassphrase = passphrase; + emit passphraseRequestFinished(); +} + +void InstallController::addEmptyServer() +{ + QJsonObject server; + server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); + server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName); + server.insert(config_key::password, m_currentlyInstalledServerCredentials.secretData); + server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); + server.insert(config_key::description, m_settings->nextAvailableServerName()); + + server.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); + + m_serversModel->addServer(server); + + emit installServerFinished(tr("Server added successfully")); +} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h new file mode 100644 index 00000000..a5fd2875 --- /dev/null +++ b/client/ui/controllers/installController.h @@ -0,0 +1,92 @@ +#ifndef INSTALLCONTROLLER_H +#define INSTALLCONTROLLER_H + +#include +#include + +#include "containers/containers_defs.h" +#include "core/defs.h" +#include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" +#include "ui/models/protocols_model.h" + +class InstallController : public QObject +{ + Q_OBJECT +public: + explicit InstallController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const QSharedPointer &protocolsModel, + const std::shared_ptr &settings, QObject *parent = nullptr); + ~InstallController(); + +public slots: + void install(DockerContainer container, int port, TransportProto transportProto); + void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, + const QString &secretData); + void setShouldCreateServer(bool shouldCreateServer); + + void scanServerForInstalledContainers(); + + void updateContainer(QJsonObject config); + + void removeCurrentlyProcessedServer(); + void removeAllContainers(); + void removeCurrentlyProcessedContainer(); + + QRegularExpression ipAddressPortRegExp(); + QRegularExpression ipAddressRegExp(); + + void mountSftpDrive(const QString &port, const QString &password, const QString &username); + + bool checkSshConnection(); + + void setEncryptedPassphrase(QString passphrase); + + void addEmptyServer(); + +signals: + void installContainerFinished(const QString &finishMessage, bool isServiceInstall); + void installServerFinished(const QString &finishMessage); + + void updateContainerFinished(const QString& message); + + void scanServerFinished(bool isInstalledContainerFound); + + void removeCurrentlyProcessedServerFinished(const QString &finishedMessage); + void removeAllContainersFinished(const QString &finishedMessage); + void removeCurrentlyProcessedContainerFinished(const QString &finishedMessage); + + void installationErrorOccurred(const QString &errorMessage); + + void serverAlreadyExists(int serverIndex); + + void passphraseRequestStarted(); + void passphraseRequestFinished(); + + void serverIsBusy(const bool isBusy); + + void currentContainerUpdated(); + +private: + void installServer(DockerContainer container, QJsonObject &config); + void installContainer(DockerContainer container, QJsonObject &config); + bool isServerAlreadyExists(); + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + QSharedPointer m_protocolModel; + std::shared_ptr m_settings; + + ServerCredentials m_currentlyInstalledServerCredentials; + + bool m_shouldCreateServer; + + QString m_privateKeyPassphrase; + +#ifndef Q_OS_IOS + QList> m_sftpMountProcesses; +#endif +}; + +#endif // INSTALLCONTROLLER_H diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp new file mode 100644 index 00000000..ed60500a --- /dev/null +++ b/client/ui/controllers/pageController.cpp @@ -0,0 +1,164 @@ +#include "pageController.h" + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif + +#ifdef Q_OS_ANDROID + #include "../../platforms/android/androidutils.h" + #include +#endif +#if defined Q_OS_MAC + #include "ui/macos_util.h" +#endif + +PageController::PageController(const QSharedPointer &serversModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_settings(settings) +{ +#ifdef Q_OS_ANDROID + // Change color of navigation and status bar's + auto initialPageNavigationBarColor = getInitialPageNavigationBarColor(); + AndroidUtils::runOnAndroidThreadSync([&initialPageNavigationBarColor]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + window.callMethod("addFlags", "(I)V", 0x80000000); + window.callMethod("clearFlags", "(I)V", 0x04000000); + window.callMethod("setStatusBarColor", "(I)V", 0xFF0E0E11); + window.callMethod("setNavigationBarColor", "(I)V", initialPageNavigationBarColor); + } + }); +#endif + +#if defined Q_OS_MACX + connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); }); + connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); }); +#endif + + m_isTriggeredByConnectButton = false; +} + +QString PageController::getInitialPage() +{ + if (m_serversModel->getServersCount()) { + if (m_serversModel->getDefaultServerIndex() < 0) { + auto defaultServerIndex = m_serversModel->index(0); + m_serversModel->setData(defaultServerIndex, true, ServersModel::Roles::IsDefaultRole); + } + return getPagePath(PageLoader::PageEnum::PageStart); + } else { + return getPagePath(PageLoader::PageEnum::PageSetupWizardStart); + } +} + +QString PageController::getPagePath(PageLoader::PageEnum page) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + QString pageName = metaEnum.valueToKey(static_cast(page)); + return "qrc:/ui/qml/Pages2/" + pageName + ".qml"; +} + +void PageController::closeWindow() +{ +#ifdef Q_OS_ANDROID + qApp->quit(); +#else + if (m_serversModel->getServersCount() == 0) { + qApp->quit(); + } else { + emit hideMainWindow(); + } +#endif +} + +void PageController::keyPressEvent(Qt::Key key) +{ + switch (key) { + case Qt::Key_Back: emit closePage(); + default: return; + } +} + +unsigned int PageController::getInitialPageNavigationBarColor() +{ + if (m_serversModel->getServersCount()) { + return 0xFF1C1D21; + } else { + return 0xFF0E0E11; + } +} + +void PageController::updateNavigationBarColor(const int color) +{ +#ifdef Q_OS_ANDROID + // Change color of navigation bar + AndroidUtils::runOnAndroidThreadSync([&color]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + window.callMethod("setNavigationBarColor", "(I)V", color); + } + }); +#endif +} + +void PageController::showOnStartup() +{ + if (!m_settings->isStartMinimized()) { + emit raiseMainWindow(); + } else { +#ifdef Q_OS_WIN + emit hideMainWindow(); +#elif defined Q_OS_MACX + setDockIconVisible(false); +#endif + } +} + +void PageController::updateDrawerRootPage(PageLoader::PageEnum page) +{ + m_drawerLayer = 0; + m_currentRootPage = page; +} + +void PageController::goToDrawerRootPage() +{ + + m_drawerLayer = 0; + + emit showTopCloseButton(false); + emit forceCloseDrawer(); +} + +void PageController::drawerOpen() +{ + m_drawerLayer = m_drawerLayer + 1; + emit showTopCloseButton(true); +} + +void PageController::drawerClose() +{ + m_drawerLayer = m_drawerLayer -1; + if (m_drawerLayer <= 0) { + emit showTopCloseButton(false); + m_drawerLayer = 0; + } +} + +bool PageController::isTriggeredByConnectButton() +{ + return m_isTriggeredByConnectButton; +} + +void PageController::setTriggeredBtConnectButton(bool trigger) +{ + m_isTriggeredByConnectButton = trigger; +} + +void PageController::closeApplication() +{ + qApp->quit(); +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h new file mode 100644 index 00000000..20c3bbed --- /dev/null +++ b/client/ui/controllers/pageController.h @@ -0,0 +1,133 @@ +#ifndef PAGECONTROLLER_H +#define PAGECONTROLLER_H + +#include +#include + +#include "ui/models/servers_model.h" + +namespace PageLoader +{ + Q_NAMESPACE + enum class PageEnum { + PageStart = 0, + PageHome, + PageShare, + PageDeinstalling, + + PageSettingsServersList, + PageSettings, + PageSettingsServerData, + PageSettingsServerInfo, + PageSettingsServerProtocols, + PageSettingsServerServices, + PageSettingsServerProtocol, + PageSettingsConnection, + PageSettingsDns, + PageSettingsApplication, + PageSettingsBackup, + PageSettingsAbout, + PageSettingsLogging, + PageSettingsSplitTunneling, + + PageServiceSftpSettings, + PageServiceTorWebsiteSettings, + PageServiceDnsSettings, + + PageSetupWizardStart, + PageSetupWizardCredentials, + PageSetupWizardProtocols, + PageSetupWizardEasy, + PageSetupWizardProtocolSettings, + PageSetupWizardInstalling, + PageSetupWizardConfigSource, + PageSetupWizardTextKey, + PageSetupWizardViewConfig, + PageSetupWizardQrReader, + + PageProtocolOpenVpnSettings, + PageProtocolShadowSocksSettings, + PageProtocolCloakSettings, + PageProtocolWireGuardSettings, + PageProtocolAwgSettings, + PageProtocolIKev2Settings, + PageProtocolRaw + }; + Q_ENUM_NS(PageEnum) + + static void declareQmlPageEnum() + { + qmlRegisterUncreatableMetaObject(PageLoader::staticMetaObject, "PageEnum", 1, 0, "PageEnum", "Error: only enums"); + } +} + +class PageController : public QObject +{ + Q_OBJECT +public: + explicit PageController(const QSharedPointer &serversModel, const std::shared_ptr &settings, + QObject *parent = nullptr); + +public slots: + QString getInitialPage(); + QString getPagePath(PageLoader::PageEnum page); + + void closeWindow(); + void keyPressEvent(Qt::Key key); + + unsigned int getInitialPageNavigationBarColor(); + void updateNavigationBarColor(const int color); + + void showOnStartup(); + + void updateDrawerRootPage(PageLoader::PageEnum page); + void goToDrawerRootPage(); + void drawerOpen(); + void drawerClose(); + + bool isTriggeredByConnectButton(); + void setTriggeredBtConnectButton(bool trigger); + + void closeApplication(); + +signals: + void goToPage(PageLoader::PageEnum page, bool slide = true); + void goToStartPage(); + void goToPageHome(); + void goToPageSettings(); + void goToPageViewConfig(); + void goToPageSettingsServerServices(); + void goToPageSettingsBackup(); + + void closePage(); + + void restorePageHomeState(bool isContainerInstalled = false); + void replaceStartPage(); + + void showErrorMessage(const QString &errorMessage); + void showNotificationMessage(const QString &message); + + void showBusyIndicator(bool visible); + void enableTabBar(bool enabled); + + void hideMainWindow(); + void raiseMainWindow(); + + void showPassphraseRequestDrawer(); + void passphraseRequestDrawerClosed(QString passphrase); + + void showTopCloseButton(bool visible); + void forceCloseDrawer(); + +private: + QSharedPointer m_serversModel; + + std::shared_ptr m_settings; + + PageLoader::PageEnum m_currentRootPage; + int m_drawerLayer; + + bool m_isTriggeredByConnectButton; +}; + +#endif // PAGECONTROLLER_H diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp new file mode 100644 index 00000000..78d0dd67 --- /dev/null +++ b/client/ui/controllers/settingsController.cpp @@ -0,0 +1,196 @@ +#include "settingsController.h" + +#include + +#include "logger.h" +#include "systemController.h" +#include "ui/qautostart.h" +#include "version.h" +#ifdef Q_OS_ANDROID + #include "../../platforms/android/android_controller.h" + #include "../../platforms/android/androidutils.h" + #include +#endif + +SettingsController::SettingsController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const QSharedPointer &languageModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), + m_serversModel(serversModel), + m_containersModel(containersModel), + m_languageModel(languageModel), + m_settings(settings) +{ + m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_VERSION), __DATE__); + +#ifdef Q_OS_ANDROID + if (!m_settings->isScreenshotsEnabled()) { + // Set security screen for Android app + AndroidUtils::runOnAndroidThreadSync([]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + const int FLAG_SECURE = 8192; + window.callMethod("addFlags", "(I)V", FLAG_SECURE); + } + }); + } +#endif +} + +void SettingsController::toggleAmneziaDns(bool enable) +{ + m_settings->setUseAmneziaDns(enable); +} + +bool SettingsController::isAmneziaDnsEnabled() +{ + return m_settings->useAmneziaDns(); +} + +QString SettingsController::getPrimaryDns() +{ + return m_settings->primaryDns(); +} + +void SettingsController::setPrimaryDns(const QString &dns) +{ + m_settings->setPrimaryDns(dns); + emit primaryDnsChanged(); +} + +QString SettingsController::getSecondaryDns() +{ + return m_settings->secondaryDns(); +} + +void SettingsController::setSecondaryDns(const QString &dns) +{ + return m_settings->setSecondaryDns(dns); + emit secondaryDnsChanged(); +} + +bool SettingsController::isLoggingEnabled() +{ + return m_settings->isSaveLogs(); +} + +void SettingsController::toggleLogging(bool enable) +{ + m_settings->setSaveLogs(enable); + emit loggingStateChanged(); +} + +void SettingsController::openLogsFolder() +{ + Logger::openLogsFolder(); +} + +void SettingsController::exportLogsFile(const QString &fileName) +{ + SystemController::saveFile(fileName, Logger::getLogFile()); +} + +void SettingsController::clearLogs() +{ + Logger::clearLogs(); + Logger::clearServiceLogs(); +} + +void SettingsController::backupAppConfig(const QString &fileName) +{ + SystemController::saveFile(fileName, m_settings->backupAppConfig()); +} + +void SettingsController::restoreAppConfig(const QString &fileName) +{ + QFile file(fileName); + + file.open(QIODevice::ReadOnly); + + QByteArray data = file.readAll(); + + bool ok = m_settings->restoreAppConfig(data); + if (ok) { + m_serversModel->resetModel(); + m_languageModel->changeLanguage( + static_cast(m_languageModel->getCurrentLanguageIndex())); + emit restoreBackupFinished(); + } else { + emit changeSettingsErrorOccurred(tr("Backup file is corrupted")); + } +} + +QString SettingsController::getAppVersion() +{ + return m_appVersion; +} + +void SettingsController::clearSettings() +{ + m_settings->clearSettings(); + m_serversModel->resetModel(); + m_languageModel->changeLanguage( + static_cast(m_languageModel->getCurrentLanguageIndex())); + emit changeSettingsFinished(tr("All settings have been reset to default values")); +} + +void SettingsController::clearCachedProfiles() +{ + m_containersModel->clearCachedProfiles(); + emit changeSettingsFinished(tr("Cached profiles cleared")); +} + +bool SettingsController::isAutoConnectEnabled() +{ + return m_settings->isAutoConnect(); +} + +void SettingsController::toggleAutoConnect(bool enable) +{ + m_settings->setAutoConnect(enable); +} + +bool SettingsController::isAutoStartEnabled() +{ + return Autostart::isAutostart(); +} + +void SettingsController::toggleAutoStart(bool enable) +{ + Autostart::setAutostart(enable); +} + +bool SettingsController::isStartMinimizedEnabled() +{ + return m_settings->isStartMinimized(); +} + +void SettingsController::toggleStartMinimized(bool enable) +{ + m_settings->setStartMinimized(enable); +} + +bool SettingsController::isScreenshotsEnabled() +{ + return m_settings->isScreenshotsEnabled(); +} + +void SettingsController::toggleScreenshotsEnabled(bool enable) +{ + m_settings->setScreenshotsEnabled(enable); +#ifdef Q_OS_ANDROID + std::string command = enable ? "clearFlags" : "addFlags"; + + // Set security screen for Android app + AndroidUtils::runOnAndroidThreadSync([&command]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + const int FLAG_SECURE = 8192; + window.callMethod(command.c_str(), "(I)V", FLAG_SECURE); + } + }); +#endif +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h new file mode 100644 index 00000000..be041f3e --- /dev/null +++ b/client/ui/controllers/settingsController.h @@ -0,0 +1,82 @@ +#ifndef SETTINGSCONTROLLER_H +#define SETTINGSCONTROLLER_H + +#include + +#include "ui/models/containers_model.h" +#include "ui/models/languageModel.h" +#include "ui/models/servers_model.h" + +class SettingsController : public QObject +{ + Q_OBJECT +public: + explicit SettingsController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const QSharedPointer &languageModel, + const std::shared_ptr &settings, QObject *parent = nullptr); + + Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) + Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) + Q_PROPERTY(bool isLoggingEnabled READ isLoggingEnabled WRITE toggleLogging NOTIFY loggingStateChanged) + +public slots: + void toggleAmneziaDns(bool enable); + bool isAmneziaDnsEnabled(); + + QString getPrimaryDns(); + void setPrimaryDns(const QString &dns); + + QString getSecondaryDns(); + void setSecondaryDns(const QString &dns); + + bool isLoggingEnabled(); + void toggleLogging(bool enable); + + void openLogsFolder(); + void exportLogsFile(const QString &fileName); + void clearLogs(); + + void backupAppConfig(const QString &fileName); + void restoreAppConfig(const QString &fileName); + + QString getAppVersion(); + + void clearSettings(); + void clearCachedProfiles(); + + bool isAutoConnectEnabled(); + void toggleAutoConnect(bool enable); + + bool isAutoStartEnabled(); + void toggleAutoStart(bool enable); + + bool isStartMinimizedEnabled(); + void toggleStartMinimized(bool enable); + + bool isScreenshotsEnabled(); + void toggleScreenshotsEnabled(bool enable); + +signals: + void primaryDnsChanged(); + void secondaryDnsChanged(); + void loggingStateChanged(); + + void restoreBackupFinished(); + void changeSettingsFinished(const QString &finishedMessage); + void changeSettingsErrorOccurred(const QString &errorMessage); + + void saveFile(const QString &fileName, const QString &data); + + void importBackupFromOutside(QString filePath); + +private: + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + QSharedPointer m_languageModel; + std::shared_ptr m_settings; + + QString m_appVersion; +}; + +#endif // SETTINGSCONTROLLER_H diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp new file mode 100644 index 00000000..8c420899 --- /dev/null +++ b/client/ui/controllers/sitesController.cpp @@ -0,0 +1,151 @@ +#include "sitesController.h" + +#include +#include +#include + +#include "systemController.h" +#include "utilities.h" + +SitesController::SitesController(const std::shared_ptr &settings, + const QSharedPointer &vpnConnection, + const QSharedPointer &sitesModel, QObject *parent) + : QObject(parent), m_settings(settings), m_vpnConnection(vpnConnection), m_sitesModel(sitesModel) +{ +} + +void SitesController::addSite(QString hostname) +{ + if (hostname.isEmpty()) { + return; + } + + if (!hostname.contains(".")) { + emit errorOccurred(tr("Hostname not look like ip adress or domain name")); + return; + } + + if (!Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + // get domain name if it present + hostname.replace("https://", ""); + hostname.replace("http://", ""); + hostname.replace("ftp://", ""); + + hostname = hostname.split("/", Qt::SkipEmptyParts).first(); + } + + const auto &processSite = [this](const QString &hostname, const QString &ip) { + m_sitesModel->addSite(hostname, ip); + + if (!ip.isEmpty()) { + QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, + Q_ARG(QStringList, QStringList() << ip)); + } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, + Q_ARG(QStringList, QStringList() << hostname)); + } + QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection); + }; + + const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) { + const QList &addresses = hostInfo.addresses(); + for (const QHostAddress &addr : hostInfo.addresses()) { + if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { + processSite(hostInfo.hostName(), addr.toString()); + break; + } + } + }; + + if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + processSite(hostname, ""); + } else { + processSite(hostname, ""); + QHostInfo::lookupHost(hostname, this, resolveCallback); + } + + emit finished(tr("New site added: %1").arg(hostname)); +} + +void SitesController::removeSite(int index) +{ + auto modelIndex = m_sitesModel->index(index); + auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString(); + m_sitesModel->removeSite(modelIndex); + + QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection, + Q_ARG(QStringList, QStringList() << hostname)); + QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection); + + emit finished(tr("Site removed: %1").arg(hostname)); +} + +void SitesController::importSites(const QString &fileName, bool replaceExisting) +{ + QFile file(fileName); + + if (!file.open(QIODevice::ReadOnly)) { + emit errorOccurred(tr("Can't open file: %1").arg(fileName)); + return; + } + + QByteArray jsonData = file.readAll(); + QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); + if (jsonDocument.isNull()) { + emit errorOccurred(tr("Failed to parse JSON data from file: %1").arg(fileName)); + return; + } + + if (!jsonDocument.isArray()) { + emit errorOccurred(tr("The JSON data is not an array in file: %1").arg(fileName)); + return; + } + + auto jsonArray = jsonDocument.array(); + QMap sites; + QStringList ips; + + for (auto jsonValue : jsonArray) { + auto jsonObject = jsonValue.toObject(); + auto hostname = jsonObject.value("hostname").toString(""); + auto ip = jsonObject.value("ip").toString(""); + + if (!hostname.contains(".") && !Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + qDebug() << hostname << " not look like ip adress or domain name"; + continue; + } + + if (ip.isEmpty()) { + ips.append(hostname); + } else { + ips.append(ip); + } + sites.insert(hostname, ip); + } + + m_sitesModel->addSites(sites, replaceExisting); + + QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); + QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection); + + emit finished(tr("Import completed")); +} + +void SitesController::exportSites(const QString &fileName) +{ + auto sites = m_sitesModel->getCurrentSites(); + + QJsonArray jsonArray; + + for (const auto &site : sites) { + QJsonObject jsonObject { { "hostname", site.first }, { "ip", site.second } }; + jsonArray.append(jsonObject); + } + + QJsonDocument jsonDocument(jsonArray); + QByteArray jsonData = jsonDocument.toJson(); + + SystemController::saveFile(fileName, jsonData); + + emit finished(tr("Export completed")); +} diff --git a/client/ui/controllers/sitesController.h b/client/ui/controllers/sitesController.h new file mode 100644 index 00000000..e66478da --- /dev/null +++ b/client/ui/controllers/sitesController.h @@ -0,0 +1,38 @@ +#ifndef SITESCONTROLLER_H +#define SITESCONTROLLER_H + +#include + +#include "settings.h" +#include "ui/models/sites_model.h" +#include "vpnconnection.h" + +class SitesController : public QObject +{ + Q_OBJECT +public: + explicit SitesController(const std::shared_ptr &settings, + const QSharedPointer &vpnConnection, + const QSharedPointer &sitesModel, QObject *parent = nullptr); + +public slots: + void addSite(QString hostname); + void removeSite(int index); + + void importSites(const QString &fileName, bool replaceExisting); + void exportSites(const QString &fileName); + +signals: + void errorOccurred(const QString &errorMessage); + void finished(const QString &message); + + void saveFile(const QString &fileName, const QString &data); + +private: + std::shared_ptr m_settings; + + QSharedPointer m_vpnConnection; + QSharedPointer m_sitesModel; +}; + +#endif // SITESCONTROLLER_H diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp new file mode 100644 index 00000000..7de071cc --- /dev/null +++ b/client/ui/controllers/systemController.cpp @@ -0,0 +1,131 @@ +#include "systemController.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" +#endif + +#ifdef Q_OS_IOS + #include "platforms/ios/MobileUtils.h" + #include +#endif + +SystemController::SystemController(const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_settings(settings) +{ +} + +void SystemController::saveFile(QString fileName, const QString &data) +{ +#if defined Q_OS_ANDROID + AndroidController::instance()->shareConfig(data, fileName); + return; +#endif + +#ifdef Q_OS_IOS + QUrl fileUrl = QDir::tempPath() + "/" + fileName; + QFile file(fileUrl.toString()); +#else + QFile file(fileName); +#endif + + // todo check if save successful + file.open(QIODevice::WriteOnly); + file.write(data.toUtf8()); + file.close(); + +#ifdef Q_OS_IOS + QStringList filesToSend; + filesToSend.append(fileUrl.toString()); + MobileUtils mobileUtils; + // todo check if save successful + mobileUtils.shareText(filesToSend); + return; +#else + QFileInfo fi(fileName); + QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); +#endif +} + +QString SystemController::getFileName(const QString &acceptLabel, const QString &nameFilter, + const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix) +{ + QString fileName; +#ifdef Q_OS_IOS + + MobileUtils mobileUtils; + fileName = mobileUtils.openFile(); + if (fileName.isEmpty()) { + return fileName; + } + + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } + + return fileName; +#endif + + QObject *mainFileDialog = m_qmlRoot->findChild("mainFileDialog").parent(); + if (!mainFileDialog) { + return ""; + } + + mainFileDialog->setProperty("acceptLabel", QVariant::fromValue(acceptLabel)); + mainFileDialog->setProperty("nameFilters", QVariant::fromValue(QStringList(nameFilter))); + if (!selectedFile.isEmpty()) { + mainFileDialog->setProperty("selectedFile", QVariant::fromValue(selectedFile)); + } + mainFileDialog->setProperty("isSaveMode", QVariant::fromValue(isSaveMode)); + mainFileDialog->setProperty("defaultSuffix", QVariant::fromValue(defaultSuffix)); + QMetaObject::invokeMethod(mainFileDialog, "open"); + + bool isFileDialogAccepted = false; + QEventLoop wait; + QObject::connect(this, &SystemController::fileDialogClosed, [&wait, &isFileDialogAccepted](const bool isAccepted) { + isFileDialogAccepted = isAccepted; + wait.quit(); + }); + wait.exec(); + QObject::disconnect(this, &SystemController::fileDialogClosed, nullptr, nullptr); + + if (!isFileDialogAccepted) { + return ""; + } + + fileName = mainFileDialog->property("selectedFile").toString(); + +#ifdef Q_OS_ANDROID + // patch for files containing spaces etc + const QString sep { "raw%3A%2F" }; + if (fileName.startsWith("content://") && fileName.contains(sep)) { + QString contentUrl = fileName.split(sep).at(0); + QString rawUrl = fileName.split(sep).at(1); + rawUrl.replace(" ", "%20"); + fileName = contentUrl + sep + rawUrl; + } + + return fileName; +#endif + + return QUrl(fileName).toLocalFile(); +} + +void SystemController::setQmlRoot(QObject *qmlRoot) +{ + m_qmlRoot = qmlRoot; +} diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h new file mode 100644 index 00000000..274df234 --- /dev/null +++ b/client/ui/controllers/systemController.h @@ -0,0 +1,31 @@ +#ifndef SYSTEMCONTROLLER_H +#define SYSTEMCONTROLLER_H + +#include + +#include "settings.h" + +class SystemController : public QObject +{ + Q_OBJECT +public: + explicit SystemController(const std::shared_ptr &setting, QObject *parent = nullptr); + + static void saveFile(QString fileName, const QString &data); + +public slots: + QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "", + const bool isSaveMode = false, const QString &defaultSuffix = ""); + + void setQmlRoot(QObject *qmlRoot); + +signals: + void fileDialogClosed(const bool isAccepted); + +private: + std::shared_ptr m_settings; + + QObject *m_qmlRoot; +}; + +#endif // SYSTEMCONTROLLER_H diff --git a/client/ui/macos_util.h b/client/ui/macos_util.h index f5add902..15677e42 100644 --- a/client/ui/macos_util.h +++ b/client/ui/macos_util.h @@ -1,9 +1,12 @@ #ifndef OSXUTIL_H #define OSXUTIL_H + +#ifndef Q_OS_IOS #include #include void setDockIconVisible(bool visible); void fixWidget(QWidget *widget); +#endif #endif diff --git a/client/ui/macos_util.mm b/client/ui/macos_util.mm index 8adda58a..3947b89b 100644 --- a/client/ui/macos_util.mm +++ b/client/ui/macos_util.mm @@ -1,60 +1,86 @@ -#include -#include #include "macos_util.h" +#include +#include + +#include -#import #import +#import + +// void setDockIconVisible(bool visible) +//{ +// QProcess process; +// process.start( +// "osascript", +// { "-e tell application \"System Events\" to get properties of (get application process \"AmneziaVPN\")" }); +// process.waitForFinished(3000); +// const auto output = QString::fromLocal8Bit(process.readAllStandardOutput()); + +// qDebug() << output; + +// if (visible) { +// [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; +// } else { +// [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; +// } +//} void setDockIconVisible(bool visible) { - if (!visible) { - [NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory]; + ProcessSerialNumber psn = { 0, kCurrentProcess }; + if (visible) { + TransformProcessType(&psn, kProcessTransformToForegroundApplication); } else { - [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular]; + TransformProcessType(&psn, kProcessTransformToBackgroundApplication); } } -//this Objective-c class is used to override the action of system close button and zoom button -//https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results -@interface ButtonPasser : NSObject{ +// this Objective-c class is used to override the action of system close button and zoom button +// https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results +@interface ButtonPasser : NSObject { } -@property(readwrite) QMainWindow* window; +@property (readwrite) QMainWindow *window; + (void)closeButtonAction:(id)sender; - (void)zoomButtonAction:(id)sender; @end -@implementation ButtonPasser{ +@implementation ButtonPasser { } + (void)closeButtonAction:(id)sender { Q_UNUSED(sender); ProcessSerialNumber pn; - GetFrontProcess (&pn); - ShowHideProcess(&pn,false); + GetFrontProcess(&pn); + ShowHideProcess(&pn, false); } - (void)zoomButtonAction:(id)sender { Q_UNUSED(sender); - if (0 == self.window) return; - if (self.window->isMaximized()) self.window->showNormal(); - else self.window->showMaximized(); + if (0 == self.window) + return; + if (self.window->isMaximized()) + self.window->showNormal(); + else + self.window->showMaximized(); } @end void fixWidget(QWidget *widget) { NSView *view = (NSView *)widget->winId(); - if (0 == view) return; + if (0 == view) + return; NSWindow *window = view.window; - if (0 == window) return; + if (0 == window) + return; - //override the action of close button - //https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results - //https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Target-Action/Target-Action.html -// NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton]; -// [closeButton setTarget:[ButtonPasser class]]; -// [closeButton setAction:@selector(closeButtonAction:)]; + // override the action of close button + // https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results + // https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Target-Action/Target-Action.html + // NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton]; + // [closeButton setTarget:[ButtonPasser class]]; + // [closeButton setAction:@selector(closeButtonAction:)]; [[window standardWindowButton:NSWindowZoomButton] setHidden:YES]; [[window standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 5468452e..6a4c0e63 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -1,10 +1,10 @@ #include "containers_model.h" -ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) : - m_settings(settings), - QAbstractListModel(parent) -{ +#include "core/servercontroller.h" +ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) +{ } int ContainersModel::rowCount(const QModelIndex &parent) const @@ -13,47 +13,253 @@ int ContainersModel::rowCount(const QModelIndex &parent) const return ContainerProps::allContainers().size(); } -QHash ContainersModel::roleNames() const { - QHash roles; - roles[NameRole] = "name_role"; - roles[DescRole] = "desc_role"; - roles[DefaultRole] = "default_role"; - roles[ServiceTypeRole] = "service_type_role"; - roles[IsInstalledRole] = "is_installed_role"; - return roles; +bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + DockerContainer container = ContainerProps::allContainers().at(index.row()); + + switch (role) { + case NameRole: + // return ContainerProps::containerHumanNames().value(container); + case DescriptionRole: + // return ContainerProps::containerDescriptions().value(container); + case ConfigRole: { + m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + if (m_defaultContainerIndex != DockerContainer::None) { + break; + } else if (ContainerProps::containerService(container) == ServiceType::Other) { + break; + } + } + case ServiceTypeRole: + // return ContainerProps::containerService(container); + case DockerContainerRole: + // return container; + case IsInstalledRole: + // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + case IsDefaultRole: { //todo remove + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); + m_defaultContainerIndex = container; + emit defaultContainerChanged(); + } + } + + emit dataChanged(index, index); + return true; } QVariant ContainersModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 - || index.row() >= ContainerProps::allContainers().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { return QVariant(); } - DockerContainer c = ContainerProps::allContainers().at(index.row()); - if (role == NameRole) { - return ContainerProps::containerHumanNames().value(c); + DockerContainer container = ContainerProps::allContainers().at(index.row()); + + switch (role) { + case NameRole: return ContainerProps::containerHumanNames().value(container); + case DescriptionRole: return ContainerProps::containerDescriptions().value(container); + case DetailedDescriptionRole: return ContainerProps::containerDetailedDescriptions().value(container); + case ConfigRole: { + if (container == DockerContainer::None) { + return QJsonObject(); + } + return m_containers.value(container); } - if (role == DescRole) { - return ContainerProps::containerDescriptions().value(c); - } - if (role == DefaultRole) { - return c == m_settings->defaultContainer(m_selectedServerIndex); - } - if (role == ServiceTypeRole) { - return ContainerProps::containerService(c); - } - if (role == IsInstalledRole) { - return m_settings->containers(m_selectedServerIndex).contains(c); + case ServiceTypeRole: return ContainerProps::containerService(container); + case DockerContainerRole: return container; + case IsEasySetupContainerRole: return ContainerProps::isEasySetupContainer(container); + case EasySetupHeaderRole: return ContainerProps::easySetupHeader(container); + case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container); + case EasySetupOrderRole: return ContainerProps::easySetupOrder(container); + case IsInstalledRole: return m_containers.contains(container); + case IsCurrentlyProcessedRole: return container == static_cast(m_currentlyProcessedContainerIndex); + case IsDefaultRole: return container == m_defaultContainerIndex; + case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container); + case IsShareableRole: return ContainerProps::isShareable(container); } + return QVariant(); } -void ContainersModel::setSelectedServerIndex(int index) +QVariant ContainersModel::data(const int index, int role) const { - beginResetModel(); - m_selectedServerIndex = index; - endResetModel(); + QModelIndex modelIndex = this->index(index); + return data(modelIndex, role); } +void ContainersModel::setCurrentlyProcessedServerIndex(const int index) +{ + beginResetModel(); + m_currentlyProcessedServerIndex = index; + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + m_defaultContainerIndex = m_settings->defaultContainer(m_currentlyProcessedServerIndex); + endResetModel(); + emit defaultContainerChanged(); +} +void ContainersModel::setCurrentlyProcessedContainerIndex(int index) +{ + m_currentlyProcessedContainerIndex = index; +} + +DockerContainer ContainersModel::getDefaultContainer() +{ + return m_defaultContainerIndex; +} + +QString ContainersModel::getDefaultContainerName() +{ + return ContainerProps::containerHumanNames().value(m_defaultContainerIndex); +} + +void ContainersModel::setDefaultContainer(int index) +{ + auto container = static_cast(index); + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); + m_defaultContainerIndex = container; + emit defaultContainerChanged(); +} + +int ContainersModel::getCurrentlyProcessedContainerIndex() +{ + return m_currentlyProcessedContainerIndex; +} + +QString ContainersModel::getCurrentlyProcessedContainerName() +{ + return ContainerProps::containerHumanNames().value(static_cast(m_currentlyProcessedContainerIndex)); +} + +QJsonObject ContainersModel::getCurrentlyProcessedContainerConfig() +{ + return qvariant_cast(data(index(m_currentlyProcessedContainerIndex), ConfigRole)); +} + +QStringList ContainersModel::getAllInstalledServicesName(const int serverIndex) +{ + QStringList servicesName; + const auto &containers = m_settings->containers(serverIndex); + for (const DockerContainer &container : containers.keys()) { + if (ContainerProps::containerService(container) == ServiceType::Other && m_containers.contains(container)) { + if (container == DockerContainer::Dns) { + servicesName.append("DNS"); + } else if (container == DockerContainer::Sftp) { + servicesName.append("SFTP"); + } else if (container == DockerContainer::TorWebSite) { + servicesName.append("TOR"); + } + } + } + servicesName.sort(); + return servicesName; +} + +ErrorCode ContainersModel::removeAllContainers() +{ + + ServerController serverController(m_settings); + ErrorCode errorCode = + serverController.removeAllContainers(m_settings->serverCredentials(m_currentlyProcessedServerIndex)); + + if (errorCode == ErrorCode::NoError) { + beginResetModel(); + + m_settings->setContainers(m_currentlyProcessedServerIndex, {}); + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + + setData(index(DockerContainer::None, 0), true, IsDefaultRole); + endResetModel(); + } + return errorCode; +} + +ErrorCode ContainersModel::removeCurrentlyProcessedContainer() +{ + ServerController serverController(m_settings); + auto credentials = m_settings->serverCredentials(m_currentlyProcessedServerIndex); + auto dockerContainer = static_cast(m_currentlyProcessedContainerIndex); + + ErrorCode errorCode = serverController.removeContainer(credentials, dockerContainer); + + if (errorCode == ErrorCode::NoError) { + beginResetModel(); + m_settings->removeContainerConfig(m_currentlyProcessedServerIndex, dockerContainer); + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + + if (m_defaultContainerIndex == m_currentlyProcessedContainerIndex) { + if (m_containers.isEmpty()) { + setData(index(DockerContainer::None, 0), true, IsDefaultRole); + } else { + setData(index(m_containers.begin().key(), 0), true, IsDefaultRole); + } + } + endResetModel(); + } + return errorCode; +} + +void ContainersModel::clearCachedProfiles() +{ + const auto &containers = m_settings->containers(m_currentlyProcessedServerIndex); + for (DockerContainer container : containers.keys()) { + m_settings->clearLastConnectionConfig(m_currentlyProcessedServerIndex, container); + } +} + +bool ContainersModel::isAmneziaDnsContainerInstalled() +{ + return m_containers.contains(DockerContainer::Dns); +} + +bool ContainersModel::isAmneziaDnsContainerInstalled(const int serverIndex) +{ + QMap containers = m_settings->containers(serverIndex); + return containers.contains(DockerContainer::Dns); +} + +bool ContainersModel::isAnyContainerInstalled() +{ + for (int row=0; row < rowCount(); row++) { + QModelIndex idx = this->index(row, 0); + + if (this->data(idx, IsInstalledRole).toBool() && + this->data(idx, ServiceTypeRole).toInt() == ServiceType::Vpn) { + return true; + } + } + + return false; +} + +void ContainersModel::updateContainersConfig() +{ + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); +} + +QHash ContainersModel::roleNames() const +{ + QHash roles; + roles[NameRole] = "name"; + roles[DescriptionRole] = "description"; + roles[DetailedDescriptionRole] = "detailedDescription"; + roles[ServiceTypeRole] = "serviceType"; + roles[DockerContainerRole] = "dockerContainer"; + roles[ConfigRole] = "config"; + + roles[IsEasySetupContainerRole] = "isEasySetupContainer"; + roles[EasySetupHeaderRole] = "easySetupHeader"; + roles[EasySetupDescriptionRole] = "easySetupDescription"; + roles[EasySetupOrderRole] = "easySetupOrder"; + + roles[IsInstalledRole] = "isInstalled"; + roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; + roles[IsDefaultRole] = "isDefault"; + roles[IsSupportedRole] = "isSupported"; + roles[IsShareableRole] = "isShareable"; + return roles; +} diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 36206855..997b21e3 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -3,36 +3,84 @@ #include #include -#include #include +#include -#include "settings.h" #include "containers/containers_defs.h" +#include "settings.h" class ContainersModel : public QAbstractListModel { Q_OBJECT public: ContainersModel(std::shared_ptr settings, QObject *parent = nullptr); -public: - enum SiteRoles { + + enum Roles { NameRole = Qt::UserRole + 1, - DescRole, - DefaultRole, + DescriptionRole, + DetailedDescriptionRole, ServiceTypeRole, - IsInstalledRole + ConfigRole, + DockerContainerRole, + + IsEasySetupContainerRole, + EasySetupHeaderRole, + EasySetupDescriptionRole, + EasySetupOrderRole, + + IsInstalledRole, + IsCurrentlyProcessedRole, + IsDefaultRole, + IsSupportedRole, + IsShareableRole }; int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - Q_INVOKABLE void setSelectedServerIndex(int index); + QVariant data(const int index, int role) const; + + Q_PROPERTY(QString defaultContainerName READ getDefaultContainerName NOTIFY defaultContainerChanged) + +public slots: + DockerContainer getDefaultContainer(); + QString getDefaultContainerName(); + void setDefaultContainer(int index); + + void setCurrentlyProcessedServerIndex(const int index); + + void setCurrentlyProcessedContainerIndex(int index); + int getCurrentlyProcessedContainerIndex(); + + QString getCurrentlyProcessedContainerName(); + QJsonObject getCurrentlyProcessedContainerConfig(); + QStringList getAllInstalledServicesName(const int serverIndex); + + ErrorCode removeAllContainers(); + ErrorCode removeCurrentlyProcessedContainer(); + void clearCachedProfiles(); + + bool isAmneziaDnsContainerInstalled(); + bool isAmneziaDnsContainerInstalled(const int serverIndex); + + bool isAnyContainerInstalled(); + + void updateContainersConfig(); protected: QHash roleNames() const override; +signals: + void defaultContainerChanged(); + private: - int m_selectedServerIndex; + QMap m_containers; + + int m_currentlyProcessedServerIndex; + int m_currentlyProcessedContainerIndex; + DockerContainer m_defaultContainerIndex; + std::shared_ptr m_settings; }; diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp new file mode 100644 index 00000000..b860b9da --- /dev/null +++ b/client/ui/models/languageModel.cpp @@ -0,0 +1,78 @@ +#include "languageModel.h" + +LanguageModel::LanguageModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < metaEnum.keyCount(); i++) { + m_availableLanguages.push_back( + LanguageModelData {getLocalLanguageName(static_cast(i)), + static_cast(i) }); + } +} + +int LanguageModel::rowCount(const QModelIndex &parent) const +{ + return static_cast(m_availableLanguages.size()); +} + +QVariant LanguageModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_availableLanguages.size())) { + return QVariant(); + } + + switch (role) { + case NameRole: return m_availableLanguages[index.row()].name; + case IndexRole: return static_cast(m_availableLanguages[index.row()].index); + } + return QVariant(); +} + +QHash LanguageModel::roleNames() const +{ + QHash roles; + roles[NameRole] = "languageName"; + roles[IndexRole] = "languageIndex"; + return roles; +} + +QString LanguageModel::getLocalLanguageName(const LanguageSettings::AvailableLanguageEnum language) +{ + QString strLanguage(""); + switch (language) { + case LanguageSettings::AvailableLanguageEnum::English: strLanguage = "English"; break; + case LanguageSettings::AvailableLanguageEnum::Russian: strLanguage = "Русский"; break; + case LanguageSettings::AvailableLanguageEnum::China_cn: strLanguage = "\347\256\200\344\275\223\344\270\255\346\226\207"; break; + default: + break; + } + + return strLanguage; +} + +void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum language) +{ + switch (language) { + case LanguageSettings::AvailableLanguageEnum::English: emit updateTranslations(QLocale::English); break; + case LanguageSettings::AvailableLanguageEnum::Russian: emit updateTranslations(QLocale::Russian); break; + case LanguageSettings::AvailableLanguageEnum::China_cn: emit updateTranslations(QLocale::Chinese); break; + default: emit updateTranslations(QLocale::English); break; + } +} + +int LanguageModel::getCurrentLanguageIndex() +{ + auto locale = m_settings->getAppLanguage(); + switch (locale.language()) { + case QLocale::English: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; + case QLocale::Russian: return static_cast(LanguageSettings::AvailableLanguageEnum::Russian); break; + case QLocale::Chinese: return static_cast(LanguageSettings::AvailableLanguageEnum::China_cn); break; + default: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; + } +} + +QString LanguageModel::getCurrentLanguageName() +{ + return m_availableLanguages[getCurrentLanguageIndex()].name; +} diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h new file mode 100644 index 00000000..c8879a34 --- /dev/null +++ b/client/ui/models/languageModel.h @@ -0,0 +1,70 @@ +#ifndef LANGUAGEMODEL_H +#define LANGUAGEMODEL_H + +#include +#include + +#include "settings.h" + +namespace LanguageSettings +{ + Q_NAMESPACE + enum class AvailableLanguageEnum { + English, + Russian, + China_cn + }; + Q_ENUM_NS(AvailableLanguageEnum) + + static void declareQmlAvailableLanguageEnum() + { + qmlRegisterUncreatableMetaObject(LanguageSettings::staticMetaObject, "AvailableLanguageEnum", 1, 0, + "AvailableLanguageEnum", QString()); + } +} + +struct LanguageModelData +{ + QString name; + LanguageSettings::AvailableLanguageEnum index; +}; + +class LanguageModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + NameRole = Qt::UserRole + 1, + IndexRole + }; + + LanguageModel(std::shared_ptr settings, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Q_PROPERTY(QString currentLanguageName READ getCurrentLanguageName NOTIFY translationsUpdated) + Q_PROPERTY(int currentLanguageIndex READ getCurrentLanguageIndex NOTIFY translationsUpdated) + +public slots: + void changeLanguage(const LanguageSettings::AvailableLanguageEnum language); + int getCurrentLanguageIndex(); + QString getCurrentLanguageName(); + +signals: + void updateTranslations(const QLocale &locale); + void translationsUpdated(); + +protected: + QHash roleNames() const override; + +private: + QString getLocalLanguageName(const LanguageSettings::AvailableLanguageEnum language); + + QVector m_availableLanguages; + + std::shared_ptr m_settings; +}; + +#endif // LANGUAGEMODEL_H diff --git a/client/ui/models/protocols/awgConfigModel.cpp b/client/ui/models/protocols/awgConfigModel.cpp new file mode 100644 index 00000000..7d0277b9 --- /dev/null +++ b/client/ui/models/protocols/awgConfigModel.cpp @@ -0,0 +1,137 @@ +#include "awgConfigModel.h" + +#include + +#include "protocols/protocols_defs.h" + +AwgConfigModel::AwgConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int AwgConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::JunkPacketCountRole: m_protocolConfig.insert(config_key::junkPacketCount, value.toString()); break; + case Roles::JunkPacketMinSizeRole: m_protocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break; + case Roles::JunkPacketMaxSizeRole: m_protocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break; + case Roles::InitPacketJunkSizeRole: + m_protocolConfig.insert(config_key::initPacketJunkSize, value.toString()); + break; + case Roles::ResponsePacketJunkSizeRole: + m_protocolConfig.insert(config_key::responsePacketJunkSize, value.toString()); + break; + case Roles::InitPacketMagicHeaderRole: + m_protocolConfig.insert(config_key::initPacketMagicHeader, value.toString()); + break; + case Roles::ResponsePacketMagicHeaderRole: + m_protocolConfig.insert(config_key::responsePacketMagicHeader, value.toString()); + break; + case Roles::UnderloadPacketMagicHeaderRole: + m_protocolConfig.insert(config_key::underloadPacketMagicHeader, value.toString()); + break; + case Roles::TransportPacketMagicHeaderRole: + m_protocolConfig.insert(config_key::transportPacketMagicHeader, value.toString()); + break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant AwgConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(); + case Roles::JunkPacketCountRole: return m_protocolConfig.value(config_key::junkPacketCount); + case Roles::JunkPacketMinSizeRole: return m_protocolConfig.value(config_key::junkPacketMinSize); + case Roles::JunkPacketMaxSizeRole: return m_protocolConfig.value(config_key::junkPacketMaxSize); + case Roles::InitPacketJunkSizeRole: return m_protocolConfig.value(config_key::initPacketJunkSize); + case Roles::ResponsePacketJunkSizeRole: return m_protocolConfig.value(config_key::responsePacketJunkSize); + case Roles::InitPacketMagicHeaderRole: return m_protocolConfig.value(config_key::initPacketMagicHeader); + case Roles::ResponsePacketMagicHeaderRole: return m_protocolConfig.value(config_key::responsePacketMagicHeader); + case Roles::UnderloadPacketMagicHeaderRole: return m_protocolConfig.value(config_key::underloadPacketMagicHeader); + case Roles::TransportPacketMagicHeaderRole: return m_protocolConfig.value(config_key::transportPacketMagicHeader); + } + + return QVariant(); +} + +void AwgConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + + QJsonObject protocolConfig = config.value(config_key::awg).toObject(); + + m_protocolConfig[config_key::port] = + protocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); + m_protocolConfig[config_key::junkPacketCount] = + protocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); + m_protocolConfig[config_key::junkPacketMinSize] = + protocolConfig.value(config_key::junkPacketMinSize) + .toString(protocols::awg::defaultJunkPacketMinSize); + m_protocolConfig[config_key::junkPacketMaxSize] = + protocolConfig.value(config_key::junkPacketMaxSize) + .toString(protocols::awg::defaultJunkPacketMaxSize); + m_protocolConfig[config_key::initPacketJunkSize] = + protocolConfig.value(config_key::initPacketJunkSize) + .toString(protocols::awg::defaultInitPacketJunkSize); + m_protocolConfig[config_key::responsePacketJunkSize] = + protocolConfig.value(config_key::responsePacketJunkSize) + .toString(protocols::awg::defaultResponsePacketJunkSize); + m_protocolConfig[config_key::initPacketMagicHeader] = + protocolConfig.value(config_key::initPacketMagicHeader) + .toString(protocols::awg::defaultInitPacketMagicHeader); + m_protocolConfig[config_key::responsePacketMagicHeader] = + protocolConfig.value(config_key::responsePacketMagicHeader) + .toString(protocols::awg::defaultResponsePacketMagicHeader); + m_protocolConfig[config_key::underloadPacketMagicHeader] = + protocolConfig.value(config_key::underloadPacketMagicHeader) + .toString(protocols::awg::defaultUnderloadPacketMagicHeader); + m_protocolConfig[config_key::transportPacketMagicHeader] = + protocolConfig.value(config_key::transportPacketMagicHeader) + .toString(protocols::awg::defaultTransportPacketMagicHeader); + + endResetModel(); +} + +QJsonObject AwgConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::awg, m_protocolConfig); + return m_fullConfig; +} + +QHash AwgConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[JunkPacketCountRole] = "junkPacketCount"; + roles[JunkPacketMinSizeRole] = "junkPacketMinSize"; + roles[JunkPacketMaxSizeRole] = "junkPacketMaxSize"; + roles[InitPacketJunkSizeRole] = "initPacketJunkSize"; + roles[ResponsePacketJunkSizeRole] = "responsePacketJunkSize"; + roles[InitPacketMagicHeaderRole] = "initPacketMagicHeader"; + roles[ResponsePacketMagicHeaderRole] = "responsePacketMagicHeader"; + roles[UnderloadPacketMagicHeaderRole] = "underloadPacketMagicHeader"; + roles[TransportPacketMagicHeaderRole] = "transportPacketMagicHeader"; + + return roles; +} diff --git a/client/ui/models/protocols/awgConfigModel.h b/client/ui/models/protocols/awgConfigModel.h new file mode 100644 index 00000000..e67a3708 --- /dev/null +++ b/client/ui/models/protocols/awgConfigModel.h @@ -0,0 +1,47 @@ +#ifndef AWGCONFIGMODEL_H +#define AWGCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class AwgConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + JunkPacketCountRole, + JunkPacketMinSizeRole, + JunkPacketMaxSizeRole, + InitPacketJunkSizeRole, + ResponsePacketJunkSizeRole, + InitPacketMagicHeaderRole, + ResponsePacketMagicHeaderRole, + UnderloadPacketMagicHeaderRole, + TransportPacketMagicHeaderRole + }; + + explicit AwgConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // AWGCONFIGMODEL_H diff --git a/client/ui/models/protocols/cloakConfigModel.cpp b/client/ui/models/protocols/cloakConfigModel.cpp new file mode 100644 index 00000000..203f08b5 --- /dev/null +++ b/client/ui/models/protocols/cloakConfigModel.cpp @@ -0,0 +1,81 @@ +#include "cloakConfigModel.h" + +#include "protocols/protocols_defs.h" + +CloakConfigModel::CloakConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int CloakConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool CloakConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + case Roles::SiteRole: m_protocolConfig.insert(config_key::site, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant CloakConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::cloak::defaultPort); + case Roles::CipherRole: return m_protocolConfig.value(config_key::cipher).toString(protocols::cloak::defaultCipher); + case Roles::SiteRole: return m_protocolConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite); + } + + return QVariant(); +} + +void CloakConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::cloak).toObject(); + + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::cloak::defaultCipher)); + + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::cloak::defaultPort)); + + m_protocolConfig.insert(config_key::site, + protocolConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite)); + + endResetModel(); +} + +QJsonObject CloakConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::cloak, m_protocolConfig); + return m_fullConfig; +} + +QHash CloakConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + roles[SiteRole] = "site"; + + return roles; +} diff --git a/client/ui/models/protocols/cloakConfigModel.h b/client/ui/models/protocols/cloakConfigModel.h new file mode 100644 index 00000000..31ff8c53 --- /dev/null +++ b/client/ui/models/protocols/cloakConfigModel.h @@ -0,0 +1,40 @@ +#ifndef CLOAKCONFIGMODEL_H +#define CLOAKCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class CloakConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole, + SiteRole + }; + + explicit CloakConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // CLOAKCONFIGMODEL_H diff --git a/client/ui/models/protocols/ikev2ConfigModel.cpp b/client/ui/models/protocols/ikev2ConfigModel.cpp new file mode 100644 index 00000000..f22b965c --- /dev/null +++ b/client/ui/models/protocols/ikev2ConfigModel.cpp @@ -0,0 +1,76 @@ +#include "ikev2ConfigModel.h".h " + +#include "protocols/protocols_defs.h" + +Ikev2ConfigModel::Ikev2ConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int Ikev2ConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool Ikev2ConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant Ikev2ConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + } + + return QVariant(); +} + +void Ikev2ConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::shadowsocks).toObject(); + + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher)); + + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)); + + endResetModel(); +} + +QJsonObject Ikev2ConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::shadowsocks, m_protocolConfig); + return m_fullConfig; +} + +QHash Ikev2ConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + + return roles; +} diff --git a/client/ui/models/protocols/ikev2ConfigModel.h b/client/ui/models/protocols/ikev2ConfigModel.h new file mode 100644 index 00000000..e005f6a4 --- /dev/null +++ b/client/ui/models/protocols/ikev2ConfigModel.h @@ -0,0 +1,39 @@ +#ifndef IKEV2CONFIGMODEL_H +#define IKEV2CONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class Ikev2ConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole + }; + + explicit Ikev2ConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // IKEV2CONFIGMODEL_H diff --git a/client/ui/models/protocols/openvpnConfigModel.cpp b/client/ui/models/protocols/openvpnConfigModel.cpp new file mode 100644 index 00000000..7ef3af5b --- /dev/null +++ b/client/ui/models/protocols/openvpnConfigModel.cpp @@ -0,0 +1,151 @@ +#include "openvpnConfigModel.h" + +#include "protocols/protocols_defs.h" + +OpenVpnConfigModel::OpenVpnConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int OpenVpnConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool OpenVpnConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::SubnetAddressRole: + m_protocolConfig.insert(amnezia::config_key::subnet_address, value.toString()); + break; + case Roles::TransportProtoRole: m_protocolConfig.insert(config_key::transport_proto, value.toString()); break; + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::AutoNegotiateEncryprionRole: m_protocolConfig.insert(config_key::ncp_disable, !value.toBool()); break; + case Roles::HashRole: m_protocolConfig.insert(config_key::hash, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + case Roles::TlsAuthRole: m_protocolConfig.insert(config_key::tls_auth, value.toBool()); break; + case Roles::BlockDnsRole: m_protocolConfig.insert(config_key::block_outside_dns, value.toBool()); break; + case Roles::AdditionalClientCommandsRole: + m_protocolConfig.insert(config_key::additional_client_config, value.toString()); + break; + case Roles::AdditionalServerCommandsRole: + m_protocolConfig.insert(config_key::additional_server_config, value.toString()); + break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant OpenVpnConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::SubnetAddressRole: + return m_protocolConfig.value(amnezia::config_key::subnet_address) + .toString(amnezia::protocols::openvpn::defaultSubnetAddress); + case Roles::TransportProtoRole: + return m_protocolConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto); + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::openvpn::defaultPort); + case Roles::AutoNegotiateEncryprionRole: + return !m_protocolConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); + case Roles::HashRole: return m_protocolConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher); + case Roles::TlsAuthRole: + return m_protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); + case Roles::BlockDnsRole: + return m_protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns); + case Roles::AdditionalClientCommandsRole: + return m_protocolConfig.value(config_key::additional_client_config) + .toString(protocols::openvpn::defaultAdditionalClientConfig); + case Roles::AdditionalServerCommandsRole: + return m_protocolConfig.value(config_key::additional_server_config) + .toString(protocols::openvpn::defaultAdditionalServerConfig); + case Roles::IsPortEditable: return m_container == DockerContainer::OpenVpn ? true : false; + case Roles::IsTransportProtoEditable: return m_container == DockerContainer::OpenVpn ? true : false; + case Roles::HasRemoveButton: return m_container == DockerContainer::OpenVpn ? true : false; + } + return QVariant(); +} + +void OpenVpnConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::openvpn).toObject(); + + m_protocolConfig.insert(config_key::subnet_address, + protocolConfig.value(amnezia::config_key::subnet_address) + .toString(amnezia::protocols::openvpn::defaultSubnetAddress)); + + QString transportProto; + if (m_container == DockerContainer::OpenVpn) { + transportProto = + protocolConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto); + } else { + transportProto = "tcp"; + } + + m_protocolConfig.insert(config_key::transport_proto, transportProto); + + m_protocolConfig.insert(config_key::ncp_disable, + protocolConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable)); + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher)); + m_protocolConfig.insert(config_key::hash, + protocolConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash)); + m_protocolConfig.insert(config_key::block_outside_dns, + protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth)); + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)); + m_protocolConfig.insert( + config_key::tls_auth, + protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns)); + m_protocolConfig.insert(config_key::additional_client_config, + protocolConfig.value(config_key::additional_client_config) + .toString(protocols::openvpn::defaultAdditionalClientConfig)); + m_protocolConfig.insert(config_key::additional_server_config, + protocolConfig.value(config_key::additional_server_config) + .toString(protocols::openvpn::defaultAdditionalServerConfig)); + + endResetModel(); +} + +QJsonObject OpenVpnConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::openvpn, m_protocolConfig); + return m_fullConfig; +} + +QHash OpenVpnConfigModel::roleNames() const +{ + QHash roles; + + roles[SubnetAddressRole] = "subnetAddress"; + roles[TransportProtoRole] = "transportProto"; + roles[PortRole] = "port"; + roles[AutoNegotiateEncryprionRole] = "autoNegotiateEncryprion"; + roles[HashRole] = "hash"; + roles[CipherRole] = "cipher"; + roles[TlsAuthRole] = "tlsAuth"; + roles[BlockDnsRole] = "blockDns"; + roles[AdditionalClientCommandsRole] = "additionalClientCommands"; + roles[AdditionalServerCommandsRole] = "additionalServerCommands"; + + roles[IsPortEditable] = "isPortEditable"; + roles[IsTransportProtoEditable] = "isTransportProtoEditable"; + + roles[HasRemoveButton] = "hasRemoveButton"; + + return roles; +} diff --git a/client/ui/models/protocols/openvpnConfigModel.h b/client/ui/models/protocols/openvpnConfigModel.h new file mode 100644 index 00000000..0357700c --- /dev/null +++ b/client/ui/models/protocols/openvpnConfigModel.h @@ -0,0 +1,52 @@ +#ifndef OPENVPNCONFIGMODEL_H +#define OPENVPNCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class OpenVpnConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + SubnetAddressRole = Qt::UserRole + 1, + TransportProtoRole, + PortRole, + AutoNegotiateEncryprionRole, + HashRole, + CipherRole, + TlsAuthRole, + BlockDnsRole, + AdditionalClientCommandsRole, + AdditionalServerCommandsRole, + + IsPortEditable, + IsTransportProtoEditable, + + HasRemoveButton + }; + + explicit OpenVpnConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // OPENVPNCONFIGMODEL_H diff --git a/client/ui/models/protocols/shadowsocksConfigModel.cpp b/client/ui/models/protocols/shadowsocksConfigModel.cpp new file mode 100644 index 00000000..60c8feee --- /dev/null +++ b/client/ui/models/protocols/shadowsocksConfigModel.cpp @@ -0,0 +1,76 @@ +#include "shadowsocksConfigModel.h" + +#include "protocols/protocols_defs.h" + +ShadowSocksConfigModel::ShadowSocksConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int ShadowSocksConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool ShadowSocksConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant ShadowSocksConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + } + + return QVariant(); +} + +void ShadowSocksConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::shadowsocks).toObject(); + + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher)); + + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)); + + endResetModel(); +} + +QJsonObject ShadowSocksConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::shadowsocks, m_protocolConfig); + return m_fullConfig; +} + +QHash ShadowSocksConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + + return roles; +} diff --git a/client/ui/models/protocols/shadowsocksConfigModel.h b/client/ui/models/protocols/shadowsocksConfigModel.h new file mode 100644 index 00000000..d8fa036b --- /dev/null +++ b/client/ui/models/protocols/shadowsocksConfigModel.h @@ -0,0 +1,39 @@ +#ifndef SHADOWSOCKSCONFIGMODEL_H +#define SHADOWSOCKSCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class ShadowSocksConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole + }; + + explicit ShadowSocksConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // SHADOWSOCKSCONFIGMODEL_H diff --git a/client/ui/models/protocols/wireguardConfigModel.cpp b/client/ui/models/protocols/wireguardConfigModel.cpp new file mode 100644 index 00000000..15e89865 --- /dev/null +++ b/client/ui/models/protocols/wireguardConfigModel.cpp @@ -0,0 +1,70 @@ +#include "wireguardConfigModel.h" + +#include "protocols/protocols_defs.h" + +WireGuardConfigModel::WireGuardConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int WireGuardConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool WireGuardConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant WireGuardConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + } + + return QVariant(); +} + +void WireGuardConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::wireguard).toObject(); + + endResetModel(); +} + +QJsonObject WireGuardConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::wireguard, m_protocolConfig); + return m_fullConfig; +} + +QHash WireGuardConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + + return roles; +} diff --git a/client/ui/models/protocols/wireguardConfigModel.h b/client/ui/models/protocols/wireguardConfigModel.h new file mode 100644 index 00000000..1deeacaf --- /dev/null +++ b/client/ui/models/protocols/wireguardConfigModel.h @@ -0,0 +1,39 @@ +#ifndef WIREGUARDCONFIGMODEL_H +#define WIREGUARDCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class WireGuardConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole + }; + + explicit WireGuardConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // WIREGUARDCONFIGMODEL_H diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp index 7359bb36..5826025e 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -1,62 +1,88 @@ #include "protocols_model.h" -ProtocolsModel::ProtocolsModel(std::shared_ptr settings, QObject *parent) : - m_settings(settings), - QAbstractListModel(parent) +ProtocolsModel::ProtocolsModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) { - } int ProtocolsModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return ProtocolProps::allProtocols().size(); + return m_content.size(); } -QHash ProtocolsModel::roleNames() const { +QHash ProtocolsModel::roleNames() const +{ QHash roles; - roles[NameRole] = "name_role"; - roles[DescRole] = "desc_role"; - roles[ServiceTypeRole] = "service_type_role"; - roles[IsInstalledRole] = "is_installed_role"; + + roles[ProtocolNameRole] = "protocolName"; + roles[ProtocolPageRole] = "protocolPage"; + roles[ProtocolIndexRole] = "protocolIndex"; + roles[RawConfigRole] = "rawConfig"; + return roles; } QVariant ProtocolsModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 - || index.row() >= ProtocolProps::allProtocols().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= m_content.size()) { return QVariant(); } - Proto p = ProtocolProps::allProtocols().at(index.row()); - if (role == NameRole) { - return ProtocolProps::protocolHumanNames().value(p); + switch (role) { + case ProtocolNameRole: { + amnezia::Proto proto = ProtocolProps::protoFromString(m_content.keys().at(index.row())); + return ProtocolProps::protocolHumanNames().value(proto); } - if (role == DescRole) { - return ProtocolProps::protocolDescriptions().value(p); + case ProtocolPageRole: + return static_cast(protocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row())))); + case ProtocolIndexRole: return ProtocolProps::protoFromString(m_content.keys().at(index.row())); + case RawConfigRole: { + auto protocolConfig = m_content.value(ContainerProps::containerTypeToString(m_container)).toObject(); + auto lastConfigJsonDoc = + QJsonDocument::fromJson(protocolConfig.value(config_key::last_config).toString().toUtf8()); + auto lastConfigJson = lastConfigJsonDoc.object(); + + QString rawConfig; + QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); + for (const QString &l : lines) { + rawConfig.append(l + "\n"); + } + return rawConfig; } - if (role == ServiceTypeRole) { - return ProtocolProps::protocolService(p); - } - if (role == IsInstalledRole) { - return ContainerProps::protocolsForContainer(m_selectedDockerContainer).contains(p); } + return QVariant(); } -void ProtocolsModel::setSelectedServerIndex(int index) +void ProtocolsModel::updateModel(const QJsonObject &content) { - beginResetModel(); - m_selectedServerIndex = index; - endResetModel(); + m_container = ContainerProps::containerFromString(content.value(config_key::container).toString()); + + m_content = content; + m_content.remove(config_key::container); } -void ProtocolsModel::setSelectedDockerContainer(DockerContainer c) +QJsonObject ProtocolsModel::getConfig() { - beginResetModel(); - m_selectedDockerContainer = c; - endResetModel(); + QJsonObject config = m_content; + config.insert(config_key::container, ContainerProps::containerToString(m_container)); + return config; } - +PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const +{ + switch (protocol) { + case Proto::OpenVpn: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::Cloak: return PageLoader::PageEnum::PageProtocolCloakSettings; + case Proto::ShadowSocks: return PageLoader::PageEnum::PageProtocolShadowSocksSettings; + case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardSettings; + case Proto::Ikev2: return PageLoader::PageEnum::PageProtocolIKev2Settings; + case Proto::L2tp: return PageLoader::PageEnum::PageProtocolIKev2Settings; + // non-vpn + case Proto::TorWebSite: return PageLoader::PageEnum::PageServiceTorWebsiteSettings; + case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings; + case Proto::Sftp: return PageLoader::PageEnum::PageServiceSftpSettings; + default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + } +} diff --git a/client/ui/models/protocols_model.h b/client/ui/models/protocols_model.h index 48b6eeb6..5ee8a3dd 100644 --- a/client/ui/models/protocols_model.h +++ b/client/ui/models/protocols_model.h @@ -3,38 +3,42 @@ #include #include -#include -#include +#include "../controllers/pageController.h" #include "settings.h" -#include "containers/containers_defs.h" class ProtocolsModel : public QAbstractListModel { Q_OBJECT public: - ProtocolsModel(std::shared_ptr settings, QObject *parent = nullptr); -public: - enum SiteRoles { - NameRole = Qt::UserRole + 1, - DescRole, - ServiceTypeRole, - IsInstalledRole + enum Roles { + ProtocolNameRole = Qt::UserRole + 1, + ProtocolPageRole, + ProtocolIndexRole, + RawConfigRole }; + ProtocolsModel(std::shared_ptr settings, QObject *parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - Q_INVOKABLE void setSelectedServerIndex(int index); - Q_INVOKABLE void setSelectedDockerContainer(DockerContainer c); + +public slots: + void updateModel(const QJsonObject &content); + + QJsonObject getConfig(); protected: QHash roleNames() const override; private: - int m_selectedServerIndex; - DockerContainer m_selectedDockerContainer; + PageLoader::PageEnum protocolPage(Proto protocol) const; + std::shared_ptr m_settings; + + DockerContainer m_container; + QJsonObject m_content; }; #endif // PROTOCOLS_MODEL_H diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index c4b0efb3..a2a28630 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -1,55 +1,221 @@ #include "servers_model.h" -ServersModel::ServersModel(QObject *parent) : - QAbstractListModel(parent) +ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) { + m_servers = m_settings->serversArray(); + m_defaultServerIndex = m_settings->defaultServerIndex(); + m_currentlyProcessedServerIndex = m_defaultServerIndex; -} - -void ServersModel::clearData() -{ - beginResetModel(); - content.clear(); - endResetModel(); -} - -void ServersModel::setContent(const std::vector &data) -{ - beginResetModel(); - content = data; - endResetModel(); + connect(this, &ServersModel::defaultServerIndexChanged, this, &ServersModel::defaultServerNameChanged); } int ServersModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return static_cast(content.size()); + return static_cast(m_servers.size()); } -QHash ServersModel::roleNames() const { - QHash roles; - roles[DescRole] = "desc"; - roles[AddressRole] = "address"; - roles[IsDefaultRole] = "is_default"; - return roles; +bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_servers.size())) { + return false; + } + + QJsonObject server = m_servers.at(index.row()).toObject(); + + switch (role) { + case NameRole: { + server.insert(config_key::description, value.toString()); + m_settings->editServer(index.row(), server); + m_servers.replace(index.row(), server); + if (index.row() == m_defaultServerIndex) { + emit defaultServerNameChanged(); + } + break; + } + default: { + return true; + } + } + + emit dataChanged(index, index); + return true; } QVariant ServersModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 - || index.row() >= static_cast(content.size())) { + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_servers.size())) { return QVariant(); } - if (role == DescRole) { - return content[index.row()].desc; + + const QJsonObject server = m_servers.at(index.row()).toObject(); + + switch (role) { + case NameRole: { + auto description = server.value(config_key::description).toString(); + if (description.isEmpty()) { + return server.value(config_key::hostName).toString(); + } + return description; } - if (role == AddressRole) { - return content[index.row()].address; + case HostNameRole: return server.value(config_key::hostName).toString(); + case CredentialsRole: return QVariant::fromValue(serverCredentials(index.row())); + case CredentialsLoginRole: return serverCredentials(index.row()).userName; + case IsDefaultRole: return index.row() == m_defaultServerIndex; + case IsCurrentlyProcessedRole: return index.row() == m_currentlyProcessedServerIndex; + case HasWriteAccessRole: { + auto credentials = serverCredentials(index.row()); + return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); } - if (role == IsDefaultRole) { - return content[index.row()].isDefault; + case ContainsAmneziaDnsRole: { + QString primaryDns = server.value(config_key::dns1).toString(); + return primaryDns == protocols::dns::amneziaDnsIp; } + } + return QVariant(); } +QVariant ServersModel::data(const int index, int role) const +{ + QModelIndex modelIndex = this->index(index); + return data(modelIndex, role); +} +void ServersModel::resetModel() +{ + beginResetModel(); + m_servers = m_settings->serversArray(); + m_defaultServerIndex = m_settings->defaultServerIndex(); + m_currentlyProcessedServerIndex = m_defaultServerIndex; + endResetModel(); +} + +void ServersModel::setDefaultServerIndex(const int index) +{ + m_settings->setDefaultServer(index); + m_defaultServerIndex = m_settings->defaultServerIndex(); + emit defaultServerIndexChanged(m_defaultServerIndex); +} + +const int ServersModel::getDefaultServerIndex() +{ + return m_defaultServerIndex; +} + +const QString ServersModel::getDefaultServerName() +{ + return qvariant_cast(data(m_defaultServerIndex, NameRole)); +} + +const QString ServersModel::getDefaultServerHostName() +{ + return qvariant_cast(data(m_defaultServerIndex, HostNameRole)); +} + +const int ServersModel::getServersCount() +{ + return m_servers.count(); +} + +bool ServersModel::hasServerWithWriteAccess() +{ + for (size_t i = 0; i < getServersCount(); i++) { + if (qvariant_cast(data(i, HasWriteAccessRole))) { + return true; + } + } + return false; +} + +void ServersModel::setCurrentlyProcessedServerIndex(const int index) +{ + m_currentlyProcessedServerIndex = index; + emit currentlyProcessedServerIndexChanged(m_currentlyProcessedServerIndex); +} + +int ServersModel::getCurrentlyProcessedServerIndex() +{ + return m_currentlyProcessedServerIndex; +} + +QString ServersModel::getCurrentlyProcessedServerHostName() +{ + return qvariant_cast(data(m_currentlyProcessedServerIndex, HostNameRole)); +} + +bool ServersModel::isDefaultServerCurrentlyProcessed() +{ + return m_defaultServerIndex == m_currentlyProcessedServerIndex; +} + +bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() +{ + return qvariant_cast(data(m_currentlyProcessedServerIndex, HasWriteAccessRole)); +} + +bool ServersModel::isDefaultServerHasWriteAccess() +{ + return qvariant_cast(data(m_defaultServerIndex, HasWriteAccessRole)); +} + +void ServersModel::addServer(const QJsonObject &server) +{ + beginResetModel(); + m_settings->addServer(server); + m_servers = m_settings->serversArray(); + endResetModel(); +} + +void ServersModel::removeServer() +{ + beginResetModel(); + m_settings->removeServer(m_currentlyProcessedServerIndex); + m_servers = m_settings->serversArray(); + + if (m_settings->defaultServerIndex() == m_currentlyProcessedServerIndex) { + setDefaultServerIndex(0); + } else if (m_settings->defaultServerIndex() > m_currentlyProcessedServerIndex) { + setDefaultServerIndex(m_settings->defaultServerIndex() - 1); + } + + if (m_settings->serversCount() == 0) { + setDefaultServerIndex(-1); + } + endResetModel(); +} + +bool ServersModel::isDefaultServerConfigContainsAmneziaDns() +{ + const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); + QString primaryDns = server.value(config_key::dns1).toString(); + return primaryDns == protocols::dns::amneziaDnsIp; +} + +QHash ServersModel::roleNames() const +{ + QHash roles; + roles[NameRole] = "name"; + roles[HostNameRole] = "hostName"; + roles[CredentialsRole] = "credentials"; + roles[CredentialsLoginRole] = "credentialsLogin"; + roles[IsDefaultRole] = "isDefault"; + roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; + roles[HasWriteAccessRole] = "hasWriteAccess"; + roles[ContainsAmneziaDnsRole] = "containsAmneziaDns"; + return roles; +} + +ServerCredentials ServersModel::serverCredentials(int index) const +{ + const QJsonObject &s = m_servers.at(index).toObject(); + + ServerCredentials credentials; + credentials.hostName = s.value(config_key::hostName).toString(); + credentials.userName = s.value(config_key::userName).toString(); + credentials.secretData = s.value(config_key::password).toString(); + credentials.port = s.value(config_key::port).toInt(); + + return credentials; +} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 7f2e3ad1..ad1d5a83 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -2,38 +2,80 @@ #define SERVERSMODEL_H #include -#include -#include -struct ServerModelContent { - QString desc; - QString address; - bool isDefault; -}; +#include "settings.h" class ServersModel : public QAbstractListModel { Q_OBJECT public: - ServersModel(QObject *parent = nullptr); -public: - enum SiteRoles { - DescRole = Qt::UserRole + 1, - AddressRole, - IsDefaultRole + enum Roles { + NameRole = Qt::UserRole + 1, + HostNameRole, + CredentialsRole, + CredentialsLoginRole, + IsDefaultRole, + IsCurrentlyProcessedRole, + HasWriteAccessRole, + ContainsAmneziaDnsRole }; - void clearData(); - void setContent(const std::vector& data); + ServersModel(std::shared_ptr settings, QObject *parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant data(const int index, int role = Qt::DisplayRole) const; + + void resetModel(); + + Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerNameChanged) + Q_PROPERTY(QString defaultServerHostName READ getDefaultServerHostName NOTIFY defaultServerIndexChanged) + Q_PROPERTY(int currentlyProcessedIndex READ getCurrentlyProcessedServerIndex WRITE setCurrentlyProcessedServerIndex + NOTIFY currentlyProcessedServerIndexChanged) + +public slots: + void setDefaultServerIndex(const int index); + const int getDefaultServerIndex(); + const QString getDefaultServerName(); + const QString getDefaultServerHostName(); + bool isDefaultServerCurrentlyProcessed(); + + bool isCurrentlyProcessedServerHasWriteAccess(); + bool isDefaultServerHasWriteAccess(); + bool hasServerWithWriteAccess(); + + const int getServersCount(); + + void setCurrentlyProcessedServerIndex(const int index); + int getCurrentlyProcessedServerIndex(); + + QString getCurrentlyProcessedServerHostName(); + + void addServer(const QJsonObject &server); + void removeServer(); + + bool isDefaultServerConfigContainsAmneziaDns(); protected: QHash roleNames() const override; +signals: + void currentlyProcessedServerIndexChanged(const int index); + void defaultServerIndexChanged(const int index); + void defaultServerNameChanged(); + private: - std::vector content; + ServerCredentials serverCredentials(int index) const; + + QJsonArray m_servers; + + std::shared_ptr m_settings; + + int m_defaultServerIndex; + int m_currentlyProcessedServerIndex; }; #endif // SERVERSMODEL_H diff --git a/client/ui/models/services/sftpConfigModel.cpp b/client/ui/models/services/sftpConfigModel.cpp new file mode 100644 index 00000000..3cbb5ebc --- /dev/null +++ b/client/ui/models/services/sftpConfigModel.cpp @@ -0,0 +1,64 @@ +#include "sftpConfigModel.h" + +#include "protocols/protocols_defs.h" + +SftpConfigModel::SftpConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int SftpConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +QVariant SftpConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(); + case Roles::UserNameRole: + return m_protocolConfig.value(config_key::userName).toString(protocols::sftp::defaultUserName); + case Roles::PasswordRole: return m_protocolConfig.value(config_key::password).toString(); + } + + return QVariant(); +} + +void SftpConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::sftp).toObject(); + + m_protocolConfig.insert(config_key::userName, + protocolConfig.value(config_key::userName).toString(protocols::sftp::defaultUserName)); + + m_protocolConfig.insert(config_key::password, protocolConfig.value(config_key::password).toString()); + + m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString()); + + endResetModel(); +} + +QJsonObject SftpConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::sftp, m_protocolConfig); + return m_fullConfig; +} + +QHash SftpConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[UserNameRole] = "username"; + roles[PasswordRole] = "password"; + + return roles; +} diff --git a/client/ui/models/services/sftpConfigModel.h b/client/ui/models/services/sftpConfigModel.h new file mode 100644 index 00000000..e948591e --- /dev/null +++ b/client/ui/models/services/sftpConfigModel.h @@ -0,0 +1,39 @@ +#ifndef SFTPCONFIGMODEL_H +#define SFTPCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class SftpConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + UserNameRole, + PasswordRole + }; + + explicit SftpConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // SFTPCONFIGMODEL_H diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp index fe0f4ccf..f6cb9b13 100644 --- a/client/ui/models/sites_model.cpp +++ b/client/ui/models/sites_model.cpp @@ -1,87 +1,140 @@ #include "sites_model.h" -SitesModel::SitesModel(std::shared_ptr settings, Settings::RouteMode mode, QObject *parent) - : QAbstractListModel(parent), - m_settings(settings), - m_mode(mode) +SitesModel::SitesModel(std::shared_ptr settings, QObject *parent) + : QAbstractListModel(parent), m_settings(settings) { -} - -void SitesModel::resetCache() -{ - beginResetModel(); - m_ipsCache.clear(); - m_cacheReady = false; - endResetModel(); + auto routeMode = m_settings->routeMode(); + if (routeMode == Settings::RouteMode::VpnAllSites) { + m_isSplitTunnelingEnabled = false; + m_currentRouteMode = Settings::RouteMode::VpnOnlyForwardSites; + } else { + m_isSplitTunnelingEnabled = true; + m_currentRouteMode = routeMode; + } + fillSites(); } int SitesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) - if (!m_cacheReady) genCache(); - return m_ipsCache.size(); + return m_sites.size(); } - QVariant SitesModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) return QVariant(); - if (!m_cacheReady) genCache(); - - if (role == SitesModel::UrlRole || role == SitesModel::IpRole) { - if (m_ipsCache.isEmpty()) return QVariant(); - - if (role == SitesModel::UrlRole) { - return m_ipsCache.at(index.row()).first; - } - if (role == SitesModel::IpRole) { - return m_ipsCache.at(index.row()).second; - } + switch (role) { + case UrlRole: { + return m_sites.at(index.row()).first; + break; + } + case IpRole: { + return m_sites.at(index.row()).second; + break; + } + default: { + return true; + } } - - // if (role == Qt::TextAlignmentRole && index.column() == 1) { - // return Qt::AlignRight; - // } return QVariant(); } -QVariant SitesModel::data(int row, int column) +bool SitesModel::addSite(const QString &hostname, const QString &ip) { - if (row < 0 || row >= rowCount() || column < 0 || column >= 2) { - return QVariant(); + if (!m_settings->addVpnSite(m_currentRouteMode, hostname, ip)) { + return false; } - if (!m_cacheReady) genCache(); - - if (column == 0) { - return m_ipsCache.at(row).first; + for (int i = 0; i < m_sites.size(); i++) { + if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) { + m_sites[i].second = ip; + QModelIndex index = createIndex(i, i); + emit dataChanged(index, index); + return true; + } else if (m_sites[i].first == hostname && (m_sites[i].second == ip)) { + return false; + } } - if (column == 1) { - return m_ipsCache.at(row).second; - } - return QVariant(); + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_sites.append(qMakePair(hostname, ip)); + endInsertRows(); + return true; } -void SitesModel::genCache() const +void SitesModel::addSites(const QMap &sites, bool replaceExisting) { - qDebug() << "SitesModel::genCache"; - m_ipsCache.clear(); + beginResetModel(); - const QVariantMap &sites = m_settings->vpnSites(m_mode); - auto i = sites.constBegin(); - while (i != sites.constEnd()) { - m_ipsCache.append(qMakePair(i.key(), i.value().toString())); - ++i; + if (replaceExisting) { + m_settings->removeAllVpnSites(m_currentRouteMode); } + m_settings->addVpnSites(m_currentRouteMode, sites); + fillSites(); - m_cacheReady= true; + endResetModel(); } -QHash SitesModel::roleNames() const { +void SitesModel::removeSite(QModelIndex index) +{ + auto hostname = m_sites.at(index.row()).first; + beginRemoveRows(QModelIndex(), index.row(), index.row()); + m_settings->removeVpnSite(m_currentRouteMode, hostname); + m_sites.removeAt(index.row()); + endRemoveRows(); +} + +int SitesModel::getRouteMode() +{ + return m_currentRouteMode; +} + +void SitesModel::setRouteMode(int routeMode) +{ + beginResetModel(); + m_settings->setRouteMode(static_cast(routeMode)); + m_currentRouteMode = m_settings->routeMode(); + fillSites(); + endResetModel(); + emit routeModeChanged(); +} + +bool SitesModel::isSplitTunnelingEnabled() +{ + return m_isSplitTunnelingEnabled; +} + +void SitesModel::toggleSplitTunneling(bool enabled) +{ + if (enabled) { + setRouteMode(m_currentRouteMode); + } else { + m_settings->setRouteMode(Settings::RouteMode::VpnAllSites); + } + m_isSplitTunnelingEnabled = enabled; +} + +QVector > SitesModel::getCurrentSites() +{ + return m_sites; +} + +QHash SitesModel::roleNames() const +{ QHash roles; - roles[UrlRole] = "url_path"; + roles[UrlRole] = "url"; roles[IpRole] = "ip"; return roles; } + +void SitesModel::fillSites() +{ + m_sites.clear(); + const QVariantMap &sites = m_settings->vpnSites(m_currentRouteMode); + auto i = sites.constBegin(); + while (i != sites.constEnd()) { + m_sites.append(qMakePair(i.key(), i.value().toString())); + ++i; + } +} diff --git a/client/ui/models/sites_model.h b/client/ui/models/sites_model.h index 7bf04b50..ad16b7a3 100644 --- a/client/ui/models/sites_model.h +++ b/client/ui/models/sites_model.h @@ -10,32 +10,47 @@ class SitesModel : public QAbstractListModel Q_OBJECT public: - enum SiteRoles { + enum Roles { UrlRole = Qt::UserRole + 1, IpRole }; - explicit SitesModel(std::shared_ptr settings, Settings::RouteMode mode, QObject *parent = nullptr); - void resetCache(); + explicit SitesModel(std::shared_ptr settings, QObject *parent = nullptr); - // Basic functionality: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant data(int row, int column); + + Q_PROPERTY(int routeMode READ getRouteMode WRITE setRouteMode NOTIFY routeModeChanged) + +public slots: + bool addSite(const QString &hostname, const QString &ip); + void addSites(const QMap &sites, bool replaceExisting); + void removeSite(QModelIndex index); + + int getRouteMode(); + void setRouteMode(int routeMode); + + bool isSplitTunnelingEnabled(); + void toggleSplitTunneling(bool enabled); + + QVector> getCurrentSites(); + +signals: + void routeModeChanged(); protected: QHash roleNames() const override; private: - void genCache() const; + void fillSites(); -private: - Settings::RouteMode m_mode; std::shared_ptr m_settings; - mutable QVector> m_ipsCache; - mutable bool m_cacheReady = false; + bool m_isSplitTunnelingEnabled; + Settings::RouteMode m_currentRouteMode; + + QVector> m_sites; }; #endif // SITESMODEL_H diff --git a/client/ui/notificationhandler.cpp b/client/ui/notificationhandler.cpp index 779569d7..1f81c2c2 100644 --- a/client/ui/notificationhandler.cpp +++ b/client/ui/notificationhandler.cpp @@ -52,9 +52,9 @@ NotificationHandler::~NotificationHandler() { s_instance = nullptr; } -void NotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState state) +void NotificationHandler::setConnectionState(Vpn::ConnectionState state) { - if (state != VpnProtocol::Connected && state != VpnProtocol::Disconnected) { + if (state != Vpn::ConnectionState::Connected && state != Vpn::ConnectionState::Disconnected) { return; } @@ -62,14 +62,14 @@ void NotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState sta QString message; switch (state) { - case VpnProtocol::VpnConnectionState::Connected: + case Vpn::ConnectionState::Connected: m_connected = true; title = tr("AmneziaVPN"); message = tr("VPN Connected"); break; - case VpnProtocol::VpnConnectionState::Disconnected: + case Vpn::ConnectionState::Disconnected: if (m_connected) { m_connected = false; title = tr("AmneziaVPN"); @@ -88,6 +88,10 @@ void NotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState sta } } +void NotificationHandler::onTranslationsUpdated() +{ +} + void NotificationHandler::unsecuredNetworkNotification(const QString& networkName) { qDebug() << "Unsecured network notification shown"; diff --git a/client/ui/notificationhandler.h b/client/ui/notificationhandler.h index b87e9575..abdce27b 100644 --- a/client/ui/notificationhandler.h +++ b/client/ui/notificationhandler.h @@ -31,7 +31,8 @@ public: void messageClickHandle(); public slots: - virtual void setConnectionState(VpnProtocol::VpnConnectionState state); + virtual void setConnectionState(Vpn::ConnectionState state); + virtual void onTranslationsUpdated(); signals: void notificationShown(const QString& title, const QString& message); diff --git a/client/ui/pages.h b/client/ui/pages.h index 40054f61..f3d045b2 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -21,12 +21,12 @@ public: namespace PageEnumNS { Q_NAMESPACE -enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, - Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, - GeneralSettings, AppSettings, NetworkSettings, ServerSettings, - ServerContainers, ServersList, ShareConnection, Sites, - ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, - AdvancedServerSettings, ClientManagement, ClientInfo}; +enum class Page { Start = 0, NewServer, NewServerProtocols, Vpn, + Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, + GeneralSettings, AppSettings, NetworkSettings, ServerSettings, + ServerContainers, ServersList, ShareConnection, Sites, + ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, + AdvancedServerSettings, ClientManagement, ClientInfo}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp b/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp deleted file mode 100644 index a96827b6..00000000 --- a/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "AdvancedServerSettingsLogic.h" - -#include "VpnLogic.h" -#include "ui/uilogic.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" - -AdvancedServerSettingsLogic::AdvancedServerSettingsLogic(UiLogic *uiLogic, QObject *parent): PageLogicBase(uiLogic, parent), - m_labelWaitInfoVisible{true}, - m_pushButtonClearVisible{true}, - m_pushButtonClearText{tr("Clear server from Amnezia software")} -{ -} - -void AdvancedServerSettingsLogic::onUpdatePage() -{ - set_labelWaitInfoVisible(false); - set_labelWaitInfoText(""); - set_pushButtonClearVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - const QJsonObject &server = m_settings->server(uiLogic()->m_selectedServerIndex); - const QString &port = server.value(config_key::port).toString(); - - const QString &userName = server.value(config_key::userName).toString(); - const QString &hostName = server.value(config_key::hostName).toString(); - QString name = QString("%1%2%3%4%5").arg(userName, - userName.isEmpty() ? "" : "@", - hostName, - port.isEmpty() ? "" : ":", - port); - - set_labelServerText(name); - - DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); -} - -void AdvancedServerSettingsLogic::onPushButtonClearServerClicked() -{ - set_pageEnabled(false); - set_pushButtonClearText(tr("Uninstalling Amnezia software...")); - - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex) { - uiLogic()->pageLogic()->onDisconnect(); - } - - ServerController serverController(m_settings); - ErrorCode errorCode = serverController.removeAllContainers(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex)); - if (errorCode) { - emit uiLogic()->showWarningMessage(tr("Error occurred while cleaning the server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); - } else { - set_labelWaitInfoVisible(true); - set_labelWaitInfoText(tr("Amnezia server successfully uninstalled")); - } - - m_settings->setContainers(uiLogic()->m_selectedServerIndex, {}); - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); - - set_pageEnabled(true); - set_pushButtonClearText(tr("Clear server from Amnezia software")); -} - -void AdvancedServerSettingsLogic::onPushButtonScanServerClicked() -{ - set_labelWaitInfoVisible(false); - set_pageEnabled(false); - - bool isServerCreated; - auto containersCount = m_settings->containers(uiLogic()->m_selectedServerIndex).size(); - ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(isServerCreated); - if (errorCode != ErrorCode::NoError) { - emit uiLogic()->showWarningMessage(tr("Error occurred while scanning the server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); - } - auto newContainersCount = m_settings->containers(uiLogic()->m_selectedServerIndex).size(); - if (containersCount != newContainersCount) { - emit uiLogic()->showWarningMessage(tr("All containers installed on the server are added to the GUI")); - } else { - emit uiLogic()->showWarningMessage(tr("No installed containers found on the server")); - } - - - onUpdatePage(); - set_pageEnabled(true); -} diff --git a/client/ui/pages_logic/AdvancedServerSettingsLogic.h b/client/ui/pages_logic/AdvancedServerSettingsLogic.h deleted file mode 100644 index 692968f1..00000000 --- a/client/ui/pages_logic/AdvancedServerSettingsLogic.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef ADVANCEDSERVERSETTINGSLOGIC_H -#define ADVANCEDSERVERSETTINGSLOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class AdvancedServerSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - - AUTO_PROPERTY(QString, pushButtonClearText) - AUTO_PROPERTY(bool, pushButtonClearVisible) - - AUTO_PROPERTY(QString, labelServerText) - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - -public: - explicit AdvancedServerSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~AdvancedServerSettingsLogic() = default; - - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonClearServerClicked(); - Q_INVOKABLE void onPushButtonScanServerClicked(); -}; - -#endif // ADVANCEDSERVERSETTINGSLOGIC_H diff --git a/client/ui/pages_logic/AppSettingsLogic.cpp b/client/ui/pages_logic/AppSettingsLogic.cpp deleted file mode 100644 index 044b97b8..00000000 --- a/client/ui/pages_logic/AppSettingsLogic.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "AppSettingsLogic.h" - -#include "logger.h" -#include "version.h" -#include "ui/qautostart.h" -#include "ui/uilogic.h" - -#include -#include -#include -#include - -#ifdef Q_OS_IOS -#include -#endif - -using namespace amnezia; -using namespace PageEnumNS; - -AppSettingsLogic::AppSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_checkBoxAutostartChecked{false}, - m_checkBoxAutoConnectChecked{false}, - m_checkBoxStartMinimizedChecked{false}, - m_checkBoxSaveLogsChecked{false} -{ - -} - -void AppSettingsLogic::onUpdatePage() -{ - set_checkBoxAutostartChecked(Autostart::isAutostart()); - set_checkBoxAutoConnectChecked(m_settings->isAutoConnect()); - set_checkBoxStartMinimizedChecked(m_settings->isStartMinimized()); - set_checkBoxSaveLogsChecked(m_settings->isSaveLogs()); - - QString ver = QString("%1: %2 (%3)") - .arg(tr("Software version")) - .arg(QString(APP_MAJOR_VERSION)) - .arg(__DATE__); - set_labelVersionText(ver); -} - -void AppSettingsLogic::onCheckBoxAutostartToggled(bool checked) -{ - if (!checked) { - set_checkBoxAutoConnectChecked(false); - } - Autostart::setAutostart(checked); -} - -void AppSettingsLogic::onCheckBoxAutoconnectToggled(bool checked) -{ - m_settings->setAutoConnect(checked); -} - -void AppSettingsLogic::onCheckBoxStartMinimizedToggled(bool checked) -{ - m_settings->setStartMinimized(checked); -} - -void AppSettingsLogic::onCheckBoxSaveLogsCheckedToggled(bool checked) -{ - m_settings->setSaveLogs(checked); -} - -void AppSettingsLogic::onPushButtonOpenLogsClicked() -{ - Logger::openLogsFolder(); -} - -void AppSettingsLogic::onPushButtonExportLogsClicked() -{ - uiLogic()->saveTextFile(tr("Save log"), "AmneziaVPN.log", ".log", Logger::getLogFile()); -} - -void AppSettingsLogic::onPushButtonClearLogsClicked() -{ - Logger::clearLogs(); - Logger::clearServiceLogs(); -} - -void AppSettingsLogic::onPushButtonBackupAppConfigClicked() -{ - uiLogic()->saveTextFile("Backup application config", "AmneziaVPN.backup", ".backup", m_settings->backupAppConfig()); -} - -void AppSettingsLogic::onPushButtonRestoreAppConfigClicked() -{ - QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open backup"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); - - if (fileName.isEmpty()) return; - - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), - fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif - - file.open(QIODevice::ReadOnly); - QByteArray data = file.readAll(); - - bool ok = m_settings->restoreAppConfig(data); - if (ok) { - emit uiLogic()->goToPage(Page::Vpn); - emit uiLogic()->setStartPage(Page::Vpn); - } else { - emit uiLogic()->showWarningMessage(tr("Can't import config, file is corrupted.")); - } -} - diff --git a/client/ui/pages_logic/AppSettingsLogic.h b/client/ui/pages_logic/AppSettingsLogic.h deleted file mode 100644 index fc9f0da7..00000000 --- a/client/ui/pages_logic/AppSettingsLogic.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef APP_SETTINGS_LOGIC_H -#define APP_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class AppSettingsLogic : public PageLogicBase -{ - Q_OBJECT - AUTO_PROPERTY(bool, checkBoxAutostartChecked) - AUTO_PROPERTY(bool, checkBoxAutoConnectChecked) - AUTO_PROPERTY(bool, checkBoxStartMinimizedChecked) - AUTO_PROPERTY(bool, checkBoxSaveLogsChecked) - AUTO_PROPERTY(QString, labelVersionText) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onCheckBoxAutostartToggled(bool checked); - Q_INVOKABLE void onCheckBoxAutoconnectToggled(bool checked); - Q_INVOKABLE void onCheckBoxStartMinimizedToggled(bool checked); - Q_INVOKABLE void onCheckBoxSaveLogsCheckedToggled(bool checked); - Q_INVOKABLE void onPushButtonOpenLogsClicked(); - Q_INVOKABLE void onPushButtonExportLogsClicked(); - Q_INVOKABLE void onPushButtonClearLogsClicked(); - - Q_INVOKABLE void onPushButtonBackupAppConfigClicked(); - Q_INVOKABLE void onPushButtonRestoreAppConfigClicked(); - - -public: - explicit AppSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~AppSettingsLogic() = default; - -}; -#endif // APP_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/ClientInfoLogic.cpp b/client/ui/pages_logic/ClientInfoLogic.cpp deleted file mode 100644 index dca0b9fd..00000000 --- a/client/ui/pages_logic/ClientInfoLogic.cpp +++ /dev/null @@ -1,213 +0,0 @@ -#include "ClientInfoLogic.h" - -#include - -#include "version.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include "ui/models/clientManagementModel.h" -#include "ui/uilogic.h" - -namespace { - bool isErrorOccured(ErrorCode error) { - if (error != ErrorCode::NoError) { - QMessageBox::warning(nullptr, APPLICATION_NAME, - QObject::tr("An error occurred while saving the list of clients.") + "\n" + errorString(error)); - return true; - } - return false; - } -} - -ClientInfoLogic::ClientInfoLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} - -void ClientInfoLogic::setCurrentClientId(int index) -{ - m_currentClientIndex = index; -} - -void ClientInfoLogic::onUpdatePage() -{ - set_pageContentVisible(false); - set_busyIndicatorIsRunning(true); - - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const QString containerNameString = ContainerProps::containerHumanNames().value(container); - set_labelCurrentVpnProtocolText(tr("Service: ") + containerNameString); - - const QVector protocols = ContainerProps::protocolsForContainer(container); - if (!protocols.empty()) { - const Proto currentMainProtocol = protocols.front(); - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - - set_lineEditNameAliasText(model->data(modelIndex, ClientManagementModel::ClientRoles::NameRole).toString()); - if (currentMainProtocol == Proto::OpenVpn) { - const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString(); - QString certData = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertDataRole).toString(); - - if (certData.isEmpty() && !certId.isEmpty()) { - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'cat /opt/amnezia/openvpn/pki/issued/%1.crt'") - .arg(certId); - ServerController serverController(m_settings); - const QString script = serverController.replaceVars(getOpenVpnCertData, serverController.genVarsForScript(credentials, container)); - ErrorCode error = serverController.runScript(credentials, script, cbReadStdOut); - certData = stdOut; - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - emit uiLogic()->closePage(); - return; - } - } - set_labelOpenVpnCertId(certId); - set_textAreaOpenVpnCertData(certData); - } else if (currentMainProtocol == Proto::WireGuard) { - set_textAreaWireGuardKeyData(model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString()); - } - } - set_pageContentVisible(true); - set_busyIndicatorIsRunning(false); -} - -void ClientInfoLogic::onLineEditNameAliasEditingFinished() -{ - set_busyIndicatorIsRunning(true); - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - model->setData(modelIndex, m_lineEditNameAliasText, ClientManagementModel::ClientRoles::NameRole); - - const DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - const QVector protocols = ContainerProps::protocolsForContainer(selectedContainer); - if (!protocols.empty()) { - const Proto currentMainProtocol = protocols.front(); - const QJsonObject clientsTable = model->getContent(currentMainProtocol); - ErrorCode error = setClientsList(credentials, - selectedContainer, - currentMainProtocol, - clientsTable); - isErrorOccured(error); - } - - set_busyIndicatorIsRunning(false); -} - -void ClientInfoLogic::onRevokeOpenVpnCertificateClicked() -{ - set_busyIndicatorIsRunning(true); - const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString(); - - const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '" - "cd /opt/amnezia/openvpn ;\\" - "easyrsa revoke %1 ;\\" - "easyrsa gen-crl ;\\" - "cp pki/crl.pem .'").arg(certId); - ServerController serverController(m_settings); - const QString script = serverController.replaceVars(getOpenVpnCertData, - serverController.genVarsForScript(credentials, container)); - auto error = serverController.runScript(credentials, script); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - emit uiLogic()->goToPage(Page::ServerSettings); - return; - } - - model->removeRows(m_currentClientIndex); - const QJsonObject clientsTable = model->getContent(Proto::OpenVpn); - error = setClientsList(credentials, container, Proto::OpenVpn, clientsTable); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - const QJsonObject &containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, container); - error = serverController.startupContainerWorker(credentials, container, containerConfig); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - set_busyIndicatorIsRunning(false); -} - -void ClientInfoLogic::onRevokeWireGuardKeyClicked() -{ - set_busyIndicatorIsRunning(true); - ErrorCode error; - const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - - ServerController serverController(m_settings); - - const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf"; - const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - const QString key = model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString(); - - auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts); - for (auto §ion : configSections) { - if (section.contains(key)) { - configSections.removeOne(section); - } - } - QString newWireGuardConfig = configSections.join("["); - newWireGuardConfig.insert(0, "["); - error = serverController.uploadTextFileToContainer(container, credentials, newWireGuardConfig, - protocols::wireguard::serverConfigPath, - libssh::SftpOverwriteMode::SftpOverwriteExisting); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - model->removeRows(m_currentClientIndex); - const QJsonObject clientsTable = model->getContent(Proto::WireGuard); - error = setClientsList(credentials, container, Proto::WireGuard, clientsTable); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'"; - error = serverController.runScript(credentials, - serverController.replaceVars(script, serverController.genVarsForScript(credentials, container))); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - set_busyIndicatorIsRunning(false); -} - -ErrorCode ClientInfoLogic::setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns) -{ - const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol); - const QString clientsTableFile = QString("opt/amnezia/%1/clientsTable").arg(mainProtocolString); - ServerController serverController(m_settings); - ErrorCode error = serverController.uploadTextFileToContainer(container, credentials, QJsonDocument(clietns).toJson(), clientsTableFile); - return error; -} diff --git a/client/ui/pages_logic/ClientInfoLogic.h b/client/ui/pages_logic/ClientInfoLogic.h deleted file mode 100644 index 5ba19887..00000000 --- a/client/ui/pages_logic/ClientInfoLogic.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef CLIENTINFOLOGIC_H -#define CLIENTINFOLOGIC_H - -#include "PageLogicBase.h" - -#include "core/defs.h" -#include "containers/containers_defs.h" -#include "protocols/protocols_defs.h" - -class UiLogic; - -class ClientInfoLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, lineEditNameAliasText) - AUTO_PROPERTY(QString, labelOpenVpnCertId) - AUTO_PROPERTY(QString, textAreaOpenVpnCertData) - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - AUTO_PROPERTY(QString, textAreaWireGuardKeyData) - AUTO_PROPERTY(bool, busyIndicatorIsRunning); - AUTO_PROPERTY(bool, pageContentVisible); - -public: - ClientInfoLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ClientInfoLogic() = default; - - void setCurrentClientId(int index); - -public slots: - void onUpdatePage() override; - void onLineEditNameAliasEditingFinished(); - void onRevokeOpenVpnCertificateClicked(); - void onRevokeWireGuardKeyClicked(); - -private: - ErrorCode setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns); - - int m_currentClientIndex; -}; - -#endif // CLIENTINFOLOGIC_H diff --git a/client/ui/pages_logic/ClientManagementLogic.cpp b/client/ui/pages_logic/ClientManagementLogic.cpp deleted file mode 100644 index 673ee9ab..00000000 --- a/client/ui/pages_logic/ClientManagementLogic.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "ClientManagementLogic.h" - -#include - -#include "version.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include "ui/pages_logic/ClientInfoLogic.h" -#include "ui/models/clientManagementModel.h" -#include "ui/uilogic.h" - -ClientManagementLogic::ClientManagementLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} - -void ClientManagementLogic::onUpdatePage() -{ - set_busyIndicatorIsRunning(true); - - qobject_cast(uiLogic()->clientManagementModel())->clearData(); - DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); - - QJsonObject clients; - - auto protocols = ContainerProps::protocolsForContainer(selectedContainer); - if (!protocols.empty()) { - m_currentMainProtocol = protocols.front(); - - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - - ErrorCode error = getClientsList(credentials, selectedContainer, m_currentMainProtocol, clients); - if (error != ErrorCode::NoError) { - QMessageBox::warning(nullptr, APPLICATION_NAME, - tr("An error occurred while getting the list of clients.") + "\n" + errorString(error)); - set_busyIndicatorIsRunning(false); - return; - } - } - QVector clientsArray; - for (auto &clientId : clients.keys()) { - clientsArray.push_back(clients[clientId].toObject()); - } - qobject_cast(uiLogic()->clientManagementModel())->setContent(clientsArray); - - set_busyIndicatorIsRunning(false); -} - -void ClientManagementLogic::onClientItemClicked(int index) -{ - uiLogic()->pageLogic()->setCurrentClientId(index); - emit uiLogic()->goToClientInfoPage(m_currentMainProtocol); -} - -ErrorCode ClientManagementLogic::getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns) -{ - ErrorCode error = ErrorCode::NoError; - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol); - - ServerController serverController(m_settings); - - const QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable").arg(mainProtocolString); - const QByteArray clientsTableString = serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error); - if (error != ErrorCode::NoError) { - return error; - } - QJsonObject clientsTable = QJsonDocument::fromJson(clientsTableString).object(); - int count = 0; - - if (mainProtocol == Proto::OpenVpn) { - const QString getOpenVpnClientsList = "sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'"; - QString script = serverController.replaceVars(getOpenVpnClientsList, serverController.genVarsForScript(credentials, container)); - error = serverController.runScript(credentials, script, cbReadStdOut); - if (error != ErrorCode::NoError) { - return error; - } - - if (!stdOut.isEmpty()) { - QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts); - certsIds.removeAll("AmneziaReq.crt"); - - for (auto &openvpnCertId : certsIds) { - openvpnCertId.replace(".crt", ""); - if (!clientsTable.contains(openvpnCertId)) { - - QJsonObject client; - client["openvpnCertId"] = openvpnCertId; - client["clientName"] = QString("Client %1").arg(count); - client["openvpnCertData"] = ""; - clientsTable[openvpnCertId] = client; - count++; - } - } - } - } else if (mainProtocol == Proto::WireGuard) { - const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf"; - const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error); - if (error != ErrorCode::NoError) { - return error; - } - - auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts); - QStringList wireguardKeys; - for (const auto &line : configLines) { - auto configPair = line.split(" = ", Qt::SkipEmptyParts); - if (configPair.front() == "PublicKey") { - wireguardKeys.push_back(configPair.back()); - } - } - - for (auto &wireguardKey : wireguardKeys) { - if (!clientsTable.contains(wireguardKey)) { - QJsonObject client; - client["clientName"] = QString("Client %1").arg(count); - client["wireguardPublicKey"] = wireguardKey; - clientsTable[wireguardKey] = client; - count++; - } - } - } - - const QByteArray newClientsTableString = QJsonDocument(clientsTable).toJson(); - if (clientsTableString != newClientsTableString) { - error = serverController.uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile); - } - - if (error != ErrorCode::NoError) { - return error; - } - - clietns = clientsTable; - - return error; -} diff --git a/client/ui/pages_logic/ClientManagementLogic.h b/client/ui/pages_logic/ClientManagementLogic.h deleted file mode 100644 index 9c181716..00000000 --- a/client/ui/pages_logic/ClientManagementLogic.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef CLIENTMANAGMENTLOGIC_H -#define CLIENTMANAGMENTLOGIC_H - -#include "PageLogicBase.h" - -#include "core/defs.h" -#include "containers/containers_defs.h" -#include "protocols/protocols_defs.h" - -class UiLogic; - -class ClientManagementLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - AUTO_PROPERTY(bool, busyIndicatorIsRunning); - -public: - ClientManagementLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ClientManagementLogic() = default; - -public slots: - void onUpdatePage() override; - void onClientItemClicked(int index); - -private: - ErrorCode getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns); - - amnezia::Proto m_currentMainProtocol; -}; - -#endif // CLIENTMANAGMENTLOGIC_H diff --git a/client/ui/pages_logic/GeneralSettingsLogic.cpp b/client/ui/pages_logic/GeneralSettingsLogic.cpp deleted file mode 100644 index 0e92f8c9..00000000 --- a/client/ui/pages_logic/GeneralSettingsLogic.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "GeneralSettingsLogic.h" -#include "ShareConnectionLogic.h" - -#include "../uilogic.h" -#include "../models/protocols_model.h" - -GeneralSettingsLogic::GeneralSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} - -void GeneralSettingsLogic::onUpdatePage() -{ - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - set_existsAnyServer(m_settings->serversCount() > 0); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); - - set_pushButtonGeneralSettingsShareConnectionEnable(m_settings->haveAuthData(m_settings->defaultServerIndex())); -} - -void GeneralSettingsLogic::onPushButtonGeneralSettingsServerSettingsClicked() -{ - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); - - emit uiLogic()->goToPage(Page::ServerSettings); -} - -void GeneralSettingsLogic::onPushButtonGeneralSettingsShareConnectionClicked() -{ - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - - qobject_cast(uiLogic()->protocolsModel())->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); - qobject_cast(uiLogic()->protocolsModel())->setSelectedDockerContainer(uiLogic()->m_selectedDockerContainer); - - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - emit uiLogic()->goToPage(Page::ShareConnection); -} diff --git a/client/ui/pages_logic/GeneralSettingsLogic.h b/client/ui/pages_logic/GeneralSettingsLogic.h deleted file mode 100644 index a0cff333..00000000 --- a/client/ui/pages_logic/GeneralSettingsLogic.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef GENERAL_SETTINGS_LOGIC_H -#define GENERAL_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class GeneralSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pushButtonGeneralSettingsShareConnectionEnable) - AUTO_PROPERTY(bool, existsAnyServer) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onPushButtonGeneralSettingsServerSettingsClicked(); - Q_INVOKABLE void onPushButtonGeneralSettingsShareConnectionClicked(); - -public: - explicit GeneralSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~GeneralSettingsLogic() = default; - -}; -#endif // GENERAL_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/NetworkSettingsLogic.cpp b/client/ui/pages_logic/NetworkSettingsLogic.cpp deleted file mode 100644 index 1315aa10..00000000 --- a/client/ui/pages_logic/NetworkSettingsLogic.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "NetworkSettingsLogic.h" - -#include "version.h" -#include "utilities.h" -#include "settings.h" - -NetworkSettingsLogic::NetworkSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_checkBoxUseAmneziaDnsChecked{false}, - m_ipAddressRegex{Utils::ipAddressRegExp()} -{ - -} - -void NetworkSettingsLogic::onUpdatePage() -{ - set_checkBoxUseAmneziaDnsChecked(m_settings->useAmneziaDns()); - - set_lineEditDns1Text(m_settings->primaryDns()); - set_lineEditDns2Text(m_settings->secondaryDns()); -} - -void NetworkSettingsLogic::onLineEditDns1EditFinished(const QString &text) -{ - if (ipAddressRegex().match(text).hasMatch()) { - m_settings->setPrimaryDns(text); - } -} - -void NetworkSettingsLogic::onLineEditDns2EditFinished(const QString &text) -{ - if (ipAddressRegex().match(text).hasMatch()) { - m_settings->setSecondaryDns(text); - } -} - -void NetworkSettingsLogic::onPushButtonResetDns1Clicked() -{ - m_settings->setPrimaryDns(m_settings->cloudFlareNs1); - onUpdatePage(); -} - -void NetworkSettingsLogic::onPushButtonResetDns2Clicked() -{ - m_settings->setSecondaryDns(m_settings->cloudFlareNs2); - onUpdatePage(); -} - -void NetworkSettingsLogic::onCheckBoxUseAmneziaDnsToggled(bool checked) -{ - m_settings->setUseAmneziaDns(checked); -} diff --git a/client/ui/pages_logic/NetworkSettingsLogic.h b/client/ui/pages_logic/NetworkSettingsLogic.h deleted file mode 100644 index 237706eb..00000000 --- a/client/ui/pages_logic/NetworkSettingsLogic.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef NETWORK_SETTINGS_LOGIC_H -#define NETWORK_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -#include - -class UiLogic; - -class NetworkSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, checkBoxUseAmneziaDnsChecked) - - AUTO_PROPERTY(QString, lineEditDns1Text) - AUTO_PROPERTY(QString, lineEditDns2Text) - READONLY_PROPERTY(QRegularExpression, ipAddressRegex) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onLineEditDns1EditFinished(const QString& text); - Q_INVOKABLE void onLineEditDns2EditFinished(const QString& text); - Q_INVOKABLE void onPushButtonResetDns1Clicked(); - Q_INVOKABLE void onPushButtonResetDns2Clicked(); - - Q_INVOKABLE void onCheckBoxUseAmneziaDnsToggled(bool checked); - -public: - explicit NetworkSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~NetworkSettingsLogic() = default; - -}; -#endif // NETWORK_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/NewServerProtocolsLogic.cpp b/client/ui/pages_logic/NewServerProtocolsLogic.cpp deleted file mode 100644 index a1db7565..00000000 --- a/client/ui/pages_logic/NewServerProtocolsLogic.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "NewServerProtocolsLogic.h" -#include "../uilogic.h" - -NewServerProtocolsLogic::NewServerProtocolsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_progressBarConnectionMinimum{0}, - m_progressBarConnectionMaximum{100} -{ -} - - -void NewServerProtocolsLogic::onUpdatePage() -{ - set_progressBarConnectionMinimum(0); - set_progressBarConnectionMaximum(300); -} - -void NewServerProtocolsLogic::onPushButtonConfigureClicked(DockerContainer c, int port, TransportProto tp) -{ - Proto mainProto = ContainerProps::defaultProtocol(c); - - QJsonObject config { - { config_key::container, ContainerProps::containerToString(c) }, - { ProtocolProps::protoToString(mainProto), QJsonObject { - { config_key::port, QString::number(port) }, - { config_key::transport_proto, ProtocolProps::transportProtoToString(tp, mainProto) }} - } - }; - - QPair container(c, config); - - uiLogic()->installServer(container); -} - diff --git a/client/ui/pages_logic/NewServerProtocolsLogic.h b/client/ui/pages_logic/NewServerProtocolsLogic.h deleted file mode 100644 index abf3d102..00000000 --- a/client/ui/pages_logic/NewServerProtocolsLogic.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef NEW_SERVER_PROTOCOLS_LOGIC_H -#define NEW_SERVER_PROTOCOLS_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class NewServerProtocolsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(double, progressBarConnectionMinimum) - AUTO_PROPERTY(double, progressBarConnectionMaximum) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onPushButtonConfigureClicked(DockerContainer c, int port, TransportProto tp); - -public: - explicit NewServerProtocolsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~NewServerProtocolsLogic() = default; - -}; -#endif // NEW_SERVER_PROTOCOLS_LOGIC_H diff --git a/client/ui/pages_logic/PageLogicBase.cpp b/client/ui/pages_logic/PageLogicBase.cpp deleted file mode 100644 index 05c4e3a1..00000000 --- a/client/ui/pages_logic/PageLogicBase.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "PageLogicBase.h" - -#include "ui/uilogic.h" -#include "settings.h" -#include "configurators/vpn_configurator.h" - -PageLogicBase::PageLogicBase(UiLogic *logic, QObject *parent): - QObject(parent), - m_pageEnabled{true}, - m_uiLogic(logic) -{ - m_settings = logic->m_settings; - m_configurator = logic->m_configurator; -} - - diff --git a/client/ui/pages_logic/PageLogicBase.h b/client/ui/pages_logic/PageLogicBase.h deleted file mode 100644 index 6616de9d..00000000 --- a/client/ui/pages_logic/PageLogicBase.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef PAGE_LOGIC_BASE_H -#define PAGE_LOGIC_BASE_H - -#include "../pages.h" -#include "../property_helper.h" - -using namespace PageEnumNS; - -class UiLogic; -class Settings; -class VpnConfigurator; -class ServerController; - -class PageLogicBase : public QObject -{ - Q_OBJECT - AUTO_PROPERTY(bool, pageEnabled) - -public: - explicit PageLogicBase(UiLogic *uiLogic, QObject *parent = nullptr); - ~PageLogicBase() = default; - - Q_INVOKABLE virtual void onUpdatePage() {} - -protected: - UiLogic *m_uiLogic; - UiLogic *uiLogic() const { return m_uiLogic; } - - std::shared_ptr m_settings; - std::shared_ptr m_configurator; - -signals: - void updatePage(); -}; -#endif // PAGE_LOGIC_BASE_H diff --git a/client/ui/pages_logic/QrDecoderLogic.cpp b/client/ui/pages_logic/QrDecoderLogic.cpp deleted file mode 100644 index e1845c77..00000000 --- a/client/ui/pages_logic/QrDecoderLogic.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include "QrDecoderLogic.h" - -#include "ui/uilogic.h" -#include "ui/pages_logic/StartPageLogic.h" - -#ifdef Q_OS_ANDROID -#include -#include -#include "../../platforms/android/androidutils.h" -#endif - -using namespace amnezia; -using namespace PageEnumNS; - -namespace { - QrDecoderLogic* mInstance = nullptr; - constexpr auto CLASSNAME = "org.amnezia.vpn.qt.CameraActivity"; -} - -QrDecoderLogic::QrDecoderLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - mInstance = this; - - #if (defined(Q_OS_ANDROID)) - AndroidUtils::runOnAndroidThreadAsync([]() { - JNINativeMethod methods[]{ - {"passDataToDecoder", "(Ljava/lang/String;)V", reinterpret_cast(onNewDataChunk)}, - }; - - QJniObject javaClass(CLASSNAME); - QJniEnvironment env; - jclass objectClass = env->GetObjectClass(javaClass.object()); - env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); - env->DeleteLocalRef(objectClass); - }); - #endif -} - -void QrDecoderLogic::stopDecodingQr() -{ - #if (defined(Q_OS_ANDROID)) - QJniObject::callStaticMethod(CLASSNAME, "stopQrCodeReader", "()V"); - #endif - - emit stopDecode(); -} - -#ifdef Q_OS_ANDROID -void QrDecoderLogic::onNewDataChunk(JNIEnv *env, jobject thiz, jstring data) -{ - Q_UNUSED(thiz); - const char* buffer = env->GetStringUTFChars(data, nullptr); - if (!buffer) { - return; - } - - QString parcelBody(buffer); - env->ReleaseStringUTFChars(data, buffer); - - if (mInstance != nullptr) { - if (!mInstance->m_detectingEnabled) { - mInstance->onUpdatePage(); - } - mInstance->onDetectedQrCode(parcelBody); - } -} -#endif - -void QrDecoderLogic::onUpdatePage() -{ - m_chunks.clear(); - set_detectingEnabled(true); - set_totalChunksCount(0); - set_receivedChunksCount(0); - emit startDecode(); -} - -void QrDecoderLogic::onDetectedQrCode(const QString &code) -{ - //qDebug() << code; - if (!detectingEnabled()) return; - - // check if chunk received - QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QDataStream s(&ba, QIODevice::ReadOnly); - qint16 magic; s >> magic; - - if (magic == amnezia::qrMagicCode) { - quint8 chunksCount; s >> chunksCount; - if (totalChunksCount() != chunksCount) { - m_chunks.clear(); - } - - set_totalChunksCount(chunksCount); - - quint8 chunkId; s >> chunkId; - s >> m_chunks[chunkId]; - set_receivedChunksCount(m_chunks.size()); - - if (m_chunks.size() == totalChunksCount()) { - QByteArray data; - - for (int i = 0; i < totalChunksCount(); ++i) { - data.append(m_chunks.value(i)); - } - - bool ok = uiLogic()->pageLogic()->importConnectionFromQr(data); - if (ok) { - set_detectingEnabled(false); - stopDecodingQr(); - } else { - m_chunks.clear(); - set_totalChunksCount(0); - set_receivedChunksCount(0); - } - } - } else { - bool ok = uiLogic()->pageLogic()->importConnectionFromQr(ba); - if (ok) { - set_detectingEnabled(false); - stopDecodingQr(); - } - } -} diff --git a/client/ui/pages_logic/QrDecoderLogic.h b/client/ui/pages_logic/QrDecoderLogic.h deleted file mode 100644 index 2b24bf27..00000000 --- a/client/ui/pages_logic/QrDecoderLogic.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef QR_DECODER_LOGIC_H -#define QR_DECODER_LOGIC_H - -#include "PageLogicBase.h" - -#ifdef Q_OS_ANDROID -#include "jni.h" -#endif - -class UiLogic; - -class QrDecoderLogic : public PageLogicBase -{ - Q_OBJECT - AUTO_PROPERTY(bool, detectingEnabled) - AUTO_PROPERTY(int, totalChunksCount) - AUTO_PROPERTY(int, receivedChunksCount) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onDetectedQrCode(const QString &code); - -#ifdef Q_OS_ANDROID - static void onNewDataChunk(JNIEnv *env, jobject thiz, jstring data); -#endif - -public: - explicit QrDecoderLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~QrDecoderLogic() = default; - -private: - void stopDecodingQr(); - -signals: - void startDecode(); - void stopDecode(); - -private: - QMap m_chunks; -}; -#endif // QR_DECODER_LOGIC_H diff --git a/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp b/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp deleted file mode 100644 index 2b42fac9..00000000 --- a/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include "ServerConfiguringProgressLogic.h" -#include "version.h" -#include "core/errorstrings.h" -#include -#include - -#include "core/servercontroller.h" - -ServerConfiguringProgressLogic::ServerConfiguringProgressLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_progressBarValue{0}, - m_labelWaitInfoVisible{true}, - m_labelWaitInfoText{tr("Please wait, configuring process may take up to 5 minutes")}, - m_progressBarVisible{true}, - m_progressBarMaximum{100}, - m_progressBarTextVisible{true}, - m_progressBarText{tr("Configuring...")}, - m_labelServerBusyVisible{false}, - m_labelServerBusyText{""} -{ - -} - -void ServerConfiguringProgressLogic::onUpdatePage() -{ - set_progressBarValue(0); -} - - -ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function &action) -{ - PageFunc page; - page.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ButtonFunc noButton; - LabelFunc noWaitInfo; - ProgressFunc progress; - progress.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarVisible(visible); - }; - - progress.setValueFunc = [this] (int value) -> void { - set_progressBarValue(value); - }; - progress.getValueFunc = [this] (void) -> int { - return progressBarValue(); - }; - progress.getMaximumFunc = [this] (void) -> int { - return progressBarMaximum(); - }; - - LabelFunc busyInfo; - busyInfo.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfo.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ButtonFunc cancelButton; - cancelButton.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - return doInstallAction(action, page, progress, noButton, noWaitInfo, busyInfo, cancelButton); -} - -ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function &action, - const PageFunc &page, - const ProgressFunc &progress, - const ButtonFunc &saveButton, - const LabelFunc &waitInfo, - const LabelFunc &serverBusyInfo, - const ButtonFunc &cancelButton) -{ - progress.setVisibleFunc(true); - if (page.setEnabledFunc) { - page.setEnabledFunc(false); - } - if (saveButton.setVisibleFunc) { - saveButton.setVisibleFunc(false); - } - if (waitInfo.setVisibleFunc) { - waitInfo.setVisibleFunc(true); - } - if (waitInfo.setTextFunc) { - waitInfo.setTextFunc(tr("Please wait, configuring process may take up to 5 minutes")); - } - - QTimer timer; - connect(&timer, &QTimer::timeout, [progress](){ - progress.setValueFunc(progress.getValueFunc() + 1); - }); - - progress.setValueFunc(0); - timer.start(1000); - - ServerController serverController(m_settings); - - QMetaObject::Connection cancelDoInstallActionConnection; - if (cancelButton.setVisibleFunc) { - cancelDoInstallActionConnection = connect(this, &ServerConfiguringProgressLogic::cancelDoInstallAction, - &serverController, &ServerController::setCancelInstallation); - } - - - QMetaObject::Connection serverBusyConnection; - if (serverBusyInfo.setVisibleFunc && serverBusyInfo.setTextFunc) { - auto onServerIsBusy = [&serverBusyInfo, &timer, &cancelButton](const bool isBusy) { - isBusy ? timer.stop() : timer.start(1000); - serverBusyInfo.setVisibleFunc(isBusy); - serverBusyInfo.setTextFunc(isBusy ? "Amnesia has detected that your server is currently " - "busy installing other software. Amnesia installation " - "will pause until the server finishes installing other software" - : ""); - if (cancelButton.setVisibleFunc) { - cancelButton.setVisibleFunc(isBusy ? true : false); - } - }; - - serverBusyConnection = connect(&serverController, &ServerController::serverIsBusy, this, onServerIsBusy); - } - - ErrorCode e = action(); - qDebug() << "doInstallAction finished with code" << e; - if (cancelButton.setVisibleFunc) { - disconnect(cancelDoInstallActionConnection); - } - - if (serverBusyInfo.setVisibleFunc && serverBusyInfo.setTextFunc) { - disconnect(serverBusyConnection); - } - - if (e) { - if (page.setEnabledFunc) { - page.setEnabledFunc(true); - } - if (saveButton.setVisibleFunc) { - saveButton.setVisibleFunc(true); - } - if (waitInfo.setVisibleFunc) { - waitInfo.setVisibleFunc(false); - } - - progress.setVisibleFunc(false); - return e; - } - - // just ui progressbar tweak - timer.stop(); - - int remainingVal = progress.getMaximumFunc() - progress.getValueFunc(); - - if (remainingVal > 0) { - QTimer timer1; - QEventLoop loop1; - - connect(&timer1, &QTimer::timeout, [&](){ - progress.setValueFunc(progress.getValueFunc() + 1); - if (progress.getValueFunc() >= progress.getMaximumFunc()) { - loop1.quit(); - } - }); - - timer1.start(5); - loop1.exec(); - } - - - progress.setVisibleFunc(false); - if (saveButton.setVisibleFunc) { - saveButton.setVisibleFunc(true); - } - if (page.setEnabledFunc) { - page.setEnabledFunc(true); - } - if (waitInfo.setTextFunc) { - waitInfo.setTextFunc(tr("Operation finished")); - } - return ErrorCode::NoError; -} - -void ServerConfiguringProgressLogic::onPushButtonCancelClicked() -{ - emit cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/ServerConfiguringProgressLogic.h b/client/ui/pages_logic/ServerConfiguringProgressLogic.h deleted file mode 100644 index 9f10bc87..00000000 --- a/client/ui/pages_logic/ServerConfiguringProgressLogic.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef SERVER_CONFIGURING_PROGRESS_LOGIC_H -#define SERVER_CONFIGURING_PROGRESS_LOGIC_H - -#include -#include "PageLogicBase.h" -#include "core/defs.h" - -using namespace amnezia; - -class UiLogic; - -class ServerConfiguringProgressLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(double, progressBarValue) - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - AUTO_PROPERTY(bool, progressBarVisible) - AUTO_PROPERTY(int, progressBarMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - AUTO_PROPERTY(bool, pushButtonCancelVisible) - -public: - Q_INVOKABLE void onPushButtonCancelClicked(); - -private: - struct ProgressFunc { - std::function setVisibleFunc; - std::function setValueFunc; - std::function getValueFunc; - std::function getMaximumFunc; - std::function setTextVisibleFunc; - std::function setTextFunc; - }; - struct PageFunc { - std::function setEnabledFunc; - }; - struct ButtonFunc { - std::function setVisibleFunc; - }; - struct LabelFunc { - std::function setVisibleFunc; - std::function setTextFunc; - }; - -public: - explicit ServerConfiguringProgressLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerConfiguringProgressLogic() = default; - - friend class OpenVpnLogic; - friend class ShadowSocksLogic; - friend class CloakLogic; - friend class UiLogic; - - void onUpdatePage() override; - ErrorCode doInstallAction(const std::function &action); - ErrorCode doInstallAction(const std::function &action, - const PageFunc &page, - const ProgressFunc &progress, - const ButtonFunc &saveButton, - const LabelFunc &waitInfo, - const LabelFunc &serverBusyInfo, - const ButtonFunc &cancelButton); - -signals: - void cancelDoInstallAction(const bool cancel); - -}; -#endif // SERVER_CONFIGURING_PROGRESS_LOGIC_H diff --git a/client/ui/pages_logic/ServerContainersLogic.cpp b/client/ui/pages_logic/ServerContainersLogic.cpp deleted file mode 100644 index 80bf362c..00000000 --- a/client/ui/pages_logic/ServerContainersLogic.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "ServerContainersLogic.h" -#include "ShareConnectionLogic.h" -#include "ServerConfiguringProgressLogic.h" - -#include - -#include "protocols/PageProtocolLogicBase.h" - -#include "core/servercontroller.h" -#include - -#include "../uilogic.h" -#include "../pages_logic/VpnLogic.h" -#include "vpnconnection.h" -#include "core/errorstrings.h" - - -ServerContainersLogic::ServerContainersLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ -} - -void ServerContainersLogic::onUpdatePage() -{ - ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); - c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); - - ProtocolsModel *p_model = qobject_cast(uiLogic()->protocolsModel()); - p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); - - set_isManagedServer(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - uiLogic()->m_installCredentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - emit updatePage(); -} - -void ServerContainersLogic::onPushButtonProtoSettingsClicked(DockerContainer c, Proto p) -{ - qDebug()<< "ServerContainersLogic::onPushButtonProtoSettingsClicked" << c << p; - uiLogic()->m_selectedDockerContainer = c; - uiLogic()->protocolLogic(p)->updateProtocolPage(m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, p), - uiLogic()->m_selectedDockerContainer, - m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - - emit uiLogic()->goToProtocolPage(p); -} - -void ServerContainersLogic::onPushButtonDefaultClicked(DockerContainer c) -{ - if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == c) return; - - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c); - uiLogic()->onUpdateAllPages(); - - if (uiLogic()->m_selectedServerIndex != m_settings->defaultServerIndex()) return; - if (!uiLogic()->m_vpnConnection) return; - if (!uiLogic()->m_vpnConnection->isConnected()) return; - - uiLogic()->pageLogic()->onDisconnect(); - uiLogic()->pageLogic()->onConnect(); -} - -void ServerContainersLogic::onPushButtonShareClicked(DockerContainer c) -{ - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, c); - emit uiLogic()->goToPage(Page::ShareConnection); -} - -void ServerContainersLogic::onPushButtonRemoveClicked(DockerContainer container) -{ - //buttonSetEnabledFunc(false); - ServerController serverController(m_settings); - ErrorCode e = serverController.removeContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), container); - m_settings->removeContainerConfig(uiLogic()->m_selectedServerIndex, container); - //buttonSetEnabledFunc(true); - - if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == container) { - const auto &c = m_settings->containers(uiLogic()->m_selectedServerIndex); - if (c.isEmpty()) m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); - else m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c.keys().first()); - } - uiLogic()->onUpdateAllPages(); -} - -void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int port, TransportProto tp) -{ - ServerController serverController(m_settings); - QJsonObject config = serverController.createContainerInitialConfig(c, port, tp); - - emit uiLogic()->goToPage(Page::ServerConfiguringProgress); - qApp->processEvents(); - - bool isServerCreated = false; - ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(isServerCreated); - - if (errorCode == ErrorCode::NoError) { - if (!uiLogic()->isContainerAlreadyAddedToGui(c)) { - auto installAction = [this, c, &config]() { - ServerController serverController(m_settings); - return serverController.setupContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), c, config); - }; - errorCode = uiLogic()->pageLogic()->doInstallAction(installAction); - - if (errorCode == ErrorCode::NoError) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, c, config); - if (ContainerProps::containerService(c) == ServiceType::Vpn) { - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c); - } - } - } else { - emit uiLogic()->showWarningMessage("Attention! The container you are trying to install is already installed on the server. " - "All installed containers have been added to the application "); - } - - uiLogic()->onUpdateAllPages(); - } - if (errorCode != ErrorCode::NoError) { - emit uiLogic()->showWarningMessage(tr("Error occurred while configuring server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); - } - emit uiLogic()->closePage(); -} diff --git a/client/ui/pages_logic/ServerContainersLogic.h b/client/ui/pages_logic/ServerContainersLogic.h deleted file mode 100644 index d0081516..00000000 --- a/client/ui/pages_logic/ServerContainersLogic.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef SERVER_CONTAINERS_LOGIC_H -#define SERVER_CONTAINERS_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class ServerContainersLogic : public PageLogicBase -{ - Q_OBJECT - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonProtoSettingsClicked(DockerContainer c, Proto p); - Q_INVOKABLE void onPushButtonDefaultClicked(DockerContainer c); - Q_INVOKABLE void onPushButtonShareClicked(DockerContainer c); - Q_INVOKABLE void onPushButtonRemoveClicked(DockerContainer c); - Q_INVOKABLE void onPushButtonContinueClicked(DockerContainer c, int port, TransportProto tp); - - AUTO_PROPERTY(bool, isManagedServer) - -public: - explicit ServerContainersLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerContainersLogic() = default; - -}; -#endif // SERVER_CONTAINERS_LOGIC_H diff --git a/client/ui/pages_logic/ServerListLogic.cpp b/client/ui/pages_logic/ServerListLogic.cpp deleted file mode 100644 index 79d13c8b..00000000 --- a/client/ui/pages_logic/ServerListLogic.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "ServerListLogic.h" - -#include "vpnconnection.h" -#include "../models/servers_model.h" -#include "../uilogic.h" - -ServerListLogic::ServerListLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_serverListModel{new ServersModel(this)} -{ - -} - -void ServerListLogic::onServerListPushbuttonDefaultClicked(int index) -{ - m_settings->setDefaultServer(index); - uiLogic()->onUpdateAllPages(); - emit currServerIdxChanged(); -} - -void ServerListLogic::onServerListPushbuttonSettingsClicked(int index) -{ - uiLogic()->m_selectedServerIndex = index; - uiLogic()->goToPage(Page::ServerSettings); -} - -int ServerListLogic::currServerIdx() const -{ - return m_settings->defaultServerIndex(); -} - -void ServerListLogic::onUpdatePage() -{ - const QJsonArray &servers = m_settings->serversArray(); - int defaultServer = m_settings->defaultServerIndex(); - std::vector serverListContent; - for(int i = 0; i < servers.size(); i++) { - ServerModelContent c; - auto server = servers.at(i).toObject(); - c.desc = server.value(config_key::description).toString(); - c.address = server.value(config_key::hostName).toString(); - if (c.desc.isEmpty()) { - c.desc = c.address; - } - c.isDefault = (i == defaultServer); - serverListContent.push_back(c); - } - qobject_cast(m_serverListModel)->setContent(serverListContent); -} diff --git a/client/ui/pages_logic/ServerListLogic.h b/client/ui/pages_logic/ServerListLogic.h deleted file mode 100644 index b4f47547..00000000 --- a/client/ui/pages_logic/ServerListLogic.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SERVER_LIST_LOGIC_H -#define SERVER_LIST_LOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class ServerListLogic : public PageLogicBase -{ - Q_OBJECT - - READONLY_PROPERTY(QObject *, serverListModel) - Q_PROPERTY(int currServerIdx READ currServerIdx NOTIFY currServerIdxChanged) - -public: - int currServerIdx() const; - - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onServerListPushbuttonDefaultClicked(int index); - Q_INVOKABLE void onServerListPushbuttonSettingsClicked(int index); - -public: - explicit ServerListLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerListLogic() = default; - -signals: - void currServerIdxChanged(); - -}; -#endif // SERVER_LIST_LOGIC_H diff --git a/client/ui/pages_logic/ServerSettingsLogic.cpp b/client/ui/pages_logic/ServerSettingsLogic.cpp deleted file mode 100644 index a17f0159..00000000 --- a/client/ui/pages_logic/ServerSettingsLogic.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "ServerSettingsLogic.h" -#include "vpnconnection.h" - -#include "../uilogic.h" -#include "ShareConnectionLogic.h" -#include "VpnLogic.h" - -#include "core/errorstrings.h" -#include -#include - -#if defined(Q_OS_ANDROID) -#include "../../platforms/android/androidutils.h" -#endif - -ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_labelWaitInfoVisible{true}, - m_pushButtonClearClientCacheVisible{true}, - m_pushButtonShareFullVisible{true}, - m_pushButtonClearClientCacheText{tr("Clear client cached profile")} -{ } - -void ServerSettingsLogic::onUpdatePage() -{ - set_labelWaitInfoVisible(false); - set_labelWaitInfoText(""); - set_pushButtonClearClientCacheVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - set_pushButtonShareFullVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - const QJsonObject &server = m_settings->server(uiLogic()->m_selectedServerIndex); - const QString &port = server.value(config_key::port).toString(); - - const QString &userName = server.value(config_key::userName).toString(); - const QString &hostName = server.value(config_key::hostName).toString(); - QString name = QString("%1%2%3%4%5") - .arg(userName) - .arg(userName.isEmpty() ? "" : "@") - .arg(hostName) - .arg(port.isEmpty() ? "" : ":") - .arg(port); - - set_labelServerText(name); - set_lineEditDescriptionText(server.value(config_key::description).toString()); - - DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); -} - -void ServerSettingsLogic::onPushButtonForgetServer() -{ - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex && uiLogic()->m_vpnConnection->isConnected()) { - uiLogic()->pageLogic()->onDisconnect(); - } - m_settings->removeServer(uiLogic()->m_selectedServerIndex); - - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex) { - m_settings->setDefaultServer(0); - } - else if (m_settings->defaultServerIndex() > uiLogic()->m_selectedServerIndex) { - m_settings->setDefaultServer(m_settings->defaultServerIndex() - 1); - } - - if (m_settings->serversCount() == 0) { - m_settings->setDefaultServer(-1); - } - - - uiLogic()->m_selectedServerIndex = -1; - uiLogic()->onUpdateAllPages(); - - if (m_settings->serversCount() == 0) { - uiLogic()->setStartPage(Page::Start); - } - else { - uiLogic()->closePage(); - } -} - -void ServerSettingsLogic::onPushButtonClearClientCacheClicked() -{ - set_pushButtonClearClientCacheText(tr("Cache cleared")); - - const auto &containers = m_settings->containers(uiLogic()->m_selectedServerIndex); - for (DockerContainer container : containers.keys()) { - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, container); - } - - QTimer::singleShot(3000, this, [this]() { - set_pushButtonClearClientCacheText(tr("Clear client cached profile")); - }); -} - -void ServerSettingsLogic::onLineEditDescriptionEditingFinished() -{ - const QString &newText = lineEditDescriptionText(); - QJsonObject server = m_settings->server(uiLogic()->m_selectedServerIndex); - server.insert(config_key::description, newText); - m_settings->editServer(uiLogic()->m_selectedServerIndex, server); - uiLogic()->onUpdateAllPages(); -} - -bool ServerSettingsLogic::isCurrentServerHasCredentials() -{ - return m_settings->haveAuthData(uiLogic()->m_selectedServerIndex); -} - -#if defined(Q_OS_ANDROID) -/* Auth result handler for Android */ -void authResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) -{ - qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode; - - if (resultCode == -1) { //ResultOK - uiLogic()->pageLogic()->updateSharingPage(m_serverIndex, DockerContainer::None); - emit uiLogic()->goToShareProtocolPage(Proto::Any); - } -} -#endif - -void ServerSettingsLogic::onPushButtonShareFullClicked() -{ -#if defined(Q_OS_ANDROID) -/* We use builtin keyguard for ssh key export protection on Android */ - QJniObject activity = AndroidUtils::getActivity(); - auto appContext = activity.callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); - if (appContext.isValid()) { - QAndroidActivityResultReceiver *receiver = new authResultReceiver(uiLogic(), uiLogic()->m_selectedServerIndex); - auto intent = QJniObject::callStaticObjectMethod( - "org/amnezia/vpn/AuthHelper", "getAuthIntent", - "(Landroid/content/Context;)Landroid/content/Intent;", appContext.object()); - if (intent.isValid()) { - if (intent.object() != nullptr) { - QtAndroidPrivate::startActivity(intent.object(), 1, receiver); - } - } else { - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, DockerContainer::None); - emit uiLogic()->goToShareProtocolPage(Proto::Any); - } - } -#else - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, DockerContainer::None); - emit uiLogic()->goToShareProtocolPage(Proto::Any); -#endif -} diff --git a/client/ui/pages_logic/ServerSettingsLogic.h b/client/ui/pages_logic/ServerSettingsLogic.h deleted file mode 100644 index 3ce26164..00000000 --- a/client/ui/pages_logic/ServerSettingsLogic.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef SERVER_SETTINGS_LOGIC_H -#define SERVER_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -#if defined(Q_OS_ANDROID) -#include -#include -#endif - -class UiLogic; - -class ServerSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - AUTO_PROPERTY(QString, pushButtonClearClientCacheText) - AUTO_PROPERTY(bool, pushButtonClearClientCacheVisible) - AUTO_PROPERTY(bool, pushButtonShareFullVisible) - AUTO_PROPERTY(QString, labelServerText) - AUTO_PROPERTY(QString, lineEditDescriptionText) - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonForgetServer(); - Q_INVOKABLE void onPushButtonShareFullClicked(); - Q_INVOKABLE void onPushButtonClearClientCacheClicked(); - Q_INVOKABLE void onLineEditDescriptionEditingFinished(); - - Q_INVOKABLE bool isCurrentServerHasCredentials(); - -public: - explicit ServerSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerSettingsLogic() = default; - -}; - -#if defined(Q_OS_ANDROID) -/* Auth result handler for Android */ -class authResultReceiver final : public PageLogicBase, public QAndroidActivityResultReceiver -{ -Q_OBJECT - -public: - authResultReceiver(UiLogic *uiLogic, int serverIndex , QObject *parent = nullptr) : PageLogicBase(uiLogic, parent) { - m_serverIndex = serverIndex; - } - ~authResultReceiver() {} - -public: - void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override; - -private: - int m_serverIndex; -}; -#endif - -#endif // SERVER_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/ShareConnectionLogic.cpp b/client/ui/pages_logic/ShareConnectionLogic.cpp deleted file mode 100644 index 911c872c..00000000 --- a/client/ui/pages_logic/ShareConnectionLogic.cpp +++ /dev/null @@ -1,293 +0,0 @@ -#include -#include -#include - -#include "qrcodegen.hpp" - -#include "ShareConnectionLogic.h" - -#include "configurators/cloak_configurator.h" -#include "configurators/vpn_configurator.h" -#include "configurators/openvpn_configurator.h" -#include "configurators/shadowsocks_configurator.h" -#include "configurators/wireguard_configurator.h" -#include "configurators/ikev2_configurator.h" -#include "configurators/ssh_configurator.h" - -#include "version.h" -#include "core/defs.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include - -#include "../uilogic.h" - -#ifdef __linux__ - #include -#endif - -using namespace qrcodegen; - -ShareConnectionLogic::ShareConnectionLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_textEditShareOpenVpnCodeText{}, - m_lineEditShareShadowSocksStringText{}, - m_shareShadowSocksQrCodeText{}, - m_textEditShareCloakText{}, - m_textEditShareAmneziaCodeText{} -{ -} - -void ShareConnectionLogic::onUpdatePage() -{ - set_textEditShareAmneziaCodeText(tr("")); - set_shareAmneziaQrCodeTextSeries({}); - set_shareAmneziaQrCodeTextSeriesLength(0); - - set_textEditShareOpenVpnCodeText(""); - - set_shareShadowSocksQrCodeText(""); - set_textEditShareShadowSocksText(""); - set_lineEditShareShadowSocksStringText(""); - - set_textEditShareCloakText(""); - - set_textEditShareWireGuardCodeText(""); - set_shareWireGuardQrCodeText(""); - - set_textEditShareIkev2CertText(""); - set_textEditShareIkev2MobileConfigText(""); - set_textEditShareIkev2StrongSwanConfigText(""); -} - -void ShareConnectionLogic::onPushButtonShareAmneziaGenerateClicked() -{ - set_textEditShareAmneziaCodeText(""); - set_shareAmneziaQrCodeTextSeries({}); - set_shareAmneziaQrCodeTextSeriesLength(0); - - QJsonObject serverConfig; - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - - // Full access - if (shareFullAccess()) { - serverConfig = m_settings->server(serverIndex); - } - // Container share - else { - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - QJsonObject containerConfig = m_settings->containerConfig(serverIndex, container); - containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); - - ErrorCode e = ErrorCode::NoError; - for (Proto p: ContainerProps::protocolsForContainer(container)) { - QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, p); - - QString cfg = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, p, &e); - if (e) { - cfg = "Error generating config"; - break; - } - protoConfig.insert(config_key::last_config, cfg); - containerConfig.insert(ProtocolProps::protoToString(p), protoConfig); - } - - QByteArray ba; - if (!e) { - serverConfig = m_settings->server(serverIndex); - serverConfig.remove(config_key::userName); - serverConfig.remove(config_key::password); - serverConfig.remove(config_key::port); - serverConfig.insert(config_key::containers, QJsonArray {containerConfig}); - serverConfig.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - - auto dns = m_configurator->getDnsForConfig(serverIndex); - serverConfig.insert(config_key::dns1, dns.first); - serverConfig.insert(config_key::dns2, dns.second); - - } - else { - set_textEditShareAmneziaCodeText(tr("Error while generating connection profile")); - return; - } - } - - QByteArray ba = QJsonDocument(serverConfig).toJson(); - ba = qCompress(ba, 8); - QString code = QString("vpn://%1").arg(QString(ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); - set_textEditShareAmneziaCodeText(code); - - - QList qrChunks = genQrCodeImageSeries(ba); - set_shareAmneziaQrCodeTextSeries(qrChunks); - set_shareAmneziaQrCodeTextSeriesLength(qrChunks.size()); -} - -void ShareConnectionLogic::onPushButtonShareOpenVpnGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - QString cfg = m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, &e); - cfg = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, cfg); - - set_textEditShareOpenVpnCodeText(QJsonDocument::fromJson(cfg.toUtf8()).object()[config_key::config].toString()); -} - -void ShareConnectionLogic::onPushButtonShareShadowSocksGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, Proto::ShadowSocks); - QString cfg = protoConfig.value(config_key::last_config).toString(); - - if (cfg.isEmpty()) { - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - cfg = m_configurator->shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, &e); - } - - QJsonObject ssConfig = QJsonDocument::fromJson(cfg.toUtf8()).object(); - - QString ssString = QString("%1:%2@%3:%4") - .arg(ssConfig.value("method").toString()) - .arg(ssConfig.value("password").toString()) - .arg(ssConfig.value("server").toString()) - .arg(ssConfig.value("server_port").toString()); - - ssString = "ss://" + ssString.toUtf8().toBase64(); - set_lineEditShareShadowSocksStringText(ssString); - - QrCode qr = QrCode::encodeText(ssString.toUtf8(), QrCode::Ecc::LOW); - QString svg = QString::fromStdString(toSvgString(qr, 0)); - - set_shareShadowSocksQrCodeText(svgToBase64(svg)); - - QString humanString = QString("Server: %3\n" - "Port: %4\n" - "Encryption: %1\n" - "Password: %2") - .arg(ssConfig.value("method").toString()) - .arg(ssConfig.value("password").toString()) - .arg(ssConfig.value("server").toString()) - .arg(ssConfig.value("server_port").toString()); - - set_textEditShareShadowSocksText(humanString); -} - -void ShareConnectionLogic::onPushButtonShareCloakGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, Proto::Cloak); - QString cfg = protoConfig.value(config_key::last_config).toString(); - - if (cfg.isEmpty()) { - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - cfg = m_configurator->cloakConfigurator->genCloakConfig(credentials, container, containerConfig, &e); - } - - QJsonObject cloakConfig = QJsonDocument::fromJson(cfg.toUtf8()).object(); - cloakConfig.remove(config_key::transport_proto); - cloakConfig.insert("ProxyMethod", "shadowsocks"); - - set_textEditShareCloakText(QJsonDocument(cloakConfig).toJson()); -} - -void ShareConnectionLogic::onPushButtonShareWireGuardGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - QString cfg = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, &e); - if (e) { - emit uiLogic()->showWarningMessage(tr("Error occurred while generating the config.") + "\n" + - tr("Error message: ") + errorString(e) + "\n" + - tr("See logs for details.")); - return; - } - cfg = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, cfg); - cfg = QJsonDocument::fromJson(cfg.toUtf8()).object()[config_key::config].toString(); - - set_textEditShareWireGuardCodeText(cfg); - - QrCode qr = QrCode::encodeText(cfg.toUtf8(), QrCode::Ecc::LOW); - QString svg = QString::fromStdString(toSvgString(qr, 0)); - - set_shareWireGuardQrCodeText(svgToBase64(svg)); -} - -void ShareConnectionLogic::onPushButtonShareIkev2GenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - Ikev2Configurator::ConnectionData connData = m_configurator->ikev2Configurator->prepareIkev2Config(credentials, container); - - QString cfg = m_configurator->ikev2Configurator->genIkev2Config(connData); - cfg = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::Ikev2, cfg); - cfg = QJsonDocument::fromJson(cfg.toUtf8()).object()[config_key::cert].toString(); - - set_textEditShareIkev2CertText(cfg); - - QString mobileCfg = m_configurator->ikev2Configurator->genMobileConfig(connData); - set_textEditShareIkev2MobileConfigText(mobileCfg); - - QString strongSwanCfg = m_configurator->ikev2Configurator->genStrongSwanConfig(connData); - set_textEditShareIkev2StrongSwanConfigText(strongSwanCfg); - -} - - -void ShareConnectionLogic::updateSharingPage(int serverIndex, DockerContainer container) -{ - uiLogic()->m_selectedDockerContainer = container; - uiLogic()->m_selectedServerIndex = serverIndex; - set_shareFullAccess(container == DockerContainer::None); - - m_shareAmneziaQrCodeTextSeries.clear(); - set_shareAmneziaQrCodeTextSeriesLength(0); -} - -QList ShareConnectionLogic::genQrCodeImageSeries(const QByteArray &data) -{ - double k = 850; - - quint8 chunksCount = std::ceil(data.size() / k); - QList chunks; - for (int i = 0; i < data.size(); i = i + k) { - QByteArray chunk; - QDataStream s(&chunk, QIODevice::WriteOnly); - s << amnezia::qrMagicCode << chunksCount << (quint8)std::round(i/k) << data.mid(i, k); - - QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - - QrCode qr = QrCode::encodeText(ba, QrCode::Ecc::LOW); - QString svg = QString::fromStdString(toSvgString(qr, 0)); - chunks.append(svgToBase64(svg)); - } - - return chunks; -} - -QString ShareConnectionLogic::svgToBase64(const QString &image) -{ - return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data()); -} diff --git a/client/ui/pages_logic/ShareConnectionLogic.h b/client/ui/pages_logic/ShareConnectionLogic.h deleted file mode 100644 index 3b9655aa..00000000 --- a/client/ui/pages_logic/ShareConnectionLogic.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef SHARE_CONNECTION_LOGIC_H -#define SHARE_CONNECTION_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class ShareConnectionLogic: public PageLogicBase -{ - Q_OBJECT - -public: - AUTO_PROPERTY(bool, shareFullAccess) - - AUTO_PROPERTY(QString, textEditShareAmneziaCodeText) - AUTO_PROPERTY(QStringList, shareAmneziaQrCodeTextSeries) - AUTO_PROPERTY(int, shareAmneziaQrCodeTextSeriesLength) - - AUTO_PROPERTY(QString, textEditShareOpenVpnCodeText) - - AUTO_PROPERTY(QString, textEditShareShadowSocksText) - AUTO_PROPERTY(QString, lineEditShareShadowSocksStringText) - AUTO_PROPERTY(QString, shareShadowSocksQrCodeText) - - AUTO_PROPERTY(QString, textEditShareCloakText) - - AUTO_PROPERTY(QString, textEditShareWireGuardCodeText) - AUTO_PROPERTY(QString, shareWireGuardQrCodeText) - - AUTO_PROPERTY(QString, textEditShareIkev2CertText) - AUTO_PROPERTY(QString, textEditShareIkev2MobileConfigText) - AUTO_PROPERTY(QString, textEditShareIkev2StrongSwanConfigText) - -public: - Q_INVOKABLE void onPushButtonShareAmneziaGenerateClicked(); - Q_INVOKABLE void onPushButtonShareOpenVpnGenerateClicked(); - Q_INVOKABLE void onPushButtonShareShadowSocksGenerateClicked(); - Q_INVOKABLE void onPushButtonShareCloakGenerateClicked(); - Q_INVOKABLE void onPushButtonShareWireGuardGenerateClicked(); - Q_INVOKABLE void onPushButtonShareIkev2GenerateClicked(); - - Q_INVOKABLE virtual void onUpdatePage() override; - -public: - explicit ShareConnectionLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ShareConnectionLogic() = default; - - void updateSharingPage(int serverIndex, DockerContainer container); - QList genQrCodeImageSeries(const QByteArray &data); - - QString svgToBase64(const QString &image); -}; -#endif // SHARE_CONNECTION_LOGIC_H diff --git a/client/ui/pages_logic/SitesLogic.cpp b/client/ui/pages_logic/SitesLogic.cpp deleted file mode 100644 index 96a0fade..00000000 --- a/client/ui/pages_logic/SitesLogic.cpp +++ /dev/null @@ -1,222 +0,0 @@ -#include -#include -#include -#include - -#include "SitesLogic.h" -#include "VpnLogic.h" -#include "utilities.h" -#include "vpnconnection.h" -#include - -#include "../uilogic.h" -#include "../models/sites_model.h" - -SitesLogic::SitesLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_labelSitesAddCustomText{}, - m_tableViewSitesModel{nullptr}, - m_lineEditSitesAddCustomText{} -{ - sitesModels.insert(Settings::VpnOnlyForwardSites, new SitesModel(m_settings, Settings::VpnOnlyForwardSites)); - sitesModels.insert(Settings::VpnAllExceptSites, new SitesModel(m_settings, Settings::VpnAllExceptSites)); -} - -void SitesLogic::onUpdatePage() -{ - Settings::RouteMode m = m_settings->routeMode(); - if (m == Settings::VpnAllSites) return; - - if (m == Settings::VpnOnlyForwardSites) { - set_labelSitesAddCustomText(tr("These sites will be opened using VPN")); - } - if (m == Settings::VpnAllExceptSites) { - set_labelSitesAddCustomText(tr("These sites will be excepted from VPN")); - } - - set_tableViewSitesModel(sitesModels.value(m)); - sitesModels.value(m)->resetCache(); -} - -void SitesLogic::onPushButtonAddCustomSitesClicked() -{ - if (uiLogic()->pageLogic()->radioButtonVpnModeAllSitesChecked()) { - return; - } - Settings::RouteMode mode = m_settings->routeMode(); - - QString newSite = lineEditSitesAddCustomText(); - - if (newSite.isEmpty()) return; - if (!newSite.contains(".")) return; - - if (!Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { - // get domain name if it present - newSite.replace("https://", ""); - newSite.replace("http://", ""); - newSite.replace("ftp://", ""); - - newSite = newSite.split("/", Qt::SkipEmptyParts).first(); - } - - const auto &cbProcess = [this, mode](const QString &newSite, const QString &ip) { - m_settings->addVpnSite(mode, newSite, ip); - - if (!ip.isEmpty()) { - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", - Qt::QueuedConnection, - Q_ARG(QStringList, QStringList() << ip)); - } - else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", - Qt::QueuedConnection, - Q_ARG(QStringList, QStringList() << newSite)); - } - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", - Qt::QueuedConnection); - - onUpdatePage(); - }; - - const auto &cbResolv = [this, cbProcess](const QHostInfo &hostInfo){ - const QList &addresses = hostInfo.addresses(); - QString ipv4Addr; - for (const QHostAddress &addr: hostInfo.addresses()) { - if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { - cbProcess(hostInfo.hostName(), addr.toString()); - break; - } - } - }; - - set_lineEditSitesAddCustomText(""); - - if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { - cbProcess(newSite, ""); - return; - } - else { - cbProcess(newSite, ""); - onUpdatePage(); - QHostInfo::lookupHost(newSite, this, cbResolv); - } -} - -void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) -{ - Settings::RouteMode mode = m_settings->routeMode(); - - auto siteModel = qobject_cast (tableViewSitesModel()); - if (!siteModel || items.isEmpty()) { - return; - } - - QStringList sites; - QStringList ips; - - for (const QString &s: items) { - bool ok; - int row = s.toInt(&ok); - if (!ok || row < 0 || row >= siteModel->rowCount()) return; - sites.append(siteModel->data(row, 0).toString()); - - if (uiLogic()->m_vpnConnection && uiLogic()->m_vpnConnection->connectionState() == VpnProtocol::Connected) { - ips.append(siteModel->data(row, 1).toString()); - } - } - - m_settings->removeVpnSites(mode, sites); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "deleteRoutes", - Qt::QueuedConnection, - Q_ARG(QStringList, ips)); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", - Qt::QueuedConnection); - - onUpdatePage(); -} - -void SitesLogic::onPushButtonSitesImportClicked(const QString& fileName) -{ - QFile file(QUrl{fileName}.toLocalFile()); - if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Can't open file " << QUrl{fileName}.toLocalFile(); - return; - } - - Settings::RouteMode mode = m_settings->routeMode(); - - QStringList ips; - QMap sites; - - while (!file.atEnd()) { - QString line = file.readLine(); - QStringList line_ips; - QStringList line_sites; - - int posDomain = 0; - QRegExp domainRx = Utils::domainRegExp(); - while ((posDomain = domainRx.indexIn(line, posDomain)) != -1) { - line_sites.append(domainRx.cap(0)); - posDomain += domainRx.matchedLength(); - } - - int posIp = 0; - QRegExp ipRx = Utils::ipAddressWithSubnetRegExp(); - while ((posIp = ipRx.indexIn(line, posIp)) != -1) { - line_ips.append(ipRx.cap(0)); - posIp += ipRx.matchedLength(); - } - - // domain regex cover ip regex, so remove ips from sites - for (const QString& ip: line_ips) { - line_sites.removeAll(ip); - } - - if (line_sites.size() == 1 && line_ips.size() == 1) { - sites.insert(line_sites.at(0), line_ips.at(0)); - } - else if (line_sites.size() > 0 && line_ips.size() == 0) { - for (const QString& site: line_sites) { - sites.insert(site, ""); - } - } - else { - for (const QString& site: line_sites) { - sites.insert(site, ""); - } - for (const QString& ip: line_ips) { - ips.append(ip); - } - } - - } - - m_settings->addVpnIps(mode, ips); - m_settings->addVpnSites(mode, sites); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", - Qt::QueuedConnection, - Q_ARG(QStringList, ips)); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", - Qt::QueuedConnection); - - onUpdatePage(); -} - -void SitesLogic::onPushButtonSitesExportClicked() -{ - Settings::RouteMode mode = m_settings->routeMode(); - - QVariantMap sites = m_settings->vpnSites(mode); - - QString data; - for (auto s : sites.keys()) { - data += s + "\t" + sites.value(s).toString() + "\n"; - } - uiLogic()->saveTextFile("Export Sites", "sites.txt", ".txt", data); -} - diff --git a/client/ui/pages_logic/SitesLogic.h b/client/ui/pages_logic/SitesLogic.h deleted file mode 100644 index 35bf1f90..00000000 --- a/client/ui/pages_logic/SitesLogic.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef SITES_LOGIC_H -#define SITES_LOGIC_H - -#include "PageLogicBase.h" -#include "settings.h" - -class UiLogic; -class SitesModel; - -class SitesLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, labelSitesAddCustomText) - AUTO_PROPERTY(QObject*, tableViewSitesModel) - AUTO_PROPERTY(QString, lineEditSitesAddCustomText) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonAddCustomSitesClicked(); - Q_INVOKABLE void onPushButtonSitesDeleteClicked(QStringList items); - Q_INVOKABLE void onPushButtonSitesImportClicked(const QString &fileName); - Q_INVOKABLE void onPushButtonSitesExportClicked(); - - -public: - explicit SitesLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~SitesLogic() = default; - - QMap sitesModels; -}; -#endif // SITES_LOGIC_H diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp deleted file mode 100644 index 79bd24c0..00000000 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ /dev/null @@ -1,416 +0,0 @@ -#include "StartPageLogic.h" -#include "ViewConfigLogic.h" - -#include "../uilogic.h" -#include "configurators/ssh_configurator.h" -#include "configurators/vpn_configurator.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include "utilities.h" - -#include -#include -#include - -#ifdef Q_OS_ANDROID - #include "../../platforms/android/android_controller.h" - #include "../../platforms/android/androidutils.h" - #include -#endif - -#ifdef Q_OS_IOS - #include -#endif - -namespace -{ - enum class ConfigTypes { - Amnezia, - OpenVpn, - WireGuard - }; - - ConfigTypes checkConfigFormat(const QString &config) - { - const QString openVpnConfigPatternCli = "client"; - const QString openVpnConfigPatternProto1 = "proto tcp"; - const QString openVpnConfigPatternProto2 = "proto udp"; - const QString openVpnConfigPatternDriver1 = "dev tun"; - const QString openVpnConfigPatternDriver2 = "dev tap"; - - const QString wireguardConfigPatternSectionInterface = "[Interface]"; - const QString wireguardConfigPatternSectionPeer = "[Peer]"; - - if (config.contains(openVpnConfigPatternCli) - && (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) - && (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { - return ConfigTypes::OpenVpn; - } else if (config.contains(wireguardConfigPatternSectionInterface) - && config.contains(wireguardConfigPatternSectionPeer)) - return ConfigTypes::WireGuard; - return ConfigTypes::Amnezia; - } - -} - -StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent) - : PageLogicBase(logic, parent), - m_pushButtonConnectEnabled { true }, - m_pushButtonConnectText { tr("Connect") }, - m_pushButtonConnectKeyChecked { false }, - m_labelWaitInfoVisible { true }, - m_pushButtonBackFromStartVisible { true }, - m_ipAddressPortRegex { Utils::ipAddressPortRegExp() } -{ -#ifdef Q_OS_ANDROID - // Set security screen for Android app - AndroidUtils::runOnAndroidThreadSync([]() { - QJniObject activity = AndroidUtils::getActivity(); - QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); - if (window.isValid()) { - const int FLAG_SECURE = 8192; - window.callMethod("addFlags", "(I)V", FLAG_SECURE); - } - }); -#endif -} - -void StartPageLogic::onUpdatePage() -{ - set_lineEditStartExistingCodeText(""); - set_textEditSshKeyText(""); - set_lineEditIpText(""); - set_lineEditPasswordText(""); - set_textEditSshKeyText(""); - set_lineEditLoginText(""); - - set_labelWaitInfoVisible(false); - set_labelWaitInfoText(""); - - set_pushButtonConnectKeyChecked(false); - - set_pushButtonBackFromStartVisible(uiLogic()->pagesStackDepth() > 0); -} - -void StartPageLogic::onPushButtonConnect() -{ - if (pushButtonConnectKeyChecked()) { - if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || textEditSshKeyText().isEmpty()) { - set_labelWaitInfoText(tr("Please fill in all fields")); - return; - } - } else { - if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || lineEditPasswordText().isEmpty()) { - set_labelWaitInfoText(tr("Please fill in all fields")); - return; - } - } - - ServerCredentials serverCredentials; - serverCredentials.hostName = lineEditIpText(); - if (serverCredentials.hostName.contains(":")) { - serverCredentials.port = serverCredentials.hostName.split(":").at(1).toInt(); - serverCredentials.hostName = serverCredentials.hostName.split(":").at(0); - } - serverCredentials.userName = lineEditLoginText(); - if (pushButtonConnectKeyChecked()) { - QString key = textEditSshKeyText(); - if (key.startsWith("ssh-rsa")) { - emit uiLogic()->showPublicKeyWarning(); - return; - } - - if (key.contains("OPENSSH") && key.contains("BEGIN") && key.contains("PRIVATE KEY")) { - key = m_configurator->sshConfigurator->convertOpenSShKey(key); - } - - serverCredentials.password = key; - } else { - serverCredentials.password = lineEditPasswordText(); - } - - set_pushButtonConnectEnabled(false); - set_pushButtonConnectText(tr("Connecting...")); - - ServerController serverController(m_settings); - ErrorCode errorCode = ErrorCode::NoError; - - if (pushButtonConnectKeyChecked()) { - auto passphraseCallback = [this, &serverController]() { - emit showPassphraseRequestMessage(); - QEventLoop loop; - QObject::connect(this, &StartPageLogic::passphraseDialogClosed, &loop, &QEventLoop::quit); - loop.exec(); - - return m_privateKeyPassphrase; - }; - - QString decryptedPrivateKey; - errorCode = serverController.getDecryptedPrivateKey(serverCredentials, decryptedPrivateKey, passphraseCallback); - if (errorCode == ErrorCode::NoError) { - serverCredentials.password = decryptedPrivateKey; - } - } - - QString output; - if (errorCode == ErrorCode::NoError) { - output = serverController.checkSshConnection(serverCredentials, &errorCode); - } - - bool ok = true; - if (errorCode) { - set_labelWaitInfoVisible(true); - set_labelWaitInfoText(errorString(errorCode)); - ok = false; - } else { - if (output.contains("Please login as the user")) { - output.replace("\n", ""); - set_labelWaitInfoVisible(true); - set_labelWaitInfoText(output); - ok = false; - } - } - - set_pushButtonConnectEnabled(true); - set_pushButtonConnectText(tr("Connect")); - - uiLogic()->m_installCredentials = serverCredentials; - if (ok) - emit uiLogic()->goToPage(Page::NewServer); -} - -void StartPageLogic::onPushButtonImport() -{ - importConnectionFromCode(lineEditStartExistingCodeText()); -} - -void StartPageLogic::onPushButtonImportOpenFile() -{ - QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open config file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), - "*.vpn *.ovpn *.conf"); - if (fileName.isEmpty()) - return; - - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif - - file.open(QIODevice::ReadOnly); - QByteArray data = file.readAll(); - - importAnyFile(QString(data)); -} - -#ifdef Q_OS_ANDROID -void StartPageLogic::startQrDecoder() -{ - AndroidController::instance()->startQrReaderActivity(); -} -#endif - -void StartPageLogic::importAnyFile(const QString &configData) -{ - auto configFormat = checkConfigFormat(configData); - if (configFormat == ConfigTypes::OpenVpn) { - importConnectionFromOpenVpnConfig(configData); - } else if (configFormat == ConfigTypes::WireGuard) { - importConnectionFromWireguardConfig(configData); - } else { - importConnectionFromCode(configData); - } -} - -bool StartPageLogic::importConnection(const QJsonObject &profile) -{ - ServerCredentials credentials; - credentials.hostName = profile.value(config_key::hostName).toString(); - credentials.port = profile.value(config_key::port).toInt(); - credentials.userName = profile.value(config_key::userName).toString(); - credentials.password = profile.value(config_key::password).toString(); - - if (credentials.isValid() || profile.contains(config_key::containers)) { - // check config - uiLogic()->pageLogic()->set_configJson(profile); - emit uiLogic()->goToPage(Page::ViewConfig); - } else { - qDebug() << "Failed to import profile"; - qDebug().noquote() << QJsonDocument(profile).toJson(); - return false; - } - - return true; -} - -bool StartPageLogic::importConnectionFromCode(QString code) -{ - code.replace("vpn://", ""); - QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - - QByteArray ba_uncompressed = qUncompress(ba); - if (!ba_uncompressed.isEmpty()) { - ba = ba_uncompressed; - } - - QJsonObject o; - o = QJsonDocument::fromJson(ba).object(); - if (!o.isEmpty()) { - return importConnection(o); - } - - return false; -} - -bool StartPageLogic::importConnectionFromQr(const QByteArray &data) -{ - QJsonObject dataObj = QJsonDocument::fromJson(data).object(); - if (!dataObj.isEmpty()) { - return importConnection(dataObj); - } - - QByteArray ba_uncompressed = qUncompress(data); - if (!ba_uncompressed.isEmpty()) { - return importConnection(QJsonDocument::fromJson(ba_uncompressed).object()); - } - - return false; -} - -bool StartPageLogic::importConnectionFromOpenVpnConfig(const QString &config) -{ - QJsonObject openVpnConfig; - openVpnConfig[config_key::config] = config; - - QJsonObject lastConfig; - lastConfig[config_key::last_config] = QString(QJsonDocument(openVpnConfig).toJson()); - lastConfig[config_key::isThirdPartyConfig] = true; - - QJsonObject containers; - containers.insert(config_key::container, QJsonValue("amnezia-openvpn")); - containers.insert(config_key::openvpn, QJsonValue(lastConfig)); - - QJsonArray arr; - arr.push_back(containers); - - QString hostName; - const static QRegularExpression hostNameRegExp("remote (.*) [0-9]*"); - QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(config); - if (hostNameMatch.hasMatch()) { - hostName = hostNameMatch.captured(1); - } - - QJsonObject o; - o[config_key::containers] = arr; - o[config_key::defaultContainer] = "amnezia-openvpn"; - o[config_key::description] = m_settings->nextAvailableServerName(); - - const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); - QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(config); - if (dnsMatch.hasNext()) { - o[config_key::dns1] = dnsMatch.next().captured(1); - } - if (dnsMatch.hasNext()) { - o[config_key::dns2] = dnsMatch.next().captured(1); - } - - o[config_key::hostName] = hostName; - - return importConnection(o); -} - -bool StartPageLogic::importConnectionFromWireguardConfig(const QString &config) -{ - QJsonObject lastConfig; - lastConfig[config_key::config] = config; - - const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*):([0-9]*)"); - QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(config); - QString hostName; - QString port; - if (hostNameAndPortMatch.hasCaptured(1)) { - hostName = hostNameAndPortMatch.captured(1); - } else { - return importConnection(QJsonObject()); - } - - if (hostNameAndPortMatch.hasCaptured(2)) { - port = hostNameAndPortMatch.captured(2); - } else { - port = protocols::wireguard::defaultPort; - } - - lastConfig[config_key::hostName] = hostName; - lastConfig[config_key::port] = port.toInt(); - - const static QRegularExpression clientPrivKeyRegExp("PrivateKey = (.*)"); - QRegularExpressionMatch clientPrivKeyMatch = clientPrivKeyRegExp.match(config); - if (clientPrivKeyMatch.hasMatch()) { - lastConfig[config_key::client_priv_key] = clientPrivKeyMatch.captured(1); - } else { - return importConnection(QJsonObject()); - } - - const static QRegularExpression clientIpRegExp("Address = (\\d+\\.\\d+\\.\\d+\\.\\d+)"); - QRegularExpressionMatch clientIpMatch = clientIpRegExp.match(config); - if (clientIpMatch.hasMatch()) { - lastConfig[config_key::client_ip] = clientIpMatch.captured(1); - } else { - return importConnection(QJsonObject()); - } - - const static QRegularExpression pskKeyRegExp("PresharedKey = (.*)"); - QRegularExpressionMatch pskKeyMatch = pskKeyRegExp.match(config); - if (pskKeyMatch.hasMatch()) { - lastConfig[config_key::psk_key] = pskKeyMatch.captured(1); - } else { - return importConnection(QJsonObject()); - } - - const static QRegularExpression serverPubKeyRegExp("PublicKey = (.*)"); - QRegularExpressionMatch serverPubKeyMatch = serverPubKeyRegExp.match(config); - if (serverPubKeyMatch.hasMatch()) { - lastConfig[config_key::server_pub_key] = serverPubKeyMatch.captured(1); - } else { - return importConnection(QJsonObject()); - } - - QJsonObject wireguardConfig; - wireguardConfig[config_key::last_config] = QString(QJsonDocument(lastConfig).toJson()); - wireguardConfig[config_key::isThirdPartyConfig] = true; - wireguardConfig[config_key::port] = port; - wireguardConfig[config_key::transport_proto] = "udp"; - - QJsonObject containers; - containers.insert(config_key::container, QJsonValue("amnezia-wireguard")); - containers.insert(config_key::wireguard, QJsonValue(wireguardConfig)); - - QJsonArray arr; - arr.push_back(containers); - - QJsonObject o; - o[config_key::containers] = arr; - o[config_key::defaultContainer] = "amnezia-wireguard"; - o[config_key::description] = m_settings->nextAvailableServerName(); - - const static QRegularExpression dnsRegExp( - "DNS = " - "(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); - QRegularExpressionMatch dnsMatch = dnsRegExp.match(config); - if (dnsMatch.hasMatch()) { - o[config_key::dns1] = dnsMatch.captured(1); - o[config_key::dns2] = dnsMatch.captured(2); - } - - o[config_key::hostName] = hostName; - - return importConnection(o); -} diff --git a/client/ui/pages_logic/StartPageLogic.h b/client/ui/pages_logic/StartPageLogic.h deleted file mode 100644 index 9025a052..00000000 --- a/client/ui/pages_logic/StartPageLogic.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef START_PAGE_LOGIC_H -#define START_PAGE_LOGIC_H - -#include "PageLogicBase.h" - -#include - -class UiLogic; - -class StartPageLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pushButtonConnectEnabled) - AUTO_PROPERTY(bool, pushButtonConnectKeyChecked) - AUTO_PROPERTY(QString, pushButtonConnectText) - AUTO_PROPERTY(QString, lineEditStartExistingCodeText) - AUTO_PROPERTY(QString, textEditSshKeyText) - AUTO_PROPERTY(QString, lineEditIpText) - AUTO_PROPERTY(QString, lineEditPasswordText) - AUTO_PROPERTY(QString, lineEditLoginText) - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - AUTO_PROPERTY(bool, pushButtonBackFromStartVisible) - - AUTO_PROPERTY(QString, privateKeyPassphrase) - - READONLY_PROPERTY(QRegularExpression, ipAddressPortRegex) -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonConnect(); - Q_INVOKABLE void onPushButtonImport(); - Q_INVOKABLE void onPushButtonImportOpenFile(); - -#ifdef Q_OS_ANDROID - Q_INVOKABLE void startQrDecoder(); -#endif - - void importAnyFile(const QString &configData); - - bool importConnection(const QJsonObject &profile); - bool importConnectionFromCode(QString code); - bool importConnectionFromQr(const QByteArray &data); - bool importConnectionFromOpenVpnConfig(const QString &config); - bool importConnectionFromWireguardConfig(const QString &config); - -public: - explicit StartPageLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~StartPageLogic() = default; - -signals: - void showPassphraseRequestMessage(); - void passphraseDialogClosed(); -}; -#endif // START_PAGE_LOGIC_H diff --git a/client/ui/pages_logic/ViewConfigLogic.cpp b/client/ui/pages_logic/ViewConfigLogic.cpp deleted file mode 100644 index 5f711498..00000000 --- a/client/ui/pages_logic/ViewConfigLogic.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "ViewConfigLogic.h" -#include "core/errorstrings.h" -#include "../uilogic.h" - - -ViewConfigLogic::ViewConfigLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} - -void ViewConfigLogic::onUpdatePage() -{ - set_configText(QJsonDocument(configJson()).toJson()); - - auto s = configJson()[config_key::isThirdPartyConfig].toBool(); - - m_openVpnLastConfigs = m_openVpnMalStrings = - "
"; - - m_warningStringNumber = 3; - m_warningActive = false; - - const QJsonArray &containers = configJson()[config_key::containers].toArray(); - int i = 0; - for (const QJsonValue &v: containers) { - auto containerName = v.toObject()[config_key::container].toString(); - QJsonObject containerConfig = v.toObject()[containerName.replace("amnezia-", "")].toObject(); - if (containerConfig[config_key::isThirdPartyConfig].toBool()) { - auto lastConfig = containerConfig.value(config_key::last_config).toString(); - auto lastConfigJson = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); - QString lastConfigText; - for (const QString &l: lines) { - lastConfigText.append(l + "\n"); - } - set_configText(lastConfigText); - } - - - if (v.toObject()[config_key::container].toString() == "amnezia-openvpn") { - QString lastConfig = v.toObject()[ProtocolProps::protoToString(Proto::OpenVpn)] - .toObject()[config_key::last_config].toString(); - - QString lastConfigJson = QJsonDocument::fromJson(lastConfig.toUtf8()).object()[config_key::config] - .toString(); - - QStringList lines = lastConfigJson.replace("\r", "").split("\n"); - for (const QString &l: lines) { - i++; - QRegularExpressionMatch match = m_re.match(l); - if (dangerousTags.contains(match.captured(0))) { - QString t = QString("

%1").arg(l); - m_openVpnLastConfigs.append(t + "\n"); - m_openVpnMalStrings.append(t); - if (m_warningStringNumber == 3) m_warningStringNumber = i - 3; - m_warningActive = true; - qDebug() << "ViewConfigLogic : malicious scripts warning:" << l; - } - else { - m_openVpnLastConfigs.append("

" + l + " \n"); - } - } - } - } - - emit openVpnLastConfigsChanged(m_openVpnLastConfigs); - emit openVpnMalStringsChanged(m_openVpnMalStrings); - emit warningStringNumberChanged(m_warningStringNumber); - emit warningActiveChanged(m_warningActive); -} - -void ViewConfigLogic::importConfig() -{ - m_settings->addServer(configJson()); - m_settings->setDefaultServer(m_settings->serversCount() - 1); - - - if (!configJson().contains(config_key::containers) || configJson().value(config_key::containers).toArray().isEmpty()) { - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - uiLogic()->onUpdateAllPages(); - emit uiLogic()->goToPage(Page::Vpn); - emit uiLogic()->setStartPage(Page::Vpn); - emit uiLogic()->goToPage(Page::ServerContainers); - } else { - emit uiLogic()->goToPage(Page::Vpn); - emit uiLogic()->setStartPage(Page::Vpn); - } -} - diff --git a/client/ui/pages_logic/ViewConfigLogic.h b/client/ui/pages_logic/ViewConfigLogic.h deleted file mode 100644 index 4713158e..00000000 --- a/client/ui/pages_logic/ViewConfigLogic.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef VIEW_CONFIG_LOGIC_H -#define VIEW_CONFIG_LOGIC_H - -#include "PageLogicBase.h" - -#include - -class UiLogic; - -class ViewConfigLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, configText) - AUTO_PROPERTY(QString, openVpnLastConfigs) - AUTO_PROPERTY(QString, openVpnMalStrings) - AUTO_PROPERTY(QJsonObject, configJson) - AUTO_PROPERTY(int, warningStringNumber) - AUTO_PROPERTY(bool, warningActive) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void importConfig(); - - -public: - explicit ViewConfigLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ViewConfigLogic() = default; - -private: - QRegularExpression m_re {"(\\w+-\\w+|\\w+)"}; - - // https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/script-options.rst - QStringList dangerousTags { - "up", - "tls-verify", - "ipchange", - "client-connect", - "route-up", - "route-pre-down", - "client-disconnect", - "down", - "learn-address", - "auth-user-pass-verify" - }; -}; -#endif // VIEW_CONFIG_LOGIC_H diff --git a/client/ui/pages_logic/VpnLogic.cpp b/client/ui/pages_logic/VpnLogic.cpp deleted file mode 100644 index a75aa1b9..00000000 --- a/client/ui/pages_logic/VpnLogic.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#include - -#include "VpnLogic.h" - -#include "core/errorstrings.h" -#include "vpnconnection.h" -#include -#include -#include "../uilogic.h" -#include "version.h" -#include - - -VpnLogic::VpnLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_pushButtonConnectChecked{false}, - - m_radioButtonVpnModeAllSitesChecked{true}, - m_radioButtonVpnModeForwardSitesChecked{false}, - m_radioButtonVpnModeExceptSitesChecked{false}, - - m_labelSpeedReceivedText{tr("0 Mbps")}, - m_labelSpeedSentText{tr("0 Mbps")}, - m_labelStateText{}, - m_isContainerHaveAuthData{false}, - m_isContainerSupportedByCurrentPlatform{false}, - m_widgetVpnModeEnabled{false} -{ - connect(uiLogic()->m_vpnConnection, &VpnConnection::bytesChanged, this, &VpnLogic::onBytesChanged); - connect(uiLogic()->m_vpnConnection, &VpnConnection::connectionStateChanged, this, &VpnLogic::onConnectionStateChanged); - connect(uiLogic()->m_vpnConnection, &VpnConnection::vpnProtocolError, this, &VpnLogic::onVpnProtocolError); - - connect(this, &VpnLogic::connectToVpn, uiLogic()->m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection); - connect(this, &VpnLogic::disconnectFromVpn, uiLogic()->m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); - - connect(m_settings.get(), &Settings::saveLogsChanged, this, &VpnLogic::onUpdatePage); - - if (m_settings->isAutoConnect() && m_settings->defaultServerIndex() >= 0) { - QTimer::singleShot(1000, this, [this](){ - set_pushButtonConnectEnabled(false); - onConnect(); - }); - } - else { - onConnectionStateChanged(VpnProtocol::Disconnected); - } -} - - -void VpnLogic::onUpdatePage() -{ - Settings::RouteMode mode = m_settings->routeMode(); - DockerContainer selectedContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); - - set_isCustomRoutesSupported (selectedContainer == DockerContainer::OpenVpn || - selectedContainer == DockerContainer::ShadowSocks|| - selectedContainer == DockerContainer::Cloak); - - set_isContainerHaveAuthData(m_settings->haveAuthData(m_settings->defaultServerIndex())); - - set_radioButtonVpnModeAllSitesChecked(mode == Settings::VpnAllSites || !isCustomRoutesSupported()); - set_radioButtonVpnModeForwardSitesChecked(mode == Settings::VpnOnlyForwardSites && isCustomRoutesSupported()); - set_radioButtonVpnModeExceptSitesChecked(mode == Settings::VpnAllExceptSites && isCustomRoutesSupported()); - - const QJsonObject &server = uiLogic()->m_settings->defaultServer(); - QString serverString = QString("%2 (%3)") - .arg(server.value(config_key::description).toString()) - .arg(server.value(config_key::hostName).toString()); - set_labelCurrentServer(serverString); - - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentService(selectedContainerName); - - auto dns = m_configurator->getDnsForConfig(m_settings->defaultServerIndex()); - set_amneziaDnsEnabled(dns.first == protocols::dns::amneziaDnsIp); - if (dns.first == protocols::dns::amneziaDnsIp) { - set_labelCurrentDns("On your server"); - } - else { - set_labelCurrentDns(dns.first + ", " + dns.second); - } - - set_isContainerSupportedByCurrentPlatform(ContainerProps::isSupportedByCurrentPlatform(selectedContainer)); - if (!isContainerSupportedByCurrentPlatform()) { - set_labelErrorText(tr("AmneziaVPN not supporting selected protocol on this device. Select another protocol.")); - } - else { - set_labelErrorText(""); - } - QString ver = QString("v. %2").arg(QString(APP_MAJOR_VERSION)); - set_labelVersionText(ver); - - set_labelLogEnabledVisible(m_settings->isSaveLogs()); -} - - -void VpnLogic::onRadioButtonVpnModeAllSitesClicked() -{ - m_settings->setRouteMode(Settings::VpnAllSites); - onUpdatePage(); -} - -void VpnLogic::onRadioButtonVpnModeForwardSitesClicked() -{ - m_settings->setRouteMode(Settings::VpnOnlyForwardSites); - onUpdatePage(); -} - -void VpnLogic::onRadioButtonVpnModeExceptSitesClicked() -{ - m_settings->setRouteMode(Settings::VpnAllExceptSites); - onUpdatePage(); -} - -void VpnLogic::onBytesChanged(quint64 receivedData, quint64 sentData) -{ - set_labelSpeedReceivedText(VpnConnection::bytesPerSecToText(receivedData)); - set_labelSpeedSentText(VpnConnection::bytesPerSecToText(sentData)); -} - -void VpnLogic::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) -{ - qDebug() << "VpnLogic::onConnectionStateChanged" << VpnProtocol::textConnectionState(state); - if (uiLogic()->m_vpnConnection == NULL) { - qDebug() << "VpnLogic::onConnectionStateChanged" << VpnProtocol::textConnectionState(state) << "невозможно, соединение отсутствует (уничтожено ранее)"; - return; - } - bool pbConnectEnabled = false; - bool pbConnectChecked = false; - - bool rbModeEnabled = false; - bool pbConnectVisible = false; - set_labelStateText(VpnProtocol::textConnectionState(state)); - - switch (state) { - case VpnProtocol::Disconnected: - onBytesChanged(0,0); - pbConnectChecked = false; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = true; - break; - case VpnProtocol::Preparing: - pbConnectChecked = true; - pbConnectEnabled = false; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case VpnProtocol::Connecting: - pbConnectChecked = true; - pbConnectEnabled = true; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case VpnProtocol::Connected: - pbConnectChecked = true; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = false; - break; - case VpnProtocol::Disconnecting: - pbConnectChecked = false; - pbConnectEnabled = false; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case VpnProtocol::Reconnecting: - pbConnectChecked = true; - pbConnectEnabled = true; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case VpnProtocol::Error: - pbConnectChecked = false; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = true; - break; - case VpnProtocol::Unknown: - pbConnectChecked = false; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = true; - } - - set_pushButtonConnectEnabled(pbConnectEnabled); - set_pushButtonConnectChecked(pbConnectChecked); - - set_pushButtonConnectVisible(pbConnectVisible); - set_widgetVpnModeEnabled(rbModeEnabled); -} - -void VpnLogic::onVpnProtocolError(ErrorCode errorCode) -{ - set_labelErrorText(errorString(errorCode)); -} - -void VpnLogic::onPushButtonConnectClicked() -{ - if (! pushButtonConnectChecked()) { - onConnect(); - } else { - onDisconnect(); - } -} - -void VpnLogic::onConnect() -{ - int serverIndex = m_settings->defaultServerIndex(); - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - DockerContainer container = m_settings->defaultContainer(serverIndex); - - if (m_settings->containers(serverIndex).isEmpty()) { - set_labelErrorText(tr("VPN Protocols is not installed.\n Please install VPN container at first")); - set_pushButtonConnectChecked(false); - return; - } - - if (container == DockerContainer::None) { - set_labelErrorText(tr("VPN Protocol not chosen")); - set_pushButtonConnectChecked(false); - return; - } - - - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - onConnectWorker(serverIndex, credentials, container, containerConfig); -} - -void VpnLogic::onConnectWorker(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig) -{ - set_labelErrorText(""); - set_pushButtonConnectChecked(true); - set_pushButtonConnectEnabled(false); - - qApp->processEvents(); - - emit connectToVpn(serverIndex, credentials, container, containerConfig); -} - -void VpnLogic::onDisconnect() -{ - onConnectionStateChanged(VpnProtocol::Disconnected); - emit disconnectFromVpn(); -} diff --git a/client/ui/pages_logic/VpnLogic.h b/client/ui/pages_logic/VpnLogic.h deleted file mode 100644 index f7b21be2..00000000 --- a/client/ui/pages_logic/VpnLogic.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef VPN_LOGIC_H -#define VPN_LOGIC_H - -#include "PageLogicBase.h" -#include "protocols/vpnprotocol.h" - -class UiLogic; - -class VpnLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pushButtonConnectChecked) - AUTO_PROPERTY(QString, labelSpeedReceivedText) - AUTO_PROPERTY(QString, labelSpeedSentText) - AUTO_PROPERTY(QString, labelStateText) - AUTO_PROPERTY(QString, labelCurrentServer) - AUTO_PROPERTY(QString, labelCurrentService) - AUTO_PROPERTY(QString, labelCurrentDns) - AUTO_PROPERTY(bool, amneziaDnsEnabled) - - AUTO_PROPERTY(bool, pushButtonConnectEnabled) - AUTO_PROPERTY(bool, pushButtonConnectVisible) - AUTO_PROPERTY(bool, widgetVpnModeEnabled) - AUTO_PROPERTY(bool, isContainerSupportedByCurrentPlatform) - AUTO_PROPERTY(bool, isContainerHaveAuthData) - - AUTO_PROPERTY(QString, labelErrorText) - AUTO_PROPERTY(QString, labelVersionText) - - AUTO_PROPERTY(bool, isCustomRoutesSupported) - - AUTO_PROPERTY(bool, radioButtonVpnModeAllSitesChecked) - AUTO_PROPERTY(bool, radioButtonVpnModeForwardSitesChecked) - AUTO_PROPERTY(bool, radioButtonVpnModeExceptSitesChecked) - - AUTO_PROPERTY(bool, labelLogEnabledVisible) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onRadioButtonVpnModeAllSitesClicked(); - Q_INVOKABLE void onRadioButtonVpnModeForwardSitesClicked(); - Q_INVOKABLE void onRadioButtonVpnModeExceptSitesClicked(); - - Q_INVOKABLE void onPushButtonConnectClicked(); - -public: - explicit VpnLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~VpnLogic() = default; - - bool getPushButtonConnectChecked() const; - void setPushButtonConnectChecked(bool pushButtonConnectChecked); - -public slots: - void onConnect(); - void onConnectWorker(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); - void onDisconnect(); - - void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); - void onConnectionStateChanged(VpnProtocol::VpnConnectionState state); - void onVpnProtocolError(amnezia::ErrorCode errorCode); - -signals: - void connectToVpn(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); - - void disconnectFromVpn(); -}; -#endif // VPN_LOGIC_H diff --git a/client/ui/pages_logic/WizardLogic.cpp b/client/ui/pages_logic/WizardLogic.cpp deleted file mode 100644 index 23a20aed..00000000 --- a/client/ui/pages_logic/WizardLogic.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "WizardLogic.h" -#include "../uilogic.h" - -WizardLogic::WizardLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_radioButtonHighChecked{false}, - m_radioButtonMediumChecked{true}, - m_radioButtonLowChecked{false}, - m_lineEditHighWebsiteMaskingText{}, - m_checkBoxVpnModeChecked{false} -{ - -} - -void WizardLogic::onUpdatePage() -{ - set_lineEditHighWebsiteMaskingText(protocols::cloak::defaultRedirSite); - set_radioButtonMediumChecked(true); -} - -QPair WizardLogic::getInstallConfigsFromWizardPage() const -{ - QJsonObject cloakConfig { - { config_key::container, ContainerProps::containerToString(DockerContainer::Cloak) }, - { ProtocolProps::protoToString(Proto::Cloak), QJsonObject { - { config_key::site, lineEditHighWebsiteMaskingText() }} - } - }; - QJsonObject ssConfig { - { config_key::container, ContainerProps::containerToString(DockerContainer::ShadowSocks) } - }; - QJsonObject openVpnConfig { - { config_key::container, ContainerProps::containerToString(DockerContainer::OpenVpn) } - }; - - QPair container; - - DockerContainer dockerContainer; - - if (radioButtonHighChecked()) { - container = {DockerContainer::Cloak, cloakConfig}; - } - - if (radioButtonMediumChecked()) { - container = {DockerContainer::ShadowSocks, ssConfig}; - } - - if (radioButtonLowChecked()) { - container = {DockerContainer::OpenVpn, openVpnConfig}; - } - - return container; -} - -void WizardLogic::onPushButtonVpnModeFinishClicked() -{ - auto container = getInstallConfigsFromWizardPage(); - uiLogic()->installServer(container); - if (checkBoxVpnModeChecked()) { - m_settings->setRouteMode(Settings::VpnOnlyForwardSites); - } else { - m_settings->setRouteMode(Settings::VpnAllSites); - } -} - -void WizardLogic::onPushButtonLowFinishClicked() -{ - auto container = getInstallConfigsFromWizardPage(); - uiLogic()->installServer(container); -} diff --git a/client/ui/pages_logic/WizardLogic.h b/client/ui/pages_logic/WizardLogic.h deleted file mode 100644 index a2e45af7..00000000 --- a/client/ui/pages_logic/WizardLogic.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef WIZARD_LOGIC_H -#define WIZARD_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class WizardLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, radioButtonHighChecked) - AUTO_PROPERTY(bool, radioButtonMediumChecked) - AUTO_PROPERTY(bool, radioButtonLowChecked) - AUTO_PROPERTY(bool, checkBoxVpnModeChecked) - AUTO_PROPERTY(QString, lineEditHighWebsiteMaskingText) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onPushButtonVpnModeFinishClicked(); - Q_INVOKABLE void onPushButtonLowFinishClicked(); - -public: - explicit WizardLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~WizardLogic() = default; - - QPair getInstallConfigsFromWizardPage() const; - -}; -#endif // WIZARD_LOGIC_H diff --git a/client/ui/pages_logic/protocols/CloakLogic.cpp b/client/ui/pages_logic/protocols/CloakLogic.cpp deleted file mode 100644 index 4f21895b..00000000 --- a/client/ui/pages_logic/protocols/CloakLogic.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include "CloakLogic.h" - -#include - -#include "core/servercontroller.h" -#include "ui/uilogic.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -CloakLogic::CloakLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_comboBoxCipherText{"chacha20-poly1305"}, - m_lineEditSiteText{"tile.openstreetmap.org"}, - m_lineEditPortText{}, - m_pushButtonSaveVisible{false}, - m_progressBarResetVisible{false}, - m_lineEditPortEnabled{false}, - m_pageEnabled{true}, - m_labelInfoVisible{true}, - m_labelInfoText{}, - m_progressBarResetValue{0}, - m_progressBarResetMaximum{100} -{ - -} - -void CloakLogic::updateProtocolPage(const QJsonObject &ckConfig, DockerContainer container, bool haveAuthData) -{ - set_pageEnabled(haveAuthData); - set_pushButtonSaveVisible(haveAuthData); - set_progressBarResetVisible(haveAuthData); - - set_comboBoxCipherText(ckConfig.value(config_key::cipher). - toString(protocols::cloak::defaultCipher)); - - set_lineEditSiteText(ckConfig.value(config_key::site). - toString(protocols::cloak::defaultRedirSite)); - - set_lineEditPortText(ckConfig.value(config_key::port). - toString(protocols::cloak::defaultPort)); - - set_lineEditPortEnabled(container == DockerContainer::Cloak); -} - -QJsonObject CloakLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -{ - oldConfig.insert(config_key::cipher, comboBoxCipherText()); - - QString newSite = lineEditSiteText(); - newSite.replace("https://", ""); - oldConfig.insert(config_key::site, newSite); - - oldConfig.insert(config_key::port, lineEditPortText()); - - return oldConfig; -} - -void CloakLogic::onPushButtonSaveClicked() -{ - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::Cloak); - protocolConfig = getProtocolConfigFromPage(protocolConfig); - - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - QJsonObject newContainerConfig = containerConfig; - newContainerConfig.insert(ProtocolProps::protoToString(Proto::Cloak), protocolConfig); - - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; - saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonSaveVisible(visible); - }; - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { - set_labelInfoVisible(visible); - }; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { - set_labelInfoText(text); - }; - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarResetVisible(visible); - }; - progressBarFunc.setValueFunc = [this] (int value) -> void { - set_progressBarResetValue(value); - }; - progressBarFunc.getValueFunc = [this] (void) -> int { - return progressBarResetValue(); - }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { - return progressBarResetMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { - set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { - set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; - busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - progressBarFunc.setTextVisibleFunc(true); - progressBarFunc.setTextFunc(QString("Configuring...")); - - auto installAction = [this, containerConfig, &newContainerConfig]() { - ServerController serverController(m_settings); - return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); - }; - - ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); - - if (!e) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - } - - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; -} - -void CloakLogic::onPushButtonCancelClicked() -{ - emit uiLogic()->pageLogic()->cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/protocols/CloakLogic.h b/client/ui/pages_logic/protocols/CloakLogic.h deleted file mode 100644 index c135a621..00000000 --- a/client/ui/pages_logic/protocols/CloakLogic.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef CLOAK_LOGIC_H -#define CLOAK_LOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class CloakLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, comboBoxCipherText) - AUTO_PROPERTY(QString, lineEditSiteText) - AUTO_PROPERTY(QString, lineEditPortText) - AUTO_PROPERTY(bool, pushButtonSaveVisible) - AUTO_PROPERTY(bool, progressBarResetVisible) - AUTO_PROPERTY(bool, lineEditPortEnabled) - AUTO_PROPERTY(bool, pageEnabled) - AUTO_PROPERTY(bool, labelInfoVisible) - AUTO_PROPERTY(QString, labelInfoText) - AUTO_PROPERTY(int, progressBarResetValue) - AUTO_PROPERTY(int, progressBarResetMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - - AUTO_PROPERTY(bool, pushButtonCancelVisible) - -public: - Q_INVOKABLE void onPushButtonSaveClicked(); - Q_INVOKABLE void onPushButtonCancelClicked(); - -public: - explicit CloakLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~CloakLogic() = default; - - void updateProtocolPage(const QJsonObject &ckConfig, DockerContainer container, bool haveAuthData) override; - QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -}; -#endif // CLOAK_LOGIC_H diff --git a/client/ui/pages_logic/protocols/OpenVpnLogic.cpp b/client/ui/pages_logic/protocols/OpenVpnLogic.cpp deleted file mode 100644 index 2eb68ed9..00000000 --- a/client/ui/pages_logic/protocols/OpenVpnLogic.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include "OpenVpnLogic.h" - -#include - -#include "core/servercontroller.h" -#include "ui/uilogic.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -OpenVpnLogic::OpenVpnLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_lineEditSubnetText{""}, - - m_radioButtonTcpEnabled{true}, - m_radioButtonTcpChecked{false}, - m_radioButtonUdpEnabled{true}, - m_radioButtonUdpChecked{false}, - - m_checkBoxAutoEncryptionChecked{}, - m_comboBoxVpnCipherText{"AES-256-GCM"}, - m_comboBoxVpnHashText{"SHA512"}, - m_checkBoxBlockDnsChecked{false}, - m_lineEditPortText{}, - m_checkBoxTlsAuthChecked{false}, - m_textAreaAdditionalClientConfig{""}, - m_textAreaAdditionalServerConfig{""}, - m_pushButtonSaveVisible{false}, - m_progressBarResetVisible{false}, - - m_lineEditPortEnabled{false}, - m_labelProtoOpenVpnInfoVisible{true}, - m_labelProtoOpenVpnInfoText{}, - m_progressBarResetValue{0}, - m_progressBarResetMaximum{100} -{ - -} - -void OpenVpnLogic::updateProtocolPage(const QJsonObject &openvpnConfig, DockerContainer container, bool haveAuthData) -{ - qDebug() << "OpenVpnLogic::updateProtocolPage"; - set_pageEnabled(haveAuthData); - set_pushButtonSaveVisible(haveAuthData); - set_progressBarResetVisible(haveAuthData); - - set_radioButtonUdpEnabled(true); - set_radioButtonTcpEnabled(true); - - set_lineEditSubnetText(openvpnConfig.value(config_key::subnet_address). - toString(protocols::openvpn::defaultSubnetAddress)); - - QString transport; - if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - transport = "tcp"; - set_radioButtonUdpEnabled(false); - set_radioButtonTcpEnabled(false); - } else { - transport = openvpnConfig.value(config_key::transport_proto). - toString(protocols::openvpn::defaultTransportProto); - } - set_radioButtonUdpChecked(transport == protocols::openvpn::defaultTransportProto); - set_radioButtonTcpChecked(transport != protocols::openvpn::defaultTransportProto); - - set_comboBoxVpnCipherText(openvpnConfig.value(config_key::cipher). - toString(protocols::openvpn::defaultCipher)); - - set_comboBoxVpnHashText(openvpnConfig.value(config_key::hash). - toString(protocols::openvpn::defaultHash)); - - bool blockOutsideDns = openvpnConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns); - set_checkBoxBlockDnsChecked(blockOutsideDns); - - bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); - set_checkBoxAutoEncryptionChecked(!isNcpDisabled); - - bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); - set_checkBoxTlsAuthChecked(isTlsAuth); - - QString additionalClientConfig = openvpnConfig.value(config_key::additional_client_config). - toString(protocols::openvpn::defaultAdditionalClientConfig); - set_textAreaAdditionalClientConfig(additionalClientConfig); - - QString additionalServerConfig = openvpnConfig.value(config_key::additional_server_config). - toString(protocols::openvpn::defaultAdditionalServerConfig); - set_textAreaAdditionalServerConfig(additionalServerConfig); - - set_lineEditPortText(openvpnConfig.value(config_key::port). - toString(protocols::openvpn::defaultPort)); - - set_lineEditPortEnabled(container == DockerContainer::OpenVpn); - - auto lastConfig = openvpnConfig.value(config_key::last_config).toString(); - auto lastConfigJson = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); - QString openVpnLastConfigText; - for (const QString &l: lines) { - openVpnLastConfigText.append(l + "\n"); - } - - set_openVpnLastConfigText(openVpnLastConfigText); - set_isThirdPartyConfig(openvpnConfig.value(config_key::isThirdPartyConfig).isBool()); -} - -void OpenVpnLogic::onPushButtonSaveClicked() -{ - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::OpenVpn); - protocolConfig = getProtocolConfigFromPage(protocolConfig); - - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - QJsonObject newContainerConfig = containerConfig; - newContainerConfig.insert(ProtocolProps::protoToString(Proto::OpenVpn), protocolConfig); - - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; - saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonSaveVisible(visible); - }; - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { - set_labelProtoOpenVpnInfoVisible(visible); - }; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { - set_labelProtoOpenVpnInfoText(text); - }; - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarResetVisible(visible); - }; - progressBarFunc.setValueFunc = [this] (int value) -> void { - set_progressBarResetValue(value); - }; - progressBarFunc.getValueFunc = [this] (void) -> int { - return progressBarResetValue(); - }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { - return progressBarResetMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { - set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { - set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; - busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - progressBarFunc.setTextVisibleFunc(true); - progressBarFunc.setTextFunc(QString("Configuring...")); - - auto installAction = [this, containerConfig, &newContainerConfig]() { - ServerController serverController(m_settings); - return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); - }; - - ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); - - if (!e) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - } - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; -} - -QJsonObject OpenVpnLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -{ - oldConfig.insert(config_key::subnet_address, lineEditSubnetText()); - oldConfig.insert(config_key::transport_proto, - ProtocolProps::transportProtoToString(radioButtonUdpChecked() ? ProtocolEnumNS::Udp : ProtocolEnumNS::Tcp)); - - oldConfig.insert(config_key::ncp_disable, ! checkBoxAutoEncryptionChecked()); - oldConfig.insert(config_key::cipher, comboBoxVpnCipherText()); - oldConfig.insert(config_key::hash, comboBoxVpnHashText()); - oldConfig.insert(config_key::block_outside_dns, checkBoxBlockDnsChecked()); - oldConfig.insert(config_key::port, lineEditPortText()); - oldConfig.insert(config_key::tls_auth, checkBoxTlsAuthChecked()); - oldConfig.insert(config_key::additional_client_config, textAreaAdditionalClientConfig()); - oldConfig.insert(config_key::additional_server_config, textAreaAdditionalServerConfig()); - return oldConfig; -} - -void OpenVpnLogic::onPushButtonCancelClicked() -{ - emit uiLogic()->pageLogic()->cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/protocols/OpenVpnLogic.h b/client/ui/pages_logic/protocols/OpenVpnLogic.h deleted file mode 100644 index db7d3baf..00000000 --- a/client/ui/pages_logic/protocols/OpenVpnLogic.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef OPENVPN_LOGIC_H -#define OPENVPN_LOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class OpenVpnLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, lineEditSubnetText) - - AUTO_PROPERTY(bool, radioButtonTcpEnabled) - AUTO_PROPERTY(bool, radioButtonUdpEnabled) - AUTO_PROPERTY(bool, radioButtonTcpChecked) - AUTO_PROPERTY(bool, radioButtonUdpChecked) - - AUTO_PROPERTY(bool, checkBoxAutoEncryptionChecked) - AUTO_PROPERTY(QString, comboBoxVpnCipherText) - AUTO_PROPERTY(QString, comboBoxVpnHashText) - AUTO_PROPERTY(bool, checkBoxBlockDnsChecked) - AUTO_PROPERTY(QString, lineEditPortText) - AUTO_PROPERTY(bool, checkBoxTlsAuthChecked) - AUTO_PROPERTY(QString, textAreaAdditionalClientConfig) - AUTO_PROPERTY(QString, textAreaAdditionalServerConfig) - - AUTO_PROPERTY(bool, pushButtonSaveVisible) - AUTO_PROPERTY(bool, progressBarResetVisible) - - AUTO_PROPERTY(bool, lineEditPortEnabled) - - AUTO_PROPERTY(bool, labelProtoOpenVpnInfoVisible) - AUTO_PROPERTY(QString, labelProtoOpenVpnInfoText) - AUTO_PROPERTY(int, progressBarResetValue) - AUTO_PROPERTY(int, progressBarResetMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - - AUTO_PROPERTY(bool, pushButtonCancelVisible) - - AUTO_PROPERTY(QString, openVpnLastConfigText) - AUTO_PROPERTY(bool, isThirdPartyConfig) - -public: - Q_INVOKABLE void onPushButtonSaveClicked(); - Q_INVOKABLE void onPushButtonCancelClicked(); - -public: - explicit OpenVpnLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~OpenVpnLogic() = default; - - void updateProtocolPage(const QJsonObject &openvpnConfig, DockerContainer container, bool haveAuthData) override; - QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -}; -#endif // OPENVPN_LOGIC_H diff --git a/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp b/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp deleted file mode 100644 index 965a3baf..00000000 --- a/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#include -#include -#include -#include -#include - -#include "OtherProtocolsLogic.h" -#include -#include "../../uilogic.h" -#include "utilities.h" - -#ifdef Q_OS_WINDOWS -#include -#endif - -using namespace amnezia; -using namespace PageEnumNS; - -OtherProtocolsLogic::OtherProtocolsLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_checkBoxSftpRestoreChecked{false} - -{ - -} - -OtherProtocolsLogic::~OtherProtocolsLogic() -{ -#ifdef Q_OS_WINDOWS - for (QProcess *p: m_sftpMountProcesses) { - if (p) Utils::signalCtrl(p->processId(), CTRL_C_EVENT); - if (p) p->kill(); - if (p) p->waitForFinished(); - if (p) delete p; - } -#endif -} - -void OtherProtocolsLogic::updateProtocolPage(const QJsonObject &config, DockerContainer container, bool haveAuthData) -{ - set_labelTftpUserNameText(config.value(config_key::userName).toString()); - set_labelTftpPasswordText(config.value(config_key::password).toString(protocols::sftp::defaultUserName)); - set_labelTftpPortText(config.value(config_key::port).toString()); - - set_labelTorWebSiteAddressText(config.value(config_key::site).toString()); - set_pushButtonSftpMountEnabled(true); -} - -#ifdef Q_OS_WINDOWS -QString OtherProtocolsLogic::getNextDriverLetter() const -{ - QProcess drivesProc; - drivesProc.start("wmic logicaldisk get caption"); - drivesProc.waitForFinished(); - QString drives = drivesProc.readAll(); - qDebug() << drives; - - - QString letters = "CFGHIJKLMNOPQRSTUVWXYZ"; - QString letter; - for (int i = letters.size() - 1; i > 0; i--) { - letter = letters.at(i); - if (!drives.contains(letter + ":")) break; - } - if (letter == "C:") { - // set err info - qDebug() << "Can't find free drive letter"; - return ""; - } - return letter; -} -#endif - -//QJsonObject OtherProtocolsLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -//{ - -//} - - -void OtherProtocolsLogic::onPushButtonSftpMountDriveClicked() -{ - QString mountPath; - QString cmd; - QString host = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex).hostName; - - -#ifdef Q_OS_WINDOWS - mountPath = getNextDriverLetter() + ":"; - // QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3") - // .arg(labelTftpUserNameText()) - // .arg(labelTftpPortText()) - // .arg(labelTftpPasswordText()); - - cmd = "C:\\Program Files\\SSHFS-Win\\bin\\sshfs.exe"; -#elif defined AMNEZIA_DESKTOP - mountPath = QString("%1/sftp:%2:%3") - .arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)) - .arg(host) - .arg(labelTftpPortText()); - QDir dir(mountPath); - if (!dir.exists()){ - dir.mkpath(mountPath); - } - - cmd = "/usr/local/bin/sshfs"; -#endif - -#ifdef AMNEZIA_DESKTOP - set_pushButtonSftpMountEnabled(false); - QProcess *p = new QProcess; - m_sftpMountProcesses.append(p); - p->setProcessChannelMode(QProcess::MergedChannels); - - connect(p, &QProcess::readyRead, this, [this, p, mountPath](){ - QString s = p->readAll(); - if (s.contains("The service sshfs has been started")) { - QDesktopServices::openUrl(QUrl("file:///" + mountPath)); - set_pushButtonSftpMountEnabled(true); - } - qDebug() << s; - }); - - - - p->setProgram(cmd); - - QString args = QString( - "%1@%2:/ %3 " - "-o port=%4 " - "-f " - "-o reconnect " - "-o rellinks " - "-o fstypename=SSHFS " - "-o ssh_command=/usr/bin/ssh.exe " - "-o UserKnownHostsFile=/dev/null " - "-o StrictHostKeyChecking=no " - "-o password_stdin") - .arg(labelTftpUserNameText()) - .arg(host) - .arg(mountPath) - .arg(labelTftpPortText()); - - -// args.replace("\n", " "); -// args.replace("\r", " "); -//#ifndef Q_OS_WIN -// args.replace("reconnect-orellinks", ""); -//#endif - p->setArguments(args.split(" ", Qt::SkipEmptyParts)); - p->start(); - p->waitForStarted(50); - if (p->state() != QProcess::Running) { - qDebug() << "onPushButtonSftpMountDriveClicked process not started"; - qDebug() << args; - } - else { - p->write((labelTftpPasswordText() + "\n").toUtf8()); - } - - //qDebug().noquote() << "onPushButtonSftpMountDriveClicked" << args; - - set_pushButtonSftpMountEnabled(true); -#endif -} - -void OtherProtocolsLogic::checkBoxSftpRestoreClicked() -{ - -} diff --git a/client/ui/pages_logic/protocols/OtherProtocolsLogic.h b/client/ui/pages_logic/protocols/OtherProtocolsLogic.h deleted file mode 100644 index 508c2914..00000000 --- a/client/ui/pages_logic/protocols/OtherProtocolsLogic.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef OTHER_PROTOCOLS_LOGIC_H -#define OTHER_PROTOCOLS_LOGIC_H - -#include "PageProtocolLogicBase.h" - -#include - -class UiLogic; - -class OtherProtocolsLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, labelTftpUserNameText) - AUTO_PROPERTY(QString, labelTftpPasswordText) - AUTO_PROPERTY(QString, labelTftpPortText) - AUTO_PROPERTY(bool, pushButtonSftpMountEnabled) - AUTO_PROPERTY(bool, checkBoxSftpRestoreChecked) - - AUTO_PROPERTY(QString, labelTorWebSiteAddressText) - - -public: - Q_INVOKABLE void onPushButtonSftpMountDriveClicked(); - Q_INVOKABLE void checkBoxSftpRestoreClicked(); -public: - explicit OtherProtocolsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~OtherProtocolsLogic(); - - void updateProtocolPage(const QJsonObject &config, DockerContainer container, bool haveAuthData) override; - //QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -#ifdef AMNEZIA_DESKTOP - QList m_sftpMountProcesses; -#endif - -#ifdef Q_OS_WINDOWS - QString getNextDriverLetter() const; -#endif - -}; -#endif // OTHER_PROTOCOLS_LOGIC_H diff --git a/client/ui/pages_logic/protocols/PageProtocolLogicBase.cpp b/client/ui/pages_logic/protocols/PageProtocolLogicBase.cpp deleted file mode 100644 index 62c5aa89..00000000 --- a/client/ui/pages_logic/protocols/PageProtocolLogicBase.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "PageProtocolLogicBase.h" - - -PageProtocolLogicBase::PageProtocolLogicBase(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} diff --git a/client/ui/pages_logic/protocols/PageProtocolLogicBase.h b/client/ui/pages_logic/protocols/PageProtocolLogicBase.h deleted file mode 100644 index ab66f8a9..00000000 --- a/client/ui/pages_logic/protocols/PageProtocolLogicBase.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef PAGE_PROTOCOL_LOGIC_BASE_H -#define PAGE_PROTOCOL_LOGIC_BASE_H - -#include "settings.h" -#include "../PageLogicBase.h" - -using namespace amnezia; -using namespace PageEnumNS; - -class UiLogic; - -class PageProtocolLogicBase : public PageLogicBase -{ - Q_OBJECT - -public: - explicit PageProtocolLogicBase(UiLogic *uiLogic, QObject *parent = nullptr); - ~PageProtocolLogicBase() = default; - - virtual void updateProtocolPage(const QJsonObject &config, DockerContainer container, bool haveAuthData) {} - virtual QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) { return QJsonObject(); } - -}; -#endif // PAGE_PROTOCOL_LOGIC_BASE_H diff --git a/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp b/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp deleted file mode 100644 index f9220a92..00000000 --- a/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include "ShadowSocksLogic.h" - -#include - -#include "core/servercontroller.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" -#include "ui/uilogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -ShadowSocksLogic::ShadowSocksLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_comboBoxCipherText{"chacha20-poly1305"}, - m_lineEditPortText{}, - m_pushButtonSaveVisible{false}, - m_progressBarResetVisible{false}, - m_lineEditPortEnabled{false}, - m_labelInfoVisible{true}, - m_labelInfoText{}, - m_progressBarResetValue{0}, - m_progressBarResetMaximum{100} -{ - -} - -void ShadowSocksLogic::updateProtocolPage(const QJsonObject &ssConfig, DockerContainer container, bool haveAuthData) -{ - set_pageEnabled(haveAuthData); - set_pushButtonSaveVisible(haveAuthData); - set_progressBarResetVisible(haveAuthData); - - set_comboBoxCipherText(ssConfig.value(config_key::cipher). - toString(protocols::shadowsocks::defaultCipher)); - - set_lineEditPortText(ssConfig.value(config_key::port). - toString(protocols::shadowsocks::defaultPort)); - - set_lineEditPortEnabled(container == DockerContainer::ShadowSocks); -} - -QJsonObject ShadowSocksLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -{ - oldConfig.insert(config_key::cipher, comboBoxCipherText()); - oldConfig.insert(config_key::port, lineEditPortText()); - - return oldConfig; -} - -void ShadowSocksLogic::onPushButtonSaveClicked() -{ - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::ShadowSocks); - - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - QJsonObject newContainerConfig = containerConfig; - newContainerConfig.insert(ProtocolProps::protoToString(Proto::ShadowSocks), protocolConfig); - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; - saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonSaveVisible(visible); - }; - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { - set_labelInfoVisible(visible); - }; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { - set_labelInfoText(text); - }; - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarResetVisible(visible); - }; - progressBarFunc.setValueFunc = [this] (int value) -> void { - set_progressBarResetValue(value); - }; - progressBarFunc.getValueFunc = [this] (void) -> int { - return progressBarResetValue(); - }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { - return progressBarResetMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { - set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { - set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; - busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - progressBarFunc.setTextVisibleFunc(true); - progressBarFunc.setTextFunc(QString("Configuring...")); - - auto installAction = [this, containerConfig, &newContainerConfig]() { - ServerController serverController(m_settings); - return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); - }; - ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); - - if (!e) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - } - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; -} - -void ShadowSocksLogic::onPushButtonCancelClicked() -{ - emit uiLogic()->pageLogic()->cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/protocols/ShadowSocksLogic.h b/client/ui/pages_logic/protocols/ShadowSocksLogic.h deleted file mode 100644 index bf926928..00000000 --- a/client/ui/pages_logic/protocols/ShadowSocksLogic.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef SHADOWSOCKS_LOGIC_H -#define SHADOWSOCKS_LOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class ShadowSocksLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, comboBoxCipherText) - AUTO_PROPERTY(QString, lineEditPortText) - AUTO_PROPERTY(bool, pushButtonSaveVisible) - AUTO_PROPERTY(bool, progressBarResetVisible) - AUTO_PROPERTY(bool, lineEditPortEnabled) - AUTO_PROPERTY(bool, labelInfoVisible) - AUTO_PROPERTY(QString, labelInfoText) - AUTO_PROPERTY(int, progressBarResetValue) - AUTO_PROPERTY(int, progressBarResetMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - - AUTO_PROPERTY(bool, pushButtonCancelVisible) - -public: - Q_INVOKABLE void onPushButtonSaveClicked(); - Q_INVOKABLE void onPushButtonCancelClicked(); - -public: - explicit ShadowSocksLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ShadowSocksLogic() = default; - - void updateProtocolPage(const QJsonObject &ssConfig, DockerContainer container, bool haveAuthData) override; - QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -}; -#endif // SHADOWSOCKS_LOGIC_H diff --git a/client/ui/pages_logic/protocols/WireGuardLogic.cpp b/client/ui/pages_logic/protocols/WireGuardLogic.cpp deleted file mode 100644 index a6c661f5..00000000 --- a/client/ui/pages_logic/protocols/WireGuardLogic.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "WireGuardLogic.h" -#include "core/servercontroller.h" -#include -#include "../../uilogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -WireGuardLogic::WireGuardLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent) -{ - -} - -void WireGuardLogic::updateProtocolPage(const QJsonObject &wireGuardConfig, DockerContainer container, bool haveAuthData) -{ - qDebug() << "WireGuardLogic::updateProtocolPage"; - - auto lastConfigJsonDoc = QJsonDocument::fromJson(wireGuardConfig.value(config_key::last_config).toString().toUtf8()); - auto lastConfigJson = lastConfigJsonDoc.object(); - - QString wireGuardLastConfigText; - QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); - for (const QString &l: lines) { - wireGuardLastConfigText.append(l + "\n"); - } - - set_wireGuardLastConfigText(wireGuardLastConfigText); - set_isThirdPartyConfig(wireGuardConfig.value(config_key::isThirdPartyConfig).toBool()); -} diff --git a/client/ui/pages_logic/protocols/WireGuardLogic.h b/client/ui/pages_logic/protocols/WireGuardLogic.h deleted file mode 100644 index 0b3bfc7b..00000000 --- a/client/ui/pages_logic/protocols/WireGuardLogic.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef WIREGUARDLOGIC_H -#define WIREGUARDLOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class WireGuardLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, wireGuardLastConfigText) - AUTO_PROPERTY(bool, isThirdPartyConfig) - -public: - explicit WireGuardLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~WireGuardLogic() = default; - - void updateProtocolPage(const QJsonObject &wireGuardConfig, DockerContainer container, bool haveAuthData) override; - -private: - UiLogic *m_uiLogic; - -}; - -#endif // WIREGUARDLOGIC_H diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml new file mode 100644 index 00000000..c2ec186d --- /dev/null +++ b/client/ui/qml/Components/ConnectButton.qml @@ -0,0 +1,159 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Shapes +import Qt5Compat.GraphicalEffects + +import ConnectionState 1.0 +import PageEnum 1.0 + +Button { + id: root + + property string defaultButtonColor: "#D7D8DB" + property string progressButtonColor: "#D7D8DB" + property string connectedButtonColor: "#FBB26A" + + implicitWidth: 190 + implicitHeight: 190 + + Connections { + target: ConnectionController + + function onConnectionErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + } + + text: ConnectionController.connectionStateText + +// enabled: !ConnectionController.isConnectionInProgress + + background: Item { + implicitWidth: parent.width + implicitHeight: parent.height + transformOrigin: Item.Center + + Shape { + id: backgroundCircle + width: parent.implicitWidth + height: parent.implicitHeight + anchors.bottom: parent.bottom + anchors.right: parent.right + layer.enabled: true + layer.samples: 4 + layer.smooth: true + layer.effect: DropShadow { + anchors.fill: backgroundCircle + horizontalOffset: 0 + verticalOffset: 0 + radius: 10 + samples: 25 + color: "#FBB26A" + source: backgroundCircle + } + + ShapePath { + fillColor: "transparent" + strokeColor: { + if (ConnectionController.isConnectionInProgress) { + return "#261E1A" + } else if (ConnectionController.isConnected) { + return connectedButtonColor + } else { + return defaultButtonColor + } + } + strokeWidth: 3 + capStyle: ShapePath.RoundCap + + PathAngleArc { + centerX: backgroundCircle.width / 2 + centerY: backgroundCircle.height / 2 + radiusX: 93 + radiusY: 93 + startAngle: 0 + sweepAngle: 360 + } + } + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + Shape { + id: shape + width: parent.implicitWidth + height: parent.implicitHeight + anchors.bottom: parent.bottom + anchors.right: parent.right + layer.enabled: true + layer.samples: 4 + + visible: ConnectionController.isConnectionInProgress + + ShapePath { + fillColor: "transparent" + strokeColor: "#D7D8DB" + strokeWidth: 3 + capStyle: ShapePath.RoundCap + + PathAngleArc { + centerX: shape.width / 2 + centerY: shape.height / 2 + radiusX: 93 + radiusY: 93 + startAngle: 245 + sweepAngle: -180 + } + } + + RotationAnimator { + target: shape + running: ConnectionController.isConnectionInProgress + from: 0 + to: 360 + loops: Animation.Infinite + duration: 1000 + } + } + } + + contentItem: Text { + height: 24 + + font.family: "PT Root UI VF" + font.weight: 700 + font.pixelSize: 20 + + color: ConnectionController.isConnected ? connectedButtonColor : defaultButtonColor + text: root.text + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + onClicked: { + if (!ContainersModel.isAnyContainerInstalled()) { + PageController.setTriggeredBtConnectButton(true) + + ServersModel.currentlyProcessedIndex = ServersModel.getDefaultServerIndex() + InstallController.setShouldCreateServer(false) + PageController.goToPage(PageEnum.PageSetupWizardEasy) + + return + } + + if (ConnectionController.isConnectionInProgress) { + ConnectionController.closeConnection() + } else if (ConnectionController.isConnected) { + ConnectionController.closeConnection() + } else { + ConnectionController.openConnection() + } + } +} diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml new file mode 100644 index 00000000..1f7b2f29 --- /dev/null +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -0,0 +1,63 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +DrawerType { + id: root + + width: parent.width + height: parent.height * 0.4375 + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + spacing: 0 + + Header2Type { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + + headerText: qsTr("Add new connection") + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Configure your server") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSetupWizardCredentials) + root.visible = false + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Open config file, key or QR code") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSetupWizardConfigSource) + root.visible = false + } + } + + DividerType {} + } +} diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml new file mode 100644 index 00000000..f05b90d6 --- /dev/null +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + + +ListView { + id: menuContent + + property var rootWidth + + width: rootWidth + height: menuContent.contentItem.height + + clip: true + interactive: false + + ButtonGroup { + id: containersRadioButtonGroup + } + + delegate: Item { + implicitWidth: rootWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + VerticalRadioButton { + id: containerRadioButton + + Layout.fillWidth: true + + text: name + descriptionText: description + + ButtonGroup.group: containersRadioButtonGroup + + imageSource: "qrc:/images/controls/download.svg" + showImage: !isInstalled + + checkable: isInstalled && !ConnectionController.isConnected && isSupported + checked: isDefault + + onClicked: { + if (ConnectionController.isConnected && isInstalled) { + PageController.showNotificationMessage(qsTr("Unable change protocol while there is an active connection")) + return + } + + if (checked) { + isDefault = true + + menuContent.currentIndex = index + containersDropDown.menuVisible = false + } else { + if (!isSupported && isInstalled) { + PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) + return + } + + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) + InstallController.setShouldCreateServer(false) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + containersDropDown.menuVisible = false + } + } + + MouseArea { + anchors.fill: containerRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType { + Layout.fillWidth: true + } + } + } +} diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml new file mode 100644 index 00000000..16cdcb39 --- /dev/null +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -0,0 +1,79 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +DrawerType { + id: root + + property string headerText + property string descriptionText + property string yesButtonText + property string noButtonText + + property var yesButtonFunction + property var noButtonFunction + + width: parent.width + height: content.implicitHeight + 32 + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 8 + + Header2TextType { + Layout.fillWidth: true + + text: headerText + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 8 + + text: descriptionText + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: yesButtonText + + onClicked: { + if (yesButtonFunction && typeof yesButtonFunction === "function") { + yesButtonFunction() + } + } + } + + BasicButtonType { + Layout.fillWidth: true + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: noButtonText + + onClicked: { + if (noButtonFunction && typeof noButtonFunction === "function") { + noButtonFunction() + } + } + } + } +} diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml new file mode 100644 index 00000000..d318aab8 --- /dev/null +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -0,0 +1,138 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +DrawerType { + id: root + + width: parent.width + height: parent.height * 0.9 + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + BackButtonType { + backButtonImage: "qrc:/images/controls/arrow-left.svg" + backButtonFunction: function() { + root.close() + } + } + } + + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.fill: parent + + Header2Type { + id: header + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Choose language") + } + + ListView { + id: listView + + Layout.fillWidth: true + height: listView.contentItem.height + + clip: true + interactive: false + + model: LanguageModel + currentIndex: LanguageModel.currentLanguageIndex + + ButtonGroup { + id: buttonGroup + } + + delegate: Item { + implicitWidth: root.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + RowLayout { + id: radioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 0 + + z: 1 + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + + text: languageName + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + ButtonGroup.group: buttonGroup + checked: listView.currentIndex === index + + onClicked: { + listView.currentIndex = index + LanguageModel.changeLanguage(languageIndex) + root.close() + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml new file mode 100644 index 00000000..89eb727e --- /dev/null +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -0,0 +1,115 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerEnum 1.0 +import ContainerProps 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + + +ListView { + id: root + + width: parent.width + height: root.contentItem.height + + clip: true + interactive: false + + delegate: Item { + implicitWidth: root.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + + LabelWithButtonType { + implicitWidth: parent.width + + text: name + descriptionText: description + rightImageSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg" + + clickedFunction: function() { + if (isInstalled) { + var containerIndex = root.model.mapToSource(index) + ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) + + if (serviceType !== ProtocolEnum.Other) { + if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { + ProtocolsModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolRaw) + return + } + } + + switch (containerIndex) { + case ContainerEnum.OpenVpn: { + OpenVpnConfigModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolOpenVpnSettings) + break + } + case ContainerEnum.WireGuard: { + ProtocolsModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolRaw) + // WireGuardConfigModel.updateModel(config) + // goToPage(PageEnum.PageProtocolWireGuardSettings) + break + } + case ContainerEnum.Awg: { + AwgConfigModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolAwgSettings) + break + } + case ContainerEnum.Ipsec: { + ProtocolsModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolRaw) + // Ikev2ConfigModel.updateModel(config) + // goToPage(PageEnum.PageProtocolIKev2Settings) + break + } + case ContainerEnum.Sftp: { + SftpConfigModel.updateModel(config) + PageController.goToPage(PageEnum.PageServiceSftpSettings) + break + } + case ContainerEnum.TorWebSite: { + PageController.goToPage(PageEnum.PageServiceTorWebsiteSettings) + break + } + case ContainerEnum.Dns: { + PageController.goToPage(PageEnum.PageServiceDnsSettings) + break + } + default: { // go to the settings page of the container with multiple protocols + ProtocolsModel.updateModel(config) + PageController.goToPage(PageEnum.PageSettingsServerProtocol) + } + } + + } else { + ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) + InstallController.setShouldCreateServer(false) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml new file mode 100644 index 00000000..1158dadc --- /dev/null +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -0,0 +1,256 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +DrawerType { + id: root + + property alias headerText: header.headerText + property alias configContentHeaderText: configContentHeader.headerText + property alias contentVisible: content.visible + + property string configExtension: ".vpn" + property string configCaption: qsTr("Save AmneziaVPN config") + property string configFileName: "amnezia_config.vpn" + + width: parent.width + height: parent.height * 0.9 + + onClosed: { + configExtension = ".vpn" + configCaption = qsTr("Save AmneziaVPN config") + configFileName = "amnezia_config" + } + + Item { + anchors.fill: parent + + Header2Type { + id: header + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + } + + FlickableType { + anchors.top: header.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + 32 + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Share") + imageSource: "qrc:/images/controls/share-2.svg" + + onClicked: { + var fileName = "" + if (GC.isMobile()) { + fileName = configFileName + configExtension + } else { + fileName = SystemController.getFileName(configCaption, + qsTr("Config files (*" + configExtension + ")"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + configFileName, + true, + configExtension) + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + ExportController.exportConfig(fileName) + PageController.showBusyIndicator(false) + } + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Copy") + imageSource: "qrc:/images/controls/copy.svg" + + onClicked: { + configText.selectAll() + configText.copy() + configText.select(0, 0) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Show connection settings") + + onClicked: { + configContentDrawer.visible = true + } + } + + DrawerType { + id: configContentDrawer + + width: parent.width + height: parent.height * 0.9 + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + configContentDrawer.visible = false + } + } + + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin + + ColumnLayout { + id: configContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2Type { + id: configContentHeader + Layout.fillWidth: true + Layout.topMargin: 16 + } + + TextField { + id: configText + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + padding: 0 + leftPadding: 0 + height: 24 + + readOnly: true + + color: "#D7D8DB" + selectionColor: "#633303" + selectedTextColor: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + text: ExportController.config + + wrapMode: Text.Wrap + + background: Rectangle { + color: "transparent" + } + } + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: width + Layout.topMargin: 20 + + visible: ExportController.qrCodesCount > 0 + + color: "white" + + Image { + anchors.fill: parent + anchors.margins: 2 + smooth: false + + source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : "" + + Timer { + property int index: 0 + interval: 1000 + running: ExportController.qrCodesCount > 0 + repeat: true + onTriggered: { + if (ExportController.qrCodesCount > 0) { + index++ + if (index >= ExportController.qrCodesCount) { + index = 0 + } + parent.source = ExportController.qrCodes[index] + } + } + } + + Behavior on source { + PropertyAnimation { duration: 200 } + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 32 + + visible: ExportController.qrCodesCount > 0 + + horizontalAlignment: Text.AlignHCenter + text: qsTr("To read the QR code in the Amnezia app, select \"Add server\" → \"I have data to connect\" → \"QR code, key or settings file\"") + } + } + } + } +} diff --git a/client/ui/qml/Components/TransportProtoSelector.qml b/client/ui/qml/Components/TransportProtoSelector.qml new file mode 100644 index 00000000..bfd82cb1 --- /dev/null +++ b/client/ui/qml/Components/TransportProtoSelector.qml @@ -0,0 +1,51 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +Rectangle { + id: root + + property real rootWidth: root.width + property int currentIndex + + implicitWidth: transportProtoButtonGroup.implicitWidth + implicitHeight: transportProtoButtonGroup.implicitHeight + + color: "#1C1D21" + radius: 16 + + RowLayout { + id: transportProtoButtonGroup + + spacing: 0 + + HorizontalRadioButton { + checked: root.currentIndex === 0 + + hoverEnabled: root.enabled + + implicitWidth: (rootWidth - 32) / 2 + text: "UDP" + + onClicked: { + root.currentIndex = 0 + } + } + + HorizontalRadioButton { + checked: root.currentIndex === 1 + + hoverEnabled: root.enabled + + implicitWidth: (rootWidth - 32) / 2 + text: "TCP" + + onClicked: { + root.currentIndex = 1 + } + } + } +} diff --git a/client/ui/qml/Config/GlobalConfig.qml b/client/ui/qml/Config/GlobalConfig.qml index 5bb71b6f..0855101c 100644 --- a/client/ui/qml/Config/GlobalConfig.qml +++ b/client/ui/qml/Config/GlobalConfig.qml @@ -11,19 +11,31 @@ Item { readonly property int defaultMargin: 20 function isMobile() { - if (Qt.platform.os == "android" || - Qt.platform.os == "ios") { + if (Qt.platform.os === "android" || + Qt.platform.os === "ios") { return true } return false } function isDesktop() { - if (Qt.platform.os == "windows" || - Qt.platform.os == "linux" || - Qt.platform.os == "osx") { + if (Qt.platform.os === "windows" || + Qt.platform.os === "linux" || + Qt.platform.os === "osx") { return true } return false } + + TextEdit{ + id: clipboard + visible: false + } + + function copyToClipBoard(text) { + clipboard.text = text + clipboard.selectAll() + clipboard.copy() + clipboard.select(0, 0) + } } diff --git a/client/ui/qml/Controls/BackButton.qml b/client/ui/qml/Controls/BackButton.qml deleted file mode 100644 index 47f0970c..00000000 --- a/client/ui/qml/Controls/BackButton.qml +++ /dev/null @@ -1,33 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Button { - id: root - x: 10 - y: 5 - width: 41 - height: 35 - - hoverEnabled: true - property bool containsMouse: hovered - - background: Item {} - - MouseArea { - id: mouseArea - anchors.fill: parent - enabled: false - cursorShape: Qt.PointingHandCursor - } - - onClicked: { - UiLogic.closePage() - } - - contentItem: Image { - id: img - source: "qrc:/images/arrow_left.png" - anchors.fill: root - anchors.margins: root.containsMouse ? 9 : 10 - } -} diff --git a/client/ui/qml/Controls/BasicButtonType.qml b/client/ui/qml/Controls/BasicButtonType.qml deleted file mode 100644 index e115df29..00000000 --- a/client/ui/qml/Controls/BasicButtonType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Button { - id: root - property bool containsMouse: hovered - hoverEnabled: true - flat: true - highlighted: false - - MouseArea { - id: mouseArea - anchors.fill: parent - enabled: false - cursorShape: Qt.PointingHandCursor - } -} diff --git a/client/ui/qml/Controls/BlueButtonType.qml b/client/ui/qml/Controls/BlueButtonType.qml deleted file mode 100644 index a3602c4a..00000000 --- a/client/ui/qml/Controls/BlueButtonType.qml +++ /dev/null @@ -1,28 +0,0 @@ -import QtQuick -import QtQuick.Controls - -import "../Config" - -BasicButtonType { - id: root - width: parent.width - 2 * GC.defaultMargin - implicitHeight: 40 - - background: Rectangle { - anchors.fill: parent - radius: 4 - color: root.enabled ? (root.containsMouse ? "#211966" : "#100A44") : "#888888" - } - font.pixelSize: 16 - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: root.font.pixelSize - color: "#D4D4D4" - text: root.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true -} diff --git a/client/ui/qml/Controls/Caption.qml b/client/ui/qml/Controls/Caption.qml deleted file mode 100644 index 50fc9aca..00000000 --- a/client/ui/qml/Controls/Caption.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Text { - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 24 - color: "#100A44" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - x: 10 - y: 35 - width: parent.width - 40 - anchors.horizontalCenter: parent.horizontalCenter - wrapMode: Text.Wrap - //height: 31 -} diff --git a/client/ui/qml/Controls/CheckBoxType.qml b/client/ui/qml/Controls/CheckBoxType.qml deleted file mode 100644 index 0331706c..00000000 --- a/client/ui/qml/Controls/CheckBoxType.qml +++ /dev/null @@ -1,27 +0,0 @@ -import QtQuick -import QtQuick.Controls - -CheckBox { - id: root - property int imageWidth : 20 - property int imageHeight : 20 - indicator: Image { - id: indicator - anchors.verticalCenter: root.verticalCenter - height: imageHeight - width: imageWidth - source: root.checked ? "qrc:/images/controls/check_on.png" - : "qrc:/images/controls/check_off.png" - } - - contentItem: Text { - text: root.text - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - verticalAlignment: Text.AlignVCenter - leftPadding: root.indicator.width + root.spacing - wrapMode: Text.Wrap - } -} diff --git a/client/ui/qml/Controls/ComboBoxType.qml b/client/ui/qml/Controls/ComboBoxType.qml deleted file mode 100644 index 090ca9de..00000000 --- a/client/ui/qml/Controls/ComboBoxType.qml +++ /dev/null @@ -1,11 +0,0 @@ -import QtQuick -import QtQuick.Controls - -ComboBox { - id: root - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - - popup.font.pixelSize: 16 -} diff --git a/client/ui/qml/Controls/FadeBehavior.qml b/client/ui/qml/Controls/FadeBehavior.qml deleted file mode 100644 index e523061f..00000000 --- a/client/ui/qml/Controls/FadeBehavior.qml +++ /dev/null @@ -1,35 +0,0 @@ -import QtQuick -import QtQml - -Behavior { - id: root - - property QtObject fadeTarget: targetProperty.object - property string fadeProperty: "scale" - property int fadeDuration: 150 - property string easingType: "Quad" - - property alias outAnimation: outAnimation - property alias inAnimation: inAnimation - - SequentialAnimation { - NumberAnimation { - id: outAnimation - target: root.fadeTarget - property: root.fadeProperty - duration: root.fadeDuration - to: 0 - easing.type: Easing["In"+root.easingType] - } - PropertyAction { } - NumberAnimation { - id: inAnimation - target: root.fadeTarget - property: root.fadeProperty - duration: root.fadeDuration - to: target[property] - easing.type: Easing["Out"+root.easingType] - } - } - -} diff --git a/client/ui/qml/Controls/ImageButtonType.qml b/client/ui/qml/Controls/ImageButtonType.qml deleted file mode 100644 index 74b90c6e..00000000 --- a/client/ui/qml/Controls/ImageButtonType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import QtQuick.Controls - -BasicButtonType { - id: root - property alias iconMargin: img.anchors.margins - property alias img: img - property int imgMargin: 4 - property int imgMarginHover: 3 - background: Item {} - contentItem: Image { - id: img - source: root.icon.source - anchors.fill: root - anchors.margins: root.containsMouse ? imgMarginHover : imgMargin - } -} diff --git a/client/ui/qml/Controls/LabelType.qml b/client/ui/qml/Controls/LabelType.qml deleted file mode 100644 index 9cce61b1..00000000 --- a/client/ui/qml/Controls/LabelType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import "../Config" - -Text { - id: root - width: parent.width - 2 * GC.defaultMargin - anchors.topMargin: 10 - - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap -} - diff --git a/client/ui/qml/Controls/Logo.qml b/client/ui/qml/Controls/Logo.qml deleted file mode 100644 index 74d82872..00000000 --- a/client/ui/qml/Controls/Logo.qml +++ /dev/null @@ -1,8 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Image { - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottomMargin: 30 - source: "qrc:/images/AmneziaVPN.png" -} diff --git a/client/ui/qml/Controls/PopupWarning.qml b/client/ui/qml/Controls/PopupWarning.qml deleted file mode 100644 index 57c332eb..00000000 --- a/client/ui/qml/Controls/PopupWarning.qml +++ /dev/null @@ -1,34 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Popup { - id: root - - property string popupWarningText - - anchors.centerIn: Overlay.overlay - modal: true - closePolicy: Popup.NoAutoClose - width: parent.width - 20 - - ColumnLayout { - width: parent.width - Text { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - wrapMode: Text.WordWrap - font.pixelSize: 16 - text: root.popupWarningText - } - - BlueButtonType { - Layout.preferredWidth: parent.width / 2 - Layout.fillWidth: true - text: "Continue" - onClicked: { - root.close() - } - } - } -} diff --git a/client/ui/qml/Controls/PopupWithQuestion.qml b/client/ui/qml/Controls/PopupWithQuestion.qml deleted file mode 100644 index b55edf90..00000000 --- a/client/ui/qml/Controls/PopupWithQuestion.qml +++ /dev/null @@ -1,62 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Popup { - id: root - - property string questionText - property string yesText: "yes" - property string noText: "no" - property var yesFunc - property var noFunc - - anchors.centerIn: Overlay.overlay - modal: true - closePolicy: Popup.CloseOnEscape - - width: parent.width - 20 - focus: true - - onAboutToHide: { - parent.forceActiveFocus(true) - } - - ColumnLayout { - width: parent.width - - Text { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - wrapMode: Text.WordWrap - font.pixelSize: 16 - text: questionText - } - - RowLayout { - Layout.fillWidth: true - BlueButtonType { - id: yesButton - Layout.fillWidth: true - text: yesText - onClicked: { - root.enabled = false - if (yesFunc && typeof yesFunc === "function") { - yesFunc() - } - root.enabled = true - } - } - BlueButtonType { - id: noButton - Layout.fillWidth: true - text: noText - onClicked: { - if (noFunc && typeof noFunc === "function") { - noFunc() - } - } - } - } - } -} diff --git a/client/ui/qml/Controls/PopupWithTextField.qml b/client/ui/qml/Controls/PopupWithTextField.qml deleted file mode 100644 index acdf1247..00000000 --- a/client/ui/qml/Controls/PopupWithTextField.qml +++ /dev/null @@ -1,62 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Popup { - id: root - - property alias text: textField.text - property alias placeholderText: textField.placeholderText - property string yesText: "yes" - property string noText: "no" - property var yesFunc - property var noFunc - - signal editingFinished() - - anchors.centerIn: Overlay.overlay - modal: true - closePolicy: Popup.NoAutoClose - - width: parent.width - 20 - - ColumnLayout { - width: parent.width - - TextField { - id: textField - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - font.pixelSize: 16 - echoMode: TextInput.Password - } - - RowLayout { - Layout.fillWidth: true - BlueButtonType { - id: yesButton - Layout.preferredWidth: parent.width / 2 - Layout.fillWidth: true - text: yesText - onClicked: { - root.enabled = false - if (yesFunc && typeof yesFunc === "function") { - yesFunc() - } - root.enabled = true - } - } - BlueButtonType { - id: noButton - Layout.preferredWidth: parent.width / 2 - Layout.fillWidth: true - text: noText - onClicked: { - if (noFunc && typeof noFunc === "function") { - noFunc() - } - } - } - } - } -} diff --git a/client/ui/qml/Controls/RadioButtonType.qml b/client/ui/qml/Controls/RadioButtonType.qml deleted file mode 100644 index cda28ea5..00000000 --- a/client/ui/qml/Controls/RadioButtonType.qml +++ /dev/null @@ -1,36 +0,0 @@ -import QtQuick -import QtQuick.Controls - -RadioButton { - id: root - - indicator: Rectangle { - implicitWidth: 13 - implicitHeight: 13 - x: root.leftPadding - y: parent.height / 2 - height / 2 - radius: 13 - border.color: root.down ? "#777777" : "#777777" - - Rectangle { - width: 7 - height: 7 - x: 3 - y: 3 - radius: 4 - color: root.down ? "#15CDCB" : "#15CDCB" - visible: root.checked - } - } - - contentItem: Text { - text: root.text - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: enabled ? "#181922" : "#686972" - verticalAlignment: Text.AlignVCenter - leftPadding: root.indicator.width + root.spacing - } - height: 10 -} diff --git a/client/ui/qml/Controls/RichLabelType.qml b/client/ui/qml/Controls/RichLabelType.qml deleted file mode 100644 index f354f974..00000000 --- a/client/ui/qml/Controls/RichLabelType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick - -LabelType { - id: label_connection_code - width: parent.width - 60 - x: 30 - font.pixelSize: 14 - textFormat: Text.RichText - onLinkActivated: Qt.openUrlExternally(link) - - MouseArea { - anchors.fill: parent - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - acceptedButtons: Qt.NoButton - } -} - diff --git a/client/ui/qml/Controls/SettingButtonType.qml b/client/ui/qml/Controls/SettingButtonType.qml deleted file mode 100644 index 6166793d..00000000 --- a/client/ui/qml/Controls/SettingButtonType.qml +++ /dev/null @@ -1,33 +0,0 @@ -import QtQuick -import QtQuick.Controls - -BasicButtonType { - id: root - property alias textItem: textItem - height: 30 - - background: Item {} - contentItem: Item { - SvgImageType { - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - svg.source: root.icon.source - enabled: root.enabled - color: "#100A44" - width: 25 - height: 25 - } - Text { - id: textItem - anchors.fill: parent - leftPadding: 30 - text: root.text - color: root.enabled ? "#100A44": "#AAAAAA" - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 20 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - } -} diff --git a/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml b/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml deleted file mode 100644 index 31b3591e..00000000 --- a/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml +++ /dev/null @@ -1,26 +0,0 @@ -import QtQuick -import QtQuick.Controls - -ShareConnectionButtonType { - property string start_text: qsTr("Copy") - property string end_text: qsTr("Copied") - - property string copyText - - enabled: copyText.length > 0 - visible: copyText.length > 0 - - Timer { - id: timer - interval: 1000; running: false; repeat: false - onTriggered: text = start_text - } - - text: start_text - - onClicked: { - text = end_text - timer.running = true - UiLogic.copyToClipboard(copyText) - } -} diff --git a/client/ui/qml/Controls/ShareConnectionButtonType.qml b/client/ui/qml/Controls/ShareConnectionButtonType.qml deleted file mode 100644 index 77ebbac0..00000000 --- a/client/ui/qml/Controls/ShareConnectionButtonType.qml +++ /dev/null @@ -1,27 +0,0 @@ -import QtQuick -import QtQuick.Controls - - -BasicButtonType { - id: root - height: 40 - background: Rectangle { - anchors.fill: parent - radius: 4 - color: root.enabled - ? (root.containsMouse ? "#282932" : "#181922") - : "#484952" - } - font.pixelSize: 16 - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: root.font.pixelSize - color: "#D4D4D4" - text: root.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true -} diff --git a/client/ui/qml/Controls/ShareConnectionContent.qml b/client/ui/qml/Controls/ShareConnectionContent.qml deleted file mode 100644 index 99427aef..00000000 --- a/client/ui/qml/Controls/ShareConnectionContent.qml +++ /dev/null @@ -1,65 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Shapes 1.4 - -Item { - id: root - property bool active: false - property string text: "" - height: active ? contentLoader.item.height + 40 + 5 * 2 : 40 - signal clicked() - - Rectangle { - x: 0 - y: 0 - width: parent.width - height: 40 - color: "transparent" - clip: true - radius: 2 - gradient: LinearGradient { - x1: 0 ; y1: 0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#E1E1E1" }, - GradientStop { position: 0.4; color: "#DDDDDD" }, - GradientStop { position: 0.5; color: "#D8D8D8" }, - GradientStop { position: 1.0; color: "#D3D3D3" } - ] - } - Image { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - source: "qrc:/images/share.png" - } - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 2 - color: "#148CD2" - visible: ms.containsMouse ? true : false - } - Text { - x: 40 - anchors.verticalCenter: parent.verticalCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 18 - color: "#100A44" - font.bold: true - text: root.text - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - } - MouseArea { - id: ms - anchors.fill: parent - hoverEnabled: true - onClicked: root.clicked() - } - } -} - diff --git a/client/ui/qml/Controls/SvgButtonType.qml b/client/ui/qml/Controls/SvgButtonType.qml deleted file mode 100644 index e6f78c87..00000000 --- a/client/ui/qml/Controls/SvgButtonType.qml +++ /dev/null @@ -1,16 +0,0 @@ -import QtQuick -import QtQuick.Controls -import "." - -BasicButtonType { - id: root - icon.color: "#181922" - - background: Item {} - contentItem: SvgImageType { - svg.source: icon.source - color: icon.color - anchors.fill: parent - anchors.margins: parent.containsMouse ? 0 : 1 - } -} diff --git a/client/ui/qml/Controls/SvgImageType.qml b/client/ui/qml/Controls/SvgImageType.qml deleted file mode 100644 index aee928ba..00000000 --- a/client/ui/qml/Controls/SvgImageType.qml +++ /dev/null @@ -1,23 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt5Compat.GraphicalEffects - -Item { - id: root - property color color: "#181922" - property alias svg: image - Image { - anchors.fill: parent - id: image - sourceSize: Qt.size(root.width, root.height) - - antialiasing: true - visible: false - } - - ColorOverlay { - anchors.fill: image - source: image - color: root.enabled ? root.color : "grey" - } -} diff --git a/client/ui/qml/Controls/TextAreaType.qml b/client/ui/qml/Controls/TextAreaType.qml deleted file mode 100644 index 2f6e0843..00000000 --- a/client/ui/qml/Controls/TextAreaType.qml +++ /dev/null @@ -1,63 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt.labs.platform - -import "../Config" - -Flickable -{ - property alias textArea: root - id: flickable - flickableDirection: Flickable.VerticalFlick - clip: true - TextArea.flickable: - - TextArea { - id: root - property bool error: false - - height: 40 - anchors.topMargin: 5 - selectByMouse: false - - selectionColor: "darkgray" - font.pixelSize: 16 - color: "#333333" - background: Rectangle { - implicitWidth: 200 - implicitHeight: 40 - border.width: 1 - color: { - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - return root.enabled ? "#F4F4F4" : Qt.rgba(127, 127, 127, 255) - } - border.color: { - if (!root.enabled) { - return Qt.rgba(127, 127, 127, 255) - } - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - if (root.focus) { - return "#A7A7A7" - } - return "#A7A7A7" - } - } - -// MouseArea { -// anchors.fill: root -// enabled: GC.isDesktop() -// acceptedButtons: Qt.RightButton -// onClicked: contextMenu.open() -// } - -// ContextMenu { -// id: contextMenu -// textObj: root -// } - } - -} diff --git a/client/ui/qml/Controls/TextFieldType.qml b/client/ui/qml/Controls/TextFieldType.qml deleted file mode 100644 index 5d7b2a65..00000000 --- a/client/ui/qml/Controls/TextFieldType.qml +++ /dev/null @@ -1,52 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt.labs.platform -import "../Config" - -TextField { - id: root - property bool error: false - - width: parent.width - 2 * GC.defaultMargin - height: 40 - anchors.topMargin: 5 - selectByMouse: true - selectionColor: "darkgray" - font.pixelSize: 16 - color: "#333333" - - background: Rectangle { - implicitWidth: 200 - implicitHeight: 40 - border.width: 1 - color: { - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - return root.enabled ? "#F4F4F4" : Qt.rgba(127, 127, 127, 255) - } - border.color: { - if (!root.enabled) { - return Qt.rgba(127, 127, 127, 255) - } - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - if (root.focus) { - return "#A7A7A7" - } - return "#A7A7A7" - } - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: contextMenu.open() - } - - ContextMenu { - id: contextMenu - textObj: root - } -} diff --git a/client/ui/qml/Controls/UrlButtonType.qml b/client/ui/qml/Controls/UrlButtonType.qml deleted file mode 100644 index d77763dc..00000000 --- a/client/ui/qml/Controls/UrlButtonType.qml +++ /dev/null @@ -1,25 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 - -BasicButtonType { - property alias label: lbl - id: root - antialiasing: true - height: 21 - background: Item {} - - contentItem: Text { - id: lbl - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 18 - font.underline: true - - text: root.text - color: "#3045ee" - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } -} diff --git a/client/ui/qml/Controls/VisibleBehavior.qml b/client/ui/qml/Controls/VisibleBehavior.qml deleted file mode 100644 index 9aeb4e85..00000000 --- a/client/ui/qml/Controls/VisibleBehavior.qml +++ /dev/null @@ -1,6 +0,0 @@ -FadeBehavior { - fadeProperty: "opacity" - fadeDuration: 200 - outAnimation.duration: targetValue ? 0 : fadeDuration - inAnimation.duration: targetValue ? fadeDuration : 0 -} diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml new file mode 100644 index 00000000..f1044745 --- /dev/null +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -0,0 +1,45 @@ +import QtQuick +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +Item { + id: root + + property string backButtonImage: "qrc:/images/controls/arrow-left.svg" + property var backButtonFunction + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + visible: backButtonImage !== "" + + RowLayout { + id: content + + anchors.fill: parent + anchors.leftMargin: 8 + + ImageButtonType { + image: backButtonImage + imageColor: "#D7D8DB" + + implicitWidth: 40 + implicitHeight: 40 + + onClicked: { + if (backButtonFunction && typeof backButtonFunction === "function") { + backButtonFunction() + } else { + PageController.closePage() + } + } + } + + Rectangle { + id: background + Layout.fillWidth: true + + color: "transparent" + } + } +} diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml new file mode 100644 index 00000000..a5cde951 --- /dev/null +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -0,0 +1,117 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +import "TextTypes" + +Button { + id: root + + property string hoveredColor: "#C1C2C5" + property string defaultColor: "#D7D8DB" + property string disabledColor: "#494B50" + property string pressedColor: "#979799" + + property string textColor: "#0E0E11" + + property string borderColor: "#D7D8DB" + property int borderWidth: 0 + + property string imageSource + + property bool squareLeftSide: false + + implicitHeight: 56 + + hoverEnabled: true + + background: Rectangle { + id: background + anchors.fill: parent + radius: 16 + color: { + if (root.enabled) { + if (root.pressed) { + return pressedColor + } + return root.hovered ? hoveredColor : defaultColor + } else { + return disabledColor + } + } + border.color: borderColor + border.width: borderWidth + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + visible: root.squareLeftSide + + z: 1 + + width: parent.radius + height: parent.radius + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + color: { + if (root.enabled) { + if (root.pressed) { + return pressedColor + } + return root.hovered ? hoveredColor : defaultColor + } else { + return disabledColor + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + + MouseArea { + anchors.fill: background + enabled: false + cursorShape: Qt.PointingHandCursor + } + + contentItem: Item { + anchors.fill: background + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + RowLayout { + id: content + anchors.centerIn: parent + + Image { + Layout.preferredHeight: 20 + Layout.preferredWidth: 20 + + source: root.imageSource + visible: root.imageSource === "" ? false : true + + layer { + enabled: true + effect: ColorOverlay { + color: textColor + } + } + } + + ButtonTextType { + color: textColor + text: root.text + visible: root.text === "" ? false : true + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + } + } +} diff --git a/client/ui/qml/Controls2/BusyIndicatorType.qml b/client/ui/qml/Controls2/BusyIndicatorType.qml new file mode 100644 index 00000000..7e92998c --- /dev/null +++ b/client/ui/qml/Controls2/BusyIndicatorType.qml @@ -0,0 +1,68 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes + +Popup { + id: root + anchors.centerIn: parent + + modal: true + closePolicy: Popup.NoAutoClose + + visible: false + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + background: Rectangle { + color: "transparent" + } + + BusyIndicator { + id: busyIndicator + + visible: true + running: true + + contentItem: Item { + implicitWidth: 46 + implicitHeight: 46 + transformOrigin: Item.Center + + Shape { + id: shape + width: parent.implicitWidth + height: parent.implicitHeight + anchors.bottom: parent.bottom + anchors.right: parent.right + layer.enabled: true + layer.samples: 4 + + ShapePath { + fillColor: "transparent" + strokeColor: "#787878" + strokeWidth: 3 + capStyle: ShapePath.RoundCap + + PathAngleArc { + centerX: shape.width / 2 + centerY: shape.height / 2 + radiusX: 18 + radiusY: 18 + startAngle: 225 + sweepAngle: -90 + } + } + RotationAnimator { + target: shape + running: busyIndicator.visible && busyIndicator.running + from: 0 + to: 360 + loops: Animation.Infinite + duration: 1250 + } + } + } + } +} diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml new file mode 100644 index 00000000..32f89122 --- /dev/null +++ b/client/ui/qml/Controls2/CardType.qml @@ -0,0 +1,135 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +RadioButton { + id: root + + property string headerText + property string bodyText + property string footerText + + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) + property string defaultColor: Qt.rgba(1, 1, 1, 0) + property string disabledColor: Qt.rgba(1, 1, 1, 0) + property string pressedColor: Qt.rgba(1, 1, 1, 0.05) + property string selectedColor: Qt.rgba(1, 1, 1, 0) + + property string textColor: "#0E0E11" + + property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) + property string selectedBorderColor: "#FBB26A" + property string defaultBodredColor: "transparent" + property int borderWidth: 0 + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + hoverEnabled: true + + indicator: Rectangle { + anchors.fill: parent + radius: 16 + + color: { + if (root.enabled) { + if (root.hovered) { + return hoveredColor + } else if (root.checked) { + return selectedColor + } + return defaultColor + } else { + return disabledColor + } + } + + border.color: { + if (root.enabled) { + if (root.pressed) { + return pressedBorderColor + } else if (root.checked) { + return selectedBorderColor + } + } + return defaultBodredColor + } + + border.width: { + if (root.enabled) { + if(root.checked) { + return 1 + } + return root.pressed ? 1 : 0 + } else { + return 0 + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + } + + ColumnLayout { + id: content + anchors.fill: parent + spacing: 16 + + Text { + text: root.headerText + wrapMode: Text.WordWrap + color: "#D7D8DB" + font.pixelSize: 25 + font.weight: 700 + font.family: "PT Root UI VF" + + height: 30 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + } + + Text { + text: root.bodyText + wrapMode: Text.WordWrap + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: root.footerText !== "" ? 0 : 16 + } + + Text { + text: root.footerText + wrapMode: Text.WordWrap + visible: root.footerText !== "" + color: "#878B91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 16 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + } + } + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + enabled: false + } +} diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml new file mode 100644 index 00000000..1ad3b412 --- /dev/null +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -0,0 +1,135 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +import "TextTypes" + +CheckBox { + id: root + + property string descriptionText + property string descriptionTextColor: "#878B91" + property string descriptionTextDisabledColor: "#494B50" + + property string textColor: "#D7D8DB" + property string textDisabledColor: "#878B91" + + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) + property string defaultColor: "transparent" + property string pressedColor: Qt.rgba(1, 1, 1, 0.05) + + property string defaultBorderColor: "#D7D8DB" + property string checkedBorderColor: "#FBB26A" + property string checkedBorderDisabledColor: "#402102" + + property string checkedImageColor: "#FBB26A" + property string pressedImageColor: "#A85809" + property string defaultImageColor: "transparent" + property string checkedDisabledImageColor: "#84603D" + + property string imageSource: "qrc:/images/controls/check.svg" + + hoverEnabled: enabled ? true : false + + indicator: Rectangle { + id: background + + anchors.verticalCenter: parent.verticalCenter + + implicitWidth: 56 + implicitHeight: 56 + radius: 16 + + color: { + if (root.hovered) { + return hoveredColor + } + return defaultColor + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + id: imageBorder + + anchors.centerIn: parent + width: 24 + height: 24 + color: "transparent" + border.color: root.checked ? (root.enabled ? checkedBorderColor : checkedBorderDisabledColor) : defaultBorderColor + border.width: 1 + radius: 4 + + Image { + anchors.centerIn: parent + + source: root.pressed ? imageSource : root.checked ? imageSource : "" + layer { + enabled: true + effect: ColorOverlay { + color: { + if (root.pressed) { + return root.pressedImageColor + } else if (root.checked) { + if (root.enabled) { + return root.checkedImageColor + } else { + return root.checkedDisabledImageColor + } + } else { + return root.defaultImageColor + } + } + } + } + } + } + } + + contentItem: Item { + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + anchors.fill: parent + anchors.leftMargin: 8 + background.width + + ColumnLayout { + id: content + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + spacing: 4 + + ListItemTitleType { + Layout.fillWidth: true + + text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor + } + + CaptionTextType { + id: description + + Layout.fillWidth: true + + text: root.descriptionText + color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor + + visible: root.descriptionText !== "" + } + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } +} + + diff --git a/client/ui/qml/Controls/ContextMenu.qml b/client/ui/qml/Controls2/ContextMenuType.qml similarity index 100% rename from client/ui/qml/Controls/ContextMenu.qml rename to client/ui/qml/Controls2/ContextMenuType.qml diff --git a/client/ui/qml/Controls2/DividerType.qml b/client/ui/qml/Controls2/DividerType.qml new file mode 100644 index 00000000..bf01e7a1 --- /dev/null +++ b/client/ui/qml/Controls2/DividerType.qml @@ -0,0 +1,12 @@ +import QtQuick +import QtQuick.Layouts + +Rectangle { + Layout.fillWidth: true + + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + height: 1 + color: "#2C2D30" +} diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml new file mode 100644 index 00000000..830f59f9 --- /dev/null +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -0,0 +1,84 @@ +import QtQuick +import QtQuick.Controls + +import "../Config" + +Drawer { + id: drawer + property bool needCloseButton: true + + Connections { + target: PageController + + function onForceCloseDrawer() { + visible = false + } + } + + edge: Qt.BottomEdge + + clip: true + modal: true + dragMargin: -10 + + enter: Transition { + SmoothedAnimation { + velocity: 4 + } + } + + exit: Transition { + SmoothedAnimation { + velocity: 4 + } + } + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + color: "#1C1D21" + + border.color: "#2C2D30" + border.width: 1 + + Rectangle { + visible: GC.isMobile() + + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 10 + + width: 20 + height: 2 + color: "#2C2D30" + } + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + onAboutToShow: { + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + } + + onOpened: { + if (needCloseButton) { + PageController.drawerOpen() + } + } + + onClosed: { + if (needCloseButton) { + PageController.drawerClose() + } + + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + } +} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml new file mode 100644 index 00000000..b91f0b7a --- /dev/null +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -0,0 +1,217 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +Item { + id: root + + property string text + property string textColor: "#d7d8db" + property string textDisabledColor: "#878B91" + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight + + property string descriptionText + property string descriptionTextColor: "#878B91" + property string descriptionTextDisabledColor: "#494B50" + + property string headerText + property string headerBackButtonImage + + property var rootButtonClickedFunction + property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" + property string rootButtonImageColor: "#D7D8DB" + property string rootButtonBackgroundColor: "#1C1D21" + property string rootButtonBackgroundHoveredColor: "#1C1D21" + property string rootButtonBackgroundPressedColor: "#1C1D21" + + property string rootButtonHoveredBorderColor: "#494B50" + property string rootButtonDefaultBorderColor: "#2C2D30" + property string rootButtonPressedBorderColor: "#D7D8DB" + + property int rootButtonTextLeftMargins: 16 + property int rootButtonTextTopMargin: 16 + property int rootButtonTextBottomMargin: 16 + + property real drawerHeight: 0.9 + property Component listView + + property alias menuVisible: menu.visible + + implicitWidth: rootButtonContent.implicitWidth + implicitHeight: rootButtonContent.implicitHeight + + onMenuVisibleChanged: { + if (menuVisible) { + rootButtonBackground.border.color = rootButtonPressedBorderColor + } else { + rootButtonBackground.border.color = rootButtonDefaultBorderColor + } + } + + onEnabledChanged: { + if (enabled) { + rootButtonBackground.color = rootButtonBackgroundColor + rootButtonBackground.border.color = rootButtonDefaultBorderColor + } else { + rootButtonBackground.color = "transparent" + rootButtonBackground.border.color = rootButtonHoveredBorderColor + } + } + + Rectangle { + id: rootButtonBackground + anchors.fill: rootButtonContent + + radius: 16 + color: root.enabled ? rootButtonBackgroundColor : "transparent" + border.color: root.enabled ? rootButtonDefaultBorderColor : rootButtonHoveredBorderColor + border.width: 1 + + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + RowLayout { + id: rootButtonContent + anchors.fill: parent + + spacing: 0 + + ColumnLayout { + Layout.leftMargin: rootButtonTextLeftMargins + Layout.topMargin: rootButtonTextTopMargin + Layout.bottomMargin: rootButtonTextBottomMargin + + LabelTextType { + Layout.fillWidth: true + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + visible: root.descriptionText !== "" + + color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor + text: root.descriptionText + } + + ButtonTextType { + Layout.fillWidth: true + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + color: root.enabled ? root.textColor : root.textDisabledColor + text: root.text + maximumLineCount: root.textMaximumLineCount + elide: root.textElide + } + } + + ImageButtonType { + Layout.rightMargin: 16 + + implicitWidth: 40 + implicitHeight: 40 + + hoverEnabled: false + image: rootButtonImage + imageColor: rootButtonImageColor + } + } + + MouseArea { + anchors.fill: rootButtonContent + cursorShape: Qt.PointingHandCursor + hoverEnabled: root.enabled ? true : false + + onEntered: { + if (menu.visible === false) { + rootButtonBackground.border.color = rootButtonHoveredBorderColor + rootButtonBackground.color = rootButtonBackgroundHoveredColor + } + } + + onExited: { + if (menu.visible === false) { + rootButtonBackground.border.color = rootButtonDefaultBorderColor + rootButtonBackground.color = rootButtonBackgroundColor + } + } + + onPressed: { + if (menu.visible === false) { + rootButtonBackground.color = pressed ? rootButtonBackgroundPressedColor : entered ? rootButtonHoveredBorderColor : rootButtonDefaultBorderColor + } + } + + onClicked: { + if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") { + rootButtonClickedFunction() + } else { + menu.visible = true + } + } + } + + DrawerType { + id: menu + + width: parent.width + height: parent.height * drawerHeight + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + BackButtonType { + backButtonImage: root.headerBackButtonImage + backButtonFunction: function() { + root.menuVisible = false + } + } + } + + FlickableType { + anchors.top: header.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight + + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + Header2Type { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + headerText: root.headerText + + width: parent.width + } + + Loader { + id: listViewLoader + sourceComponent: root.listView + } + } + } + } +} diff --git a/client/ui/qml/Controls/FlickableType.qml b/client/ui/qml/Controls2/FlickableType.qml similarity index 67% rename from client/ui/qml/Controls/FlickableType.qml rename to client/ui/qml/Controls2/FlickableType.qml index 79bfabfd..073be058 100644 --- a/client/ui/qml/Controls/FlickableType.qml +++ b/client/ui/qml/Controls2/FlickableType.qml @@ -1,26 +1,23 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import "../Config" - -Flickable { - id: fl - - clip: true - width: parent.width - - anchors.topMargin: GC.defaultMargin - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - anchors.left: root.left - anchors.leftMargin: GC.defaultMargin - anchors.right: root.right - anchors.rightMargin: 1 - - Keys.onUpPressed: scrollBar.decrease() - Keys.onDownPressed: scrollBar.increase() - - ScrollBar.vertical: ScrollBar { - id: scrollBar - policy: fl.height >= fl.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn - } -} +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import "../Config" + +Flickable { + id: fl + + clip: true + width: parent.width + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 1 + + Keys.onUpPressed: scrollBar.decrease() + Keys.onDownPressed: scrollBar.increase() + + ScrollBar.vertical: ScrollBar { + id: scrollBar + policy: fl.height >= fl.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn + } +} diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml new file mode 100644 index 00000000..4d812f6c --- /dev/null +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -0,0 +1,63 @@ +import QtQuick +import QtQuick.Layouts + +import "TextTypes" + +Item { + id: root + + property string actionButtonImage + property var actionButtonFunction + + property string headerText + property string descriptionText + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + anchors.fill: parent + + RowLayout { + Header2TextType { + id: header + + Layout.fillWidth: true + + text: root.headerText + } + + ImageButtonType { + id: headerActionButton + + implicitWidth: 40 + implicitHeight: 40 + + image: root.actionButtonImage + imageColor: "#D7D8DB" + + visible: image ? true : false + + onClicked: { + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() + } + } + } + } + + ParagraphTextType { + id: description + + Layout.topMargin: 16 + Layout.fillWidth: true + + text: root.descriptionText + + color: "#878B91" + + visible: root.descriptionText !== "" + } + } +} diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml new file mode 100644 index 00000000..d0ed3418 --- /dev/null +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -0,0 +1,70 @@ +import QtQuick +import QtQuick.Layouts + +import "TextTypes" + +Item { + id: root + + property string actionButtonImage + property var actionButtonFunction + + property string headerText + property int headerTextMaximumLineCount: 2 + property int headerTextElide: Qt.ElideRight + + property string descriptionText + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + anchors.fill: parent + + RowLayout { + Header1TextType { + id: header + + Layout.fillWidth: true + + text: root.headerText + maximumLineCount: root.headerTextMaximumLineCount + elide: root.headerTextElide + } + + ImageButtonType { + id: headerActionButton + + implicitWidth: 40 + implicitHeight: 40 + + Layout.alignment: Qt.AlignRight + + image: root.actionButtonImage + imageColor: "#D7D8DB" + + visible: image ? true : false + + onClicked: { + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() + } + } + } + } + + ParagraphTextType { + id: description + + Layout.topMargin: 16 + Layout.fillWidth: true + + text: root.descriptionText + + color: "#878B91" + + visible: root.descriptionText !== "" + } + } +} diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml new file mode 100644 index 00000000..81cc8ec0 --- /dev/null +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -0,0 +1,100 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +RadioButton { + id: root + + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) + property string defaultColor: Qt.rgba(1, 1, 1, 0) + property string checkedColor: Qt.rgba(1, 1, 1, 0) + property string disabledColor: "transparent" + + property string textColor: "#D7D8DB" + property string textDisabledColor: "#878B91" + + property string pressedBorderColor: "#494B50" + property string checkedBorderColor: "#FBB26A" + property string defaultBodredColor: "transparent" + property string checkedDisabledBorderColor: "#84603D" + property int borderWidth: 0 + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + indicator: Rectangle { + anchors.fill: parent + radius: 16 + + color: { + if (root.enabled) { + if (root.hovered) { + return root.hoveredColor + } else if (root.checked) { + return root.checkedColor + } + return root.defaultColor + } else { + return root.disabledColor + } + } + + border.color: { + if (root.enabled) { + if (root.pressed) { + return root.pressedBorderColor + } else if (root.checked) { + return root.checkedBorderColor + } + return root.defaultBodredColor + } else { + if (root.checked) { + return root.checkedDisabledBorderColor + } + return root.defaultBodredColor + } + } + + border.width: { + if(root.checked) { + return 1 + } + return root.pressed ? 1 : 0 + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + } + + ColumnLayout { + id: content + anchors.fill: parent + spacing: 0 + + ButtonTextType { + text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor + + Layout.fillWidth: true + Layout.rightMargin: 24 + Layout.leftMargin: 24 + Layout.topMargin: 12 + Layout.bottomMargin: 12 + + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } +} diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml new file mode 100644 index 00000000..1ab57511 --- /dev/null +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -0,0 +1,54 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Button { + id: root + + property string image + + property string hoveredColor: Qt.rgba(1, 1, 1, 0.08) + property string defaultColor: "transparent" + property string pressedColor: Qt.rgba(1, 1, 1, 0.12) + property string disableColor: "#2C2D30" + + property string imageColor: "#878B91" + property string disableImageColor: "#2C2D30" + + property alias backgroundColor: background.color + property alias backgroundRadius: background.radius + + hoverEnabled: true + + icon.source: image + icon.color: root.enabled ? imageColor : disableImageColor + + Behavior on icon.color { + PropertyAnimation { duration: 200 } + } + + background: Rectangle { + id: background + + anchors.fill: parent + color: { + if (root.enabled) { + if (root.pressed) { + return pressedColor + } + return hovered ? hoveredColor : defaultColor + } + return defaultColor + } + radius: 12 + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + MouseArea { + anchors.fill: parent + enabled: false + cursorShape: Qt.PointingHandCursor + } +} diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml new file mode 100644 index 00000000..8b85d591 --- /dev/null +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -0,0 +1,210 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +Item { + id: root + + property string text + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight + + property string descriptionText + + property var clickedFunction + + property string rightImageSource + property string leftImageSource + property bool isLeftImageHoverEnabled: true //todo separete this qml file to 3 + + property string textColor: "#d7d8db" + property string textDisabledColor: "#878B91" + property string descriptionColor: "#878B91" + property string descriptionDisabledColor: "#494B50" + property real textOpacity: 1.0 + + property string rightImageColor: "#d7d8db" + + property bool descriptionOnTop: false + + implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin + implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin + + RowLayout { + id: content + anchors.fill: parent + anchors.leftMargin: 16 + anchors.rightMargin: 16 + anchors.topMargin: 16 + anchors.bottomMargin: 16 + + Rectangle { + id: leftImageBackground + + visible: leftImageSource ? true : false + + Layout.preferredHeight: rightImageSource || !isLeftImageHoverEnabled ? leftImage.implicitHeight : 56 + Layout.preferredWidth: rightImageSource || !isLeftImageHoverEnabled ? leftImage.implicitWidth : 56 + Layout.rightMargin: rightImageSource || !isLeftImageHoverEnabled ? 16 : 0 + + radius: 12 + color: "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + Image { + id: leftImage + + anchors.centerIn: parent + source: leftImageSource + } + } + + ColumnLayout { + property real textLineHeight: 21.6 + property real descriptionTextLineHeight: 16 + + property int textPixelSize: 18 + property int descriptionTextSize: 13 + + ListItemTitleType { + text: root.text + color: { + if (root.enabled) { + return root.descriptionOnTop ? root.descriptionColor : root.textColor + } else { + return root.descriptionOnTop ? root.descriptionDisabledColor : root.textDisabledColor + } + } + + maximumLineCount: root.textMaximumLineCount + elide: root.textElide + + opacity: root.textOpacity + + Layout.fillWidth: true + + lineHeight: root.descriptionOnTop ? parent.descriptionTextLineHeight : parent.textLineHeight + font.pixelSize: root.descriptionOnTop ? parent.descriptionTextSize : parent.textPixelSize + font.letterSpacing: root.descriptionOnTop ? 0.02 : 0 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } + } + + + CaptionTextType { + id: description + + text: root.descriptionText + color: { + if (root.enabled) { + return root.descriptionOnTop ? root.textColor : root.descriptionColor + } else { + return root.descriptionOnTop ? root.textDisabledColor : root.descriptionDisabledColor + } + } + + opacity: root.textOpacity + + visible: root.descriptionText !== "" + + Layout.fillWidth: true + + lineHeight: root.descriptionOnTop ? parent.textLineHeight : parent.descriptionTextLineHeight + font.pixelSize: root.descriptionOnTop ? parent.textPixelSize : parent.descriptionTextSize + font.letterSpacing: root.descriptionOnTop ? 0 : 0.02 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } + } + } + + ImageButtonType { + id: rightImage + + implicitWidth: 40 + implicitHeight: 40 + + hoverEnabled: false + image: rightImageSource + imageColor: rightImageColor + visible: rightImageSource ? true : false + + Layout.alignment: Qt.AlignRight + + Rectangle { + id: rightImageBackground + anchors.fill: parent + radius: 12 + color: "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + } + + Rectangle { + id: background + anchors.fill: root + color: "transparent" + + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: root.enabled + + onEntered: { + if (rightImageSource) { + rightImageBackground.color = rightImage.hoveredColor + } else if (leftImageSource) { + leftImageBackground.color = rightImage.hoveredColor + } + root.textOpacity = 0.8 + } + + onExited: { + if (rightImageSource) { + rightImageBackground.color = rightImage.defaultColor + } else if (leftImageSource) { + leftImageBackground.color = rightImage.defaultColor + } + root.textOpacity = 1 + } + + onPressedChanged: { + if (rightImageSource) { + rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor + } else if (leftImageSource) { + leftImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor + } + root.textOpacity = 0.7 + } + + onClicked: { + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() + } + } + } +} diff --git a/client/ui/qml/Controls2/ListViewWithLabelsType.qml b/client/ui/qml/Controls2/ListViewWithLabelsType.qml new file mode 100644 index 00000000..5b614c43 --- /dev/null +++ b/client/ui/qml/Controls2/ListViewWithLabelsType.qml @@ -0,0 +1,70 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +ListView { + id: menuContent + + property var rootWidth + + property var selectedText + + property string imageSource: "qrc:/images/controls/check.svg" + + property var clickedFunction + + property bool dividerVisible: false + + currentIndex: 0 + + width: rootWidth + height: menuContent.contentItem.height + + clip: true + interactive: false + + ButtonGroup { + id: buttonGroup + } + + delegate: Item { + implicitWidth: rootWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.fill: parent + + LabelWithButtonType { + Layout.fillWidth: true + + text: name + rightImageSource: imageSource + + clickedFunction: function() { + menuContent.currentIndex = index + menuContent.selectedText = name + if (menuContent.clickedFunction && typeof menuContent.clickedFunction === "function") { + menuContent.clickedFunction() + } + } + } + + DividerType { + Layout.fillWidth: true + Layout.bottomMargin: 4 + + visible: dividerVisible + } + } + + Component.onCompleted: { + if (menuContent.currentIndex === index) { + menuContent.selectedText = name + } + } + } +} diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml new file mode 100644 index 00000000..4138c087 --- /dev/null +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -0,0 +1,121 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +ListView { + id: root + + property var rootWidth + + property var selectedText + + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight + + property string imageSource: "qrc:/images/controls/check.svg" + + property var clickedFunction + + currentIndex: 0 + + width: rootWidth + height: root.contentItem.height + + clip: true + interactive: false + + ButtonGroup { + id: buttonGroup + } + + function triggerCurrentItem() { + var item = root.itemAtIndex(currentIndex) + var radioButton = item.children[0].children[0] + radioButton.clicked() + } + + delegate: Item { + implicitWidth: rootWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.fill: parent + + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + RowLayout { + id: radioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + + text: name + maximumLineCount: root.textMaximumLineCount + elide: root.textElide + + } + + Image { + source: imageSource + visible: radioButton.checked + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + ButtonGroup.group: buttonGroup + checked: root.currentIndex === index + + onClicked: { + root.currentIndex = index + root.selectedText = name + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() + } + } + } + } + + Component.onCompleted: { + if (root.currentIndex === index) { + root.selectedText = name + } + } + } +} diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml new file mode 100644 index 00000000..2c176b40 --- /dev/null +++ b/client/ui/qml/Controls2/PageType.qml @@ -0,0 +1,22 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property StackView stackView: StackView.view + +// MouseArea { +// id: globalMouseArea +// z: 99 +// anchors.fill: parent + +// enabled: true + +// onPressed: function(mouse) { +// forceActiveFocus() +// mouse.accepted = false +// } +// } +} diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml new file mode 100644 index 00000000..e4d2a449 --- /dev/null +++ b/client/ui/qml/Controls2/PopupType.qml @@ -0,0 +1,75 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +Popup { + id: root + + property string text + property bool closeButtonVisible: true + + leftMargin: 25 + rightMargin: 25 + bottomMargin: 70 + + width: parent.width - leftMargin - rightMargin + + anchors.centerIn: parent + modal: root.closeButtonVisible + closePolicy: Popup.CloseOnEscape + + Overlay.modal: Rectangle { + visible: root.closeButtonVisible + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + background: Rectangle { + anchors.fill: parent + + color: "white" + radius: 4 + } + + contentItem: Item { + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + anchors.fill: parent + + RowLayout { + id: content + + anchors.fill: parent + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + CaptionTextType { + horizontalAlignment: Text.AlignLeft + Layout.fillWidth: true + + text: root.text + } + + BasicButtonType { + visible: closeButtonVisible + + implicitHeight: 32 + + defaultColor: "white" + hoveredColor: "#C1C2C5" + pressedColor: "#AEB0B7" + disabledColor: "#494B50" + + textColor: "#0E0E11" + borderWidth: 0 + + text: qsTr("Close") + onClicked: { + root.close() + } + } + } + } +} diff --git a/client/ui/qml/Controls2/ProgressBarType.qml b/client/ui/qml/Controls2/ProgressBarType.qml new file mode 100644 index 00000000..e642c3eb --- /dev/null +++ b/client/ui/qml/Controls2/ProgressBarType.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ProgressBar { + id: root + + implicitHeight: 4 + + background: Rectangle { + color: "#633303" + } + + contentItem: Item { + Rectangle { + width: root.visualPosition * parent.width + height: parent.height + color: "#FBB26A" + } + } +} diff --git a/client/ui/qml/Controls2/StackViewType.qml b/client/ui/qml/Controls2/StackViewType.qml new file mode 100644 index 00000000..e2646e45 --- /dev/null +++ b/client/ui/qml/Controls2/StackViewType.qml @@ -0,0 +1,61 @@ +import QtQuick +import QtQuick.Controls + +StackView { + id: root + + pushEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + + pushExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + + popEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + + popExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + + replaceEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + + replaceExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } +} + diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml new file mode 100644 index 00000000..1dbd0e84 --- /dev/null +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -0,0 +1,116 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +Switch { + id: root + + property alias descriptionText: description.text + property string descriptionTextColor: "#878B91" + property string descriptionTextDisabledColor: "#494B50" + + property string textColor: "#D7D8DB" + property string textDisabledColor: "#878B91" + + property string checkedIndicatorColor: "#633303" + property string defaultIndicatorColor: "transparent" + property string checkedDisabledIndicatorColor: "#402102" + + property string checkedIndicatorBorderColor: "#633303" + property string defaultIndicatorBorderColor: "#494B50" + property string checkedDisabledIndicatorBorderColor: "#402102" + + property string checkedInnerCircleColor: "#FBB26A" + property string defaultInnerCircleColor: "#D7D8DB" + property string checkedDisabledInnerCircleColor: "#84603D" + property string defaultDisabledInnerCircleColor: "#494B50" + + property string hoveredIndicatorBackgroundColor: Qt.rgba(1, 1, 1, 0.08) + property string defaultIndicatorBackgroundColor: "transparent" + + hoverEnabled: enabled ? true : false + + indicator: Rectangle { + id: switcher + + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + implicitWidth: 52 + implicitHeight: 32 + + radius: 16 + color: root.checked ? (root.enabled ? root.checkedIndicatorColor : root.checkedDisabledIndicatorColor) + : root.defaultIndicatorColor + border.color: root.checked ? (root.enabled ? root.checkedIndicatorBorderColor : root.checkedDisabledIndicatorBorderColor) + : root.defaultIndicatorBorderColor + + Behavior on color { + PropertyAnimation { duration: 200 } + } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + id: innerCircle + + anchors.verticalCenter: parent.verticalCenter + x: root.checked ? parent.width - width - 4 : 8 + width: root.checked ? 24 : 16 + height: root.checked ? 24 : 16 + radius: 23 + color: root.checked ? (root.enabled ? root.checkedInnerCircleColor : root.checkedDisabledInnerCircleColor) + : (root.enabled ? root.defaultInnerCircleColor : root.defaultDisabledInnerCircleColor) + + Behavior on x { + PropertyAnimation { duration: 200 } + } + } + + Rectangle { + anchors.centerIn: innerCircle + width: 40 + height: 40 + radius: 23 + color: root.hovered ? root.hoveredIndicatorBackgroundColor : root.defaultIndicatorBackgroundColor + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + + contentItem: ColumnLayout { + id: content + + anchors.verticalCenter: parent.verticalCenter + + ListItemTitleType { + Layout.fillWidth: true + rightPadding: indicator.width + + text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor + } + + CaptionTextType { + id: description + + Layout.fillWidth: true + rightPadding: indicator.width + + color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor + + visible: text !== "" + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } +} diff --git a/client/ui/qml/Controls2/TabButtonType.qml b/client/ui/qml/Controls2/TabButtonType.qml new file mode 100644 index 00000000..f8011f0d --- /dev/null +++ b/client/ui/qml/Controls2/TabButtonType.qml @@ -0,0 +1,62 @@ +import QtQuick +import QtQuick.Controls + +TabButton { + id: root + + property string hoveredColor: "#633303" + property string defaultColor: "#2C2D30" + property string selectedColor: "#FBB26A" + + property string textColor: "#D7D8DB" + + property bool isSelected: false + + implicitHeight: 48 + + hoverEnabled: true + + background: Rectangle { + id: background + + anchors.fill: parent + color: "transparent" + + Rectangle { + width: parent.width + height: 1 + y: parent.height - height + color: { + if(root.isSelected) { + return selectedColor + } + return hovered ? hoveredColor : defaultColor + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + + MouseArea { + anchors.fill: background + cursorShape: Qt.PointingHandCursor + enabled: false + } + + contentItem: Text { + anchors.fill: background + height: 24 + + font.family: "PT Root UI VF" + font.styleName: "normal" + font.weight: 500 + font.pixelSize: 16 + color: textColor + text: root.text + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } +} diff --git a/client/ui/qml/Controls2/TabImageButtonType.qml b/client/ui/qml/Controls2/TabImageButtonType.qml new file mode 100644 index 00000000..4d745a0b --- /dev/null +++ b/client/ui/qml/Controls2/TabImageButtonType.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Controls + +TabButton { + id: root + + property string hoveredColor: "#633303" + property string defaultColor: "#D7D8DB" + property string selectedColor: "#FBB26A" + + property string image + + property bool isSelected: false + + hoverEnabled: true + + icon.source: image + icon.color: isSelected ? selectedColor : defaultColor + + background: Rectangle { + id: background + anchors.fill: parent + color: "transparent" + } + + MouseArea { + anchors.fill: background + cursorShape: Qt.PointingHandCursor + enabled: false + } +} diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml new file mode 100644 index 00000000..f4f75417 --- /dev/null +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -0,0 +1,107 @@ +import QtQuick +import QtQuick.Controls + +Rectangle { + id: root + + property string placeholderText + property string text + property alias textArea: textArea + property alias textAreaText: textArea.text + + property string borderHoveredColor: "#494B50" + property string borderNormalColor: "#2C2D30" + property string borderFocusedColor: "#d7d8db" + + height: 148 + color: "#1C1D21" + border.width: 1 + border.color: getBorderColor(borderNormalColor) + radius: 16 + + MouseArea { + id: parentMouse + anchors.fill: parent + cursorShape: Qt.IBeamCursor + onClicked: textArea.forceActiveFocus() + hoverEnabled: true + + FlickableType { + id: fl + interactive: false + + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: textArea.implicitHeight + TextArea { + id: textArea + + width: parent.width + + topPadding: 16 + leftPadding: 16 + anchors.topMargin: 16 + anchors.bottomMargin: 16 + + color: "#D7D8DB" + selectionColor: "#633303" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + placeholderText: root.placeholderText + text: root.text + + onCursorVisibleChanged: { + if (textArea.cursorVisible) { + fl.interactive = true + } else { + fl.interactive = false + } + } + + wrapMode: Text.Wrap + + MouseArea { + id: textAreaMouse + anchors.fill: parent + acceptedButtons: Qt.RightButton + hoverEnabled: true + onClicked: { + fl.interactive = true + contextMenu.open() + } + } + + onFocusChanged: { + root.border.color = getBorderColor(borderNormalColor) + } + + ContextMenuType { + id: contextMenu + textObj: textArea + } + } + } + + onPressed: { + root.border.color = getBorderColor(borderFocusedColor) + } + + onExited: { + root.border.color = getBorderColor(borderNormalColor) + } + + onEntered: { + root.border.color = getBorderColor(borderHoveredColor) + } + } + + + function getBorderColor(noneFocusedColor) { + return textArea.focus ? root.borderFocusedColor : noneFocusedColor + } +} diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml new file mode 100644 index 00000000..ac0473cf --- /dev/null +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -0,0 +1,189 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +Item { + id: root + + property string headerText + property string headerTextDisabledColor: "#494B50" + property string headerTextColor: "#878b91" + + property alias errorText: errorField.text + property bool checkEmptyText: false + + property string buttonText + property string buttonImageSource + property var clickedFunc + + property alias textField: textField + property alias textFieldText: textField.text + property string textFieldTextColor: "#d7d8db" + property string textFieldTextDisabledColor: "#878B91" + + property string textFieldPlaceholderText + property bool textFieldEditable: true + + property string borderColor: "#2C2D30" + property string borderFocusedColor: "#d7d8db" + + property string backgroundColor: "#1c1d21" + property string backgroundDisabledColor: "transparent" + property string bgBorderHoveredColor: "#494B50" + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + anchors.fill: parent + + Rectangle { + id: backgroud + Layout.fillWidth: true + Layout.preferredHeight: input.implicitHeight + color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor + radius: 16 + border.color: getBackgroundBorderColor(root.borderColor) + border.width: 1 + + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + + RowLayout { + id: input + anchors.fill: backgroud + ColumnLayout { + Layout.margins: 16 + LabelTextType { + text: root.headerText + color: root.enabled ? root.headerTextColor : root.headerTextDisabledColor + + visible: text !== "" + + Layout.fillWidth: true + } + + TextField { + id: textField + + enabled: root.textFieldEditable + color: root.enabled ? root.textFieldTextColor : root.textFieldTextDisabledColor + + placeholderText: root.textFieldPlaceholderText + placeholderTextColor: "#494B50" + + selectionColor: "#633303" + selectedTextColor: "#D7D8DB" + + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + bottomPadding: 0 + + background: Rectangle { + anchors.fill: parent + color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor + } + + onTextChanged: { + root.errorText = "" + } + + onActiveFocusChanged: { + if (checkEmptyText && textFieldText === "") { + errorText = qsTr("The field can't be empty") + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: contextMenu.open() + enabled: true + } + + ContextMenuType { + id: contextMenu + textObj: textField + } + + onFocusChanged: { + backgroud.border.color = getBackgroundBorderColor(root.borderColor) + } + } + } + + BasicButtonType { + visible: (root.buttonText !== "") || (root.buttonImageSource !== "") + +// defaultColor: "transparent" +// hoveredColor: Qt.rgba(1, 1, 1, 0.08) +// pressedColor: Qt.rgba(1, 1, 1, 0.12) +// disabledColor: "#878B91" +// textColor: "#D7D8DB" +// borderWidth: 0 + + text: root.buttonText + imageSource: root.buttonImageSource + +// Layout.rightMargin: 24 + Layout.preferredHeight: content.implicitHeight + Layout.preferredWidth: content.implicitHeight + squareLeftSide: true + + onClicked: { + if (root.clickedFunc && typeof root.clickedFunc === "function") { + root.clickedFunc() + } + } + } + } + } + + SmallTextType { + id: errorField + + text: root.errorText + visible: root.errorText !== "" + color: "#EB5757" + } + } + + MouseArea { + anchors.fill: root + cursorShape: Qt.IBeamCursor + + hoverEnabled: true + + onPressed: function(mouse) { + textField.forceActiveFocus() + mouse.accepted = false + + backgroud.border.color = getBackgroundBorderColor(root.borderColor) + } + + onEntered: { + backgroud.border.color = getBackgroundBorderColor(bgBorderHoveredColor) + } + + + onExited: { + backgroud.border.color = getBackgroundBorderColor(root.borderColor) + } + } + + function getBackgroundBorderColor(noneFocusedColor) { + return textField.focus ? root.borderFocusedColor : noneFocusedColor + } +} diff --git a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml new file mode 100644 index 00000000..94b48081 --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml @@ -0,0 +1,13 @@ +import QtQuick + +Text { + lineHeight: 24 + lineHeightMode: Text.FixedHeight + + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 600 + font.family: "PT Root UI VF" + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml new file mode 100644 index 00000000..f7acb6dd --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml @@ -0,0 +1,14 @@ +import QtQuick + +Text { + lineHeight: 16 + lineHeightMode: Text.FixedHeight + + color: "#0E0E11" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + wrapMode: Text.Wrap +} diff --git a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml new file mode 100644 index 00000000..86b61d7a --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml @@ -0,0 +1,15 @@ +import QtQuick + +Text { + lineHeight: 38 + lineHeightMode: Text.FixedHeight + + color: "#D7D8DB" + font.pixelSize: 36 + font.weight: 700 + font.family: "PT Root UI VF" + font.letterSpacing: -1.08 + + wrapMode: Text.WordWrap +} + diff --git a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml new file mode 100644 index 00000000..9e2a8ae9 --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml @@ -0,0 +1,13 @@ +import QtQuick + +Text { + lineHeight: 30 + lineHeightMode: Text.FixedHeight + + color: "#D7D8DB" + font.pixelSize: 25 + font.weight: 700 + font.family: "PT Root UI VF" + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml new file mode 100644 index 00000000..d47d460d --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml @@ -0,0 +1,14 @@ +import QtQuick + +Text { + lineHeight: 16 + lineHeightMode: Text.FixedHeight + + color: "#878B91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml new file mode 100644 index 00000000..917e65de --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml @@ -0,0 +1,13 @@ +import QtQuick + +Text { + lineHeight: 21.6 + lineHeightMode: Text.FixedHeight + + color: "#D7D8DB" + font.pixelSize: 18 + font.weight: 400 + font.family: "PT Root UI VF" + + wrapMode: Text.Wrap +} diff --git a/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml new file mode 100644 index 00000000..a53ca67e --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml @@ -0,0 +1,13 @@ +import QtQuick + +Text { + lineHeight: 24 + lineHeightMode: Text.FixedHeight + + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Controls2/TextTypes/SmallTextType.qml b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml new file mode 100644 index 00000000..d1b24e6a --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml @@ -0,0 +1,13 @@ +import QtQuick + +Text { + lineHeight: 20 + lineHeightMode: Text.FixedHeight + + color: "#D7D8DB" + font.pixelSize: 14 + font.weight: 400 + font.family: "PT Root UI VF" + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml new file mode 100644 index 00000000..e29b0be4 --- /dev/null +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -0,0 +1,37 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes + +Popup { + id: root + + property alias buttonWidth: button.implicitWidth + + modal: false + closePolicy: Popup.NoAutoClose + padding: 4 + + visible: false + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + background: Rectangle { + color: "transparent" + } + + ImageButtonType { + id: button + + image: "qrc:/images/svg/close_black_24dp.svg" + imageColor: "#D7D8DB" + + implicitWidth: 40 + implicitHeight: 40 + + onClicked: { + PageController.goToDrawerRootPage() + } + } +} diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml new file mode 100644 index 00000000..dd9a5590 --- /dev/null +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -0,0 +1,146 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +import "TextTypes" + +RadioButton { + id: root + + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight + property string descriptionText + + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) + property string defaultColor: Qt.rgba(1, 1, 1, 0) + property string disabledColor: Qt.rgba(1, 1, 1, 0) + property string selectedColor: Qt.rgba(1, 1, 1, 0) + + property string textColor: "#D7D8DB" + property string selectedTextColor: "#FBB26A" + + property string imageSource + property bool showImage + + hoverEnabled: true + + indicator: Rectangle { + id: background + + anchors.verticalCenter: parent.verticalCenter + + implicitWidth: 56 + implicitHeight: 56 + radius: 16 + + color: { + if (root.enabled) { + if (root.hovered) { + return hoveredColor + } else if (root.checked) { + return selectedColor + } + return defaultColor + } else { + return disabledColor + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + Image { + source: { + if (showImage) { + return imageSource + } else if (root.pressed) { + return "qrc:/images/controls/radio-button-inner-circle-pressed.png" + } else if (root.checked) { + return "qrc:/images/controls/radio-button-inner-circle.png" + } + + return "" + } + + anchors.centerIn: parent + + width: 24 + height: 24 + } + + Image { + source: { + if (showImage) { + return "" + } else if (root.pressed || root.checked) { + return "qrc:/images/controls/radio-button-pressed.svg" + } else { + return "qrc:/images/controls/radio-button.svg" + } + } + + anchors.centerIn: parent + + width: 24 + height: 24 + } + } + + contentItem: Item { + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + anchors.fill: parent + anchors.leftMargin: 8 + background.width + + ColumnLayout { + id: content + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + spacing: 4 + + ListItemTitleType { + text: root.text + maximumLineCount: root.textMaximumLineCount + elide: root.textElide + + color: { + if (root.checked) { + return selectedTextColor + } + return textColor + } + + Layout.fillWidth: true + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + CaptionTextType { + id: description + + color: "#878B91" + text: root.descriptionText + + visible: root.descriptionText !== "" + + Layout.fillWidth: true + } + } + } + + MouseArea { + anchors.fill: root + cursorShape: Qt.PointingHandCursor + enabled: false + } +} + + diff --git a/client/ui/qml/Filters/ContainersModelFilters.qml b/client/ui/qml/Filters/ContainersModelFilters.qml new file mode 100644 index 00000000..8c51c7ee --- /dev/null +++ b/client/ui/qml/Filters/ContainersModelFilters.qml @@ -0,0 +1,47 @@ +pragma Singleton + +import QtQuick 2.15 + +import SortFilterProxyModel 0.2 + +import ProtocolEnum 1.0 + +Item { + ValueFilter { + id: vpnTypeFilter + roleName: "serviceType" + value: ProtocolEnum.Vpn + } + + ValueFilter { + id: serviceTypeFilter + roleName: "serviceType" + value: ProtocolEnum.Other + } + + ValueFilter { + id: supportedFilter + roleName: "isSupported" + value: true + } + + ValueFilter { + id: installedFilter + roleName: "isInstalled" + value: true + } + + function getWriteAccessProtocolsListFilters() { + return [vpnTypeFilter] + } + function getReadAccessProtocolsListFilters() { + return [vpnTypeFilter, installedFilter] + } + + function getWriteAccessServicesListFilters() { + return [serviceTypeFilter] + } + function getReadAccessServicesListFilters() { + return [serviceTypeFilter, installedFilter] + } +} diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml deleted file mode 100644 index 7fde5bab..00000000 --- a/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml +++ /dev/null @@ -1,15 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageBase { - id: root - property var protocol: ProtocolEnum.Any - page: PageEnum.ClientInfo - logic: ClientInfoLogic -} diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml deleted file mode 100644 index da5ba53c..00000000 --- a/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml +++ /dev/null @@ -1,115 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageClientInfoBase { - id: root - protocol: ProtocolEnum.OpenVpn - - BackButton { - id: back - enabled: !ClientInfoLogic.busyIndicatorIsRunning - } - - Caption { - id: caption - text: qsTr("Client Info") - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: ClientInfoLogic.busyIndicatorIsRunning - running: ClientInfoLogic.busyIndicatorIsRunning - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - visible: ClientInfoLogic.pageContentVisible - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ClientInfoLogic.labelCurrentVpnProtocolText - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - height: 21 - text: qsTr("Client name") - } - - TextFieldType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 31 - text: ClientInfoLogic.lineEditNameAliasText - onEditingFinished: { - if (text !== ClientInfoLogic.lineEditNameAliasText) { - ClientInfoLogic.lineEditNameAliasText = text - ClientInfoLogic.onLineEditNameAliasEditingFinished() - } - } - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.topMargin: 20 - height: 21 - text: qsTr("Certificate id") - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: ClientInfoLogic.labelOpenVpnCertId - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.topMargin: 20 - height: 21 - text: qsTr("Certificate") - } - - TextAreaType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ClientInfoLogic.textAreaOpenVpnCertData - } - - BlueButtonType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 41 - text: qsTr("Revoke Certificate") - onClicked: { - ClientInfoLogic.onRevokeOpenVpnCertificateClicked() - UiLogic.closePage() - } - } - } - } -} diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml deleted file mode 100644 index befaf7f8..00000000 --- a/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml +++ /dev/null @@ -1,100 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageClientInfoBase { - id: root - protocol: ProtocolEnum.WireGuard - - BackButton { - id: back - enabled: !ClientInfoLogic.busyIndicatorIsRunning - } - - Caption { - id: caption - text: qsTr("Client Info") - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: ClientInfoLogic.busyIndicatorIsRunning - running: ClientInfoLogic.busyIndicatorIsRunning - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ClientInfoLogic.labelCurrentVpnProtocolText - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - height: 21 - text: qsTr("Client name") - } - - TextFieldType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 31 - text: ClientInfoLogic.lineEditNameAliasText - onEditingFinished: { - if (text !== ClientInfoLogic.lineEditNameAliasText) { - ClientInfoLogic.lineEditNameAliasText = text - ClientInfoLogic.onLineEditNameAliasEditingFinished() - } - } - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.topMargin: 20 - height: 21 - text: qsTr("Public Key") - } - - TextAreaType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ClientInfoLogic.textAreaWireGuardKeyData - } - - BlueButtonType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 41 - text: qsTr("Revoke Key") - onClicked: { - ClientInfoLogic.onRevokeWireGuardKeyClicked() - UiLogic.closePage() - } - } - } - } -} diff --git a/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml b/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml deleted file mode 100644 index 8e04c605..00000000 --- a/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml +++ /dev/null @@ -1,75 +0,0 @@ -import QtQuick -import QtQuick.Controls -import "./" -import "../../Controls" -import "../../Config" - -Rectangle { - signal containerChecked(bool checked) - property bool initiallyChecked: false - property string containerDescription - default property alias itemSettings: container.data - - x: 5 - y: 5 - width: parent.width - 20 - anchors.horizontalCenter: parent.horizontalCenter - - height: frame_settings.visible ? 140 : 72 - border.width: 1 - border.color: "lightgray" - radius: 2 - Rectangle { - id: frame_settings - height: 77 - width: parent.width - border.width: 1 - border.color: "lightgray" - anchors.bottom: parent.bottom - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - visible: false - radius: 2 - Grid { - id: container - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 10 - spacing: 5 - } - } - Row { - anchors.top: parent.top - anchors.topMargin: 5 - leftPadding: 15 - rightPadding: 5 - height: 55 - width: parent.width - CheckBoxType { - text: containerDescription - height: parent.height - width: parent.width - 50 - checked: initiallyChecked - onCheckedChanged: containerChecked(checked) - } - ImageButtonType { - width: 35 - height: 35 - anchors.verticalCenter: parent.verticalCenter - icon.source: "qrc:/images/settings.png" - checkable: true - checked: initiallyChecked - onCheckedChanged: { - //NewServerProtocolsLogic.pushButtonSettingsCloakChecked = checked - if (checked) { - frame_settings.visible = true - } else { - frame_settings.visible = false - } - } - } - } -} diff --git a/client/ui/qml/Pages/InstallSettings/SelectContainer.qml b/client/ui/qml/Pages/InstallSettings/SelectContainer.qml deleted file mode 100644 index 59e8f464..00000000 --- a/client/ui/qml/Pages/InstallSettings/SelectContainer.qml +++ /dev/null @@ -1,200 +0,0 @@ -import QtQuick -import QtQuick.Controls -import SortFilterProxyModel 0.2 -import ProtocolEnum 1.0 -import "./" -import "../../Controls" -import "../../Config" - -Drawer { - id: root - signal containerSelected(int c_index) - property int selectedIndex: -1 - - y: 0 - x: 0 - edge: Qt.RightEdge - width: parent.width * 0.85 - height: parent.height - - modal: true - interactive: activeFocus - - SortFilterProxyModel { - id: proxyModel - sourceModel: UiLogic.containersModel - filters: [ - ValueFilter { - roleName: "is_installed_role" - value: false }, - ValueFilter { - roleName: "service_type_role" - value: ProtocolEnum.Vpn } - ] - - } - - SortFilterProxyModel { - id: proxyModel_other - sourceModel: UiLogic.containersModel - filters: [ - ValueFilter { - roleName: "is_installed_role" - value: false }, - ValueFilter { - roleName: "service_type_role" - value: ProtocolEnum.Other } - ] - - } - - FlickableType { - anchors.fill: parent - contentHeight: col.height - - Column { - id: col - anchors { - left: parent.left; - right: parent.right; - } - topPadding: 20 - spacing: 10 - - Caption { - id: cap1 - text: qsTr("VPN containers") - font.pixelSize: 20 - - } - - ListView { - id: tb - x: 10 - currentIndex: -1 - width: parent.width - 20 - height: contentItem.height - - spacing: 0 - clip: true - interactive: false - model: proxyModel - - delegate: Item { - implicitWidth: 170 * 2 - implicitHeight: 30 - Item { - width: parent.width - height: 30 - anchors.left: parent.left - id: c1 - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb.currentIndex - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb.currentIndex - - } - Text { - id: text_name - text: name_role - font.pixelSize: 16 - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - tb.currentIndex = index - tb_other.currentIndex = -1 - containerSelected(proxyModel.mapToSource(index)) - selectedIndex = proxyModel.mapToSource(index) - root.close() - } - } - } - } - - - Caption { - id: cap2 - font.pixelSize: 20 - text: qsTr("Other containers") - } - - ListView { - id: tb_other - x: 10 - currentIndex: -1 - width: parent.width - 20 - height: contentItem.height - - spacing: 0 - clip: true - interactive: false - model: proxyModel_other - - delegate: Item { - implicitWidth: 170 * 2 - implicitHeight: 30 - Item { - width: parent.width - height: 30 - anchors.left: parent.left - id: c1_other - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb_other.currentIndex - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb_other.currentIndex - - } - Text { - id: text_name_other - text: name_role - font.pixelSize: 16 - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - tb_other.currentIndex = index - tb.currentIndex = -1 - containerSelected(proxyModel_other.mapToSource(index)) - selectedIndex = proxyModel_other.mapToSource(index) - root.close() - } - } - } - } - - - } - - - } - -} diff --git a/client/ui/qml/Pages/PageAbout.qml b/client/ui/qml/Pages/PageAbout.qml deleted file mode 100644 index ab25c23e..00000000 --- a/client/ui/qml/Pages/PageAbout.qml +++ /dev/null @@ -1,90 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.About - - BackButton { - id: back_from_start - } - - Caption { - id: caption - font.pixelSize: 22 - text: qsTr("About Amnezia") - } - - RichLabelType { - id: label_about - anchors.top: caption.bottom - - text: qsTr("AmneziaVPN is opensource software, it's free forever. Our goal is to make the best VPN client in the world. -

-") - } - - Caption { - id: caption2 - anchors.topMargin: 20 - font.pixelSize: 22 - text: qsTr("Support") - anchors.top: label_about.bottom - } - - RichLabelType { - id: label_support - anchors.top: caption2.bottom - - text: qsTr("Have questions? You can get support by: -") - } - - Caption { - id: caption3 - anchors.topMargin: 20 - font.pixelSize: 22 - text: qsTr("Donate") - width: undefined - anchors.top: label_support.bottom - } - - LabelType { - anchors.bottom: caption3.bottom - anchors.left: caption3.right - anchors.leftMargin: 5 - font.pixelSize: 24 - text: "♥" - color: "red" - } - - RichLabelType { - id: label_donate - anchors.top: caption3.bottom - - text: qsTr("Please support Amnezia project by donation, we really need it now more than ever. - -") - } - - Logo { - id: logo - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageAdvancedServerSettings.qml b/client/ui/qml/Pages/PageAdvancedServerSettings.qml deleted file mode 100644 index 9f71b2ba..00000000 --- a/client/ui/qml/Pages/PageAdvancedServerSettings.qml +++ /dev/null @@ -1,118 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.AdvancedServerSettings - logic: AdvancedServerSettingsLogic - - enabled: AdvancedServerSettingsLogic.pageEnabled - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Advanced server settings") - anchors.horizontalCenter: parent.horizontalCenter - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: !AdvancedServerSettingsLogic.pageEnabled - running: !AdvancedServerSettingsLogic.pageEnabled - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: logo.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: AdvancedServerSettingsLogic.labelCurrentVpnProtocolText - } - - TextFieldType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: AdvancedServerSettingsLogic.labelServerText - readOnly: true - background: Item {} - } - - LabelType { - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: AdvancedServerSettingsLogic.labelWaitInfoText - visible: AdvancedServerSettingsLogic.labelWaitInfoVisible - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: "Scan the server for installed containers" - visible: AdvancedServerSettingsLogic.pushButtonClearVisible - onClicked: { - AdvancedServerSettingsLogic.onPushButtonScanServerClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: AdvancedServerSettingsLogic.pushButtonClearText - visible: AdvancedServerSettingsLogic.pushButtonClearVisible - onClicked: { - popupClearServer.open() - } - } - - BlueButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - text: qsTr("Clients Management") - onClicked: { - UiLogic.goToPage(PageEnum.ClientManagement) - } - } - - PopupWithQuestion { - id: popupClearServer - questionText: "Attention! All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. Continue?" - yesFunc: function() { - close() - AdvancedServerSettingsLogic.onPushButtonClearServerClicked() - } - noFunc: function() { - close() - } - } - } - } - - Logo { - id : logo - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageAppSetting.qml b/client/ui/qml/Pages/PageAppSetting.qml deleted file mode 100644 index 2bf0e306..00000000 --- a/client/ui/qml/Pages/PageAppSetting.qml +++ /dev/null @@ -1,152 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.AppSettings - logic: AppSettingsLogic - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Application Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - CheckBoxType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Auto connect") - checked: AppSettingsLogic.checkBoxAutoConnectChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxAutoConnectChecked = checked - AppSettingsLogic.onCheckBoxAutoconnectToggled(checked) - } - } - CheckBoxType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Auto start") - checked: AppSettingsLogic.checkBoxAutostartChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxAutostartChecked = checked - AppSettingsLogic.onCheckBoxAutostartToggled(checked) - } - } - CheckBoxType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Start minimized") - checked: AppSettingsLogic.checkBoxStartMinimizedChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxStartMinimizedChecked = checked - AppSettingsLogic.onCheckBoxStartMinimizedToggled(checked) - } - } - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: AppSettingsLogic.labelVersionText - } - BlueButtonType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Check for updates") - onClicked: { - Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") - } - } - - CheckBoxType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr("Keep logs") - checked: AppSettingsLogic.checkBoxSaveLogsChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxSaveLogsChecked = checked - AppSettingsLogic.onCheckBoxSaveLogsCheckedToggled(checked) - } - } - BlueButtonType { - Layout.fillWidth: true - text: qsTr("Open logs folder") - onClicked: { - AppSettingsLogic.onPushButtonOpenLogsClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Export logs") - onClicked: { - AppSettingsLogic.onPushButtonExportLogsClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - - property string start_text: qsTr("Clear logs") - property string end_text: qsTr("Cleared") - text: start_text - - Timer { - id: timer - interval: 1000; running: false; repeat: false - onTriggered: parent.text = parent.start_text - } - onClicked: { - text = end_text - timer.running = true - AppSettingsLogic.onPushButtonClearLogsClicked() - } - } - - LabelType { - Layout.fillWidth: true - Layout.topMargin: 30 - text: qsTr("Backup and restore configuration") - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - Layout.preferredHeight: 41 - text: qsTr("Backup app config") - onClicked: { - AppSettingsLogic.onPushButtonBackupAppConfigClicked() - } - } - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - Layout.preferredHeight: 41 - text: qsTr("Restore app config") - onClicked: { - AppSettingsLogic.onPushButtonRestoreAppConfigClicked() - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageBase.qml b/client/ui/qml/Pages/PageBase.qml deleted file mode 100644 index c398515b..00000000 --- a/client/ui/qml/Pages/PageBase.qml +++ /dev/null @@ -1,20 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -Item { - id: root - property var page: PageEnum.Start - property var logic: UiLogic - - property bool pageActive: false - - signal activated(bool reset) - signal deactivated() - - onActivated: pageActive = true - onDeactivated: pageActive = false -} diff --git a/client/ui/qml/Pages/PageClientManagement.qml b/client/ui/qml/Pages/PageClientManagement.qml deleted file mode 100644 index 39defe4c..00000000 --- a/client/ui/qml/Pages/PageClientManagement.qml +++ /dev/null @@ -1,119 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Shapes 1.4 -import SortFilterProxyModel 0.2 -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ClientManagement - logic: ClientManagementLogic - enabled: !ClientManagementLogic.busyIndicatorIsRunning - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Clients Management") - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: ClientManagementLogic.busyIndicatorIsRunning - running: ClientManagementLogic.busyIndicatorIsRunning - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - Column { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - LabelType { - font.pixelSize: 20 - leftPadding: -20 - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - text: ClientManagementLogic.labelCurrentVpnProtocolText - } - - SortFilterProxyModel { - id: proxyClientManagementModel - sourceModel: UiLogic.clientManagementModel - sorters: RoleSorter { roleName: "clientName" } - } - - ListView { - id: lv_clients - width: parent.width - implicitHeight: contentHeight + 20 - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 20 - topMargin: 10 - spacing: 10 - clip: true - model: proxyClientManagementModel - highlightRangeMode: ListView.ApplyRange - highlightMoveVelocity: -1 - delegate: Item { - implicitWidth: lv_clients.width - implicitHeight: 60 - - MouseArea { - id: ms - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - ClientManagementLogic.onClientItemClicked(proxyClientManagementModel.mapToSource(index)) - } - } - - Rectangle { - anchors.fill: parent - gradient: ms.containsMouse ? gradient_containsMouse : gradient_notContainsMouse - LinearGradient { - id: gradient_notContainsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#ECEEFF" } - ] - } - LinearGradient { - id: gradient_containsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#DCDEDF" } - ] - } - } - - LabelType { - x: 20 - y: 20 - font.pixelSize: 20 - text: clientName - } - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageGeneralSettings.qml b/client/ui/qml/Pages/PageGeneralSettings.qml deleted file mode 100644 index c85aa8a7..00000000 --- a/client/ui/qml/Pages/PageGeneralSettings.qml +++ /dev/null @@ -1,166 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.GeneralSettings - logic: GeneralSettingsLogic - - BackButton { - id: back - z: -1 - } - - FlickableType { - id: fl - anchors.top: back.bottom - anchors.topMargin: 0 - anchors.bottomMargin: 10 - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.topMargin: 10 - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - - spacing: 15 - - - // ---------- App settings ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/settings_black_24dp.svg" - text: qsTr("App settings") - onClicked: { - UiLogic.goToPage(PageEnum.AppSettings) - } - } - - // ---------- Network settings ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/settings_suggest_black_24dp.svg" - text: qsTr("Network settings") - onClicked: { - UiLogic.goToPage(PageEnum.NetworkSettings) - } - } - - // ---------- Server settings ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/vpn_key_black_24dp.svg" - text: qsTr("Server Settings") - enabled: GeneralSettingsLogic.existsAnyServer - onClicked: { - GeneralSettingsLogic.onPushButtonGeneralSettingsServerSettingsClicked() - } - } - - // ---------- Share connection ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/share_black_24dp.svg" - text: qsTr("Share connection") - enabled: GeneralSettingsLogic.pushButtonGeneralSettingsShareConnectionEnable && - GeneralSettingsLogic.existsAnyServer - onClicked: { - GeneralSettingsLogic.onPushButtonGeneralSettingsShareConnectionClicked() - } - } - - // ---------- Servers ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/format_list_bulleted_black_24dp.svg" - text: qsTr("Servers") - enabled: GeneralSettingsLogic.existsAnyServer - onClicked: { - UiLogic.goToPage(PageEnum.ServersList) - } - } - - // ---------- Add server ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/control_point_black_24dp.svg" - text: qsTr("Add server") - onClicked: { - if(GeneralSettingsLogic.existsAnyServer) - // If there is any server set we will go to Start Page - UiLogic.goToPage(PageEnum.Start) - else - // Else just come back to start page - UiLogic.closePage() - } - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: fl.height > (75+1) * 6 ? fl.height - (75+1) * 6 : 0 - } - - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - Layout.bottomMargin: 20 - icon.source: "qrc:/images/svg/logout_black_24dp.svg" - text: qsTr("Exit") - onClicked: { - Qt.quit() - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageNetworkSetting.qml b/client/ui/qml/Pages/PageNetworkSetting.qml deleted file mode 100644 index e14c04a6..00000000 --- a/client/ui/qml/Pages/PageNetworkSetting.qml +++ /dev/null @@ -1,113 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.NetworkSettings - logic: NetworkSettingsLogic - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("DNS Servers") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - CheckBoxType { - Layout.preferredWidth: parent.width - text: qsTr("Use AmneziaDNS service (recommended)") - checked: NetworkSettingsLogic.checkBoxUseAmneziaDnsChecked - onCheckedChanged: { - NetworkSettingsLogic.checkBoxUseAmneziaDnsChecked = checked - NetworkSettingsLogic.onCheckBoxUseAmneziaDnsToggled(checked) - UiLogic.onUpdateAllPages() - } - } - - LabelType { - Layout.preferredWidth: parent.width - text: qsTr("Use AmneziaDNS container on your server, when it installed.\n -Your AmneziaDNS server available only when it installed and VPN connected, it has internal IP address 172.29.172.254\n -If AmneziaDNS service is not installed on the same server, or this option is unchecked, the following DNS servers will be used:") - } - - LabelType { - Layout.topMargin: 15 - text: qsTr("Primary DNS server") - } - TextFieldType { - height: 40 - implicitWidth: parent.width - text: NetworkSettingsLogic.lineEditDns1Text - onEditingFinished: { - NetworkSettingsLogic.lineEditDns1Text = text - NetworkSettingsLogic.onLineEditDns1EditFinished(text) - UiLogic.onUpdateAllPages() - } - validator: RegularExpressionValidator { - regularExpression: NetworkSettingsLogic.ipAddressRegex - } - } - - UrlButtonType { - text: qsTr("Reset to default") - label.horizontalAlignment: Text.AlignLeft - label.verticalAlignment: Text.AlignTop - label.font.pixelSize: 14 - icon.source: "qrc:/images/svg/refresh_black_24dp.svg" - onClicked: { - NetworkSettingsLogic.onPushButtonResetDns1Clicked() - UiLogic.onUpdateAllPages() - } - } - - LabelType { - text: qsTr("Secondary DNS server") - } - TextFieldType { - height: 40 - implicitWidth: parent.width - text: NetworkSettingsLogic.lineEditDns2Text - onEditingFinished: { - NetworkSettingsLogic.lineEditDns2Text = text - NetworkSettingsLogic.onLineEditDns2EditFinished(text) - UiLogic.onUpdateAllPages() - } - validator: RegularExpressionValidator { - regularExpression: NetworkSettingsLogic.ipAddressRegex - } - } - - UrlButtonType { - text: qsTr("Reset to default") - label.horizontalAlignment: Text.AlignLeft - label.verticalAlignment: Text.AlignTop - label.font.pixelSize: 14 - icon.source: "qrc:/images/svg/refresh_black_24dp.svg" - onClicked: { - NetworkSettingsLogic.onPushButtonResetDns2Clicked() - UiLogic.onUpdateAllPages() - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageNewServer.qml b/client/ui/qml/Pages/PageNewServer.qml deleted file mode 100644 index 00cb51bc..00000000 --- a/client/ui/qml/Pages/PageNewServer.qml +++ /dev/null @@ -1,61 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.NewServer - - BackButton { - id: back_from_new_server - } - Caption { - id: caption - text: qsTr("Setup your server to use VPN") - } - LabelType { - id: labelWizard - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: qsTr("If you want easily configure your server just run Wizard") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: caption.bottom - anchors.topMargin: 30 - } - BlueButtonType { - id: pushButtonWizard - text: qsTr("Run Setup Wizard") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: labelWizard.bottom - anchors.topMargin: 10 - onClicked: { - UiLogic.goToPage(PageEnum.Wizard); - } - } - LabelType { - id: labelManual - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: qsTr("Press configure manually to choose VPN protocols you want to install") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: pushButtonWizard.bottom - anchors.topMargin: 40 - } - - BlueButtonType { - text: qsTr("Configure") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: labelManual.bottom - anchors.topMargin: 10 - onClicked: { - UiLogic.goToPage(PageEnum.NewServerProtocols); - } - } - - Logo { - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageNewServerProtocols.qml b/client/ui/qml/Pages/PageNewServerProtocols.qml deleted file mode 100644 index 0ce2090f..00000000 --- a/client/ui/qml/Pages/PageNewServerProtocols.qml +++ /dev/null @@ -1,154 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ContainerProps 1.0 -import ProtocolProps 1.0 -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" -import "InstallSettings" - -PageBase { - id: root - page: PageEnum.NewServerProtocols - logic: NewServerProtocolsLogic - - onActivated: { - container_selector.selectedIndex = -1 - UiLogic.containersModel.setSelectedServerIndex(-1) - } - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Select VPN protocols") - } - - BlueButtonType { - id: pushButtonConfigure - enabled: container_selector.selectedIndex > 0 - anchors.horizontalCenter: parent.horizontalCenter - y: parent.height - 60 - width: parent.width - 40 - height: 40 - text: qsTr("Setup server") - onClicked: { - let cont = container_selector.selectedIndex - let tp = ProtocolProps.transportProtoFromString(cb_port_proto.currentText) - let port = tf_port_num.text - NewServerProtocolsLogic.onPushButtonConfigureClicked(cont, port, tp) - } - } - - BlueButtonType { - id: pb_add_container - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: caption.bottom - anchors.topMargin: 10 - - width: parent.width - 40 - height: 40 - text: qsTr("Select protocol container") - font.pixelSize: 16 - onClicked: container_selector.visible ? container_selector.close() : container_selector.open() - - } - - SelectContainer { - id: container_selector - onAboutToHide: { - pageLoader.focus = true - } - - onContainerSelected: function(c_index){ - var containerProto = ContainerProps.defaultProtocol(c_index) - - tf_port_num.text = ProtocolProps.defaultPort(containerProto) - cb_port_proto.currentIndex = ProtocolProps.defaultTransportProto(containerProto) - - tf_port_num.enabled = ProtocolProps.defaultPortChangeable(containerProto) - cb_port_proto.enabled = ProtocolProps.defaultTransportProtoChangeable(containerProto) - } - } - - Column { - id: c1 - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: pb_add_container.bottom - anchors.topMargin: 10 - - Caption { - font.pixelSize: 22 - text: UiLogic.containerName(container_selector.selectedIndex) - } - - Text { - width: parent.width - anchors.topMargin: 10 - padding: 10 - - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - - text: UiLogic.containerDesc(container_selector.selectedIndex) - } - } - - - - Rectangle { - id: frame_settings - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: c1.bottom - anchors.topMargin: 10 - - border.width: 1 - border.color: "lightgray" - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - radius: 2 - Grid { - id: grid - visible: container_selector.selectedIndex > 0 - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 10 - spacing: 5 - - - LabelType { - width: 130 - text: qsTr("Port") - } - TextFieldType { - id: tf_port_num - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - } - LabelType { - width: 130 - text: qsTr("Network Protocol") - } - ComboBoxType { - id: cb_port_proto - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - model: [ - qsTr("udp"), - qsTr("tcp"), - ] - } - } - } -} diff --git a/client/ui/qml/Pages/PageQrDecoderIos.qml b/client/ui/qml/Pages/PageQrDecoderIos.qml deleted file mode 100644 index 21bdbfe7..00000000 --- a/client/ui/qml/Pages/PageQrDecoderIos.qml +++ /dev/null @@ -1,94 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import QRCodeReader 1.0 - -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.QrDecoderIos - logic: QrDecoderLogic - - onDeactivated: { - console.debug("Stopping QR decoder") - loader.sourceComponent = undefined - } - - BackButton { - } - Caption { - id: caption - text: qsTr("Import configuration") - } - - Connections { - target: Qt.platform.os == "ios" ? QrDecoderLogic : null - function onStartDecode() { - console.debug("Starting QR decoder") - loader.sourceComponent = component - } - function onStopDecode() { - console.debug("Stopping QR decoder") - loader.sourceComponent = undefined - } - } - - Loader { - id: loader - - anchors.top: caption.bottom - anchors.bottom: progressColumn.top - anchors.left: parent.left - anchors.right: parent.right - } - - Column{ - height: 40 - id: progressColumn - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - ProgressBar { - id: progress - anchors.left: parent.left - anchors.right: parent.right - value: QrDecoderLogic.totalChunksCount === 0? 0 : (QrDecoderLogic.receivedChunksCount/QrDecoderLogic.totalChunksCount) - } - Text { - id: chunksCount - text: "Progress: " + QrDecoderLogic.receivedChunksCount +"/"+QrDecoderLogic.totalChunksCount - } - } - - Component { - id: component - - Item { - anchors.fill: parent - - QRCodeReader { - id: qrCodeReader - - onCodeReaded: { - QrDecoderLogic.onDetectedQrCode(code) - } - - Component.onCompleted: { - qrCodeReader.setCameraSize(Qt.rect(loader.x, - loader.y, - loader.width, - loader.height)) - qrCodeReader.startReading() - } - Component.onDestruction: qrCodeReader.stopReading() - } - - } - - } - - -} diff --git a/client/ui/qml/Pages/PageServerConfiguringProgress.qml b/client/ui/qml/Pages/PageServerConfiguringProgress.qml deleted file mode 100644 index 04f1b6f5..00000000 --- a/client/ui/qml/Pages/PageServerConfiguringProgress.qml +++ /dev/null @@ -1,121 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ServerConfiguringProgress - logic: ServerConfiguringProgressLogic - - Caption { - id: caption - text: qsTr("Configuring...") - } - LabelType { - id: label - x: 0 - anchors.top: caption.bottom - anchors.topMargin: 10 - - width: parent.width - height: 31 - text: qsTr("Please wait.") - horizontalAlignment: Text.AlignHCenter - } - - LabelType { - id: labelServerBusy - x: 0 - anchors.top: label.bottom - anchors.topMargin: 30 - - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - - width: parent.width - 40 - height: 41 - - text: ServerConfiguringProgressLogic.labelServerBusyText - visible: ServerConfiguringProgressLogic.labelServerBusyVisible - } - - LabelType { - anchors.bottom: pr.top - anchors.bottomMargin: 20 - - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - - width: parent.width - 40 - height: 41 - text: ServerConfiguringProgressLogic.labelWaitInfoText - visible: ServerConfiguringProgressLogic.labelWaitInfoVisible - } - - - BlueButtonType { - id: pb_cancel - z: 1 - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: logo.bottom - anchors.bottomMargin: 40 - width: root.width - 60 - height: 40 - text: qsTr("Cancel") - visible: ServerConfiguringProgressLogic.pushButtonCancelVisible - enabled: ServerConfiguringProgressLogic.pushButtonCancelVisible - onClicked: { - ServerConfiguringProgressLogic.onPushButtonCancelClicked() - } - } - - ProgressBar { - id: pr - enabled: ServerConfiguringProgressLogic.pageEnabled - anchors.fill: pb_cancel - from: 0 - to: ServerConfiguringProgressLogic.progressBarMaximum - value: ServerConfiguringProgressLogic.progressBarValue - visible: ServerConfiguringProgressLogic.progressBarVisible - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: pr.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: ServerConfiguringProgressLogic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: ServerConfiguringProgressLogic.progressBarTextVisible - } - } - - Logo { - id : logo - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageServerContainers.qml b/client/ui/qml/Pages/PageServerContainers.qml deleted file mode 100644 index ddd607b0..00000000 --- a/client/ui/qml/Pages/PageServerContainers.qml +++ /dev/null @@ -1,434 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt.labs.platform -import QtQuick.Layouts -import SortFilterProxyModel 0.2 -import ContainerProps 1.0 -import ProtocolProps 1.0 -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./" -import "../Controls" -import "../Config" -import "InstallSettings" - -PageBase { - id: root - page: PageEnum.ServerContainers - logic: ServerContainersLogic - - enabled: ServerContainersLogic.pageEnabled - - function resetPage() { - container_selector.selectedIndex = -1 - } - - Connections { - target: logic - function onUpdatePage() { - root.resetPage() - } - } - - BackButton { - id: back - onClicked: tb_c.currentIndex = -1 - } - Caption { - id: caption - text: container_selector.selectedIndex > 0 ? qsTr("Install new service") : qsTr("Installed services") - } - - SelectContainer { - id: container_selector - - onAboutToHide: { - pageLoader.focus = true - } - - onContainerSelected: function(c_index) { - var containerProto = ContainerProps.defaultProtocol(c_index) - - - if (ProtocolProps.defaultPort(containerProto) < 0) { - tf_port_num.enabled = false - tf_port_num.text = qsTr("Default") - } - else tf_port_num.text = ProtocolProps.defaultPort(containerProto) - cb_port_proto.currentIndex = ProtocolProps.defaultTransportProto(containerProto) - - tf_port_num.enabled = ProtocolProps.defaultPortChangeable(containerProto) - cb_port_proto.enabled = ProtocolProps.defaultTransportProtoChangeable(containerProto) - } - } - - Column { - id: c1 - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: caption.bottom - anchors.topMargin: 10 - - Caption { - font.pixelSize: 22 - text: UiLogic.containerName(container_selector.selectedIndex) - } - - Text { - width: parent.width - anchors.topMargin: 10 - padding: 10 - - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - - text: UiLogic.containerDesc(container_selector.selectedIndex) - } - } - - Rectangle { - id: frame_settings - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: c1.bottom - anchors.topMargin: 10 - - border.width: 1 - border.color: "lightgray" - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - radius: 2 - Grid { - id: grid - visible: container_selector.selectedIndex > 0 - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 10 - spacing: 5 - - - LabelType { - width: 130 - text: qsTr("Port") - } - TextFieldType { - id: tf_port_num - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - } - LabelType { - width: 130 - text: qsTr("Network Protocol") - } - ComboBoxType { - id: cb_port_proto - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - model: [ - qsTr("udp"), - qsTr("tcp"), - ] - } - } - } - - BlueButtonType { - id: pb_cancel_add - visible: container_selector.selectedIndex > 0 - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: pb_continue_add.top - anchors.bottomMargin: 20 - - width: parent.width - 40 - height: 40 - text: qsTr("Cancel") - font.pixelSize: 16 - onClicked: container_selector.selectedIndex = -1 - - } - - BlueButtonType { - id: pb_continue_add - visible: container_selector.selectedIndex > 0 - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - - width: parent.width - 40 - height: 40 - text: qsTr("Continue") - font.pixelSize: 16 - onClicked: { - let cont = container_selector.selectedIndex - let tp = ProtocolProps.transportProtoFromString(cb_port_proto.currentText) - let port = tf_port_num.text - ServerContainersLogic.onPushButtonContinueClicked(cont, port, tp) - } - } - - FlickableType { - visible: container_selector.selectedIndex <= 0 - clip: true - width: parent.width - anchors.top: caption.bottom - anchors.bottom: pb_add_container.top - contentHeight: col.height - - Column { - visible: container_selector.selectedIndex <= 0 - id: col - anchors { - left: parent.left; - right: parent.right; - } - spacing: 10 - - Caption { - id: cap1 - text: qsTr("Installed Protocols and Services") - leftPadding: -20 - font.pixelSize: 20 - - } - - SortFilterProxyModel { - id: proxyContainersModel - sourceModel: UiLogic.containersModel - filters: ValueFilter { - roleName: "is_installed_role" - value: true - } - } - - SortFilterProxyModel { - id: proxyProtocolsModel - sourceModel: UiLogic.protocolsModel - filters: ValueFilter { - roleName: "is_installed_role" - value: true - } - } - - - ListView { - id: tb_c - width: parent.width - 10 - height: tb_c.contentItem.height - currentIndex: -1 - spacing: 5 - clip: true - interactive: false - model: proxyContainersModel - - delegate: Item { - implicitWidth: tb_c.width - 10 - implicitHeight: c_item.height - Item { - id: c_item - width: parent.width - height: row_container.height + tb_p.height - anchors.left: parent.left - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb_c.currentIndex - } - Rectangle { - anchors.top: row_container.top - anchors.bottom: row_container.bottom - anchors.left: parent.left - anchors.right: parent.right - - color: "#63B4FB" - visible: index === tb_c.currentIndex - } - - RowLayout { - id: row_container - anchors.left: parent.left - anchors.right: parent.right - - Text { - id: lb_container_name - text: name_role - font.pixelSize: 17 - color: "#100A44" - topPadding: 16 - bottomPadding: 12 - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - Layout.fillWidth: true - - MouseArea { - enabled: col.visible - anchors.top: lb_container_name.top - anchors.bottom: lb_container_name.bottom - anchors.left: parent.left - anchors.right: parent.right - propagateComposedEvents: true - onClicked: { - if (tb_c.currentIndex === index) tb_c.currentIndex = -1 - else tb_c.currentIndex = index - - UiLogic.protocolsModel.setSelectedDockerContainer(proxyContainersModel.mapToSource(index)) - } - } - } - - ImageButtonType { - id: button_remove - visible: (index === tb_c.currentIndex) && ServerContainersLogic.isManagedServer - Layout.alignment: Qt.AlignRight - checkable: true - icon.source: "qrc:/images/delete.png" - implicitWidth: 30 - implicitHeight: 30 - - checked: default_role - onClicked: popupRemove.open() - - VisibleBehavior on visible { } - } - - PopupWithQuestion { - id: popupRemove - questionText: qsTr("Remove container") + " " + name_role + "?" + "\n" + qsTr("This action will erase all data of this container on the server.") - yesFunc: function() { - tb_c.currentIndex = -1 - ServerContainersLogic.onPushButtonRemoveClicked(proxyContainersModel.mapToSource(index)) - close() - } - noFunc: function() { - close() - } - } - - ImageButtonType { - id: button_share - visible: (index === tb_c.currentIndex) && ServerContainersLogic.isManagedServer - Layout.alignment: Qt.AlignRight - icon.source: "qrc:/images/share.png" - implicitWidth: 30 - implicitHeight: 30 - onClicked: { - ServerContainersLogic.onPushButtonShareClicked(proxyContainersModel.mapToSource(index)) - } - - VisibleBehavior on visible { } - } - - ImageButtonType { - id: button_default - visible: service_type_role == ProtocolEnum.Vpn - - Layout.alignment: Qt.AlignRight - checkable: true - img.source: checked ? "qrc:/images/check.png" : "qrc:/images/uncheck.png" - implicitWidth: 30 - implicitHeight: 30 - - checked: default_role - onClicked: { - ServerContainersLogic.onPushButtonDefaultClicked(proxyContainersModel.mapToSource(index)) - } - } - } - - - ListView { - id: tb_p - currentIndex: -1 - x: 10 - anchors.top: row_container.bottom - - width: parent.width - 40 - height: index === tb_c.currentIndex ? tb_p.contentItem.height : 0 - implicitHeight: height - - spacing: 0 - clip: true - interactive: false - model: proxyProtocolsModel - - - Behavior on height { - NumberAnimation { - duration: 200 - } - } - - delegate: Item { - id: dp_item - - implicitWidth: tb_p.width - 10 - implicitHeight: p_item.height - Item { - id: p_item - width: parent.width - height: lb_protocol_name.height - anchors.left: parent.left - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index > 0 - } - - SettingButtonType { - id: lb_protocol_name - topPadding: 10 - bottomPadding: 10 - - anchors.left: parent.left - anchors.leftMargin: 10 - - width: parent.width - height: 45 - text: qsTr(name_role + " settings") - textItem.font.pixelSize: 16 - icon.source: "qrc:/images/settings.png" - onClicked: { - tb_p.currentIndex = index - ServerContainersLogic.onPushButtonProtoSettingsClicked( - proxyContainersModel.mapToSource(tb_c.currentIndex), - proxyProtocolsModel.mapToSource(tb_p.currentIndex)) - } - } - } - } - } - } - } - } - } - } - - - BlueButtonType { - id: pb_add_container - visible: container_selector.selectedIndex < 0 && ServerContainersLogic.isManagedServer - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.topMargin: 10 - anchors.bottomMargin: 20 - - width: parent.width - 40 - height: 40 - text: qsTr("Install new service") - font.pixelSize: 16 - onClicked: container_selector.visible ? container_selector.close() : container_selector.open() - } -} diff --git a/client/ui/qml/Pages/PageServerList.qml b/client/ui/qml/Pages/PageServerList.qml deleted file mode 100644 index 80ac9a1b..00000000 --- a/client/ui/qml/Pages/PageServerList.qml +++ /dev/null @@ -1,185 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Shapes 1.4 -import PageEnum 1.0 -import "../Controls" -import "./" -import "../Config" - -PageBase { - id: root - page: PageEnum.ServersList - logic: ServerListLogic - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Servers") - width: undefined - } - - SvgButtonType { - anchors.verticalCenter: caption.verticalCenter - anchors.leftMargin: 10 - anchors.left: caption.right - width: 27 - height: 27 - - icon.source: "qrc:/images/svg/control_point_black_24dp.svg" - onClicked: { - UiLogic.goToPage(PageEnum.Start); - } - } - - ListView { - id: listWidget_servers - x: GC.defaultMargin - anchors.top: caption.bottom - anchors.topMargin: 15 - width: parent.width - GC.defaultMargin - 1 - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - model: ServerListLogic.serverListModel - highlightRangeMode: ListView.ApplyRange - highlightMoveVelocity: -1 - currentIndex: ServerListLogic.currServerIdx - spacing: 5 - clip: true - delegate: Item { - height: 60 - width: listWidget_servers.width - 15 - MouseArea { - id: ms - anchors.fill: parent - hoverEnabled: true - onClicked: { - if (GC.isMobile()) { - ServerListLogic.onServerListPushbuttonSettingsClicked(index) - } - mouse.accepted = false - } - onEntered: { - mouseExitAni.stop() - mouseEnterAni.start() - } - onExited: { - mouseEnterAni.stop() - mouseExitAni.start() - } - } - Rectangle { - anchors.fill: parent - gradient: ms.containsMouse ? gradient_containsMouse : gradient_notContainsMouse - LinearGradient { - id: gradient_notContainsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#ECEEFF" } - ] - } - LinearGradient { - id: gradient_containsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#DCDEDF" } - ] - } - } - - LabelType { - id: label_address - x: 20 - y: 40 - width: listWidget_servers.width - 100 - height: 16 - text: address - } - Text { - x: 10 - y: 10 - width: listWidget_servers.width - 100 - height: 21 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - font.bold: true - color: "#181922" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: desc - } - ImageButtonType { - x: parent.width - 30 - y: 15 - width: 30 - height: 30 - checkable: true - icon.source: checked ? "qrc:/images/check.png" - : "qrc:/images/uncheck.png" - onClicked: { - ServerListLogic.onServerListPushbuttonDefaultClicked(index) - } - checked: is_default - enabled: !is_default - } - SvgButtonType { - id: pushButtonSetting - x: parent.width - 70 - y: 15 - width: 30 - height: 30 - icon.source: "qrc:/images/svg/settings_black_24dp.svg" - opacity: 0 - - OpacityAnimator { - id: mouseEnterAni - target: pushButtonSetting; - from: 0; - to: 1; - duration: 150 - running: false - easing.type: Easing.InOutQuad - } - OpacityAnimator { - id: mouseExitAni - target: pushButtonSetting; - from: 1; - to: 0; - duration: 150 - running: false - easing.type: Easing.InOutQuad - } - MouseArea { - cursorShape: Qt.PointingHandCursor - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - - onEntered: { - mouseExitAni.stop() - mouseEnterAni.start() - } - onExited: { - mouseEnterAni.stop() - mouseExitAni.start() - } - - onClicked: { - ServerListLogic.onServerListPushbuttonSettingsClicked(index) - } - } - } - } - - ScrollBar.vertical: ScrollBar { - policy: ScrollBar.AsNeeded - } - } -} diff --git a/client/ui/qml/Pages/PageServerSettings.qml b/client/ui/qml/Pages/PageServerSettings.qml deleted file mode 100644 index 4ef22ce4..00000000 --- a/client/ui/qml/Pages/PageServerSettings.qml +++ /dev/null @@ -1,140 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ServerSettings - logic: ServerSettingsLogic - - enabled: ServerSettingsLogic.pageEnabled - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Server settings") - anchors.horizontalCenter: parent.horizontalCenter - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: logo.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ServerSettingsLogic.labelCurrentVpnProtocolText - } - - TextFieldType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ServerSettingsLogic.labelServerText - readOnly: true - background: Item {} - } - - LabelType { - Layout.fillWidth: true - text: ServerSettingsLogic.labelWaitInfoText - visible: ServerSettingsLogic.labelWaitInfoVisible - } - TextFieldType { - Layout.fillWidth: true - text: ServerSettingsLogic.lineEditDescriptionText - onEditingFinished: { - ServerSettingsLogic.lineEditDescriptionText = text - ServerSettingsLogic.onLineEditDescriptionEditingFinished() - } - } - - BlueButtonType { - text: qsTr("Protocols and Services") - Layout.topMargin: 20 - Layout.fillWidth: true - onClicked: { - UiLogic.goToPage(PageEnum.ServerContainers) - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Share Server (FULL ACCESS)") - visible: ServerSettingsLogic.pushButtonShareFullVisible - onClicked: { - ServerSettingsLogic.onPushButtonShareFullClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Advanced server settings") - onClicked: { - UiLogic.goToPage(PageEnum.AdvancedServerSettings) - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 60 - text: ServerSettingsLogic.pushButtonClearClientCacheText - visible: ServerSettingsLogic.pushButtonClearClientCacheVisible - onClicked: { - ServerSettingsLogic.onPushButtonClearClientCacheClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Forget this server") - onClicked: { - if (ServerSettingsLogic.isCurrentServerHasCredentials()) { - popupForgetServer.questionText = "Attention! This action will not remove any data from the server, it will just remove server from the list. Continue?" - } - else { - popupForgetServer.questionText = "Remove server from the list?" - } - popupForgetServer.open() - } - } - - PopupWithQuestion { - id: popupForgetServer - yesFunc: function() { - ServerSettingsLogic.onPushButtonForgetServer() - close() - } - noFunc: function() { - close() - } - } - } - } - - Logo { - id : logo - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageSetupWizard.qml b/client/ui/qml/Pages/PageSetupWizard.qml deleted file mode 100644 index a7e93a3a..00000000 --- a/client/ui/qml/Pages/PageSetupWizard.qml +++ /dev/null @@ -1,108 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Wizard - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup your server to use VPN") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - RadioButtonType { - id: radioButton_setup_wizard_high - Layout.fillWidth: true - text: qsTr("High censorship level") - checked: WizardLogic.radioButtonHighChecked - onCheckedChanged: { - WizardLogic.radioButtonHighChecked = checked - } - } - LabelType { - Layout.fillWidth: true - Layout.leftMargin: 25 - verticalAlignment: Text.AlignTop - text: qsTr("I'm living in a country with a high censorship level. Many of the foreign websites and VPNs are blocked by my government. I want to setup a reliable VPN, which can not be detected by my internet provider and my government. -OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed.\n") - } - - - RadioButtonType { - id: radioButton_setup_wizard_medium - Layout.fillWidth: true - text: qsTr("Medium censorship level") - checked: WizardLogic.radioButtonMediumChecked - onCheckedChanged: { - WizardLogic.radioButtonMediumChecked = checked - } - } - LabelType { - Layout.fillWidth: true - Layout.leftMargin: 25 - verticalAlignment: Text.AlignTop - text: qsTr("I'm living in a country with a medium censorship level. Some websites are blocked by my government, but VPNs are not blocked at all. I want to setup a flexible solution. -OpenVPN over ShadowSocks profile will be installed.\n") - } - - - RadioButtonType { - id: radioButton_setup_wizard_low - Layout.fillWidth: true - text: qsTr("Low censorship level") - checked: WizardLogic.radioButtonLowChecked - onCheckedChanged: { - WizardLogic.radioButtonLowChecked = checked - } - } - LabelType { - Layout.fillWidth: true - Layout.leftMargin: 25 - verticalAlignment: Text.AlignTop - text: qsTr("I want to improve my privacy on the internet. -OpenVPN profile will be installed.\n") - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Next") - onClicked: { - if (radioButton_setup_wizard_high.checked) { - UiLogic.goToPage(PageEnum.WizardHigh, false); - } else if (radioButton_setup_wizard_medium.checked) { - UiLogic.goToPage(PageEnum.WizardMedium, false); - } else if (radioButton_setup_wizard_low.checked) { - UiLogic.goToPage(PageEnum.WizardLow, false); - } - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardHighLevel.qml b/client/ui/qml/Pages/PageSetupWizardHighLevel.qml deleted file mode 100644 index e0f194ff..00000000 --- a/client/ui/qml/Pages/PageSetupWizardHighLevel.qml +++ /dev/null @@ -1,96 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardHigh - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr("AmneziaVPN will install a VPN protocol which is not visible to your internet provider and government firewall. Your VPN connection will be seen by your internet provider as regular web traffic to a particular website. - -You SHOULD set this website address to some foreign website which is not blocked by your internet provider. In other words, you need to type some foreign website address which is accessible to you without a VPN.") - } - - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - verticalAlignment: Text.AlignTop - text: qsTr("Type another web site address for masking or keep it by default. Your internet provider will think you working on this web site when you connected to VPN.") - } - - TextFieldType { - id: website_masking - Layout.fillWidth: true - text: WizardLogic.lineEditHighWebsiteMaskingText - onEditingFinished: { - let _text = website_masking.text - _text = _text.replace("http://", ""); - _text = _text.replace("https://", ""); - if (!_text) { - return - } - _text = _text.split("/")[0]; - WizardLogic.lineEditHighWebsiteMaskingText = _text - } - onAccepted: { - next_button.clicked() - } - } - - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - verticalAlignment: Text.AlignTop - text: qsTr("OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed. - -This protocol support exporting connection profiles to mobile devices by exporting ShadowSocks and Cloak configs (you should launch the 3rd party open source VPN client - ShadowSocks VPN and install Cloak plugin).") - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Next") - onClicked: { - let domain = website_masking.text; - if (!domain || !domain.includes(".")) { - return - } - UiLogic.goToPage(PageEnum.WizardVpnMode, false) - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardLowLevel.qml b/client/ui/qml/Pages/PageSetupWizardLowLevel.qml deleted file mode 100644 index d4e590c1..00000000 --- a/client/ui/qml/Pages/PageSetupWizardLowLevel.qml +++ /dev/null @@ -1,65 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardLow - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr('AmneziaVPN will install the OpenVPN protocol with public/private key pairs generated on both server and client sides. - -You can also configure the connection on your mobile device by copying the exported ".ovpn" file to your device, and setting up the official OpenVPN client. - -We recommend not to use messaging applications for sending the connection profile - it contains VPN private keys.') - } - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr('OpenVPN profile will be installed') - verticalAlignment: Text.AlignBottom - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Start configuring") - onClicked: { - WizardLogic.onPushButtonLowFinishClicked() - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml b/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml deleted file mode 100644 index 6e1a45c1..00000000 --- a/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml +++ /dev/null @@ -1,60 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardMedium - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr('AmneziaVPN will install a VPN protocol which is difficult to detect by your internet provider and government firewall (but possible). In most cases, this is the most suitable protocol. This protocol is faster compared to the VPN protocols with "VPN masking".\n\nThis protocol supports exporting connection profiles to mobile devices by using QR codes (you should launch the 3rd party open source VPN client - ShadowSocks VPN).') - } - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr('OpenVPN over ShadowSocks profile will be installed') - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Next") - onClicked: { - UiLogic.goToPage(PageEnum.WizardVpnMode, false) - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardVPNMode.qml b/client/ui/qml/Pages/PageSetupWizardVPNMode.qml deleted file mode 100644 index 497ccad8..00000000 --- a/client/ui/qml/Pages/PageSetupWizardVPNMode.qml +++ /dev/null @@ -1,65 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardVpnMode - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: vpn_mode_finish.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr('Optional.\n -You can enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later.\n\nPlease note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address.') - } - - CheckBoxType { - Layout.fillWidth: true - text: qsTr('Turn on mode "VPN for selected sites"') - checked: WizardLogic.checkBoxVpnModeChecked - onCheckedChanged: { - WizardLogic.checkBoxVpnModeChecked = checked - } - } - } - } - - BlueButtonType { - id: vpn_mode_finish - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Start configuring") - onClicked: { - WizardLogic.onPushButtonVpnModeFinishClicked() - } - } -} diff --git a/client/ui/qml/Pages/PageShareConnection.qml b/client/ui/qml/Pages/PageShareConnection.qml deleted file mode 100644 index b5439b47..00000000 --- a/client/ui/qml/Pages/PageShareConnection.qml +++ /dev/null @@ -1,87 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Dialogs -import QtQuick.Layouts -import SortFilterProxyModel 0.2 -import ContainerProps 1.0 -import ProtocolProps 1.0 -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ShareConnection - logic: ShareConnectionLogic - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Share protocol config") - width: undefined - } - - - FlickableType { - clip: true - anchors.top: caption.bottom - contentHeight: col.height - boundsBehavior: Flickable.StopAtBounds - - Column { - id: col - anchors { - left: parent.left; - right: parent.right; - } - topPadding: 20 - spacing: 10 - - SortFilterProxyModel { - id: proxyProtocolsModel - sourceModel: UiLogic.protocolsModel - filters: ValueFilter { - roleName: "is_installed_role" - value: true - } - } - - - ShareConnectionContent { - text: qsTr("Share for Amnezia") - height: 40 - width: tb_c.width - 10 - onClicked: UiLogic.goToShareProtocolPage(ProtocolEnum.Any) - } - - ListView { - id: tb_c - width: parent.width - 10 - height: tb_c.contentItem.height - currentIndex: -1 - spacing: 10 - clip: true - interactive: false - model: proxyProtocolsModel - - delegate: Item { - implicitWidth: tb_c.width - 10 - implicitHeight: c_item.height - - ShareConnectionContent { - id: c_item - text: qsTr("Share for ") + name_role - height: 40 - width: tb_c.width - 10 - onClicked: UiLogic.goToShareProtocolPage(proxyProtocolsModel.mapToSource(index)) - } - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageSites.qml b/client/ui/qml/Pages/PageSites.qml deleted file mode 100644 index 673e7e7a..00000000 --- a/client/ui/qml/Pages/PageSites.qml +++ /dev/null @@ -1,315 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQml.Models -import Qt.labs.platform -import QtQuick.Dialogs -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Sites - logic: SitesLogic - - property int lastIndex: 0 - - BackButton { - id: back - } - - Caption { - id: caption - text: SitesLogic.labelSitesAddCustomText - } - - LabelType { - id: lb_addr - color: "#333333" - text: qsTr("Web site/Hostname/IP address/Subnet") - x: 20 - anchors.top: caption.bottom - anchors.topMargin: 10 - width: parent.width - height: 21 - } - - TextFieldType { - anchors.top: lb_addr.bottom - anchors.topMargin: 10 - anchors.left: parent.left - anchors.leftMargin: 20 - anchors.right: sites_add.left - anchors.rightMargin: 10 - height: 31 - placeholderText: qsTr("yousite.com or IP address") - text: SitesLogic.lineEditSitesAddCustomText - onEditingFinished: { - SitesLogic.lineEditSitesAddCustomText = text - } - onAccepted: { - SitesLogic.onPushButtonAddCustomSitesClicked() - } - } - - BlueButtonType { - id: sites_add - anchors.right: sites_import.left - anchors.rightMargin: 10 - anchors.top: lb_addr.bottom - anchors.topMargin: 10 - width: 51 - height: 31 - font.pixelSize: 24 - text: "+" - onClicked: { - SitesLogic.onPushButtonAddCustomSitesClicked() - } - } - - BasicButtonType { - id: sites_import - anchors.right: parent.right - anchors.rightMargin: 20 - anchors.top: lb_addr.bottom - anchors.topMargin: 10 - width: 51 - height: 31 - background: Rectangle { - anchors.fill: parent - radius: 4 - color: parent.containsMouse ? "#211966" : "#100A44" - } - font.pixelSize: 16 - contentItem: Item { - anchors.fill: parent - Image { - anchors.centerIn: parent - width: 20 - height: 20 - source: "qrc:/images/folder.png" - fillMode: Image.Stretch - } - } - antialiasing: true - onClicked: { - fileDialog.open() - } - } - FileDialog { - id: fileDialog - title: qsTr("Import IP addresses") - visible: false - currentFolder: StandardPaths.writableLocation(StandardPaths.DocumentsLocation) - onAccepted: { - SitesLogic.onPushButtonSitesImportClicked(fileUrl) - } - } - - DelegateModel { - id: visualModel - model: SitesLogic.tableViewSitesModel - groups: [ - DelegateModelGroup { - id : delegateModelGroup - name: "multiSelect" - function removeAll(){ - var count = delegateModelGroup.count; - if (count !== 0){ - delegateModelGroup.remove(0,count); - } - } - function selectAll(){ - for(var i = 0; i < visualModel.count; i++){ - visualModel.items.get(i).inMultiSelect = true - } - } - } - ] - delegate: Rectangle { - id: item - focus: true - height: 25 - width: root.width - color: item.DelegateModel.inMultiSelect ? '#63b4fb' : 'transparent' - implicitWidth: 170 * 2 - implicitHeight: 30 - Item { - width: 170 - height: 30 - anchors.left: parent.left - id: c1 - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb.currentRow - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb.currentRow - - } - Text { - text: url_path - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - } - } - Item { - anchors.left: c1.right - width: 170 - height: 30 - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb.currentRow - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb.currentRow - - } - Text { - text: ip - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - } - } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked:{ - tb.focus = true - if(mouse.button === Qt.RightButton){ - //copyPasteMenu.popup() - console.log("RightButton") - } - if(mouse.button === Qt.LeftButton){ - switch(mouse.modifiers){ - case Qt.ControlModifier : - item.DelegateModel.inMultiSelect = !item.DelegateModel.inMultiSelect - break; - case Qt.ShiftModifier : - delegateModelGroup.removeAll(); - var start = lastIndex <= index? lastIndex: index; - var end = lastIndex >= index? lastIndex: index; - for(var i = start;i <= end;i++){ - visualModel.items.get(i).inMultiSelect = true - } - break; - default: - delegateModelGroup.removeAll(); - item.DelegateModel.inMultiSelect = true - lastIndex = index - break; - } - } - } - } - } - } - - ListView { - id: tb - x: 20 - anchors.top: sites_add.bottom - anchors.topMargin: 10 - width: parent.width - 40 - anchors.bottom: sites_delete.top - anchors.bottomMargin: 10 - spacing: 1 - clip: true - focus: true - activeFocusOnTab: true - keyNavigationEnabled: true - property int currentRow: -1 - model: visualModel - - Keys.onPressed: { - if (event.key === Qt.Key_PageUp) { - let idx = tb.indexAt(1, tb.contentY) - tb.positionViewAtIndex(idx-20, ListView.Beginning) - event.accepted = true - } - else if (event.key === Qt.Key_PageDown) { - let idx = tb.indexAt(1, tb.contentY) - tb.positionViewAtIndex(idx+20, ListView.Beginning) - event.accepted = true - } - else if (event.key === Qt.Key_Home) { - tb.positionViewAtBeginning() - event.accepted = true - } - else if (event.key === Qt.Key_End) { - tb.positionViewAtEnd() - event.accepted = true - } - else if (event.key === Qt.Key_Delete) { - let items = [] - for(let i = 0; i < visualModel.count; i++){ - if (visualModel.items.get(i).inMultiSelect) items.push(i) - } - SitesLogic.onPushButtonSitesDeleteClicked(items) - event.accepted = true - } - else if (event.key === Qt.Key_A) { - delegateModelGroup.selectAll() - event.accepted = true - } - } - - } - - BlueButtonType { - id: sites_delete - anchors.bottom: select_all.top - anchors.bottomMargin: 10 - anchors.horizontalCenter: parent.horizontalCenter - height: 31 - font.pixelSize: 16 - text: qsTr("Delete selected") - onClicked: { - let items = [] - for(let i = 0; i < visualModel.count; i++){ - if (visualModel.items.get(i).inMultiSelect) items.push(i) - } - - SitesLogic.onPushButtonSitesDeleteClicked(items) - } - } - - BlueButtonType { - id: select_all - anchors.bottom: sites_export.top - anchors.bottomMargin: 10 - anchors.horizontalCenter: parent.horizontalCenter - height: 31 - font.pixelSize: 16 - text: qsTr("Select all") - onClicked: { - delegateModelGroup.selectAll() - } - } - - BlueButtonType { - id: sites_export - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - anchors.horizontalCenter: parent.horizontalCenter - height: 31 - font.pixelSize: 16 - text: qsTr("Export all") - onClicked: { - SitesLogic.onPushButtonSitesExportClicked() - } - } -} diff --git a/client/ui/qml/Pages/PageStart.qml b/client/ui/qml/Pages/PageStart.qml deleted file mode 100644 index a752817f..00000000 --- a/client/ui/qml/Pages/PageStart.qml +++ /dev/null @@ -1,356 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Start - logic: StartPageLogic - - Connections { - target: StartPageLogic - - function onShowPassphraseRequestMessage() { - popupWithTextField.open() - } - } - - BackButton { - id: back_from_start - visible: pageLoader.depth > 1 - } - - ImageButtonType { - anchors { - right: parent.right - top: parent.top - } - - width: 41 - height: 41 - imgMarginHover: 8 - imgMargin: 9 - icon.source: "qrc:/images/settings_grey.png" - visible: !GeneralSettingsLogic.existsAnyServer - onClicked: { - UiLogic.goToPage(PageEnum.GeneralSettings) - } - } - - Caption { - id: caption - text: start_switch_page.checked ? - qsTr("Setup your server to use VPN") : - qsTr("Connect to the already created VPN server") - } - - Logo { - id: logo - anchors.bottom: parent.bottom - } - - BasicButtonType { - id: start_switch_page - width: parent.width - 2 * GC.defaultMargin - implicitHeight: 40 - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: logo.top - anchors.bottomMargin: 10 - anchors.topMargin: 20 - - text: qsTr("Set up your own server") - checked: false - checkable: true - onCheckedChanged: { - if (checked) { - page_start_new_server.visible = true - page_start_import.visible = false - text = qsTr("Import connection"); - } - else { - page_start_new_server.visible = false - page_start_import.visible = true - text = qsTr("Set up your own server"); - } - } - - background: Rectangle { - anchors.fill: parent - border.width: 1 - border.color: "#211C4A" - radius: 4 - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#100A44" - text: start_switch_page.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - } - - Item { - id: page_start_import - width: parent.width - anchors.top: caption.bottom - anchors.bottom: start_switch_page.top - anchors.bottomMargin: 10 - - visible: true - - LabelType { - id: label_connection_code - anchors.top: parent.top - anchors.topMargin: 20 - anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("Connection code") - } - TextFieldType { - id: lineEdit_start_existing_code - anchors.top: label_connection_code.bottom - anchors.horizontalCenter: parent.horizontalCenter - placeholderText: "vpn://..." - text: StartPageLogic.lineEditStartExistingCodeText - onEditingFinished: { - StartPageLogic.lineEditStartExistingCodeText = text - } - } - BlueButtonType { - id: new_sever_import - anchors.horizontalCenter: parent.horizontalCenter - y: 210 - anchors.top: lineEdit_start_existing_code.bottom - anchors.topMargin: 10 - text: qsTr("Connect") - onClicked: { - StartPageLogic.onPushButtonImport() - } - } - - - BlueButtonType { - id: qr_code_import_open - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: new_sever_import.bottom - anchors.topMargin: 40 - - text: qsTr("Open file") - onClicked: { - StartPageLogic.onPushButtonImportOpenFile() - } - enabled: StartPageLogic.pushButtonConnectEnabled - } - - BlueButtonType { - id: qr_code_import - visible: GC.isMobile() - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: qr_code_import_open.bottom - anchors.topMargin: 10 - - text: qsTr("Scan QR code") - onClicked: { - if (Qt.platform.os === "ios") { - UiLogic.goToPage(PageEnum.QrDecoderIos) - } else { - StartPageLogic.startQrDecoder() - } - } - enabled: StartPageLogic.pushButtonConnectEnabled - } - - BlueButtonType { - id: btn_restore_cfg - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: qr_code_import.bottom - anchors.topMargin: 30 - visible: UiLogic.pagesStackDepth === 1 - enabled: StartPageLogic.pushButtonConnectEnabled - - text: qsTr("Restore app config") - onClicked: { - AppSettingsLogic.onPushButtonRestoreAppConfigClicked() - } - } - } - - - Item { - id: page_start_new_server - width: parent.width - anchors.top: caption.bottom - anchors.bottom: start_switch_page.top - anchors.bottomMargin: 10 - - visible: false - - BasicButtonType { - id: new_sever_get_info - width: parent.width - 80 - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 5 - - text: qsTr("How to get own server? →") - background: Item { - anchors.fill: parent - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#15CDCB"; - text: new_sever_get_info.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - checkable: true - checked: true - onClicked: { - Qt.openUrlExternally("https://amnezia.org/instruction.html") - } - } - LabelType { - id: label_server_ip - x: 40 - anchors.top: new_sever_get_info.bottom - text: qsTr("Server IP address [:port]") - } - TextFieldType { - id: new_server_ip - anchors.top: label_server_ip.bottom - anchors.horizontalCenter: parent.horizontalCenter - text: StartPageLogic.lineEditIpText - onEditingFinished: { StartPageLogic.lineEditIpText = text } - onTextEdited: { StartPageLogic.lineEditIpText = text } - - validator: RegularExpressionValidator { - regularExpression: StartPageLogic.ipAddressPortRegex - } - } - - LabelType { - id:label_login - x: 40 - anchors.top: new_server_ip.bottom - text: qsTr("Login to connect via SSH") - } - TextFieldType { - id: new_server_login - anchors.top: label_login.bottom - anchors.horizontalCenter: parent.horizontalCenter - text: StartPageLogic.lineEditLoginText - onEditingFinished: { StartPageLogic.lineEditLoginText = text } - onTextEdited: { StartPageLogic.lineEditLoginText = text } - } - - LabelType { - id: label_new_server_password - x: 40 - anchors.top: new_server_login.bottom - text: qsTr("Password") - } - TextFieldType { - id: new_server_password - anchors.top: label_new_server_password.bottom - anchors.horizontalCenter: parent.horizontalCenter - - inputMethodHints: Qt.ImhSensitiveData - echoMode: TextInput.Password - text: StartPageLogic.lineEditPasswordText - onEditingFinished: { StartPageLogic.lineEditPasswordText = text } - onTextEdited: { StartPageLogic.lineEditPasswordText = text } - onAccepted: { StartPageLogic.onPushButtonConnect() } - } - TextFieldType { - id: new_server_ssh_key - anchors.top: label_new_server_password.bottom - anchors.horizontalCenter: parent.horizontalCenter - - visible: false - height: 71 - font.pixelSize: 10 - verticalAlignment: Text.AlignTop - inputMethodHints: Qt.ImhSensitiveData - - text: StartPageLogic.textEditSshKeyText - onEditingFinished: { StartPageLogic.textEditSshKeyText = text } - onTextEdited: { StartPageLogic.textEditSshKeyText = text } - onAccepted: { StartPageLogic.onPushButtonConnect() } - } - - LabelType { - x: 40 - y: 390 - width: 301 - height: 41 - text: StartPageLogic.labelWaitInfoText - visible: StartPageLogic.labelWaitInfoVisible - wrapMode: Text.Wrap - } - - BlueButtonType { - id: new_sever_connect - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: new_server_ssh_key.bottom - anchors.topMargin: 10 - - text: StartPageLogic.pushButtonConnectText - onClicked: { - StartPageLogic.onPushButtonConnect() - } - enabled: StartPageLogic.pushButtonConnectEnabled - } - - UrlButtonType { - id: new_sever_connect_key - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: new_sever_connect.bottom - - width: 281 - height: 21 - text: qsTr("Connect using SSH key") - - label.font.pixelSize: 16 - checkable: true - checked: StartPageLogic.pushButtonConnectKeyChecked - onCheckedChanged: { - StartPageLogic.pushButtonConnectKeyChecked = checked - label_new_server_password.text = checked ? qsTr("Private key") : qsTr("Password") - new_sever_connect_key.text = checked ? qsTr("Connect using SSH password") : qsTr("Connect using SSH key") - new_server_password.visible = !checked - new_server_ssh_key.visible = checked - } - } - } - - PopupWithTextField { - id: popupWithTextField - placeholderText: "Enter private key passphrase" - yesFunc: function() { - editingFinished() - close() - StartPageLogic.passphraseDialogClosed() - text = "" - } - noFunc: function() { - close() - StartPageLogic.passphraseDialogClosed() - } - onEditingFinished: { - StartPageLogic.privateKeyPassphrase = text - } - } -} diff --git a/client/ui/qml/Pages/PageVPN.qml b/client/ui/qml/Pages/PageVPN.qml deleted file mode 100644 index 342fb129..00000000 --- a/client/ui/qml/Pages/PageVPN.qml +++ /dev/null @@ -1,387 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Vpn - logic: VpnLogic - - Image { - id: bg_top - anchors.horizontalCenter: parent.horizontalCenter - y: 0 - width: parent.width - height: parent.height * 0.28 - source: "qrc:/images/background_connected.png" - } - - LabelType { - x: 10 - y: 10 - width: 100 - height: 21 - text: VpnLogic.labelVersionText - color: "#dddddd" - font.pixelSize: 12 - } - - UrlButtonType { - id: button_donate - y: 10 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - label.color: "#D4D4D4" - label.text: qsTr("Donate") - - onClicked: { - UiLogic.goToPage(PageEnum.About) - } - } - - ImageButtonType { - x: parent.width - 40 - y: 0 - width: 41 - height: 41 - imgMarginHover: 8 - imgMargin: 9 - icon.source: "qrc:/images/settings_grey.png" - onClicked: { - UiLogic.goToPage(PageEnum.GeneralSettings) - } - } - - LabelType { - id: lb_log_enabled - anchors.top: button_donate.bottom - anchors.horizontalCenter: parent.horizontalCenter - width: parent.width - height: 21 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - text: "Logging enabled!" - color: "#D4D4D4" - - visible: VpnLogic.labelLogEnabledVisible - } - - AnimatedImage { - id: connect_anim - source: "qrc:/images/animation.gif" - anchors.top: bg_top.bottom - anchors.topMargin: 10 - anchors.horizontalCenter: root.horizontalCenter - width: Math.min(parent.width, parent.height) / 4 - height: width - - visible: !VpnLogic.pushButtonConnectVisible - paused: VpnLogic.pushButtonConnectVisible && !root.pageActive - //VisibleBehavior on visible { } - } - - BasicButtonType { - id: button_connect - anchors.horizontalCenter: connect_anim.horizontalCenter - anchors.verticalCenter: connect_anim.verticalCenter - width: connect_anim.width - height: width - checkable: true - checked: VpnLogic.pushButtonConnectChecked - onClicked: VpnLogic.onPushButtonConnectClicked() - background: Image { - anchors.fill: parent - source: button_connect.checked ? "qrc:/images/connected.png" - : "qrc:/images/disconnected.png" - } - contentItem: Item {} - antialiasing: true - enabled: VpnLogic.pushButtonConnectEnabled && VpnLogic.isContainerSupportedByCurrentPlatform - opacity: VpnLogic.pushButtonConnectVisible ? 1 : 0 - -// transitions: Transition { -// NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; duration: 500 } -// } - } - - - LabelType { - id: lb_state - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: button_connect.bottom - width: parent.width - height: 21 - horizontalAlignment: Text.AlignHCenter - text: VpnLogic.labelStateText - } - - RowLayout { - id: layout1 - anchors.top: lb_state.bottom - //anchors.topMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - - - LabelType { - Layout.alignment: Qt.AlignRight - height: 21 - text: ( VpnLogic.isContainerHaveAuthData ? qsTr("Server") : qsTr("Profile")) + ": " - } - - BasicButtonType { - Layout.alignment: Qt.AlignLeft - height: 21 - background: Item {} - text: VpnLogic.labelCurrentServer + (VpnLogic.isContainerHaveAuthData ? " →" : "") - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - onClicked: { - UiLogic.goToPage(PageEnum.ServersList) - } - } - } - - RowLayout { - id: layout2 - anchors.top: layout1.bottom - anchors.topMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - - - LabelType { - Layout.alignment: Qt.AlignRight - height: 21 - text: qsTr("Proto") + ": " - } - - BasicButtonType { - Layout.alignment: Qt.AlignLeft - height: 21 - background: Item {} - text: VpnLogic.labelCurrentService + (VpnLogic.isContainerHaveAuthData ? " →" : "") - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - onClicked: { - if (VpnLogic.isContainerHaveAuthData) UiLogic.onGotoCurrentProtocolsPage() - } - } - } - - RowLayout { - id: layout3 - anchors.top: layout2.bottom - anchors.topMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - - - LabelType { - Layout.alignment: Qt.AlignRight - height: 21 - text: qsTr("DNS") + ": " - } - - BasicButtonType { - Layout.alignment: Qt.AlignLeft - height: 21 - implicitWidth: implicitContentWidth > root.width * 0.6 ? root.width * 0.6 : implicitContentWidth + leftPadding + rightPadding - background: Item {} - text: VpnLogic.labelCurrentDns + (VpnLogic.isContainerHaveAuthData ? " →" : "") - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - onClicked: { - if (VpnLogic.isContainerHaveAuthData) UiLogic.goToPage(PageEnum.NetworkSettings) - } - } - - SvgImageType { - svg.source: VpnLogic.amneziaDnsEnabled ? "qrc:/images/svg/gpp_good_black_24dp.svg" : "qrc:/images/svg/gpp_maybe_black_24dp.svg" - color: VpnLogic.amneziaDnsEnabled ? "#22aa33" : "orange" - width: 25 - height: 25 - } - } - - - LabelType { - id: error_text - anchors.top: layout3.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 20 - width: parent.width - 20 - - height: 21 - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.Wrap - text: VpnLogic.labelErrorText - color: "red" - } - - Item { - x: 0 - anchors.bottom: line.top - anchors.bottomMargin: GC.isMobile() ? 0 :10 - width: parent.width - height: 51 - Image { - anchors.horizontalCenter: upload_label.horizontalCenter - y: 10 - width: 15 - height: 15 - source: "qrc:/images/upload.png" - } - Image { - anchors.horizontalCenter: download_label.horizontalCenter - y: 10 - width: 15 - height: 15 - source: "qrc:/images/download.png" - } - Text { - id: download_label - x: 0 - y: 20 - width: 130 - height: 30 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#4171D6" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: VpnLogic.labelSpeedReceivedText - } - Text { - id: upload_label - x: parent.width - width - y: 20 - width: 130 - height: 30 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#42D185" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: VpnLogic.labelSpeedSentText - } - } - - Rectangle { - id: line - x: 20 - width: parent.width - 40 - height: 1 - anchors.bottom: GC.isMobile() ? root.bottom : conn_type_label.top - anchors.bottomMargin: 10 - color: "#DDDDDD" - } - - Text { - id: conn_type_label - visible: !GC.isMobile() - x: 20 - anchors.bottom: conn_type_group.top - anchors.bottomMargin: GC.isMobile() ? 0 :10 - width: 281 - height: GC.isMobile() ? 0: 21 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 15 - color: "#181922" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: qsTr("How to use VPN") - } - - Item { - id: conn_type_group - x: 20 - visible: !GC.isMobile() - anchors.bottom: button_add_site.top - width: 351 - height: GC.isMobile() ? 0: 91 - enabled: VpnLogic.widgetVpnModeEnabled - RadioButtonType { - x: 0 - y: 0 - width: 341 - height: 19 - checked: VpnLogic.radioButtonVpnModeAllSitesChecked - text: qsTr("For all connections") - onClicked: VpnLogic.onRadioButtonVpnModeAllSitesClicked(true) - } - RadioButtonType { - enabled: VpnLogic.isCustomRoutesSupported - x: 0 - y: 60 - width: 341 - height: 19 - text: qsTr("Except selected sites") - checked: VpnLogic.radioButtonVpnModeExceptSitesChecked - onClicked: VpnLogic.onRadioButtonVpnModeExceptSitesClicked(true) - } - RadioButtonType { - enabled: VpnLogic.isCustomRoutesSupported - x: 0 - y: 30 - width: 341 - height: 19 - text: qsTr("For selected sites") - checked: VpnLogic.radioButtonVpnModeForwardSitesChecked - onClicked: VpnLogic.onRadioButtonVpnModeForwardSitesClicked(true) - } - } - - BasicButtonType { - id: button_add_site - visible: !GC.isMobile() - anchors.horizontalCenter: parent.horizontalCenter - y: parent.height - 60 - width: parent.width - 40 - height: GC.isMobile() ? 0: 40 - text: qsTr("+ Add site") - enabled: ! VpnLogic.radioButtonVpnModeAllSitesChecked - background: Rectangle { - anchors.fill: parent - radius: 4 - color: { - if (!button_add_site.enabled) { - return "#484952" - } - if (button_add_site.containsMouse) { - return "#282932" - } - return "#181922" - } - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - text: button_add_site.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - onClicked: { - UiLogic.goToPage(PageEnum.Sites) - } - } -} diff --git a/client/ui/qml/Pages/PageViewConfig.qml b/client/ui/qml/Pages/PageViewConfig.qml deleted file mode 100644 index 24ccda45..00000000 --- a/client/ui/qml/Pages/PageViewConfig.qml +++ /dev/null @@ -1,138 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ViewConfig - logic: ViewConfigLogic - - readonly property double rowHeight: ta_last_config.contentHeight / ta_last_config.textArea.lineCount - - BackButton {} - - Caption { - id: caption - text: qsTr("Check config") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - TextAreaType { - id: ta_config - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: ViewConfigLogic.warningActive ? 250 : fl.height - 70 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.configText - } - - LabelType { - id: lb_att - visible: ViewConfigLogic.warningActive - text: qsTr("Attention! -The config above contains cached OpenVPN connection profile. -AmneziaVPN detected this profile may contain malicious scripts. Please, carefully review the config and import this config only if you completely trust it.") - Layout.fillWidth: true - } - - LabelType { - visible: ViewConfigLogic.warningActive - text: qsTr("Suspicious string:") - Layout.fillWidth: true - } - - TextAreaType { - id: ta_mal - visible: ViewConfigLogic.warningActive - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: 60 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.openVpnMalStrings - textArea.textFormat: TextEdit.RichText - } - - LabelType { - visible: ViewConfigLogic.warningActive - text: qsTr("Cached connection profile:") - Layout.fillWidth: true - } - - TextAreaType { - id: ta_last_config - visible: ViewConfigLogic.warningActive - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: 350 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.openVpnLastConfigs - textArea.textFormat: TextEdit.RichText - - Connections { - target: logic - function onWarningStringNumberChanged(n) { - ta_last_config.contentY = rowHeight * n - ta_last_config.height / 2 - } - } - } - - RowLayout { - id: btns_row - - BasicButtonType { - Layout.preferredWidth: (content.width - parent.spacing) /2 - Layout.preferredHeight: 40 - font.pixelSize: btn_import.font.pixelSize - text: qsTr("Cancel") - onClicked: { - UiLogic.closePage() - } - } - - BlueButtonType { - id: btn_import - Layout.preferredWidth: (content.width - parent.spacing) /2 - text: qsTr("Import config") - onClicked: { - logic.importConfig() - } - } - } - } - } - - } diff --git a/client/ui/qml/Pages/Protocols/PageProtoCloak.qml b/client/ui/qml/Pages/Protocols/PageProtoCloak.qml deleted file mode 100644 index 3475a82b..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoCloak.qml +++ /dev/null @@ -1,204 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.Cloak - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - enabled: !logic.pushButtonCancelVisible - } - - Caption { - id: caption - text: qsTr("Cloak Settings") - } - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: caption.bottom - anchors.left: root.left - anchors.right: root.right - anchors.bottom: pb_save.top - anchors.margins: 20 - anchors.topMargin: 10 - - - RowLayout { - Layout.fillWidth: true - - LabelType { - height: 31 - text: qsTr("Cipher") - Layout.preferredWidth: 0.3 * root.width - 10 - } - - ComboBoxType { - Layout.fillWidth: true - height: 31 - model: [ - qsTr("chacha20-poly1305"), - qsTr("aes-256-gcm"), - qsTr("aes-192-gcm"), - qsTr("aes-128-gcm") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxCipherText === model[i]) { - return i - } - } - return -1 - } - onCurrentTextChanged: { - logic.comboBoxCipherText = currentText - } - } - } - - RowLayout { - Layout.fillWidth: true - - LabelType { - Layout.preferredWidth: 0.3 * root.width - 10 - height: 31 - text: qsTr("Fake Web Site") - } - - TextFieldType { - id: lineEdit_proto_cloak_site - Layout.fillWidth: true - focus: true - height: 31 - text: logic.lineEditSiteText - onEditingFinished: { - logic.lineEditSiteText = text - } - - onCursorRectangleChanged: { - logic.lineEditSiteText = text - } - } - } - - RowLayout { - Layout.fillWidth: true - - LabelType { - Layout.preferredWidth: 0.3 * root.width - 10 - height: 31 - text: qsTr("Port") - } - - TextFieldType { - id: lineEdit_proto_cloak_port - Layout.fillWidth: true - focus: true - height: 31 - text: logic.lineEditPortText - onEditingFinished: { - logic.lineEditPortText = text - } - enabled: logic.lineEditPortEnabled - - onCursorRectangleChanged: { - logic.lineEditPortText = text - } - } - } - - Item { - Layout.fillHeight: true - } - } - - LabelType { - id: label_server_busy - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelServerBusyVisible - text: logic.labelServerBusyText - } - - LabelType { - id: label_proto_cloak_info - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelInfoVisible - text: logic.labelInfoText - } - - ProgressBar { - id: progressBar_proto_cloak_reset - anchors.horizontalCenter: parent.horizontalCenter - anchors.fill: pb_save - from: 0 - to: logic.progressBarResetMaximum - value: logic.progressBarResetValue - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: progressBar_proto_cloak_reset.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - visible: logic.progressBarResetVisible - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: logic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: logic.progressBarTextVisible - } - } - BlueButtonType { - id: pb_save - anchors.horizontalCenter: parent.horizontalCenter - enabled: logic.pageEnabled - anchors.bottom: root.bottom - anchors.bottomMargin: 20 - width: root.width - 60 - height: 40 - text: qsTr("Save and restart VPN") - visible: logic.pushButtonSaveVisible - onClicked: { - logic.onPushButtonSaveClicked() - } - } - - BlueButtonType { - anchors.fill: pb_save - text: qsTr("Cancel") - visible: logic.pushButtonCancelVisible - enabled: logic.pushButtonCancelVisible - onClicked: { - logic.onPushButtonCancelClicked() - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml b/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml deleted file mode 100644 index 507f5f09..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml +++ /dev/null @@ -1,454 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.OpenVpn - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - enabled: !logic.pushButtonCancelVisible - } - - Caption { - id: caption - text: qsTr("OpenVPN Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - 1 - - ColumnLayout { - visible: !logic.isThirdPartyConfig - - LabelType { - id: lb_subnet - enabled: logic.pageEnabled - height: 21 - text: qsTr("VPN Addresses Subnet") - } - - TextFieldType { - id: tf_subnet - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 31 - text: logic.lineEditSubnetText - onEditingFinished: { - logic.lineEditSubnetText = text - } - } - - LabelType { - id: lb_proto - enabled: logic.pageEnabled - Layout.topMargin: 20 - height: 21 - text: qsTr("Network protocol") - } - - Rectangle { - id: rect_proto - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 71 - border.width: 1 - border.color: "lightgray" - radius: 2 - RadioButtonType { - x: 10 - y: 40 - width: 171 - height: 19 - text: qsTr("TCP") - enabled: logic.radioButtonTcpEnabled - checked: logic.radioButtonTcpChecked - onCheckedChanged: { - logic.radioButtonTcpChecked = checked - } - } - RadioButtonType { - x: 10 - y: 10 - width: 171 - height: 19 - text: qsTr("UDP") - checked: logic.radioButtonUdpChecked - onCheckedChanged: { - logic.radioButtonUdpChecked = checked - } - enabled: logic.radioButtonUdpEnabled - } - } - - RowLayout { - enabled: logic.pageEnabled - Layout.topMargin: 10 - Layout.fillWidth: true - LabelType { - id: lb_port - height: 31 - text: qsTr("Port") - Layout.preferredWidth: root.width / 2 - 10 - } - TextFieldType { - id: tf_port - Layout.fillWidth: true - - height: 31 - text: logic.lineEditPortText - onEditingFinished: { - logic.lineEditPortText = text - } - enabled: logic.lineEditPortEnabled - } - } - - CheckBoxType { - id: check_auto_enc - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Auto-negotiate encryption") - checked: logic.checkBoxAutoEncryptionChecked - onCheckedChanged: { - logic.checkBoxAutoEncryptionChecked = checked - } - onClicked: { - logic.checkBoxAutoEncryptionClicked() - } - } - - LabelType { - id: lb_cipher - enabled: logic.pageEnabled - height: 21 - text: qsTr("Cipher") - } - - ComboBoxType { - id: cb_cipher - enabled: logic.pageEnabled && !check_auto_enc.checked - implicitWidth: parent.width - - height: 31 - model: [ - qsTr("AES-256-GCM"), - qsTr("AES-192-GCM"), - qsTr("AES-128-GCM"), - qsTr("AES-256-CBC"), - qsTr("AES-192-CBC"), - qsTr("AES-128-CBC"), - qsTr("ChaCha20-Poly1305"), - qsTr("ARIA-256-CBC"), - qsTr("CAMELLIA-256-CBC"), - qsTr("none") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxVpnCipherText === model[i]) { - return i - } - } - return -1 - } - onCurrentTextChanged: { - logic.comboBoxVpnCipherText = currentText - } - } - - LabelType { - id: lb_hash - enabled: logic.pageEnabled - height: 21 - Layout.topMargin: 20 - text: qsTr("Hash") - } - - ComboBoxType { - id: cb_hash - enabled: logic.pageEnabled && !check_auto_enc.checked - height: 31 - implicitWidth: parent.width - model: [ - qsTr("SHA512"), - qsTr("SHA384"), - qsTr("SHA256"), - qsTr("SHA3-512"), - qsTr("SHA3-384"), - qsTr("SHA3-256"), - qsTr("whirlpool"), - qsTr("BLAKE2b512"), - qsTr("BLAKE2s256"), - qsTr("SHA1") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxVpnHashText === model[i]) { - return i - } - } - return -1 - } - onCurrentTextChanged: { - logic.comboBoxVpnHashText = currentText - } - } - - CheckBoxType { - id: check_tls - enabled: logic.pageEnabled - implicitWidth: parent.width - Layout.topMargin: 20 - height: 21 - text: qsTr("Enable TLS auth") - checked: logic.checkBoxTlsAuthChecked - onCheckedChanged: { - logic.checkBoxTlsAuthChecked = checked - } - - } - - CheckBoxType { - id: check_block_dns - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Block DNS requests outside of VPN") - checked: logic.checkBoxBlockDnsChecked - onCheckedChanged: { - logic.checkBoxBlockDnsChecked = checked - } - } - - BasicButtonType { - id: pb_client_config - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Additional client config commands →") - background: Item { - anchors.fill: parent - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#15CDCB"; - text: pb_client_config.text - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - checkable: true - checked: StartPageLogic.pushButtonConnectKeyChecked - } - - Rectangle { - id: rect_client_conf - enabled: logic.pageEnabled - implicitWidth: root.width - 60 - height: 101 - border.width: 1 - border.color: "lightgray" - radius: 2 - visible: pb_client_config.checked - - ScrollView { - anchors.fill: parent - TextArea { - id: te_client_config - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - text: logic.textAreaAdditionalClientConfig - onEditingFinished: { - logic.textAreaAdditionalClientConfig = text - } - } - } - } - - BasicButtonType { - id: pb_server_config - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Additional server config commands →") - background: Item { - anchors.fill: parent - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#15CDCB"; - text: pb_server_config.text - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - checkable: true - checked: StartPageLogic.pushButtonConnectKeyChecked - } - - Rectangle { - id: rect_server_conf - enabled: logic.pageEnabled - implicitWidth: root.width - 60 - height: 101 - border.width: 1 - border.color: "lightgray" - radius: 2 - visible: pb_server_config.checked - - ScrollView { - anchors.fill: parent - TextArea { - id: te_server_config - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - text: logic.textAreaAdditionalServerConfig - onEditingFinished: { - logic.textAreaAdditionalServerConfig = text - } - } - } - } - - LabelType { - id: label_server_busy - enabled: logic.pageEnabled - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelServerBusyVisible - text: logic.labelServerBusyText - } - - LabelType { - id: label_proto_openvpn_info - enabled: logic.pageEnabled - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - height: 41 - visible: logic.labelProtoOpenVpnInfoVisible - text: logic.labelProtoOpenVpnInfoText - } - - Rectangle { - id: it_save - implicitWidth: parent.width - Layout.topMargin: 20 - height: 40 - - BlueButtonType { - id: pb_save - enabled: logic.pageEnabled - z: 1 - height: 40 - text: qsTr("Save and restart VPN") - width: parent.width - visible: logic.pushButtonSaveVisible - onClicked: { - logic.onPushButtonSaveClicked() - } - } - - BlueButtonType { - z: 1 - anchors.fill: pb_save - text: qsTr("Cancel") - visible: logic.pushButtonCancelVisible - enabled: logic.pushButtonCancelVisible - onClicked: { - logic.onPushButtonCancelClicked() - } - } - - ProgressBar { - id: progress_save - anchors.fill: pb_save - from: 0 - to: logic.progressBarResetMaximum - value: logic.progressBarResetValue - visible: logic.progressBarResetVisible - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: progress_save.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - } - - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: logic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: logic.progressBarTextVisible - } - } - - } - - ColumnLayout { - visible: logic.isThirdPartyConfig - TextAreaType { - id: ta_config - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: fl.height - 70 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.openVpnLastConfigText - } - } - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoSftp.qml b/client/ui/qml/Pages/Protocols/PageProtoSftp.qml deleted file mode 100644 index c6a0602f..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoSftp.qml +++ /dev/null @@ -1,140 +0,0 @@ -import QtQuick -import QtQuick.Controls -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.Sftp - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("SFTP settings") - } - - Rectangle { - id: frame_settings - width: parent.width - anchors.top: caption.bottom - anchors.topMargin: 10 - - border.width: 1 - border.color: "lightgray" - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - radius: 2 - Grid { - id: grid - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 30 - rightPadding: 30 - spacing: 5 - - - LabelType { - width: 130 - text: qsTr("Port") - } - TextFieldType { - id: tf_port_num - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - text: logic.labelTftpPortText - readOnly: true - } - - LabelType { - width: 130 - text: qsTr("User Name") - } - TextFieldType { - id: tf_user_name - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - text: logic.labelTftpUserNameText - readOnly: true - } - - LabelType { - width: 130 - text: qsTr("Password") - } - TextFieldType { - id: tf_password - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - text: logic.labelTftpPasswordText - readOnly: true - } - } - } - - RichLabelType { - anchors.bottom: check_persist.top - anchors.bottomMargin: 10 - width: parent.width - 60 - x: 30 - font.pixelSize: 14 - - readonly property string windows_text: "In order to mount remote SFTP folder as local drive, perform following steps: -
    -
  • Install the latest version of WinFsp.
  • -
  • Install the latest version of SSHFS-Win. Choose the x64 or x86 installer according to your computer's architecture.
  • -
" - - readonly property string macos_text: "In order to mount remote SFTP folder as local folder, perform following steps: -
    -
  • Install the latest version of macFUSE.
  • -
  • Install the latest version of SSHFS.
  • -
" - - text: { - if (Qt.platform.os == "windows") return windows_text - else if (Qt.platform.os == "osx") return macos_text - else if (Qt.platform.os == "linux") return "" - else return "" - } - } - - CheckBoxType { - id: check_persist - visible: false - anchors.bottom: pb_mount.top - anchors.bottomMargin: 10 - x: 30 - width: parent.width - height: 21 - text: qsTr("Restore drive when client starts") - checked: logic.checkBoxSftpRestoreChecked - onCheckedChanged: { - logic.checkBoxSftpRestoreChecked = checked - } - onClicked: { - logic.checkBoxSftpRestoreClicked() - } - } - - BlueButtonType { - id: pb_mount - visible: GC.isDesktop() - enabled: logic.pushButtonSftpMountEnabled - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - x: 30 - width: parent.width - 60 - height: 40 - text: qsTr("Mount drive") - onClicked: { - logic.onPushButtonSftpMountDriveClicked() - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml b/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml deleted file mode 100644 index f3b1f83d..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml +++ /dev/null @@ -1,175 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.ShadowSocks - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - enabled: !logic.pushButtonCancelVisible - } - - Caption { - id: caption - text: qsTr("ShadowSocks Settings") - } - - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: caption.bottom - anchors.left: root.left - anchors.right: root.right - anchors.bottom: pb_save.top - anchors.margins: 20 - anchors.topMargin: 10 - - - - RowLayout { - Layout.fillWidth: true - - LabelType { - height: 31 - text: qsTr("Cipher") - Layout.preferredWidth: 0.3 * root.width - 10 - } - - ComboBoxType { - height: 31 - Layout.fillWidth: true - - model: [ - qsTr("chacha20-ietf-poly1305"), - qsTr("xchacha20-ietf-poly1305"), - qsTr("aes-256-gcm"), - qsTr("aes-192-gcm"), - qsTr("aes-128-gcm") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxCipherText === model[i]) { - return i - } - } - return -1 - } - } - } - - RowLayout { - Layout.fillWidth: true - - LabelType { - Layout.preferredWidth: 0.3 * root.width - 10 - height: 31 - text: qsTr("Port") - } - - TextFieldType { - id: lineEdit_proto_shadowsocks_port - Layout.fillWidth: true - height: 31 - text: logic.lineEditPortText - onEditingFinished: { - logic.lineEditPortText = text - } - enabled: logic.lineEditPortEnabled - } - } - - Item { - Layout.fillHeight: true - } - - LabelType { - id: label_server_busy - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelServerBusyVisible - text: logic.labelServerBusyText - } - - LabelType { - id: label_proto_shadowsocks_info - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelInfoVisible - text: logic.labelInfoText - } - } - - ProgressBar { - id: progressBar_reset - anchors.fill: pb_save - from: 0 - to: logic.progressBarResetMaximum - value: logic.progressBarResetValue - visible: logic.progressBarResetVisible - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: progressBar_reset.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: logic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: logic.progressBarTextVisible - } - } - - BlueButtonType { - id: pb_save - enabled: logic.pageEnabled - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: root.bottom - anchors.bottomMargin: 20 - width: root.width - 60 - height: 40 - text: qsTr("Save and restart VPN") - visible: logic.pushButtonSaveVisible - onClicked: { - logic.onPushButtonSaveClicked() - } - } - - BlueButtonType { - anchors.fill: pb_save - text: qsTr("Cancel") - visible: logic.pushButtonCancelVisible - enabled: logic.pushButtonCancelVisible - onClicked: { - logic.onPushButtonCancelClicked() - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml b/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml deleted file mode 100644 index aa4d35d4..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml +++ /dev/null @@ -1,69 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.TorWebSite - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Tor Web Site settings") - } - - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: caption.bottom - anchors.left: root.left - anchors.right: root.right - anchors.margins: 20 - anchors.topMargin: 10 - - - - RowLayout { - Layout.fillWidth: true - - LabelType { - id: lbl_onion - Layout.preferredWidth: 0.3 * root.width - 10 - text: qsTr("Web site onion address") - } - TextFieldType { - id: tf_site_address - Layout.fillWidth: true - text: logic.labelTorWebSiteAddressText - readOnly: true - } - } - - ShareConnectionButtonCopyType { - Layout.fillWidth: true - Layout.topMargin: 5 - copyText: tf_site_address.text - } - - RichLabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr("Notes:
    -
  • Use Tor Browser to open this url.
  • -
  • After installation it takes several minutes while your onion site will become available in the Tor Network.
  • -
  • When configuring WordPress set the domain as this onion address.
  • -
-") - } - } - -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoWireGuard.qml b/client/ui/qml/Pages/Protocols/PageProtoWireGuard.qml deleted file mode 100644 index e4dde50e..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoWireGuard.qml +++ /dev/null @@ -1,60 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.WireGuard - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("WireGuard Settings") - } - - Flickable { - id: fl - width: root.width - anchors.top: caption.bottom - anchors.topMargin: 20 - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - anchors.left: root.left - anchors.leftMargin: 30 - anchors.right: root.right - anchors.rightMargin: 30 - - contentHeight: content.height - clip: true - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - TextAreaType { - id: ta_config - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: fl.height - 70 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.wireGuardLastConfigText - } - } - } - -} diff --git a/client/ui/qml/Pages/Protocols/PageProtocolBase.qml b/client/ui/qml/Pages/Protocols/PageProtocolBase.qml deleted file mode 100644 index 97a0f1eb..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtocolBase.qml +++ /dev/null @@ -1,13 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./.." -import "../../Controls" -import "../../Config" - -PageBase { - id: root - property var protocol: ProtocolEnum.Any - page: PageEnum.ProtocolSettings -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml b/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml deleted file mode 100644 index 4e52e501..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml +++ /dev/null @@ -1,145 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Any - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share for Amnezia") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height + 20 - - Behavior on contentY{ - NumberAnimation { - duration: 300 - easing.type: Easing.InOutCubic - } - } - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - Text { - id: lb_desc - Layout.fillWidth: true - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: ShareConnectionLogic.shareFullAccess - ? qsTr("Anyone who logs in with this code will have the same permissions to use VPN and YOUR SERVER as you. \n -This code includes your server credentials!\n -Provide this code only to TRUSTED users.") - : qsTr("Anyone who logs in with this code will be able to connect to this VPN server. \n -This code does not include server credentials.\n -New encryption keys pair will be generated.") - } - - ShareConnectionButtonType { - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: ShareConnectionLogic.shareFullAccess - ? showConfigText - : (genConfigProcess ? generatingConfigText : generateConfigText) - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareAmneziaGenerateClicked() - enabled = true - genConfigProcess = false - fl.contentY = tfShareCode.mapToItem(fl.contentItem, 0, 0).y - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.bottomMargin: 20 - Layout.preferredHeight: 200 - - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareAmneziaCodeText - - visible: tfShareCode.textArea.length > 0 - } - - - ShareConnectionButtonCopyType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - copyText: tfShareCode.textArea.text - } - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save AmneziaVPN config"), "amnezia_config.vpn", "*.vpn", tfShareCode.textArea.text) - } - } - - Image { - id: image_share_code - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: width - smooth: false - - Timer { - property int idx: 0 - interval: 1000 - running: root.pageActive && ShareConnectionLogic.shareAmneziaQrCodeTextSeriesLength > 0 - repeat: true - onTriggered: { - idx++ - if (idx >= ShareConnectionLogic.shareAmneziaQrCodeTextSeriesLength) { - idx = 0 - } - image_share_code.source = ShareConnectionLogic.shareAmneziaQrCodeTextSeries[idx] - } - } - - visible: ShareConnectionLogic.shareAmneziaQrCodeTextSeriesLength > 0 - } - - LabelType { - Layout.fillWidth: true - text: qsTr("Scan QR code using AmneziaVPN mobile") - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoCloak.qml b/client/ui/qml/Pages/Share/PageShareProtoCloak.qml deleted file mode 100644 index 92bcb832..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoCloak.qml +++ /dev/null @@ -1,99 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Cloak - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share Cloak Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("Note: Cloak protocol using same password for all connections") - } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareCloakGenerateClicked() - enabled = true - genConfigProcess = false - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: 200 - - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareCloakText - - visible: tfShareCode.textArea.length > 0 - } - - ShareConnectionButtonCopyType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - copyText: tfShareCode.textArea.text - } - - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save AmneziaVPN config"), "amnezia_config_cloak.json", "*.json", tfShareCode.textArea.text) - } - } - - } - } - -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml b/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml deleted file mode 100644 index 7e41650a..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml +++ /dev/null @@ -1,131 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Ikev2 - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share IKEv2 Settings") - } - - TextAreaType { - id: tfCert - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareIkev2CertText - - visible: false - } - - TextAreaType { - id: tfMobileConfig - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareIkev2MobileConfigText - - visible: false - } - - TextAreaType { - id: tfStrongSwanConfig - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareIkev2StrongSwanConfigText - - visible: false - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - -// LabelType { -// id: lb_desc -// Layout.fillWidth: true -// Layout.topMargin: 10 - -// horizontalAlignment: Text.AlignHCenter - -// wrapMode: Text.Wrap -// text: qsTr("Note: ShadowSocks protocol using same password for all connections") -// } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareIkev2GenerateClicked() - enabled = true - genConfigProcess = false - } - } - - ShareConnectionButtonType { - Layout.topMargin: 30 - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: qsTr("Export p12 certificate") - enabled: tfCert.textArea.length > 0 - visible: tfCert.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Export p12 certificate"), "amnezia_ikev2_cert_for_windows.p12", "*.p12", tfCert.textArea.text) - } - } - - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: qsTr("Export config for Apple") - enabled: tfMobileConfig.textArea.length > 0 - visible: tfMobileConfig.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Export config for Apple"), "amnezia_for_apple.plist", "*.plist", tfMobileConfig.textArea.text) - } - } - - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: qsTr("Export config for StrongSwan") - enabled: tfStrongSwanConfig.textArea.length > 0 - visible: tfStrongSwanConfig.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Export config for StrongSwan"), "amnezia_for_StrongSwan.profile", "*.profile", tfStrongSwanConfig.textArea.text) - } - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml b/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml deleted file mode 100644 index 4246be21..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml +++ /dev/null @@ -1,96 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.OpenVpn - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share OpenVPN Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("New encryption keys pair will be generated.") - } - - ShareConnectionButtonType { - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareOpenVpnGenerateClicked() - genConfigProcess = false - enabled = true - } - } - - TextAreaType { - id: tfShareCode - Layout.topMargin: 20 - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareOpenVpnCodeText - - visible: tfShareCode.textArea.length > 0 - } - - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - - copyText: tfShareCode.textArea.text - } - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save OpenVPN config"), "amnezia_for_openvpn.ovpn", "*.ovpn", tfShareCode.textArea.text) - } - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoSftp.qml b/client/ui/qml/Pages/Share/PageShareProtoSftp.qml deleted file mode 100644 index 8f990ac1..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoSftp.qml +++ /dev/null @@ -1,21 +0,0 @@ -import QtQuick -import QtQuick.Controls -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Sftp - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Share SFTP settings") - } - - } diff --git a/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml b/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml deleted file mode 100644 index 91f5e8d2..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml +++ /dev/null @@ -1,115 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.ShadowSocks - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share ShadowSocks Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("Note: ShadowSocks protocol using same password for all connections") - } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareShadowSocksGenerateClicked() - enabled = true - genConfigProcess = false - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareShadowSocksText - - visible: tfShareCode.textArea.length > 0 - } - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - Layout.bottomMargin: 20 - - start_text: qsTr("Copy config") - copyText: tfShareCode.textArea.text - } - - LabelType { - height: 20 - visible: tfConnString.length > 0 - text: qsTr("Connection string") - } - TextFieldType { - id: tfConnString - height: 100 - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - text: ShareConnectionLogic.lineEditShareShadowSocksStringText - visible: tfConnString.length > 0 - - readOnly: true - } - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - start_text: qsTr("Copy string") - copyText: tfConnString.text - } - - Image { - id: label_share_ss_qr_code - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: width - smooth: false - source: ShareConnectionLogic.shareShadowSocksQrCodeText - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml b/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml deleted file mode 100644 index 1c68f6cb..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml +++ /dev/null @@ -1,20 +0,0 @@ -import QtQuick -import QtQuick.Controls -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.TorWebSite - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Share Tor Web site") - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml b/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml deleted file mode 100644 index 7265de81..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml +++ /dev/null @@ -1,103 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.WireGuard - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share WireGuard Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("New encryption keys pair will be generated.") - } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareWireGuardGenerateClicked() - enabled = true - genConfigProcess = false - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareWireGuardCodeText - - visible: tfShareCode.textArea.length > 0 - } - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - - copyText: tfShareCode.textArea.text - } - - ShareConnectionButtonType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save OpenVPN config"), "amnezia_for_wireguard.conf", "*.conf", tfShareCode.textArea.text) - } - } - - Image { - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: width - smooth: false - source: ShareConnectionLogic.shareWireGuardQrCodeText - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtocolBase.qml b/client/ui/qml/Pages/Share/PageShareProtocolBase.qml deleted file mode 100644 index 603abdfa..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtocolBase.qml +++ /dev/null @@ -1,19 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./.." -import "../../Controls" -import "../../Config" - -PageBase { - id: root - property var protocol: ProtocolEnum.Any - page: PageEnum.ProtocolShare - logic: ShareConnectionLogic - - readonly property string generateConfigText: qsTr("Generate config") - readonly property string generatingConfigText: qsTr("Generating config...") - readonly property string showConfigText: qsTr("Show config") - property bool genConfigProcess: false -} diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml new file mode 100644 index 00000000..8dffbbce --- /dev/null +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -0,0 +1,94 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + Component.onCompleted: PageController.enableTabBar(false) + Component.onDestruction: PageController.enableTabBar(true) + + SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + } + + FlickableType { + id: fl + anchors.fill: parent + contentHeight: content.height + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + Repeater { + model: proxyServersModel + delegate: Item { + implicitWidth: parent.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + headerText: qsTr("Removing services from %1").arg(name) + } + + ProgressBarType { + id: progressBar + + Layout.fillWidth: true + Layout.topMargin: 32 + + Timer { + id: timer + + interval: 300 + repeat: true + running: true + onTriggered: { + progressBar.value += 0.003 + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 8 + + text: qsTr("Usually it takes no more than 5 minutes") + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml new file mode 100644 index 00000000..cc49e4f0 --- /dev/null +++ b/client/ui/qml/Pages2/PageHome.qml @@ -0,0 +1,535 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 +import ContainersModelFilters 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + property string defaultColor: "#1C1D21" + + property string borderColor: "#2C2D30" + + property string defaultServerName: ServersModel.defaultServerName + property string defaultServerHostName: ServersModel.defaultServerHostName + property string defaultContainerName: ContainersModel.defaultContainerName + + Connections { + target: PageController + + function onRestorePageHomeState(isContainerInstalled) { + buttonContent.state = "expanded" + if (isContainerInstalled) { + containersDropDown.menuVisible = true + } + } + function onForceCloseDrawer() { + buttonContent.state = "collapsed" + } + } + + Connections { + target: ServersModel + + function onDefaultServerIndexChanged() { + updateDescriptions() + } + } + + Connections { + target: ContainersModel + + function onDefaultContainerChanged() { + updateDescriptions() + } + } + + function updateDescriptions() { + var description = "" + if (ServersModel.isDefaultServerHasWriteAccess()) { + if (SettingsController.isAmneziaDnsEnabled() + && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { + description += "Amnezia DNS | " + } + } else { + if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { + description += "Amnezia DNS | " + } + } + + collapsedServerMenuDescription.text = description + root.defaultContainerName + " | " + root.defaultServerHostName + expandedServersMenuDescription.text = description + root.defaultServerHostName + } + + Component.onCompleted: updateDescriptions() + + MouseArea { + anchors.fill: parent + enabled: buttonContent.state === "expanded" + onClicked: { + buttonContent.state = "collapsed" + } + } + + Item { + anchors.fill: parent + anchors.bottomMargin: buttonContent.collapsedHeight + + ConnectButton { + anchors.centerIn: parent + } + } + + MouseArea { + id: dragArea + + anchors.fill: buttonBackground + cursorShape: buttonContent.state === "collapsed" ? Qt.PointingHandCursor : Qt.ArrowCursor + hoverEnabled: true + + drag.target: buttonContent + drag.axis: Drag.YAxis + drag.maximumY: root.height - buttonContent.collapsedHeight + drag.minimumY: root.height - root.height * 0.9 + + /** If drag area is released at any point other than min or max y, transition to the other state */ + onReleased: { + if (buttonContent.state === "collapsed" && buttonContent.y < dragArea.drag.maximumY) { + buttonContent.state = "expanded" + return + } + if (buttonContent.state === "expanded" && buttonContent.y > dragArea.drag.minimumY) { + buttonContent.state = "collapsed" + return + } + } + + onEntered: { + collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor + collapsedButtonHeader.opacity = 0.8 + } + onExited: { + collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor + collapsedButtonHeader.opacity = 1 + } + onPressedChanged: { + collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor + collapsedButtonHeader.opacity = 0.7 + } + + + onClicked: { + if (buttonContent.state === "collapsed") { + buttonContent.state = "expanded" + } + } + } + + Rectangle { + id: buttonBackground + + anchors { left: buttonContent.left; right: buttonContent.right; top: buttonContent.top } + height: root.height + radius: 16 + color: root.defaultColor + border.color: root.borderColor + border.width: 1 + + Rectangle { + width: parent.radius + height: parent.radius + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + color: parent.color + } + } + + ColumnLayout { + id: buttonContent + + /** Initial height of button content */ + property int collapsedHeight: 0 + /** True when expanded objects should be visible */ + property bool expandedVisibility: buttonContent.state === "expanded" || (buttonContent.state === "collapsed" && dragArea.drag.active === true) + /** True when collapsed objects should be visible */ + property bool collapsedVisibility: buttonContent.state === "collapsed" && dragArea.drag.active === false + + Drag.active: dragArea.drag.active + anchors.right: root.right + anchors.left: root.left + y: root.height - buttonContent.height + + Component.onCompleted: { + buttonContent.state = "collapsed" + } + + /** Set once based on first implicit height change once all children are layed out */ + onImplicitHeightChanged: { + if (buttonContent.state === "collapsed" && collapsedHeight == 0) { + collapsedHeight = implicitHeight + } + } + + onStateChanged: { + if (buttonContent.state === "collapsed") { + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + PageController.drawerClose() + return + } + if (buttonContent.state === "expanded") { + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + PageController.drawerOpen() + return + } + } + + /** Two states of buttonContent, great place to add any future animations for the drawer */ + states: [ + State { + name: "collapsed" + PropertyChanges { + target: buttonContent + y: root.height - collapsedHeight + } + }, + State { + name: "expanded" + PropertyChanges { + target: buttonContent + y: dragArea.drag.minimumY + + } + } + ] + + transitions: [ + Transition { + from: "collapsed" + to: "expanded" + PropertyAnimation { + target: buttonContent + properties: "y" + duration: 200 + } + }, + Transition { + from: "expanded" + to: "collapsed" + PropertyAnimation { + target: buttonContent + properties: "y" + duration: 200 + } + } + ] + + DividerType { + Layout.topMargin: 10 + Layout.fillWidth: false + Layout.preferredWidth: 20 + Layout.preferredHeight: 2 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + visible: (buttonContent.collapsedVisibility || buttonContent.expandedVisibility) + } + + RowLayout { + Layout.topMargin: 14 + Layout.leftMargin: 24 + Layout.rightMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: buttonContent.collapsedVisibility + + spacing: 0 + + Header1TextType { + id: collapsedButtonHeader + Layout.maximumWidth: buttonContent.width - 48 - 18 - 12 // todo + + maximumLineCount: 2 + elide: Qt.ElideRight + + text: root.defaultServerName + horizontalAlignment: Qt.AlignHCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } + } + + ImageButtonType { + id: collapsedButtonChevron + + Layout.leftMargin: 8 + + hoverEnabled: false + image: "qrc:/images/controls/chevron-down.svg" + imageColor: "#d7d8db" + + icon.width: 18 + icon.height: 18 + backgroundRadius: 16 + horizontalPadding: 4 + topPadding: 4 + bottomPadding: 3 + + onClicked: { + if (buttonContent.state === "collapsed") { + buttonContent.state = "expanded" + } + } + } + } + + LabelTextType { + id: collapsedServerMenuDescription + Layout.bottomMargin: 44 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: buttonContent.collapsedVisibility + } + + ColumnLayout { + id: serversMenuHeader + + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.fillWidth: true + visible: buttonContent.expandedVisibility + + Header1TextType { + Layout.fillWidth: true + Layout.topMargin: 14 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: root.defaultServerName + horizontalAlignment: Qt.AlignHCenter + maximumLineCount: 2 + elide: Qt.ElideRight + } + + LabelTextType { + id: expandedServersMenuDescription + Layout.bottomMargin: 24 + Layout.fillWidth: true + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + spacing: 8 + + DropDownType { + id: containersDropDown + + rootButtonImageColor: "#0E0E11" + rootButtonBackgroundColor: "#D7D8DB" + rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8) + rootButtonBackgroundPressedColor: Qt.rgba(215, 216, 219, 0.65) + rootButtonHoveredBorderColor: "transparent" + rootButtonDefaultBorderColor: "transparent" + rootButtonTextTopMargin: 8 + rootButtonTextBottomMargin: 8 + + text: root.defaultContainerName + textColor: "#0E0E11" + headerText: qsTr("VPN protocol") + headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" + + rootButtonClickedFunction: function() { + ServersModel.currentlyProcessedIndex = serversMenuContent.currentIndex + containersDropDown.menuVisible = true + } + + listView: HomeContainersListView { + rootWidth: root.width + + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() + } + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + } + + Component.onCompleted: updateContainersModelFilters() + } + } + } + + Header2Type { + Layout.fillWidth: true + Layout.topMargin: 48 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + visible: buttonContent.expandedVisibility + + headerText: qsTr("Servers") + } + } + + Flickable { + id: serversContainer + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: 16 + contentHeight: col.implicitHeight + implicitHeight: root.height - (root.height * 0.1) - serversMenuHeader.implicitHeight - 52 //todo 52 is tabbar height + visible: buttonContent.expandedVisibility + clip: true + + ScrollBar.vertical: ScrollBar { + id: scrollBar + policy: serversContainer.height >= serversContainer.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn + } + + Keys.onUpPressed: scrollBar.decrease() + Keys.onDownPressed: scrollBar.increase() + + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + ButtonGroup { + id: serversRadioButtonGroup + } + + ListView { + id: serversMenuContent + width: parent.width + height: serversMenuContent.contentItem.height + + model: ServersModel + currentIndex: ServersModel.defaultIndex + + clip: true + interactive: false + + delegate: Item { + id: menuContentDelegate + + property variant delegateData: model + + implicitWidth: serversMenuContent.width + implicitHeight: serverRadioButtonContent.implicitHeight + + ColumnLayout { + id: serverRadioButtonContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 0 + + RowLayout { + VerticalRadioButton { + id: serverRadioButton + + Layout.fillWidth: true + + text: name + descriptionText: { + var description = "" + if (hasWriteAccess) { + if (SettingsController.isAmneziaDnsEnabled() + && ContainersModel.isAmneziaDnsContainerInstalled(index)) { + description += "Amnezia DNS | " + } + } else { + if (containsAmneziaDns) { + description += "Amnezia DNS | " + } + } + + return description += hostName + } + + checked: index === serversMenuContent.currentIndex + checkable: !ConnectionController.isConnected + + ButtonGroup.group: serversRadioButtonGroup + + onClicked: { + if (ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection")) + return + } + + serversMenuContent.currentIndex = index + + ServersModel.currentlyProcessedIndex = index + ServersModel.defaultIndex = index + } + + MouseArea { + anchors.fill: serverRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + ImageButtonType { + image: "qrc:/images/controls/settings.svg" + imageColor: "#D7D8DB" + + implicitWidth: 56 + implicitHeight: 56 + + z: 1 + + onClicked: function() { + ServersModel.currentlyProcessedIndex = index + PageController.goToPage(PageEnum.PageSettingsServerInfo) + buttonContent.state = "collapsed" + } + } + } + + DividerType { + Layout.fillWidth: true + Layout.leftMargin: 0 + Layout.rightMargin: 0 + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml new file mode 100644 index 00000000..237a8b46 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -0,0 +1,329 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: AwgConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("AmneziaWG settings") + } + + TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true + Layout.topMargin: 40 + + headerText: qsTr("Port") + textFieldText: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: junkPacketCountTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Junk packet count") + textFieldText: junkPacketCount + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + console.log("1") + if (textFieldText === "") { + textFieldText = "0" + } + + if (textFieldText !== junkPacketCount) { + junkPacketCount = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: junkPacketMinSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Junk packet minimum size") + textFieldText: junkPacketMinSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== junkPacketMinSize) { + junkPacketMinSize = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: junkPacketMaxSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Junk packet maximum size") + textFieldText: junkPacketMaxSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== junkPacketMaxSize) { + junkPacketMaxSize = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: initPacketJunkSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Init packet junk size") + textFieldText: initPacketJunkSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== initPacketJunkSize) { + initPacketJunkSize = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: responsePacketJunkSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Response packet junk size") + textFieldText: responsePacketJunkSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== responsePacketJunkSize) { + responsePacketJunkSize = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: initPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Init packet magic header") + textFieldText: initPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== initPacketMagicHeader) { + initPacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: responsePacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Response packet magic header") + textFieldText: responsePacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== responsePacketMagicHeader) { + responsePacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: transportPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Transport packet magic header") + textFieldText: transportPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== transportPacketMagicHeader) { + transportPacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: underloadPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Underload packet magic header") + textFieldText: underloadPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== underloadPacketMagicHeader) { + underloadPacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + BasicButtonType { + Layout.topMargin: 24 + Layout.leftMargin: -8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove AmneziaWG") + + onClicked: { + questionDrawer.headerText = qsTr("Remove AmneziaWG from server?") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + enabled: underloadPacketMagicHeaderTextField.errorText === "" && + transportPacketMagicHeaderTextField.errorText === "" && + responsePacketMagicHeaderTextField.errorText === "" && + initPacketMagicHeaderTextField.errorText === "" && + responsePacketJunkSizeTextField.errorText === "" && + initPacketJunkSizeTextField.errorText === "" && + junkPacketMaxSizeTextField.errorText === "" && + junkPacketMinSizeTextField.errorText === "" && + junkPacketCountTextField.errorText === "" && + portTextField.errorText === "" + + text: qsTr("Save and Restart Amnezia") + + onClicked: { + forceActiveFocus() + PageController.showBusyIndicator(true) + InstallController.updateContainer(AwgConfigModel.getConfig()) + PageController.showBusyIndicator(false) + } + } + } + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml new file mode 100644 index 00000000..78e666a7 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -0,0 +1,173 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: CloakConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Cloak settings") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 32 + + headerText: qsTr("Disguised as traffic from") + textFieldText: site + + textField.onEditingFinished: { + if (textFieldText !== site) { + var tmpText = textFieldText + tmpText = tmpText.toLocaleLowerCase() + + var indexHttps = tmpText.indexOf("https://") + if (indexHttps === 0) { + tmpText = textFieldText.substring(8) + } else { + site = textFieldText + } + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Port") + textFieldText: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + DropDownType { + id: cipherDropDown + Layout.fillWidth: true + Layout.topMargin: 16 + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + listView: ListViewWithRadioButtonType { + id: cipherListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : "chacha20-ietf-poly1305" } + ListElement { name : "xchacha20-ietf-poly1305" } + ListElement { name : "aes-256-gcm" } + ListElement { name : "aes-192-gcm" } + ListElement { name : "aes-128-gcm" } + } + + clickedFunction: function() { + cipherDropDown.text = selectedText + cipher = cipherDropDown.text + cipherDropDown.menuVisible = false + } + + Component.onCompleted: { + cipherDropDown.text = cipher + + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + currentIndex = i + } + } + } + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("Save and Restart Amnezia") + + onClicked: { + forceActiveFocus() + PageController.showBusyIndicator(true) + InstallController.updateContainer(CloakConfigModel.getConfig()) + PageController.showBusyIndicator(false) + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml new file mode 100644 index 00000000..55cdcf04 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -0,0 +1,407 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: OpenVpnConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("OpenVPN settings") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 32 + + headerText: qsTr("VPN Addresses Subnet") + textFieldText: subnetAddress + + textField.onEditingFinished: { + if (textFieldText !== subnetAddress) { + subnetAddress = textFieldText + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: qsTr("Network protocol") + } + + TransportProtoSelector { + Layout.fillWidth: true + Layout.topMargin: 16 + rootWidth: root.width + + enabled: isTransportProtoEditable + + currentIndex: { + return transportProto === "tcp" ? 1 : 0 + } + + onCurrentIndexChanged: { + if (transportProto === "tcp" && currentIndex === 0) { + transportProto = "udp" + } else if (transportProto === "udp" && currentIndex === 1) { + transportProto = "tcp" + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 40 + + enabled: isPortEditable + + headerText: qsTr("Port") + textFieldText: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + SwitcherType { + id: autoNegotiateEncryprionSwitcher + + Layout.fillWidth: true + Layout.topMargin: 24 + + text: qsTr("Auto-negotiate encryption") + checked: autoNegotiateEncryprion + + onCheckedChanged: { + if (checked !== autoNegotiateEncryprion) { + autoNegotiateEncryprion = checked + } + } + } + + DropDownType { + id: hashDropDown + Layout.fillWidth: true + Layout.topMargin: 20 + + enabled: !autoNegotiateEncryprionSwitcher.checked + + descriptionText: qsTr("Hash") + headerText: qsTr("Hash") + + listView: ListViewWithRadioButtonType { + id: hashListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : qsTr("SHA512") } + ListElement { name : qsTr("SHA384") } + ListElement { name : qsTr("SHA256") } + ListElement { name : qsTr("SHA3-512") } + ListElement { name : qsTr("SHA3-384") } + ListElement { name : qsTr("SHA3-256") } + ListElement { name : qsTr("whirlpool") } + ListElement { name : qsTr("BLAKE2b512") } + ListElement { name : qsTr("BLAKE2s256") } + ListElement { name : qsTr("SHA1") } + } + + clickedFunction: function() { + hashDropDown.text = selectedText + hash = hashDropDown.text + hashDropDown.menuVisible = false + } + + Component.onCompleted: { + hashDropDown.text = hash + + for (var i = 0; i < hashListView.model.count; i++) { + if (hashListView.model.get(i).name === hashDropDown.text) { + currentIndex = i + } + } + } + } + } + + DropDownType { + id: cipherDropDown + Layout.fillWidth: true + Layout.topMargin: 16 + + enabled: !autoNegotiateEncryprionSwitcher.checked + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + listView: ListViewWithRadioButtonType { + id: cipherListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : qsTr("AES-256-GCM") } + ListElement { name : qsTr("AES-192-GCM") } + ListElement { name : qsTr("AES-128-GCM") } + ListElement { name : qsTr("AES-256-CBC") } + ListElement { name : qsTr("AES-192-CBC") } + ListElement { name : qsTr("AES-128-CBC") } + ListElement { name : qsTr("ChaCha20-Poly1305") } + ListElement { name : qsTr("ARIA-256-CBC") } + ListElement { name : qsTr("CAMELLIA-256-CBC") } + ListElement { name : qsTr("none") } + } + + clickedFunction: function() { + cipherDropDown.text = selectedText + cipher = cipherDropDown.text + cipherDropDown.menuVisible = false + } + + Component.onCompleted: { + cipherDropDown.text = cipher + + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + currentIndex = i + } + } + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.preferredHeight: checkboxLayout.implicitHeight + color: "#1C1D21" + radius: 16 + + ColumnLayout { + id: checkboxLayout + + anchors.fill: parent + CheckBoxType { + Layout.fillWidth: true + + text: qsTr("TLS auth") + checked: tlsAuth + + onCheckedChanged: { + if (checked !== tlsAuth) { + tlsAuth = checked + } + } + } + + DividerType {} + + CheckBoxType { + Layout.fillWidth: true + + text: qsTr("Block DNS requests outside of VPN") + checked: blockDns + + onCheckedChanged: { + if (checked !== blockDns) { + blockDns = checked + } + } + } + } + } + + SwitcherType { + id: additionalClientCommandsSwitcher + Layout.fillWidth: true + Layout.topMargin: 32 + + checked: additionalClientCommands !== "" + + text: qsTr("Additional client configuration commands") + + onCheckedChanged: { + if (!checked) { + additionalClientCommands = "" + } + } + } + + TextAreaType { + Layout.fillWidth: true + Layout.topMargin: 16 + + visible: additionalClientCommandsSwitcher.checked + + textAreaText: additionalClientCommands + placeholderText: qsTr("Commands:") + + textArea.onEditingFinished: { + if (additionalClientCommands !== textAreaText) { + additionalClientCommands = textAreaText + } + } + } + + SwitcherType { + id: additionalServerCommandsSwitcher + Layout.fillWidth: true + Layout.topMargin: 16 + + checked: additionalServerCommands !== "" + + text: qsTr("Additional server configuration commands") + + onCheckedChanged: { + if (!checked) { + additionalServerCommands = "" + } + } + } + + TextAreaType { + Layout.fillWidth: true + Layout.topMargin: 16 + + visible: additionalServerCommandsSwitcher.checked + + textAreaText: additionalServerCommands + placeholderText: qsTr("Commands:") + + textArea.onEditingFinished: { + if (additionalServerCommands !== textAreaText) { + additionalServerCommands = textAreaText + } + } + } + + BasicButtonType { + Layout.topMargin: 24 + Layout.leftMargin: -8 + implicitHeight: 32 + + visible: ContainersModel.getCurrentlyProcessedContainerIndex() === ContainerEnum.OpenVpn + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove OpenVPN") + + onClicked: { + questionDrawer.headerText = qsTr("Remove OpenVpn from server?") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("Save and Restart Amnezia") + + onClicked: { + forceActiveFocus() + PageController.showBusyIndicator(true) + InstallController.updateContainer(OpenVpnConfigModel.getConfig()) + PageController.showBusyIndicator(false) + } + } + } + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml new file mode 100644 index 00000000..967b605b --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -0,0 +1,208 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" settings") + } + } + + FlickableType { + id: fl + anchors.top: header.bottom + anchors.left: parent.left + anchors.right: parent.right + contentHeight: content.height + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 32 + + ListView { + width: parent.width + height: contentItem.height + clip: true + interactive: false + model: ProtocolsModel + + delegate: Item { + implicitWidth: parent.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + + LabelWithButtonType { + id: button + + Layout.fillWidth: true + + text: qsTr("Show connection options") + + clickedFunction: function() { + configContentDrawer.open() + } + + MouseArea { + anchors.fill: button + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + + DrawerType { + id: configContentDrawer + + width: parent.width + height: parent.height * 0.9 + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + configContentDrawer.visible = false + } + } + + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin + + ColumnLayout { + id: configContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2Type { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Connection options %1").arg(protocolName) + } + + TextArea { + id: configText + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + padding: 0 + leftPadding: 0 + height: 24 + + color: "#D7D8DB" + selectionColor: "#633303" + selectedTextColor: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + text: rawConfig + + wrapMode: Text.Wrap + + background: Rectangle { + color: "transparent" + } + } + } + } + } + } + } + } + + LabelWithButtonType { + id: removeButton + + width: parent.width + + visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + textColor: "#EB5757" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml new file mode 100644 index 00000000..2453281f --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -0,0 +1,151 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: ShadowSocksConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("ShadowSocks settings") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 40 + + headerText: qsTr("Port") + textFieldText: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + DropDownType { + id: cipherDropDown + Layout.fillWidth: true + Layout.topMargin: 20 + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + listView: ListViewWithRadioButtonType { + id: cipherListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : "chacha20-ietf-poly1305" } + ListElement { name : "xchacha20-ietf-poly1305" } + ListElement { name : "aes-256-gcm" } + ListElement { name : "aes-192-gcm" } + ListElement { name : "aes-128-gcm" } + } + + clickedFunction: function() { + cipherDropDown.text = selectedText + cipher = cipherDropDown.text + cipherDropDown.menuVisible = false + } + + Component.onCompleted: { + cipherDropDown.text = cipher + + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + currentIndex = i + } + } + } + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("Save and Restart Amnezia") + + onClicked: { + forceActiveFocus() + PageController.showBusyIndicator(true) + InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) + PageController.showBusyIndicator(false) + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml new file mode 100644 index 00000000..10fe6f56 --- /dev/null +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -0,0 +1,95 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: "Amnezia DNS" + descriptionText: qsTr("A DNS service is installed on your server, and it is only accessible via VPN.\n") + + qsTr("The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab.") + } + + LabelWithButtonType { + id: removeButton + + Layout.topMargin: 24 + width: parent.width + + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + textColor: "#EB5757" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + + QuestionDrawer { + id: questionDrawer + } + } + } +} diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml new file mode 100644 index 00000000..b12302dd --- /dev/null +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -0,0 +1,275 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + Connections { + target: InstallController + + function onUpdateContainerFinished() { + PageController.showNotificationMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: SftpConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("SFTP settings") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: qsTr("Host") + descriptionText: ServersModel.getCurrentlyProcessedServerHostName() + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Port") + descriptionText: port + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Login") + descriptionText: username + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Password") + descriptionText: password + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + BasicButtonType { + visible: !GC.isMobile() + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Mount folder on device") + + onClicked: { + PageController.showBusyIndicator(true) + InstallController.mountSftpDrive(port, password, username) + PageController.showBusyIndicator(false) + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + readonly property string windowsFirstLink: "WinFsp" + readonly property string windowsSecondLink: "SSHFS-Win" + + readonly property string macosFirstLink: "macFUSE" + readonly property string macosSecondLink: "SSHFS" + + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } + textFormat: Text.RichText + text: { + var str = qsTr("In order to mount remote SFTP folder as local drive, perform following steps:
") + if (Qt.platform.os === "windows") { + str += qsTr("
1. Install the latest version of ") + windowsFirstLink + "\n" + str += qsTr("
2. Install the latest version of ") + windowsSecondLink + "\n" + } else if (Qt.platform.os === "osx") { + str += qsTr("
1. Install the latest version of ") + macosFirstLink + "\n" + str += qsTr("
2. Install the latest version of ") + macosSecondLink + "\n" + } else if (Qt.platform.os === "linux") { + return "" + } else return "" + + return str + } + + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } + } + + BasicButtonType { + Layout.topMargin: 16 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#FBB26A" + + text: qsTr("Detailed instructions") + + onClicked: { +// Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") + } + } + + BasicButtonType { + Layout.topMargin: 24 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove SFTP and all data stored there") + + onClicked: { + questionDrawer.headerText = qsTr("Remove SFTP and all data stored there?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + } + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml new file mode 100644 index 00000000..3bfa5bb0 --- /dev/null +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -0,0 +1,150 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + Connections { + target: InstallController + + function onUpdateContainerFinished() { + PageController.showNotificationMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Tor website settings") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: qsTr("Website address") + descriptionText: { + var config = ContainersModel.getCurrentlyProcessedContainerConfig() + var containerIndex = ContainersModel.getCurrentlyProcessedContainerIndex() + return config[ContainerProps.containerTypeToString(containerIndex)]["site"] + } + + descriptionOnTop: true + textColor: "#FBB26A" + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 40 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + onLinkActivated: Qt.openUrlExternally(link) + textFormat: Text.RichText + text: qsTr("Use Tor Browser to open this url.") + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("After installation it takes several minutes while your onion site will become available in the Tor Network.") + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("When configuring WordPress set the this onion address as domain.") + } + + BasicButtonType { + Layout.topMargin: 24 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove website") + + onClicked: { + questionDrawer.headerText = qsTr("The site with all data will be removed from the tor network.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml new file mode 100644 index 00000000..92575dda --- /dev/null +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -0,0 +1,131 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Settings") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Servers") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/server.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsServersList) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Connection") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/radio.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsConnection) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Application") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/app.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsApplication) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Backup") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/save.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsBackup) + } + } + + DividerType {} + + LabelWithButtonType { + id: about + Layout.fillWidth: true + + text: qsTr("About AmneziaVPN") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/amnezia.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsAbout) + } + } + + DividerType {} + + LabelWithButtonType { + visible: GC.isDesktop() + Layout.fillWidth: true + Layout.preferredHeight: about.height + + text: qsTr("Close application") + leftImageSource: "qrc:/images/controls/x-circle.svg" + isLeftImageHoverEnabled: false + + clickedFunction: function() { + PageController.closeApplication() + } + } + + DividerType { + visible: GC.isDesktop() + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml new file mode 100644 index 00000000..eaa9eb3d --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -0,0 +1,200 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Image { + id: image + source: "qrc:/images/amneziaBigLogo.png" + + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.preferredWidth: 291 + Layout.preferredHeight: 224 + } + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Support the project with a donation") + horizontalAlignment: Text.AlignHCenter + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + horizontalAlignment: Text.AlignHCenter + + height: 20 + font.pixelSize: 14 + + text: qsTr("This is a free and open source application. If you like it, support the developers with a donation. ") + + qsTr("And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application.") + color: "#CCCAC8" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Card on Patreon") + + onClicked: function() { + Qt.openUrlExternally(qsTr("https://www.patreon.com/amneziavpn")) + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Show other methods on Github") + + onClicked: Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client#donate") + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Contacts") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Telegram group") + descriptionText: qsTr("To discuss features") + leftImageSource: "qrc:/images/controls/telegram.svg" + + clickedFunction: function() { + Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en")) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Mail") + descriptionText: qsTr("For reviews and bug reports") + leftImageSource: "qrc:/images/controls/mail.svg" + + clickedFunction: function() { + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Github") + leftImageSource: "qrc:/images/controls/github.svg" + + clickedFunction: function() { + Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client")) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Website") + leftImageSource: "qrc:/images/controls/amnezia.svg" + + clickedFunction: function() { + Qt.openUrlExternally(qsTr("https://amnezia.org")) + } + } + + DividerType {} + + CaptionTextType { + Layout.fillWidth: true + Layout.topMargin: 40 + + horizontalAlignment: Text.AlignHCenter + + text: SettingsController.getAppVersion() + color: "#878B91" + } + + BasicButtonType { + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 8 + Layout.bottomMargin: 16 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#FBB26A" + + text: qsTr("Check for updates") + + onClicked: { + Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml new file mode 100644 index 00000000..05e468f0 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -0,0 +1,172 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Application") + } + + SwitcherType { + visible: GC.isMobile() + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Allow application screenshots") + + checked: SettingsController.isScreenshotsEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isScreenshotsEnabled()) { + SettingsController.toggleScreenshotsEnabled(checked) + } + } + } + + DividerType { + visible: GC.isMobile() + } + + SwitcherType { + visible: !GC.isMobile() + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Auto start") + descriptionText: qsTr("Launch the application every time the device is starts") + + checked: SettingsController.isAutoStartEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isAutoStartEnabled()) { + SettingsController.toggleAutoStart(checked) + } + } + } + + DividerType { + visible: !GC.isMobile() + } + + SwitcherType { + visible: !GC.isMobile() + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Start minimized") + descriptionText: qsTr("Launch application minimized") + + checked: SettingsController.isStartMinimizedEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isStartMinimizedEnabled()) { + SettingsController.toggleStartMinimized(checked) + } + } + } + + DividerType { + visible: !GC.isMobile() + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Language") + descriptionText: LanguageModel.currentLanguageName + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + selectLanguageDrawer.open() + } + } + + SelectLanguageDrawer { + id: selectLanguageDrawer + } + + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Logging") + descriptionText: SettingsController.isLoggingEnabled ? qsTr("Enabled") : qsTr("Disabled") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsLogging) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Reset settings and remove all data from the application") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Reset settings and remove all data from the application?") + questionDrawer.descriptionText = qsTr("All settings will be reset to default. All installed AmneziaVPN services will still remain on the server.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + SettingsController.clearSettings() + PageController.replaceStartPage() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + DividerType {} + + QuestionDrawer { + id: questionDrawer + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml new file mode 100644 index 00000000..81be0465 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -0,0 +1,156 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Components" +import "../Controls2/TextTypes" + +PageType { + id: root + + Connections { + target: SettingsController + + function onChangeSettingsErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onRestoreBackupFinished() { + PageController.showNotificationMessage(qsTr("Settings restored from backup file")) + //goToStartPage() + PageController.goToPageHome() + } + + function onImportBackupFromOutside(filePath) { + restoreBackup(filePath) + } + } + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Backup") + } + + ListItemTitleType { + Layout.fillWidth: true + Layout.topMargin: 10 + + text: qsTr("Configuration backup") + } + + CaptionTextType { + Layout.fillWidth: true + Layout.topMargin: -12 + + text: qsTr("You can save your settings to a backup file to restore them the next time you install the application.") + color: "#878B91" + } + + BasicButtonType { + id: makeBackupButton + Layout.fillWidth: true + Layout.topMargin: 14 + + text: qsTr("Make a backup") + + onClicked: { + var fileName = "" + if (GC.isMobile()) { + fileName = "AmneziaVPN.backup" + } else { + fileName = SystemController.getFileName(qsTr("Save backup file"), + qsTr("Backup files (*.backup)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", + true, + ".backup") + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + SettingsController.backupAppConfig(fileName) + PageController.showBusyIndicator(false) + PageController.showNotificationMessage(qsTr("Backup file saved")) + } + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: -8 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Restore from backup") + + onClicked: { + var filePath = SystemController.getFileName(qsTr("Open backup file"), + qsTr("Backup files (*.backup)")) + if (filePath !== "") { + restoreBackup(filePath) + } + } + } + } + } + + function restoreBackup(filePath) { + questionDrawer.headerText = qsTr("Import settings from a backup file?") + questionDrawer.descriptionText = qsTr("All current settings will be reset"); + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(filePath) + PageController.showBusyIndicator(false) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + + QuestionDrawer { + id: questionDrawer + } +} diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml new file mode 100644 index 00000000..565ae7db --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -0,0 +1,132 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 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 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Connection") + } + + SwitcherType { + visible: !GC.isMobile() + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Auto connect") + descriptionText: qsTr("Connect to VPN on app start") + + checked: SettingsController.isAutoConnectEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isAutoConnectEnabled()) { + SettingsController.toggleAutoConnect(checked) + } + } + } + + DividerType { + visible: !GC.isMobile() + } + + SwitcherType { + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Use AmneziaDNS") + descriptionText: qsTr("If AmneziaDNS is installed on the server") + + checked: SettingsController.isAmneziaDnsEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isAmneziaDnsEnabled()) { + SettingsController.toggleAmneziaDns(checked) + } + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("DNS servers") + descriptionText: qsTr("If AmneziaDNS is not used or installed") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsDns) + } + } + + DividerType {} + + LabelWithButtonType { + visible: GC.isDesktop() + + Layout.fillWidth: true + + text: qsTr("Site-based split tunneling") + descriptionText: qsTr("Allows you to select which sites you want to access through the VPN") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsSplitTunneling) + } + } + + DividerType { + visible: GC.isDesktop() + } + + LabelWithButtonType { + visible: false + + Layout.fillWidth: true + + text: qsTr("App-based split tunneling") + descriptionText: qsTr("Allows you to use the VPN only for certain applications") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + } + } + + DividerType { + visible: false + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml new file mode 100644 index 00000000..5670464f --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -0,0 +1,129 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("DNS servers") + } + + ParagraphTextType { + Layout.fillWidth: true + text: qsTr("If AmneziaDNS is not used or installed") + } + + TextFieldWithHeaderType { + id: primaryDns + + Layout.fillWidth: true + headerText: qsTr("Primary DNS") + + textFieldText: SettingsController.primaryDns + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressRegExp() + } + } + + TextFieldWithHeaderType { + id: secondaryDns + + Layout.fillWidth: true + headerText: qsTr("Secondary DNS") + + textFieldText: SettingsController.secondaryDns + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressRegExp() + } + } + + BasicButtonType { + Layout.fillWidth: true + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Restore default") + + onClicked: function() { + questionDrawer.headerText = qsTr("Restore default DNS settings?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + SettingsController.primaryDns = "1.1.1.1" + primaryDns.textFieldText = SettingsController.primaryDns + SettingsController.secondaryDns = "1.0.0.1" + secondaryDns.textFieldText = SettingsController.secondaryDns + PageController.showNotificationMessage(qsTr("Settings have been reset")) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + + text: qsTr("Save") + + onClicked: function() { + if (primaryDns.textFieldText !== SettingsController.primaryDns) { + SettingsController.primaryDns = primaryDns.textFieldText + } + if (secondaryDns.textFieldText !== SettingsController.secondaryDns) { + SettingsController.secondaryDns = secondaryDns.textFieldText + } + PageController.showNotificationMessage(qsTr("Settings saved")) + } + } + } + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml new file mode 100644 index 00000000..840c41d4 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -0,0 +1,178 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore + +import PageEnum 1.0 + +import "../Controls2" +import "../Config" +import "../Components" +import "../Controls2/TextTypes" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Logging") + } + + SwitcherType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Save logs") + + checked: SettingsController.isLoggingEnabled + onCheckedChanged: { + if (checked !== SettingsController.isLoggingEnabled) { + SettingsController.isLoggingEnabled = checked + } + } + } + + RowLayout { + Layout.fillWidth: true + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/folder-open.svg" + + onClicked: SettingsController.openLogsFolder() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Open folder with logs") + color: "#D7D8DB" + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/save.svg" + + onClicked: { + var fileName = "" + if (GC.isMobile()) { + fileName = "AmneziaVPN.log" + } else { + fileName = SystemController.getFileName(qsTr("Save"), + qsTr("Logs files (*.log)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", + true, + ".log") + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + SettingsController.exportLogsFile(fileName) + PageController.showBusyIndicator(false) + PageController.showNotificationMessage(qsTr("Logs file saved")) + } + } + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Save logs to file") + color: "#D7D8DB" + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/delete.svg" + + onClicked: function() { + questionDrawer.headerText = qsTr("Clear logs?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.showBusyIndicator(true) + SettingsController.clearLogs() + PageController.showBusyIndicator(false) + PageController.showNotificationMessage(qsTr("Logs have been cleaned up")) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Clear logs") + color: "#D7D8DB" + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml new file mode 100644 index 00000000..3eb07ce9 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -0,0 +1,198 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + Connections { + target: InstallController + + function onScanServerFinished(isInstalledContainerFound) { + var message = "" + if (isInstalledContainerFound) { + message = qsTr("All installed containers have been added to the application") + } else { + message = qsTr("No new installed containers found") + } + + PageController.showErrorMessage(message) + } + + function onRemoveCurrentlyProcessedServerFinished(finishedMessage) { + if (!ServersModel.getServersCount()) { + PageController.replaceStartPage() + } else { + PageController.goToStartPage() + PageController.goToPage(PageEnum.PageSettingsServersList) + } + PageController.showNotificationMessage(finishedMessage) + } + + function onRemoveAllContainersFinished(finishedMessage) { + PageController.closePage() // close deInstalling page + PageController.showNotificationMessage(finishedMessage) + } + + function onRemoveCurrentlyProcessedContainerFinished(finishedMessage) { + PageController.closePage() // close deInstalling page + PageController.closePage() // close page with remove button + PageController.showNotificationMessage(finishedMessage) + } + } + + Connections { + target: SettingsController + function onChangeSettingsFinished(finishedMessage) { + PageController.showNotificationMessage(finishedMessage) + } + } + + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + content.isServerWithWriteAccess = ServersModel.isCurrentlyProcessedServerHasWriteAccess() + } + } + + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + property bool isServerWithWriteAccess: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + LabelWithButtonType { + visible: content.isServerWithWriteAccess + Layout.fillWidth: true + + text: qsTr("Clear Amnezia cache") + descriptionText: qsTr("May be needed when changing other settings") + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Clear cached profiles?") + questionDrawer.descriptionText = qsTr("") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.showBusyIndicator(true) + SettingsController.clearCachedProfiles() + PageController.showBusyIndicator(false) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + DividerType { + visible: content.isServerWithWriteAccess + } + + LabelWithButtonType { + visible: content.isServerWithWriteAccess + Layout.fillWidth: true + + text: qsTr("Check the server for previously installed Amnezia services") + descriptionText: qsTr("Add them to the application if they were not displayed") + + clickedFunction: function() { + PageController.showBusyIndicator(true) + InstallController.scanServerForInstalledContainers() + PageController.showBusyIndicator(false) + } + } + + DividerType { + visible: content.isServerWithWriteAccess + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Remove server from application") + textColor: "#EB5757" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Remove server?") + questionDrawer.descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.showBusyIndicator(true) + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + ConnectionController.closeConnection() + } + InstallController.removeCurrentlyProcessedServer() + PageController.showBusyIndicator(false) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + DividerType {} + + LabelWithButtonType { + visible: content.isServerWithWriteAccess + Layout.fillWidth: true + + text: qsTr("Clear server from Amnezia software") + textColor: "#EB5757" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Clear server from Amnezia software?") + questionDrawer.descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + ConnectionController.closeConnection() + } + InstallController.removeAllContainers() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + DividerType { + visible: content.isServerWithWriteAccess + } + + QuestionDrawer { + id: questionDrawer + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml new file mode 100644 index 00000000..e2e7868c --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -0,0 +1,170 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 +import ProtocolProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + Connections { + target: PageController + + function onGoToPageSettingsServerServices() { + tabBar.currentIndex = 1 + } + } + + SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + } + + ColumnLayout { + anchors.fill: parent + + spacing: 16 + + Repeater { + id: header + model: proxyServersModel + + delegate: ColumnLayout { + id: content + + Layout.topMargin: 20 + + BackButtonType { + } + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + actionButtonImage: "qrc:/images/controls/edit-3.svg" + + headerText: name + descriptionText: { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + return credentialsLogin + " · " + hostName + } else { + return hostName + } + } + + actionButtonFunction: function() { + serverNameEditDrawer.visible = true + } + } + + DrawerType { + id: serverNameEditDrawer + + width: root.width + height: root.height * 0.35 + + onVisibleChanged: { + if (serverNameEditDrawer.visible) { + serverName.textField.forceActiveFocus() + } + } + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + TextFieldWithHeaderType { + id: serverName + + Layout.fillWidth: true + headerText: qsTr("Server name") + textFieldText: name + textField.maximumLength: 30 + } + + BasicButtonType { + Layout.fillWidth: true + + text: qsTr("Save") + + onClicked: { + if (serverName.textFieldText !== name) { + name = serverName.textFieldText + serverNameEditDrawer.visible = false + } + } + } + } + } + } + } + + TabBar { + id: tabBar + + Layout.fillWidth: true + + background: Rectangle { + color: "transparent" + } + + TabButtonType { + visible: protocolsPage.installedProtocolsCount + width: protocolsPage.installedProtocolsCount ? undefined : 0 + isSelected: tabBar.currentIndex === 0 + text: qsTr("Protocols") + } + TabButtonType { + visible: servicesPage.installedServicesCount + width: servicesPage.installedServicesCount ? undefined : 0 + isSelected: tabBar.currentIndex === 1 + text: qsTr("Services") + } + TabButtonType { + isSelected: tabBar.currentIndex === 2 + text: qsTr("Data") + } + } + + StackLayout { + Layout.preferredWidth: root.width + Layout.preferredHeight: root.height - tabBar.implicitHeight - header.implicitHeight + + currentIndex: tabBar.currentIndex + + PageSettingsServerProtocols { + id: protocolsPage + stackView: root.stackView + } + PageSettingsServerServices { + id: servicesPage + stackView: root.stackView + } + PageSettingsServerData { + stackView: root.stackView + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml new file mode 100644 index 00000000..a961cf56 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -0,0 +1,146 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" settings") + } + } + + FlickableType { + id: fl + anchors.top: header.bottom + anchors.left: parent.left + anchors.right: parent.right + contentHeight: content.height + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 32 + + ListView { + id: protocols + width: parent.width + height: protocols.contentItem.height + clip: true + interactive: false + model: ProtocolsModel + + delegate: Item { + implicitWidth: protocols.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + + LabelWithButtonType { + id: button + + Layout.fillWidth: true + + text: protocolName + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + switch (protocolIndex) { + case ProtocolEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; + } + PageController.goToPage(protocolPage); + } + + MouseArea { + anchors.fill: button + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + } + } + } + + LabelWithButtonType { + id: removeButton + + width: parent.width + + visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + textColor: "#EB5757" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml new file mode 100644 index 00000000..21401bf5 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -0,0 +1,64 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 +import ContainersModelFilters 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + property var installedProtocolsCount + + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + SettingsContainersListView { + id: settingsContainersListView + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + settingsContainersListView.updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() + } + root.installedProtocolsCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + } + + Component.onCompleted: updateContainersModelFilters() + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml new file mode 100644 index 00000000..4f832651 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -0,0 +1,64 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 +import ContainersModelFilters 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + property var installedServicesCount + + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + SettingsContainersListView { + id: settingsContainersListView + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + settingsContainersListView.updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters() + } + root.installedServicesCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + } + + Component.onCompleted: updateContainersModelFilters() + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml new file mode 100644 index 00000000..040aafc3 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -0,0 +1,101 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Servers") + } + } + + FlickableType { + anchors.top: header.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight + + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + ListView { + id: servers + width: parent.width + height: servers.contentItem.height + + model: ServersModel + + clip: true + interactive: false + + delegate: Item { + implicitWidth: servers.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelWithButtonType { + id: server + Layout.fillWidth: true + + text: name + descriptionText: { + var servicesNameString = "" + var servicesName = ContainersModel.getAllInstalledServicesName(index) + for (var i = 0; i < servicesName.length; i++) { + servicesNameString += servicesName[i] + " · " + } + + return servicesNameString + hostName + } + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + ServersModel.currentlyProcessedIndex = index + PageController.goToPage(PageEnum.PageSettingsServerInfo) + } + } + + DividerType {} + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml new file mode 100644 index 00000000..873ae997 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -0,0 +1,419 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + property bool pageEnabled: { + return !ConnectionController.isConnected + } + + Connections { + target: SitesController + + function onFinished(message) { + PageController.showNotificationMessage(message) + } + + function onErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + } + + QtObject { + id: routeMode + property int allSites: 0 + property int onlyForwardSites: 1 + property int allExceptSites: 2 + } + + property list routeModesModel: [ + onlyForwardSites, + allExceptSites + ] + + QtObject { + id: onlyForwardSites + property string name: qsTr("Addresses from the list should be accessed via VPN") + property int type: routeMode.onlyForwardSites + } + QtObject { + id: allExceptSites + property string name: qsTr("Addresses from the list should not be accessed via VPN") + property int type: routeMode.allExceptSites + } + + function getRouteModesModelIndex() { + var currentRouteMode = SitesModel.routeMode + if ((routeMode.onlyForwardSites === currentRouteMode) || (routeMode.allSites === currentRouteMode)) { + return 0 + } else if (routeMode.allExceptSites === currentRouteMode) { + return 1 + } + } + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + + RowLayout { + HeaderType { + enabled: root.pageEnabled + + Layout.fillWidth: true + Layout.leftMargin: 16 + + headerText: qsTr("Split tunneling") + } + + SwitcherType { + id: switcher + + enabled: root.pageEnabled + + Layout.fillWidth: true + Layout.rightMargin: 16 + + checked: SitesModel.isSplitTunnelingEnabled() + onToggled: { + SitesModel.toggleSplitTunneling(checked) + selector.text = root.routeModesModel[getRouteModesModelIndex()].name + } + } + } + + DropDownType { + id: selector + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + drawerHeight: 0.4375 + + enabled: root.pageEnabled + + headerText: qsTr("Mode") + + listView: ListViewWithRadioButtonType { + rootWidth: root.width + + model: root.routeModesModel + + currentIndex: getRouteModesModelIndex() + + clickedFunction: function() { + selector.text = selectedText + selector.menuVisible = false + if (SitesModel.routeMode !== root.routeModesModel[currentIndex].type) { + SitesModel.routeMode = root.routeModesModel[currentIndex].type + } + } + + Component.onCompleted: { + if (root.routeModesModel[currentIndex].type === SitesModel.routeMode) { + selector.text = selectedText + } else { + selector.text = root.routeModesModel[0].name + } + } + + Connections { + target: SitesModel + function onRouteModeChanged() { + currentIndex = getRouteModesModelIndex() + } + } + } + } + } + + FlickableType { + anchors.top: header.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight + addSiteButton.implicitHeight + addSiteButton.anchors.bottomMargin + addSiteButton.anchors.topMargin + + enabled: root.pageEnabled + + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + ListView { + id: sites + width: parent.width + height: sites.contentItem.height + + model: SitesModel + + clip: true + interactive: false + + delegate: Item { + implicitWidth: sites.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelWithButtonType { + Layout.fillWidth: true + + text: url + descriptionText: ip + rightImageSource: "qrc:/images/controls/trash.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Remove ") + url + "?" + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + SitesController.removeSite(index) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + DividerType {} + + QuestionDrawer { + id: questionDrawer + } + } + } + } + } + } + + Rectangle { + anchors.fill: addSiteButton + anchors.bottomMargin: -24 + color: "#0E0E11" + opacity: 0.8 + } + + RowLayout { + id: addSiteButton + + enabled: root.pageEnabled + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 24 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.bottomMargin: 24 + + TextFieldWithHeaderType { + Layout.fillWidth: true + + textFieldPlaceholderText: qsTr("Site or IP") + buttonImageSource: "qrc:/images/controls/plus.svg" + + clickedFunc: function() { + PageController.showBusyIndicator(true) + SitesController.addSite(textFieldText) + textFieldText = "" + PageController.showBusyIndicator(false) + } + } + + ImageButtonType { + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/more-vertical.svg" + imageColor: "#D7D8DB" + + onClicked: function () { + moreActionsDrawer.open() + } + } + } + + DrawerType { + id: moreActionsDrawer + + width: parent.width + height: parent.height * 0.4375 + + FlickableType { + anchors.fill: parent + contentHeight: moreActionsDrawerContent.height + ColumnLayout { + id: moreActionsDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import/Export Sites") + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Import") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + importSitesDrawer.open() + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + text: qsTr("Save site list") + + clickedFunction: function() { + var fileName = "" + if (GC.isMobile()) { + fileName = "amnezia_sites.json" + } else { + fileName = SystemController.getFileName(qsTr("Save sites"), + qsTr("Sites files (*.json)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/amnezia_sites", + true, + ".json") + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + SitesController.exportSites(fileName) + moreActionsDrawer.close() + PageController.showBusyIndicator(false) + } + } + } + + DividerType {} + } + } + } + + DrawerType { + id: importSitesDrawer + + width: parent.width + height: parent.height * 0.4375 + + BackButtonType { + id: importSitesDrawerBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + importSitesDrawer.close() + } + } + + FlickableType { + anchors.top: importSitesDrawerBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + contentHeight: importSitesDrawerContent.height + + ColumnLayout { + id: importSitesDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import a list of sites") + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Replace site list") + + clickedFunction: function() { + var fileName = SystemController.getFileName(qsTr("Open sites file"), + qsTr("Sites files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, true) + } + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + text: qsTr("Add imported sites to existing ones") + + clickedFunction: function() { + var fileName = SystemController.getFileName(qsTr("Open sites file"), + qsTr("Sites files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, false) + } + } + } + + function importSites(fileName, replaceExistingSites) { + PageController.showBusyIndicator(true) + SitesController.importSites(fileName, replaceExistingSites) + PageController.showBusyIndicator(false) + importSitesDrawer.close() + moreActionsDrawer.close() + } + + DividerType {} + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml new file mode 100644 index 00000000..07189eb7 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -0,0 +1,126 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + Connections { + target: ImportController + + function onQrDecodingFinished() { + PageController.closePage() + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + } + } + + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 0 + + BackButtonType { + Layout.topMargin: 20 + } + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Server connection") + descriptionText: qsTr("Do not use connection code from public sources. It may have been created to intercept your data.\n +It's okay as long as it's from someone you trust.") + } + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 48 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + text: qsTr("What do you have?") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: !ServersModel.getServersCount() ? qsTr("File with connection settings or backup") : qsTr("File with connection settings") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/folder-open.svg" + + clickedFunction: function() { + var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.backup)" : + "Config files (*.vpn *.ovpn *.conf)" + var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter) + if (fileName !== "") { + if (fileName.indexOf(".backup") !== -1 && !ServersModel.getServersCount()) { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(fileName) + PageController.showBusyIndicator(false) + } else { + ImportController.extractConfigFromFile(fileName) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + } + } + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + visible: GC.isMobile() + + text: qsTr("QR-code") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/qr-code.svg" + + clickedFunction: function() { + ImportController.startDecodingQr() + if (Qt.platform.os === "ios") { + PageController.goToPage(PageEnum.PageSetupWizardQrReader) + } + } + } + + DividerType { + visible: GC.isMobile() + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Key as text") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/text-cursor.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSetupWizardTextKey) + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml new file mode 100644 index 00000000..5c32b0c5 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -0,0 +1,141 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Configure your server") + } + + TextFieldWithHeaderType { + id: hostname + + Layout.fillWidth: true + headerText: qsTr("Server IP address [:port]") + textFieldPlaceholderText: qsTr("255.255.255.255:88") + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressPortRegExp() + } + + onTextFieldTextChanged: { + textField.text = textField.text.replace(/^\s+|\s+$/g, ''); + } + } + + TextFieldWithHeaderType { + id: username + + Layout.fillWidth: true + headerText: qsTr("Login to connect via SSH") + textFieldPlaceholderText: "root" + } + + TextFieldWithHeaderType { + id: secretData + + property bool hidePassword: true + + Layout.fillWidth: true + headerText: qsTr("Password / SSH private key") + textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal + buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") + : "" + + clickedFunc: function() { + hidePassword = !hidePassword + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + + text: qsTr("Continue") + + onClicked: function() { + if (!isCredentialsFilled()) { + return + } + + InstallController.setShouldCreateServer(true) + InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) + + PageController.showBusyIndicator(true) + var isConnectionOpened = InstallController.checkSshConnection() + PageController.showBusyIndicator(false) + if (!isConnectionOpened) { + return + } + + PageController.goToPage(PageEnum.PageSetupWizardEasy) + } + } + + LabelTextType { + Layout.fillWidth: true + Layout.topMargin: 12 + + text: qsTr("All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties") + } + } + } + + function isCredentialsFilled() { + var hasEmptyField = false + + if (hostname.textFieldText === "") { + hostname.errorText = qsTr("Ip address cannot be empty") + hasEmptyField = true + } else if (!hostname.textField.acceptableInput) { + hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88") + } + + if (username.textFieldText === "") { + username.errorText = qsTr("Login cannot be empty") + hasEmptyField = true + } + if (secretData.textFieldText === "") { + secretData.errorText = qsTr("Password/private key cannot be empty") + hasEmptyField = true + } + return !hasEmptyField + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml new file mode 100644 index 00000000..c393d0ce --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -0,0 +1,206 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerProps 1.0 +import ProtocolProps 1.0 + +import "./" +import "../Controls2" +import "../Config" + +PageType { + id: root + + property bool isEasySetup: true + + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isEasySetupContainer" + value: true + } + ] + sorters: RoleSorter { + roleName: "easySetupOrder" + sortOrder: Qt.DescendingOrder + } + } + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + setupLaterButton.anchors.bottomMargin + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 16 + + HeaderType { + id: header + + implicitWidth: parent.width + headerTextMaximumLineCount: 10 + + headerText: qsTr("What is the level of internet control in your region?") + } + + ButtonGroup { + id: buttonGroup + } + + ListView { + id: containers + width: parent.width + height: containers.contentItem.height + spacing: 16 + + currentIndex: 1 + clip: true + interactive: false + model: proxyContainersModel + + property int dockerContainer + property int containerDefaultPort + property int containerDefaultTransportProto + + delegate: Item { + implicitWidth: containers.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + CardType { + id: card + + Layout.fillWidth: true + + headerText: easySetupHeader + bodyText: easySetupDescription + + ButtonGroup.group: buttonGroup + + onClicked: function() { + isEasySetup = true + var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) + + containers.dockerContainer = dockerContainer + containers.containerDefaultPort = ProtocolProps.defaultPort(defaultContainerProto) + containers.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto) + } + } + } + + Component.onCompleted: { + if (index === containers.currentIndex) { + card.checked = true + card.clicked() + } + } + } + } + + DividerType { + implicitWidth: parent.width + } + + CardType { + implicitWidth: parent.width + + headerText: qsTr("Set up a VPN yourself") + bodyText: qsTr("I want to choose a VPN protocol") + + ButtonGroup.group: buttonGroup + + onClicked: function() { + isEasySetup = false + } + } + + Item { + implicitWidth: 1 + implicitHeight: 54 + } + + BasicButtonType { + id: continueButton + + implicitWidth: parent.width + + text: qsTr("Continue") + + onClicked: function() { + if (root.isEasySetup) { + ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) + PageController.goToPage(PageEnum.PageSetupWizardInstalling) + InstallController.install(containers.dockerContainer, + containers.containerDefaultPort, + containers.containerDefaultTransportProto) + } else { + PageController.goToPage(PageEnum.PageSetupWizardProtocols) + } + } + } + + BasicButtonType { + id: setupLaterButton + + implicitWidth: parent.width + anchors.topMargin: 8 + anchors.bottomMargin: 24 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + visible: { + if (PageController.isTriggeredByConnectButton()) { + PageController.setTriggeredBtConnectButton(false) + + return ContainersModel.isAnyContainerInstalled() + } + + + return true + } + + text: qsTr("Set up later") + + onClicked: function() { + PageController.goToPage(PageEnum.PageSetupWizardInstalling) + InstallController.addEmptyServer() + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml new file mode 100644 index 00000000..a223f646 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -0,0 +1,164 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + Component.onCompleted: PageController.enableTabBar(false) + Component.onDestruction: PageController.enableTabBar(true) + + property bool isTimerRunning: true + property string progressBarText: qsTr("Usually it takes no more than 5 minutes") + + Connections { + target: InstallController + + function onInstallContainerFinished(finishedMessage, isServiceInstall) { + if (!ConnectionController.isConnected && !isServiceInstall) { + ContainersModel.setDefaultContainer(ContainersModel.getCurrentlyProcessedContainerIndex()) + } + + PageController.goToStartPage() + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { + PageController.restorePageHomeState(true) + } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { + PageController.goToPage(PageEnum.PageSettingsServersList, false) + PageController.goToPage(PageEnum.PageSettingsServerInfo, false) + if (isServiceInstall) { + PageController.goToPageSettingsServerServices() + } + } else { + PageController.goToPage(PageEnum.PageHome) + } + + PageController.showNotificationMessage(finishedMessage) + } + + function onInstallServerFinished(finishedMessage) { + if (!ConnectionController.isConnected) { + ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); + } + + PageController.goToStartPage() + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSetupWizardStart)) { + PageController.replaceStartPage() + } + + PageController.showNotificationMessage(finishedMessage) + } + + function onServerAlreadyExists(serverIndex) { + PageController.goToStartPage() + ServersModel.currentlyProcessedIndex = serverIndex + PageController.goToPage(PageEnum.PageSettingsServerInfo, false) + + PageController.showErrorMessage(qsTr("The server has already been added to the application")) + } + + function onServerIsBusy(isBusy) { + if (isBusy) { + root.progressBarText = qsTr("Amnezia has detected that your server is currently ") + + qsTr("busy installing other software. Amnezia installation ") + + qsTr("will pause until the server finishes installing other software") + root.isTimerRunning = false + } else { + root.progressBarText = qsTr("Usually it takes no more than 5 minutes") + root.isTimerRunning = true + } + } + } + + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + } + + FlickableType { + anchors.fill: parent + contentHeight: content.height + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + ListView { + id: container + width: parent.width + height: container.contentItem.height + currentIndex: -1 + clip: true + interactive: false + model: proxyContainersModel + + delegate: Item { + implicitWidth: container.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + headerText: qsTr("Installing") + descriptionText: name + } + + ProgressBarType { + id: progressBar + + Layout.fillWidth: true + Layout.topMargin: 32 + + Timer { + id: timer + + interval: 300 + repeat: true + running: root.isTimerRunning + onTriggered: { + progressBar.value += 0.003 + } + } + } + + ParagraphTextType { + id: progressText + + Layout.fillWidth: true + Layout.topMargin: 8 + + text: root.progressBarText + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml new file mode 100644 index 00000000..7698c755 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -0,0 +1,241 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerProps 1.0 +import ProtocolProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + } + + FlickableType { + anchors.fill: parent + contentHeight: content.height + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + ListView { + id: processedContainerListView + width: parent.width + height: contentItem.height + currentIndex: -1 + clip: true + interactive: false + model: proxyContainersModel + + delegate: Item { + implicitWidth: processedContainerListView.width + implicitHeight: (delegateContent.implicitHeight > root.height) ? delegateContent.implicitHeight : root.height + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + BackButtonType { + id: backButton + + Layout.topMargin: 20 + Layout.rightMargin: -16 + Layout.leftMargin: -16 + } + + HeaderType { + id: header + + Layout.fillWidth: true + + headerText: qsTr("Installing %1").arg(name) + descriptionText: description + } + + BasicButtonType { + id: showDetailsButton + + Layout.topMargin: 16 + Layout.leftMargin: -8 + + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#FBB26A" + + text: qsTr("More detailed") + + onClicked: { + showDetailsDrawer.open() + } + } + + DrawerType { + id: showDetailsDrawer + + width: parent.width + height: parent.height * 0.9 + + BackButtonType { + id: showDetailsBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + showDetailsDrawer.close() + } + } + + FlickableType { + anchors.top: showDetailsBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: { + var emptySpaceHeight = parent.height - showDetailsBackButton.implicitHeight - showDetailsBackButton.anchors.topMargin + return (showDetailsDrawerContent.height > emptySpaceHeight) ? + showDetailsDrawerContent.height : emptySpaceHeight + } + + ColumnLayout { + id: showDetailsDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2Type { + id: showDetailsDrawerHeader + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: name + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + text: detailedDescription + textFormat: Text.MarkdownText + } + + + Rectangle { + Layout.fillHeight: true + color: "transparent" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.bottomMargin: 32 + + text: qsTr("Close") + + onClicked: function() { + showDetailsDrawer.close() + } + } + } + } + } + + ParagraphTextType { + id: transportProtoHeader + + Layout.topMargin: 16 + + text: qsTr("Network protocol") + } + + TransportProtoSelector { + id: transportProtoSelector + + Layout.fillWidth: true + rootWidth: root.width + } + + TextFieldWithHeaderType { + id: port + + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Port") + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + } + + Rectangle { + Layout.fillHeight: true + color: "transparent" + } + + BasicButtonType { + id: installButton + + Layout.fillWidth: true + Layout.bottomMargin: 32 + + text: qsTr("Install") + + onClicked: function() { + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.install(dockerContainer, port.textFieldText, transportProtoSelector.currentIndex) + } + } + + Component.onCompleted: { + var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) + + if (ProtocolProps.defaultPort(defaultContainerProto) < 0) { + port.visible = false + } else { + port.textFieldText = ProtocolProps.getPortForInstall(defaultContainerProto) + } + transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) + + port.enabled = ProtocolProps.defaultPortChangeable(defaultContainerProto) + var protocolSelectorVisible = ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) + transportProtoSelector.visible = protocolSelectorVisible + transportProtoHeader.visible = protocolSelectorVisible + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml new file mode 100644 index 00000000..71b33eb0 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -0,0 +1,118 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" + +PageType { + id: root + + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "serviceType" + value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "isSupported" + value: true + } + ] + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottomMargin: 20 + + Item { + width: parent.width + height: header.implicitHeight + + HeaderType { + id: header + + anchors.fill: parent + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + width: parent.width + + headerText: qsTr("VPN protocol") + descriptionText: qsTr("Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP.") + } + } + + ListView { + id: containers + width: parent.width + height: containers.contentItem.height + currentIndex: -1 + clip: true + interactive: false + model: proxyContainersModel + + delegate: Item { + implicitWidth: containers.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelWithButtonType { + id: container + Layout.fillWidth: true + + text: name + descriptionText: description + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + } + } + + DividerType {} + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml new file mode 100644 index 00000000..1fa71592 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml @@ -0,0 +1,85 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 +import QRCodeReader 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + BackButtonType { + id: backButton + anchors.left: parent.left + anchors.top: parent.top + + anchors.topMargin: 20 + } + + ParagraphTextType { + id: header + + property string progressString + + anchors.left: parent.left + anchors.top: backButton.bottom + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + text: qsTr("Point the camera at the QR code and hold for a couple of seconds. ") + progressString + } + + ProgressBarType { + id: progressBar + + anchors.left: parent.left + anchors.top: header.bottom + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + } + + Rectangle { + id: qrCodeRectange + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.top: progressBar.bottom + + anchors.topMargin: 34 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + anchors.bottomMargin: 34 + + color: "transparent" + //radius: 16 + + QRCodeReader { + id: qrCodeReader + + onCodeReaded: function(code) { + ImportController.parseQrCodeChunk(code) + progressBar.value = ImportController.getQrCodeScanProgressBarValue() + header.progressString = ImportController.getQrCodeScanProgressString() + } + + Component.onCompleted: { + qrCodeReader.setCameraSize(Qt.rect(qrCodeRectange.x, + qrCodeRectange.y, + qrCodeRectange.width, + qrCodeRectange.height)) + qrCodeReader.startReading() + } + Component.onDestruction: qrCodeReader.stopReading() + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml new file mode 100644 index 00000000..994ec200 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -0,0 +1,151 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + Connections { + target: PageController + + function onGoToPageViewConfig() { + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + } + + function onShowBusyIndicator(visible) { + busyIndicator.visible = visible + } + + function onClosePage() { + if (stackView.depth <= 1) { + return + } + stackView.pop() + } + + function onGoToPage(page, slide) { + var pagePath = PageController.getPagePath(page) + if (slide) { + stackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + } else { + stackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) + } + } + + function onGoToStartPage() { + while (stackView.depth > 1) { + stackView.pop() + } + } + } + + Connections { + target: SettingsController + + function onRestoreBackupFinished() { + PageController.showNotificationMessage(qsTr("Settings restored from backup file")) + PageController.replaceStartPage() + } + } + + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + + var currentPageName = stackView.currentItem.objectName + + if (currentPageName === PageController.getPagePath(PageEnum.PageSetupWizardInstalling)) { + PageController.closePage() + } + } + } + + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + spacing: 0 + + Image { + id: image + source: "qrc:/images/amneziaBigLogo.png" + + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 32 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.preferredWidth: 344 + Layout.preferredHeight: 279 + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 50 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Free service for creating a personal VPN on your server.") + + qsTr(" Helps you access blocked content without revealing your privacy, even to VPN providers.") + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("I have the data to connect") + + onClicked: { + connectionTypeSelection.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("I have nothing") + + onClicked: Qt.openUrlExternally("https://amnezia.org/instructions/0_starter-guide") + } + } + + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection + } + } + + BusyIndicatorType { + id: busyIndicator + anchors.centerIn: parent + z: 1 + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml new file mode 100644 index 00000000..4cdfc444 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -0,0 +1,78 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + BackButtonType { + Layout.topMargin: 20 + } + + HeaderType { + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Connection key") + descriptionText: qsTr("A line that starts with vpn://...") + } + + TextFieldWithHeaderType { + id: textKey + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Key") + textFieldPlaceholderText: "vpn://" + buttonText: qsTr("Insert") + + clickedFunc: function() { + textField.text = "" + textField.paste() + } + } + } + } + + BasicButtonType { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.bottomMargin: 32 + + text: qsTr("Continue") + + onClicked: function() { + ImportController.extractConfigFromCode(textKey.textFieldText) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml new file mode 100644 index 00000000..ac35651f --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -0,0 +1,154 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + property bool showContent: false + + Connections { + target: ImportController + + function onImportErrorOccurred(errorMessage) { + PageController.closePage() + PageController.showErrorMessage(errorMessage) + } + + function onImportFinished() { + if (ConnectionController.isConnected) { + ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); + } + + PageController.goToStartPage() + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSetupWizardStart)) { + PageController.replaceStartPage() + } + } + } + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + connectButton.implicitHeight + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HeaderType { + headerText: qsTr("New connection") + } + + RowLayout { + Layout.topMargin: 32 + spacing: 8 + + visible: fileName.text !== "" + + Image { + source: "qrc:/images/controls/file-cog-2.svg" + } + + Header2TextType { + id: fileName + + Layout.fillWidth: true + + text: ImportController.getConfigFileName() + wrapMode: Text.Wrap + } + } + + CaptionTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Do not use connection code from public sources. It could be created to intercept your data.") + color: "#878B91" + } + + BasicButtonType { + Layout.topMargin: 16 + Layout.leftMargin: -8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#FBB26A" + + text: showContent ? qsTr("Collapse content") : qsTr("Show content") + + onClicked: { + showContent = !showContent + } + } + + Rectangle { + Layout.fillWidth: true + Layout.bottomMargin: 16 + + implicitHeight: configContent.implicitHeight + + radius: 10 + color: "#1C1D21" + + visible: showContent + + ParagraphTextType { + id: configContent + + anchors.fill: parent + anchors.margins: 16 + + text: ImportController.getConfig() + } + } + } + } + + ColumnLayout { + id: connectButton + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + BasicButtonType { + Layout.fillWidth: true + Layout.bottomMargin: 32 + + text: qsTr("Connect") + onClicked: { + ImportController.importConfig() + } + } + } +} diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml new file mode 100644 index 00000000..ced7a5ff --- /dev/null +++ b/client/ui/qml/Pages2/PageShare.qml @@ -0,0 +1,395 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + enum ConfigType { + AmneziaConnection, + AmneziaFullAccess, + OpenVpn, + WireGuard + } + + Connections { + target: ExportController + + function onGenerateConfig(type) { + shareConnectionDrawer.needCloseButton = false + + shareConnectionDrawer.open() + shareConnectionDrawer.contentVisible = false + PageController.showBusyIndicator(true) + + switch (type) { + case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(); break; + case PageShare.ConfigType.AmneziaFullAccess: { + if (Qt.platform.os === "android") { + ExportController.generateFullAccessConfigAndroid(); + } else { + ExportController.generateFullAccessConfig(); + } + break; + } + case PageShare.ConfigType.OpenVpn: { + ExportController.generateOpenVpnConfig(); + shareConnectionDrawer.configCaption = qsTr("Save OpenVPN config") + shareConnectionDrawer.configExtension = ".ovpn" + shareConnectionDrawer.configFileName = "amnezia_for_openvpn" + break; + } + case PageShare.ConfigType.WireGuard: { + ExportController.generateWireGuardConfig(); + shareConnectionDrawer.configCaption = qsTr("Save WireGuard config") + shareConnectionDrawer.configExtension = ".conf" + shareConnectionDrawer.configFileName = "amnezia_for_wireguard" + break; + } + } + + PageController.showBusyIndicator(false) + + shareConnectionDrawer.needCloseButton = true + PageController.showTopCloseButton(true) + + shareConnectionDrawer.contentVisible = true + } + + function onExportErrorOccurred(errorMessage) { + shareConnectionDrawer.close() + PageController.showErrorMessage(errorMessage) + } + } + + property string fullConfigServerSelectorText + property string connectionServerSelectorText + property bool showContent: false + property bool shareButtonEnabled: true + property list connectionTypesModel: [ + amneziaConnectionFormat + ] + + QtObject { + id: amneziaConnectionFormat + property string name: qsTr("For the AmneziaVPN app") + property var type: PageShare.ConfigType.AmneziaConnection + } + QtObject { + id: openVpnConnectionFormat + property string name: qsTr("OpenVpn native format") + property var type: PageShare.ConfigType.OpenVpn + } + QtObject { + id: wireGuardConnectionFormat + property string name: qsTr("WireGuard native format") + property var type: PageShare.ConfigType.WireGuard + } + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 24 + + headerText: qsTr("Share VPN Access") + } + + Rectangle { + id: accessTypeSelector + + property int currentIndex + + Layout.topMargin: 32 + + implicitWidth: accessTypeSelectorContent.implicitWidth + implicitHeight: accessTypeSelectorContent.implicitHeight + + color: "#1C1D21" + radius: 16 + + RowLayout { + id: accessTypeSelectorContent + + spacing: 0 + + HorizontalRadioButton { + checked: accessTypeSelector.currentIndex === 0 + + implicitWidth: (root.width - 32) / 2 + text: qsTr("Connection") + + onClicked: { + accessTypeSelector.currentIndex = 0 + serverSelector.text = root.connectionServerSelectorText + } + } + + HorizontalRadioButton { + checked: root.currentIndex === 1 + + implicitWidth: (root.width - 32) / 2 + text: qsTr("Full access") + + onClicked: { + accessTypeSelector.currentIndex = 1 + serverSelector.text = root.fullConfigServerSelectorText + root.shareButtonEnabled = true + } + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: accessTypeSelector.currentIndex === 0 ? qsTr("Share VPN access without the ability to manage the server") : + qsTr("Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings.") + color: "#878B91" + } + + DropDownType { + id: serverSelector + + signal severSelectorIndexChanged + property int currentIndex: 0 + + Layout.fillWidth: true + Layout.topMargin: 16 + + drawerHeight: 0.4375 + + descriptionText: qsTr("Server") + headerText: qsTr("Server") + + listView: ListViewWithRadioButtonType { + id: serverSelectorListView + + rootWidth: root.width + imageSource: "qrc:/images/controls/check.svg" + + model: SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "hasWriteAccess" + value: true + } + ] + } + + currentIndex: 0 + + clickedFunction: function() { + handler() + + if (serverSelector.currentIndex !== serverSelectorListView.currentIndex) { + serverSelector.currentIndex = serverSelectorListView.currentIndex + serverSelector.severSelectorIndexChanged() + } + + if (accessTypeSelector.currentIndex !== 0) { + shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text + } + serverSelector.menuVisible = false + } + + Component.onCompleted: { + handler() + serverSelector.severSelectorIndexChanged() + } + + function handler() { + serverSelector.text = selectedText + root.fullConfigServerSelectorText = selectedText + root.connectionServerSelectorText = selectedText + ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex) + } + } + } + + DropDownType { + id: protocolSelector + + visible: accessTypeSelector.currentIndex === 0 + + Layout.fillWidth: true + Layout.topMargin: 16 + + drawerHeight: 0.5 + + descriptionText: qsTr("Protocol") + headerText: qsTr("Protocol") + + listView: ListViewWithRadioButtonType { + id: protocolSelectorListView + + rootWidth: root.width + imageSource: "qrc:/images/controls/check.svg" + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isInstalled" + value: true + }, + ValueFilter { + roleName: "isShareable" + value: true + } + ] + } + + currentIndex: 0 + + clickedFunction: function() { + handler() + + protocolSelector.menuVisible = false + } + + Component.onCompleted: { + if (accessTypeSelector.currentIndex === 0) { + handler() + } + } + + Connections { + target: serverSelector + + function onSeverSelectorIndexChanged() { + protocolSelectorListView.currentIndex = 0 + protocolSelectorListView.triggerCurrentItem() + } + } + + function handler() { + if (!proxyContainersModel.count) { + root.shareButtonEnabled = false + return + } else { + root.shareButtonEnabled = true + } + + protocolSelector.text = selectedText + root.connectionServerSelectorText = serverSelector.text + + shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) + + fillConnectionTypeModel() + } + + function fillConnectionTypeModel() { + root.connectionTypesModel = [amneziaConnectionFormat] + + var index = proxyContainersModel.mapToSource(currentIndex) + + if (index === ContainerProps.containerFromString("amnezia-openvpn")) { + root.connectionTypesModel.push(openVpnConnectionFormat) + } else if (index === ContainerProps.containerFromString("amnezia-wireguard")) { + root.connectionTypesModel.push(wireGuardConnectionFormat) + } + } + } + } + + DropDownType { + id: exportTypeSelector + + property int currentIndex: 0 + + Layout.fillWidth: true + Layout.topMargin: 16 + + drawerHeight: 0.4375 + + visible: accessTypeSelector.currentIndex === 0 + enabled: root.connectionTypesModel.length > 1 + + descriptionText: qsTr("Connection format") + headerText: qsTr("Connection format") + + listView: ListViewWithRadioButtonType { + onCurrentIndexChanged: { + exportTypeSelector.currentIndex = currentIndex + exportTypeSelector.text = selectedText + } + + rootWidth: root.width + + imageSource: "qrc:/images/controls/check.svg" + + model: root.connectionTypesModel + currentIndex: 0 + + clickedFunction: function() { + exportTypeSelector.text = selectedText + exportTypeSelector.currentIndex = currentIndex + exportTypeSelector.menuVisible = false + } + + Component.onCompleted: { + exportTypeSelector.text = selectedText + exportTypeSelector.currentIndex = currentIndex + } + } + } + + ShareConnectionDrawer { + id: shareConnectionDrawer + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 40 + + enabled: shareButtonEnabled + + text: qsTr("Share") + imageSource: "qrc:/images/controls/share-2.svg" + + onClicked: { + if (accessTypeSelector.currentIndex === 0) { + ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type) + } else { + ExportController.generateConfig(PageShare.ConfigType.AmneziaFullAccess) + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml new file mode 100644 index 00000000..ab02ace4 --- /dev/null +++ b/client/ui/qml/Pages2/PageStart.qml @@ -0,0 +1,251 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Shapes + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + Connections { + target: PageController + + function onGoToPageHome() { + tabBar.setCurrentIndex(0) + tabBarStackView.goToTabBarPage(PageEnum.PageHome) + + PageController.updateDrawerRootPage(PageEnum.PageHome) + } + + function onGoToPageSettings() { + tabBar.setCurrentIndex(2) + tabBarStackView.goToTabBarPage(PageEnum.PageSettings) + + PageController.updateDrawerRootPage(PageEnum.PageSettings) + } + + function onGoToPageViewConfig() { + var pagePath = PageController.getPagePath(PageEnum.PageSetupWizardViewConfig) + tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + + PageController.updateDrawerRootPage(PageEnum.PageSetupWizardViewConfig) + } + + function onShowBusyIndicator(visible) { + busyIndicator.visible = visible + tabBarStackView.enabled = !visible + tabBar.enabled = !visible + } + +// function onShowTopCloseButton(visible) { +// topCloseButton.visible = visible +// } + + function onEnableTabBar(enabled) { + tabBar.enabled = enabled + } + + function onClosePage() { + if (tabBarStackView.depth <= 1) { + return + } + tabBarStackView.pop() + } + + function onGoToPage(page, slide) { + var pagePath = PageController.getPagePath(page) + if (slide) { + tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + } else { + tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) + } + + PageController.updateDrawerRootPage(page) + } + + function onGoToStartPage() { + connectionTypeSelection.close() + while (tabBarStackView.depth > 1) { + tabBarStackView.pop() + } + } + } + + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + + var needCloseCurrentPage = false + var currentPageName = tabBarStackView.currentItem.objectName + + if (currentPageName === PageController.getPagePath(PageEnum.PageSetupWizardInstalling)) { + needCloseCurrentPage = true + } else if (currentPageName === PageController.getPagePath(PageEnum.PageDeinstalling)) { + needCloseCurrentPage = true + } + if (needCloseCurrentPage) { + PageController.closePage() + } + } + + function onUpdateContainerFinished(message) { + PageController.showNotificationMessage(message) + } + } + + Connections { + target: ConnectionController + + function onReconnectWithUpdatedContainer(message) { + PageController.showNotificationMessage(message) + } + } + + StackViewType { + id: tabBarStackView + + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: tabBar.top + + width: parent.width + height: root.height - tabBar.implicitHeight + + function goToTabBarPage(page) { + connectionTypeSelection.close() + + var pagePath = PageController.getPagePath(page) + tabBarStackView.clear(StackView.Immediate) + tabBarStackView.replace(pagePath, { "objectName" : pagePath }, StackView.Immediate) + + PageController.updateDrawerRootPage(page) + } + + Component.onCompleted: { + var pagePath = PageController.getPagePath(PageEnum.PageHome) + ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex + tabBarStackView.push(pagePath, { "objectName" : pagePath }) + } + +// onWidthChanged: { +// topCloseButton.x = tabBarStackView.x + tabBarStackView.width - +// topCloseButton.buttonWidth - topCloseButton.rightPadding +// } + } + + TabBar { + id: tabBar + + property int previousIndex: 0 + + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + topPadding: 8 + bottomPadding: 8 + leftPadding: 96 + rightPadding: 96 + + background: Shape { + width: parent.width + height: parent.height + + ShapePath { + startX: 0 + startY: 0 + + PathLine { x: width; y: 0 } + PathLine { x: width; y: height - 1 } + PathLine { x: 0; y: height - 1 } + PathLine { x: 0; y: 0 } + + strokeWidth: 1 + strokeColor: "#2C2D30" + fillColor: "#1C1D21" + } + } + + TabImageButtonType { + isSelected: tabBar.currentIndex === 0 + image: "qrc:/images/controls/home.svg" + onClicked: { + tabBarStackView.goToTabBarPage(PageEnum.PageHome) + ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex + tabBar.previousIndex = 0 + } + } + + TabImageButtonType { + id: shareTabButton + + Connections { + target: ServersModel + + function onModelReset() { + var hasServerWithWriteAccess = ServersModel.hasServerWithWriteAccess() + shareTabButton.visible = hasServerWithWriteAccess + shareTabButton.width = hasServerWithWriteAccess ? undefined : 0 + } + } + + visible: ServersModel.hasServerWithWriteAccess() + width: ServersModel.hasServerWithWriteAccess() ? undefined : 0 + + isSelected: tabBar.currentIndex === 1 + image: "qrc:/images/controls/share-2.svg" + onClicked: { + tabBarStackView.goToTabBarPage(PageEnum.PageShare) + tabBar.previousIndex = 1 + } + } + + TabImageButtonType { + isSelected: tabBar.currentIndex === 2 + image: "qrc:/images/controls/settings-2.svg" + onClicked: { + tabBarStackView.goToTabBarPage(PageEnum.PageSettings) + tabBar.previousIndex = 2 + } + } + + TabImageButtonType { + isSelected: tabBar.currentIndex === 3 + image: "qrc:/images/controls/plus.svg" + onClicked: { + connectionTypeSelection.open() + } + } + } + + BusyIndicatorType { + id: busyIndicator + anchors.centerIn: parent + z: 1 + } + +// TopCloseButtonType { +// id: topCloseButton + +// x: tabBarStackView.width - topCloseButton.buttonWidth - topCloseButton.rightPadding +// z: 1 +// } + + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection + + onAboutToHide: { + tabBar.setCurrentIndex(tabBar.previousIndex) + } + } +} diff --git a/client/ui/qml/main.qml b/client/ui/qml/main.qml deleted file mode 100644 index 4e20cac4..00000000 --- a/client/ui/qml/main.qml +++ /dev/null @@ -1,388 +0,0 @@ -import QtCore -import QtQuick -import QtQuick.Controls -import QtQuick.Controls.Basic -import QtQuick.Dialogs -import QtQuick.Layouts -import QtQuick.Window - -import Qt.labs.platform as LabsPlatform -import Qt.labs.folderlistmodel as LabsFolderlistmodel - -import PageEnum 1.0 -import PageType 1.0 - -import "Controls" -import "Pages" -import "Pages/Protocols" -import "Pages/Share" -import "Pages/ClientInfo" -import "Config" - -Window { - property var pages: ({}) - property var protocolPages: ({}) - property var sharePages: ({}) - property var clientInfoPages: ({}) - - id: root - visible: true - width: GC.screenWidth - height: GC.screenHeight - minimumWidth: GC.isDesktop() ? 360 : 0 - minimumHeight: GC.isDesktop() ? 640 : 0 - onClosing: function() { - console.debug("QML onClosing signal") - UiLogic.onCloseWindow() - } - - title: "AmneziaVPN" - - function gotoPage(type, page, reset, slide) { - - let p_obj; - if (type === PageType.Basic) p_obj = pages[page] - else if (type === PageType.Proto) p_obj = protocolPages[page] - else if (type === PageType.ShareProto) p_obj = sharePages[page] - else if (type === PageType.ClientInfo) p_obj = clientInfoPages[page] - else return - - //console.debug("QML gotoPage " + type + " " + page + " " + p_obj) - - if (pageLoader.depth > 0) { - pageLoader.currentItem.deactivated() - } - - if (slide) { - pageLoader.push(p_obj, {}, StackView.PushTransition) - } else { - pageLoader.push(p_obj, {}, StackView.Immediate) - } - - if (reset) { - p_obj.logic.onUpdatePage(); - } - - p_obj.activated(reset) - } - - function close_page() { - if (pageLoader.depth <= 1) { - if (GC.isMobile()) { - root.close() - } - return - } - - pageLoader.currentItem.deactivated() - pageLoader.pop() - } - - function set_start_page(page, slide) { - if (pageLoader.depth > 0) { - pageLoader.currentItem.deactivated() - } - - pageLoader.clear() - if (slide) { - pageLoader.push(pages[page], {}, StackView.PushTransition) - } else { - pageLoader.push(pages[page], {}, StackView.Immediate) - } - if (page === PageEnum.Start) { - UiLogic.pushButtonBackFromStartVisible = !pageLoader.empty - UiLogic.onUpdatePage(); - } - } - - Rectangle { - y: 0 - anchors.fill: parent - color: "white" - } - - StackView { - id: pageLoader - y: 0 - anchors.fill: parent - focus: true - - onCurrentItemChanged: function() { - UiLogic.currentPageValue = currentItem.page - } - - onDepthChanged: function() { - UiLogic.pagesStackDepth = depth - } - - Keys.onPressed: function(event) { - UiLogic.keyPressEvent(event.key) - event.accepted = true - } - } - - LabsFolderlistmodel.FolderListModel { - id: folderModelPages - folder: "qrc:/ui/qml/Pages/" - nameFilters: ["*.qml"] - showDirs: false - - onStatusChanged: if (status == LabsFolderlistmodel.FolderListModel.Ready) { - for (var i=0; iquit(); }); m_systemTrayIcon.setContextMenu(&m_menu); - setTrayState(VpnProtocol::Disconnected); + setTrayState(Vpn::ConnectionState::Disconnected); } SystemTrayNotificationHandler::~SystemTrayNotificationHandler() { } -void SystemTrayNotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState state) +void SystemTrayNotificationHandler::setConnectionState(Vpn::ConnectionState state) { setTrayState(state); NotificationHandler::setConnectionState(state); } +void SystemTrayNotificationHandler::onTranslationsUpdated() +{ + m_trayActionShow->setText(tr("Show") + " " + APPLICATION_NAME); + m_trayActionConnect->setText(tr("Connect")); + m_trayActionDisconnect->setText(tr("Disconnect")); + m_trayActionVisitWebSite->setText(tr("Visit Website")); + m_trayActionQuit->setText(tr("Quit")+ " " + APPLICATION_NAME); +} + void SystemTrayNotificationHandler::setTrayIcon(const QString &iconPath) { QIcon trayIconMask(QPixmap(iconPath).scaled(128,128)); @@ -73,47 +80,47 @@ void SystemTrayNotificationHandler::onTrayActivated(QSystemTrayIcon::ActivationR #endif } -void SystemTrayNotificationHandler::setTrayState(VpnProtocol::VpnConnectionState state) +void SystemTrayNotificationHandler::setTrayState(Vpn::ConnectionState state) { QString resourcesPath = ":/images/tray/%1"; switch (state) { - case VpnProtocol::Disconnected: + case Vpn::ConnectionState::Disconnected: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(true); m_trayActionDisconnect->setEnabled(false); break; - case VpnProtocol::Preparing: + case Vpn::ConnectionState::Preparing: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Connecting: + case Vpn::ConnectionState::Connecting: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Connected: + case Vpn::ConnectionState::Connected: setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Disconnecting: + case Vpn::ConnectionState::Disconnecting: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Reconnecting: + case Vpn::ConnectionState::Reconnecting: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Error: + case Vpn::ConnectionState::Error: setTrayIcon(QString(resourcesPath).arg(ErrorTrayIconName)); m_trayActionConnect->setEnabled(true); m_trayActionDisconnect->setEnabled(false); break; - case VpnProtocol::Unknown: + case Vpn::ConnectionState::Unknown: default: m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); diff --git a/client/ui/systemtray_notificationhandler.h b/client/ui/systemtray_notificationhandler.h index 46563bde..60bf0b35 100644 --- a/client/ui/systemtray_notificationhandler.h +++ b/client/ui/systemtray_notificationhandler.h @@ -17,7 +17,9 @@ public: explicit SystemTrayNotificationHandler(QObject* parent); ~SystemTrayNotificationHandler(); - void setConnectionState(VpnProtocol::VpnConnectionState state) override; + void setConnectionState(Vpn::ConnectionState state) override; + + void onTranslationsUpdated() override; protected: virtual void notify(Message type, const QString& title, @@ -26,7 +28,7 @@ protected: private: void showHideWindow(); - void setTrayState(VpnProtocol::VpnConnectionState state); + void setTrayState(Vpn::ConnectionState state); void onTrayActivated(QSystemTrayIcon::ActivationReason reason); void setTrayIcon(const QString &iconPath); @@ -35,9 +37,11 @@ private: QMenu m_menu; QSystemTrayIcon m_systemTrayIcon; + QAction* m_trayActionShow = nullptr; QAction* m_trayActionConnect = nullptr; QAction* m_trayActionDisconnect = nullptr; - QAction* m_preferencesAction = nullptr; + QAction* m_trayActionVisitWebSite = nullptr; + QAction* m_trayActionQuit = nullptr; QAction* m_statusLabel = nullptr; QAction* m_separator = nullptr; diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp deleted file mode 100644 index f3c0b1b4..00000000 --- a/client/ui/uilogic.cpp +++ /dev/null @@ -1,604 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "amnezia_application.h" - -#include "configurators/cloak_configurator.h" -#include "configurators/vpn_configurator.h" -#include "configurators/openvpn_configurator.h" -#include "configurators/shadowsocks_configurator.h" -#include "configurators/ssh_configurator.h" - -#include "core/servercontroller.h" -#include "core/server_defs.h" -#include "core/errorstrings.h" - -#include "containers/containers_defs.h" - -#include "ui/qautostart.h" - -#include "logger.h" -#include "version.h" -#include "uilogic.h" -#include "utilities.h" -#include "vpnconnection.h" -#include - -#if defined Q_OS_MAC || defined Q_OS_LINUX -#include "ui/macos_util.h" -#endif - -#ifdef Q_OS_ANDROID -#include "platforms/android/android_controller.h" -#endif - -#include "platforms/ios/MobileUtils.h" - -#include "pages_logic/AppSettingsLogic.h" -#include "pages_logic/GeneralSettingsLogic.h" -#include "pages_logic/NetworkSettingsLogic.h" -#include "pages_logic/NewServerProtocolsLogic.h" -#include "pages_logic/QrDecoderLogic.h" -#include "pages_logic/ServerConfiguringProgressLogic.h" -#include "pages_logic/ServerListLogic.h" -#include "pages_logic/ServerSettingsLogic.h" -#include "pages_logic/ServerContainersLogic.h" -#include "pages_logic/ShareConnectionLogic.h" -#include "pages_logic/SitesLogic.h" -#include "pages_logic/StartPageLogic.h" -#include "pages_logic/ViewConfigLogic.h" -#include "pages_logic/VpnLogic.h" -#include "pages_logic/WizardLogic.h" -#include "pages_logic/AdvancedServerSettingsLogic.h" -#include "pages_logic/ClientManagementLogic.h" -#include "pages_logic/ClientInfoLogic.h" - -#include "pages_logic/protocols/CloakLogic.h" -#include "pages_logic/protocols/OpenVpnLogic.h" -#include "pages_logic/protocols/ShadowSocksLogic.h" -#include "pages_logic/protocols/OtherProtocolsLogic.h" -#include "pages_logic/protocols/WireGuardLogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr configurator, - QObject *parent) : - QObject(parent), - m_settings(settings), - m_configurator(configurator) -{ - m_containersModel = new ContainersModel(settings, this); - m_protocolsModel = new ProtocolsModel(settings, this); - m_clientManagementModel = new ClientManagementModel(this); - m_vpnConnection = new VpnConnection(settings, configurator); - m_vpnConnection->moveToThread(&m_vpnConnectionThread); - m_vpnConnectionThread.start(); - - - m_protocolLogicMap.insert(Proto::OpenVpn, new OpenVpnLogic(this)); - m_protocolLogicMap.insert(Proto::ShadowSocks, new ShadowSocksLogic(this)); - m_protocolLogicMap.insert(Proto::Cloak, new CloakLogic(this)); - m_protocolLogicMap.insert(Proto::WireGuard, new WireGuardLogic(this)); - - m_protocolLogicMap.insert(Proto::Dns, new OtherProtocolsLogic(this)); - m_protocolLogicMap.insert(Proto::Sftp, new OtherProtocolsLogic(this)); - m_protocolLogicMap.insert(Proto::TorWebSite, new OtherProtocolsLogic(this)); - -} - -UiLogic::~UiLogic() -{ - emit hide(); - - m_vpnConnection->deleteLater(); - m_vpnConnectionThread.quit(); - m_vpnConnectionThread.wait(3000); - - qDebug() << "Application closed"; -} - -void UiLogic::initializeUiLogic() -{ -#ifdef Q_OS_ANDROID - connect(AndroidController::instance(), &AndroidController::initialized, [this](bool status, bool connected, const QDateTime& connectionDate) { - if (connected) { - pageLogic()->onConnectionStateChanged(VpnProtocol::Connected); - if (m_vpnConnection) m_vpnConnection->restoreConnection(); - } - }); - if (!AndroidController::instance()->initialize(pageLogic())) { - qCritical() << QString("Init failed"); - if (m_vpnConnection) m_vpnConnection->connectionStateChanged(VpnProtocol::Error); - return; - } -#endif - - m_notificationHandler = NotificationHandler::create(qmlRoot()); - - connect(m_vpnConnection, &VpnConnection::connectionStateChanged, m_notificationHandler, &NotificationHandler::setConnectionState); - connect(m_notificationHandler, &NotificationHandler::raiseRequested, this, &UiLogic::raise); - connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic(), &VpnLogic::onConnect); - connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), &VpnLogic::onDisconnect); - - if (m_settings->serversCount() > 0) { - if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); - emit goToPage(Page::Vpn, true, false); - } - else { - emit goToPage(Page::Start, true, false); - } - - m_selectedServerIndex = m_settings->defaultServerIndex(); - - qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME).arg(APP_VERSION); - qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName()).arg(QSysInfo::currentCpuArchitecture()); -} - -void UiLogic::showOnStartup() -{ - if (! m_settings->isStartMinimized()) { - emit show(); - } - else { -#ifdef Q_OS_WIN - emit hide(); -#elif defined Q_OS_MACX - // TODO: fix: setDockIconVisible(false); -#endif - } -} - -void UiLogic::onUpdateAllPages() -{ - for (auto logic : m_logicMap) { - if (dynamic_cast(logic) || dynamic_cast(logic) || dynamic_cast(logic)) { - continue; - } - logic->onUpdatePage(); - } -} - -void UiLogic::keyPressEvent(Qt::Key key) -{ - switch (key) { - case Qt::Key_AsciiTilde: - case Qt::Key_QuoteLeft: emit toggleLogPanel(); - break; - case Qt::Key_L: Logger::openLogsFolder(); - break; - case Qt::Key_K: Logger::openServiceLogsFolder(); - break; -#ifdef QT_DEBUG - case Qt::Key_Q: - qApp->quit(); - break; - case Qt::Key_H: - m_selectedServerIndex = m_settings->defaultServerIndex(); - m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); - - //updateSharingPage(selectedServerIndex, m_settings->serverCredentials(selectedServerIndex), selectedDockerContainer); - emit goToPage(Page::ShareConnection); - break; -#endif - case Qt::Key_C: - qDebug().noquote() << "Def server" << m_settings->defaultServerIndex() << m_settings->defaultContainerName(m_settings->defaultServerIndex()); - qDebug().noquote() << QJsonDocument(m_settings->defaultServer()).toJson(); - break; - case Qt::Key_A: - emit goToPage(Page::Start); - break; - case Qt::Key_S: - m_selectedServerIndex = m_settings->defaultServerIndex(); - emit goToPage(Page::ServerSettings); - break; - case Qt::Key_P: - onGotoCurrentProtocolsPage(); - break; - case Qt::Key_T: - m_configurator->sshConfigurator->openSshTerminal(m_settings->serverCredentials(m_settings->defaultServerIndex())); - break; - case Qt::Key_Escape: - if (currentPage() == Page::Vpn) break; - if (currentPage() == Page::ServerConfiguringProgress) break; - case Qt::Key_Back: - -// if (currentPage() == Page::Start && pagesStack.size() < 2) break; -// if (currentPage() == Page::Sites && -// ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { -// ui->tableView_sites->clearSelection(); -// break; -// } - - emit closePage(); - //} - default: - ; - } -} - -void UiLogic::onCloseWindow() -{ -#ifdef Q_OS_ANDROID - qApp->quit(); -#else - if (m_settings->serversCount() == 0) - { - qApp->quit(); - } else { - emit hide(); - } -#endif -} - -QString UiLogic::containerName(int container) -{ - return ContainerProps::containerHumanNames().value(static_cast(container)); -} - -QString UiLogic::containerDesc(int container) -{ - return ContainerProps::containerDescriptions().value(static_cast(container)); - -} - -void UiLogic::onGotoCurrentProtocolsPage() -{ - m_selectedServerIndex = m_settings->defaultServerIndex(); - m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); - emit goToPage(Page::ServerContainers); -} - -void UiLogic::installServer(QPair &container) -{ - emit goToPage(Page::ServerConfiguringProgress); - QEventLoop loop; - QTimer::singleShot(500, &loop, SLOT(quit())); - loop.exec(); - qApp->processEvents(); - - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { - pageLogic()->set_pageEnabled(enabled); - }; - - ServerConfiguringProgressLogic::ButtonFunc noButton; - - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { - pageLogic()->set_labelWaitInfoText(text); - }; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { - pageLogic()->set_labelWaitInfoVisible(visible); - }; - - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { - pageLogic()->set_progressBarVisible(visible); - }; - progressBarFunc.setValueFunc = [this] (int value) -> void { - pageLogic()->set_progressBarValue(value); - }; - progressBarFunc.getValueFunc = [this] (void) -> int { - return pageLogic()->progressBarValue(); - }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { - return pageLogic()->progressBarMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { - pageLogic()->set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { - pageLogic()->set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFunc; - busyInfoFunc.setTextFunc = [this] (const QString& text) -> void { - pageLogic()->set_labelServerBusyText(text); - }; - busyInfoFunc.setVisibleFunc = [this] (bool visible) -> void { - pageLogic()->set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - pageLogic()->set_pushButtonCancelVisible(visible); - }; - - bool isServerCreated = false; - ErrorCode errorCode = addAlreadyInstalledContainersGui(isServerCreated); - if (errorCode == ErrorCode::NoError) { - if (!isContainerAlreadyAddedToGui(container.first)) { - progressBarFunc.setTextFunc(QString("Installing %1").arg(ContainerProps::containerToString(container.first))); - auto installAction = [&] () { - ServerController serverController(m_settings); - return serverController.setupContainer(m_installCredentials, container.first, container.second); - }; - errorCode = pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - noButton, waitInfoFunc, - busyInfoFunc, cancelButtonFunc); - if (errorCode == ErrorCode::NoError) { - if (!isServerCreated) { - QJsonObject server; - server.insert(config_key::hostName, m_installCredentials.hostName); - server.insert(config_key::userName, m_installCredentials.userName); - server.insert(config_key::password, m_installCredentials.password); - server.insert(config_key::port, m_installCredentials.port); - server.insert(config_key::description, m_settings->nextAvailableServerName()); - - server.insert(config_key::containers, QJsonArray{container.second}); - server.insert(config_key::defaultContainer, ContainerProps::containerToString(container.first)); - - m_settings->addServer(server); - m_settings->setDefaultServer(m_settings->serversCount() - 1); - } else { - m_settings->setContainerConfig(m_settings->serversCount() - 1, container.first, container.second); - m_settings->setDefaultContainer(m_settings->serversCount() - 1, container.first); - } - onUpdateAllPages(); - - emit setStartPage(Page::Vpn); - qApp->processEvents(); - return; - } - } else { - onUpdateAllPages(); - emit showWarningMessage("Attention! The container you are trying to install is already installed on the server. " - "All installed containers have been added to the application "); - emit setStartPage(Page::Vpn); - return; - } - } - emit showWarningMessage(tr("Error occurred while configuring server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); - emit closePage(); -} - -PageProtocolLogicBase *UiLogic::protocolLogic(Proto p) -{ - PageProtocolLogicBase *logic = m_protocolLogicMap.value(p); - if (logic) return logic; - else { - qCritical() << "UiLogic::protocolLogic Warning: logic missing for" << p; - return new PageProtocolLogicBase(this); - } -} - -QObject *UiLogic::qmlRoot() const -{ - return m_qmlRoot; -} - -void UiLogic::setQmlRoot(QObject *newQmlRoot) -{ - m_qmlRoot = newQmlRoot; -} - -NotificationHandler *UiLogic::notificationHandler() const -{ - return m_notificationHandler; -} - -void UiLogic::setQmlContextProperty(PageLogicBase *logic) -{ - amnApp->qmlEngine()->rootContext()->setContextProperty(logic->metaObject()->className(), logic); -} - -PageEnumNS::Page UiLogic::currentPage() -{ - return static_cast(currentPageValue()); -} - -void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QString ext, const QString& data) -{ -#ifdef Q_OS_IOS - shareTempFile(suggestedName, ext, data); - return; -#endif - - ext.replace("*", ""); - QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - QUrl fileName; -#ifdef AMNEZIA_DESKTOP - fileName = QFileDialog::getSaveFileUrl(nullptr, desc, - QUrl::fromLocalFile(docDir + "/" + suggestedName), "*" + ext); - if (fileName.isEmpty()) return; - if (!fileName.toString().endsWith(ext)) fileName = QUrl(fileName.toString() + ext); -#elif defined Q_OS_ANDROID - qDebug() << "UiLogic::shareConfig" << data; - AndroidController::instance()->shareConfig(data, suggestedName); - return; -#endif - - if (fileName.isEmpty()) return; - -#ifdef AMNEZIA_DESKTOP - QFile save(fileName.toLocalFile()); -#else - QFile save(QQmlFile::urlToLocalFileOrQrc(fileName)); -#endif - - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); - - QFileInfo fi(fileName.toLocalFile()); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -} - -void UiLogic::saveBinaryFile(const QString &desc, QString ext, const QString &data) -{ - ext.replace("*", ""); - QString fileName = QFileDialog::getSaveFileName(nullptr, desc, - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*" + ext); - - if (fileName.isEmpty()) return; - if (!fileName.endsWith(ext)) fileName.append(ext); - - QFile save(fileName); - save.open(QIODevice::WriteOnly); - save.write(QByteArray::fromBase64(data.toUtf8())); - save.close(); - - QFileInfo fi(fileName); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -} - -void UiLogic::copyToClipboard(const QString &text) -{ - qApp->clipboard()->setText(text); -} - -void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString& data) { - ext.replace("*", ""); - QString fileName = QDir::tempPath() + "/" + suggestedName; - - if (fileName.isEmpty()) return; - if (!fileName.endsWith(ext)) fileName.append(ext); - - QFile::remove(fileName); - - QFile save(fileName); - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); - - QStringList filesToSend; - filesToSend.append(fileName); - MobileUtils::shareText(filesToSend); -} - -QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, - const QString &filter, QString *selectedFilter, QFileDialog::Options options) -{ - QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); - -#ifdef Q_OS_ANDROID - // patch for files containing spaces etc - const QString sep {"raw%3A%2F"}; - if (fileName.startsWith("content://") && fileName.contains(sep)) { - QString contentUrl = fileName.split(sep).at(0); - QString rawUrl = fileName.split(sep).at(1); - rawUrl.replace(" ", "%20"); - fileName = contentUrl + sep + rawUrl; - } -#endif - return fileName; -} - -void UiLogic::registerPagesLogic() -{ - amnApp->qmlEngine()->rootContext()->setContextProperty("UiLogic", this); - - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); -} - -ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool &isServerCreated) -{ - isServerCreated = false; - ServerCredentials installCredentials = m_installCredentials; - bool createNewServer = true; - int serverIndex; - - for (int i = 0; i < m_settings->serversCount(); i++) { - const ServerCredentials credentials = m_settings->serverCredentials(i); - if (m_installCredentials.hostName == credentials.hostName && m_installCredentials.port == credentials.port) { - createNewServer = false; - isServerCreated = true; - installCredentials = credentials; - serverIndex = i; - break; - } - } - - QMap installedContainers; - ServerController serverController(m_settings); - ErrorCode errorCode = serverController.getAlreadyInstalledContainers(installCredentials, installedContainers); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - if (!installedContainers.empty()) { - QJsonObject server; - QJsonArray containerConfigs; - if (createNewServer) { - server.insert(config_key::hostName, installCredentials.hostName); - server.insert(config_key::userName, installCredentials.userName); - server.insert(config_key::password, installCredentials.password); - server.insert(config_key::port, installCredentials.port); - server.insert(config_key::description, m_settings->nextAvailableServerName()); - } - - for (auto container = installedContainers.begin(); container != installedContainers.end(); container++) { - if (isContainerAlreadyAddedToGui(container.key())) { - continue; - } - - if (createNewServer) { - containerConfigs.append(container.value()); - server.insert(config_key::containers, containerConfigs); - } else { - m_settings->setContainerConfig(serverIndex, container.key(), container.value()); - m_settings->setDefaultContainer(serverIndex, installedContainers.firstKey()); - } - } - - if (createNewServer) { - server.insert(config_key::defaultContainer, ContainerProps::containerToString(installedContainers.firstKey())); - m_settings->addServer(server); - m_settings->setDefaultServer(m_settings->serversCount() - 1); - isServerCreated = true; - } - } - - return ErrorCode::NoError; -} - -bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container) -{ - for (int i = 0; i < m_settings->serversCount(); i++) { - const ServerCredentials credentials = m_settings->serverCredentials(i); - if (m_installCredentials.hostName == credentials.hostName && m_installCredentials.port == credentials.port) { - const QJsonObject containerConfig = m_settings->containerConfig(i, container); - if (!containerConfig.isEmpty()) { - return true; - } - } - } - return false; -} - diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h deleted file mode 100644 index 3b7797a8..00000000 --- a/client/ui/uilogic.h +++ /dev/null @@ -1,202 +0,0 @@ -#ifndef UILOGIC_H -#define UILOGIC_H - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "property_helper.h" -#include "pages.h" -#include "protocols/vpnprotocol.h" -#include "containers/containers_defs.h" - -#include "models/containers_model.h" -#include "models/protocols_model.h" -#include "models/clientManagementModel.h" - -#include "notificationhandler.h" - -class Settings; -class VpnConfigurator; -class ServerController; - -class PageLogicBase; - -class AppSettingsLogic; -class GeneralSettingsLogic; -class NetworkSettingsLogic; -class NewServerProtocolsLogic; -class QrDecoderLogic; -class ServerConfiguringProgressLogic; -class ServerListLogic; -class ServerSettingsLogic; -class ServerContainersLogic; -class ShareConnectionLogic; -class SitesLogic; -class StartPageLogic; -class ViewConfigLogic; -class VpnLogic; -class WizardLogic; -class ClientManagementLogic; -class ClientInfoLogic; -class AdvancedServerSettingsLogic; - -class PageProtocolLogicBase; -class OpenVpnLogic; -class ShadowSocksLogic; -class CloakLogic; - -class OtherProtocolsLogic; - -class VpnConnection; - -class CreateServerTest; - -class UiLogic : public QObject -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pageEnabled) - AUTO_PROPERTY(int, pagesStackDepth) - AUTO_PROPERTY(int, currentPageValue) - - READONLY_PROPERTY(QObject *, containersModel) - READONLY_PROPERTY(QObject *, protocolsModel) - READONLY_PROPERTY(QObject *, clientManagementModel) - -public: - explicit UiLogic(std::shared_ptr settings, std::shared_ptr configurator, QObject *parent = nullptr); - ~UiLogic(); - void showOnStartup(); - - friend class PageLogicBase; - - friend class AppSettingsLogic; - friend class GeneralSettingsLogic; - friend class NetworkSettingsLogic; - friend class ServerConfiguringProgressLogic; - friend class NewServerProtocolsLogic; - friend class ServerListLogic; - friend class ServerSettingsLogic; - friend class ServerContainersLogic; - friend class ShareConnectionLogic; - friend class SitesLogic; - friend class StartPageLogic; - friend class ViewConfigLogic; - friend class VpnLogic; - friend class WizardLogic; - friend class ClientManagementLogic; - friend class ClientInfoLogic; - friend class AdvancedServerSettingsLogic; - - friend class PageProtocolLogicBase; - friend class OpenVpnLogic; - friend class ShadowSocksLogic; - friend class CloakLogic; - - friend class OtherProtocolsLogic; - - friend class CreateServerTest; - - Q_INVOKABLE virtual void onUpdatePage() {} // UiLogic is set as logic class for some qml pages - Q_INVOKABLE void onUpdateAllPages(); - - Q_INVOKABLE void initializeUiLogic(); - Q_INVOKABLE void onCloseWindow(); - - Q_INVOKABLE QString containerName(int container); - Q_INVOKABLE QString containerDesc(int container); - - Q_INVOKABLE void onGotoCurrentProtocolsPage(); - - Q_INVOKABLE void keyPressEvent(Qt::Key key); - - Q_INVOKABLE void saveTextFile(const QString& desc, const QString &suggestedName, QString ext, const QString& data); - Q_INVOKABLE void saveBinaryFile(const QString& desc, QString ext, const QString& data); - Q_INVOKABLE void copyToClipboard(const QString& text); - - Q_INVOKABLE amnezia::ErrorCode addAlreadyInstalledContainersGui(bool &isServerCreated); - - void shareTempFile(const QString &suggestedName, QString ext, const QString& data); - static QString getOpenFileName(QWidget *parent = nullptr, - const QString &caption = QString(), - const QString &dir = QString(), - const QString &filter = QString(), - QString *selectedFilter = nullptr, - QFileDialog::Options options = QFileDialog::Options()); -signals: - void goToPage(PageEnumNS::Page page, bool reset = true, bool slide = true); - void goToProtocolPage(Proto protocol, bool reset = true, bool slide = true); - void goToShareProtocolPage(Proto protocol, bool reset = true, bool slide = true); - void goToClientInfoPage(Proto protocol, bool reset = true, bool slide = true); - - void closePage(); - void setStartPage(PageEnumNS::Page page, bool slide = true); - void showPublicKeyWarning(); - void showConnectErrorDialog(); - void show(); - void hide(); - void raise(); - void toggleLogPanel(); - void showWarningMessage(QString message); - -private slots: - // containers - INOUT arg - void installServer(QPair &container); - -private: - PageEnumNS::Page currentPage(); - bool isContainerAlreadyAddedToGui(DockerContainer container); - -public: - Q_INVOKABLE PageProtocolLogicBase *protocolLogic(Proto p); - - QObject *qmlRoot() const; - void setQmlRoot(QObject *newQmlRoot); - - NotificationHandler *notificationHandler() const; - - void setQmlContextProperty(PageLogicBase *logic); - void registerPagesLogic(); - - template - void registerPageLogic() - { - T* logic = new T(this); - m_logicMap[std::type_index(typeid(T))] = logic; - setQmlContextProperty(logic); - } - - template - T* pageLogic() - { - return static_cast(m_logicMap.value(std::type_index(typeid(T)))); - } - -private: - QObject *m_qmlRoot{nullptr}; - - QMap m_logicMap; - - QMap m_protocolLogicMap; - - VpnConnection* m_vpnConnection; - QThread m_vpnConnectionThread; - - std::shared_ptr m_settings; - std::shared_ptr m_configurator; - - NotificationHandler* m_notificationHandler; - - int m_selectedServerIndex = -1; // server index to use when proto settings page opened - DockerContainer m_selectedDockerContainer; // same - ServerCredentials m_installCredentials; // used to save cred between pages new_server and new_server_protocols and wizard -}; -#endif // UILOGIC_H diff --git a/client/utilities.cpp b/client/utilities.cpp index 731dd2cb..158bce93 100644 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -7,16 +7,17 @@ #include #include #include +#include -#include "version.h" #include "utilities.h" +#include "version.h" QString Utils::getRandomString(int len) { const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); QString randomString; - for(int i=0; igenerate() % possibleCharacters.length(); QChar nextChar = possibleCharacters.at(index); randomString.append(nextChar); @@ -29,7 +30,7 @@ QString Utils::systemLogPath() #ifdef Q_OS_WIN QStringList locationList = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); QString primaryLocation = "ProgramData"; - foreach (const QString& location, locationList) { + foreach (const QString &location, locationList) { if (location.contains(primaryLocation)) { return QString("%1/%2/log").arg(location).arg(APPLICATION_NAME); } @@ -40,7 +41,7 @@ QString Utils::systemLogPath() #endif } -bool Utils::initializePath(const QString& path) +bool Utils::initializePath(const QString &path) { QDir dir; if (!dir.mkpath(path)) { @@ -50,13 +51,13 @@ bool Utils::initializePath(const QString& path) return true; } -bool Utils::createEmptyFile(const QString& path) +bool Utils::createEmptyFile(const QString &path) { QFile f(path); return f.open(QIODevice::WriteOnly | QIODevice::Truncate); } -QString Utils::executable(const QString& baseName, bool absPath) +QString Utils::executable(const QString &baseName, bool absPath) { QString ext; #ifdef Q_OS_WIN @@ -69,7 +70,7 @@ QString Utils::executable(const QString& baseName, bool absPath) return QCoreApplication::applicationDirPath() + "/" + fileName; } -QString Utils::usrExecutable(const QString& baseName) +QString Utils::usrExecutable(const QString &baseName) { if (QFileInfo::exists("/usr/sbin/" + baseName)) return ("/usr/sbin/" + baseName); @@ -77,18 +78,22 @@ QString Utils::usrExecutable(const QString& baseName) return ("/usr/bin/" + baseName); } -bool Utils::processIsRunning(const QString& fileName) +bool Utils::processIsRunning(const QString &fileName) { #ifdef Q_OS_WIN QProcess process; process.setReadChannel(QProcess::StandardOutput); process.setProcessChannelMode(QProcess::MergedChannels); - process.start("wmic.exe", QStringList() << "/OUTPUT:STDOUT" << "PROCESS" << "get" << "Caption"); + process.start("wmic.exe", + QStringList() << "/OUTPUT:STDOUT" + << "PROCESS" + << "get" + << "Caption"); process.waitForStarted(); process.waitForFinished(); QString processData(process.readAll()); - QStringList processList = processData.split(QRegularExpression("[\r\n]"),Qt::SkipEmptyParts); - foreach (const QString& rawLine, processList) { + QStringList processList = processData.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts); + foreach (const QString &rawLine, processList) { const QString line = rawLine.simplified(); if (line.isEmpty()) { continue; @@ -97,7 +102,6 @@ bool Utils::processIsRunning(const QString& fileName) if (line == fileName) { return true; } - } return false; #elif defined(Q_OS_IOS) @@ -105,7 +109,7 @@ bool Utils::processIsRunning(const QString& fileName) #else QProcess process; process.setProcessChannelMode(QProcess::MergedChannels); - process.start("pgrep", QStringList({fileName})); + process.start("pgrep", QStringList({ fileName })); process.waitForFinished(); if (process.exitStatus() == QProcess::NormalExit) { return (process.readAll().toUInt() > 0); @@ -114,7 +118,7 @@ bool Utils::processIsRunning(const QString& fileName) #endif } -QString Utils::getIPAddress(const QString& host) +QString Utils::getIPAddress(const QString &host) { if (ipAddressRegExp().match(host).hasMatch()) { return host; @@ -128,42 +132,48 @@ QString Utils::getIPAddress(const QString& host) return ""; } -QString Utils::getStringBetween(const QString& s, const QString& a, const QString& b) +QString Utils::getStringBetween(const QString &s, const QString &a, const QString &b) { int ap = s.indexOf(a), bp = s.indexOf(b, ap + a.length()); - if(ap < 0 || bp < 0) + if (ap < 0 || bp < 0) return QString(); ap += a.length(); - if(bp - ap <= 0) + if (bp - ap <= 0) return QString(); return s.mid(ap, bp - ap).trimmed(); } -bool Utils::checkIPv4Format(const QString& ip) +bool Utils::checkIPv4Format(const QString &ip) { - if (ip.isEmpty()) return false; + if (ip.isEmpty()) + return false; int count = ip.count("."); - if(count != 3) return false; + if (count != 3) + return false; QHostAddress addr(ip); - return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol); + return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol); } bool Utils::checkIpSubnetFormat(const QString &ip) { - if (!ip.contains("/")) return checkIPv4Format(ip); + if (!ip.contains("/")) + return checkIPv4Format(ip); QStringList parts = ip.split("/"); - if (parts.size() != 2) return false; + if (parts.size() != 2) + return false; bool ok; int subnet = parts.at(1).toInt(&ok); - if (subnet >= 0 && subnet <= 32 && ok) return checkIPv4Format(parts.at(0)); - else return false; + if (subnet >= 0 && subnet <= 32 && ok) + return checkIPv4Format(parts.at(0)); + else + return false; } void Utils::killProcessByName(const QString &name) -{ +{ qDebug().noquote() << "Kill process" << name; #ifdef Q_OS_WIN QProcess::execute("taskkill", QStringList() << "/IM" << name << "/F"); @@ -176,40 +186,39 @@ void Utils::killProcessByName(const QString &name) QString Utils::netMaskFromIpWithSubnet(const QString ip) { - if (!ip.contains("/")) return "255.255.255.255"; + if (!ip.contains("/")) + return "255.255.255.255"; bool ok; int prefix = ip.split("/").at(1).toInt(&ok); - if (!ok) return "255.255.255.255"; + if (!ok) + return "255.255.255.255"; unsigned long mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF; - return QString("%1.%2.%3.%4") - .arg(mask >> 24) - .arg((mask >> 16) & 0xFF) - .arg((mask >> 8) & 0xFF) - .arg( mask & 0xFF); + return QString("%1.%2.%3.%4").arg(mask >> 24).arg((mask >> 16) & 0xFF).arg((mask >> 8) & 0xFF).arg(mask & 0xFF); } QString Utils::ipAddressFromIpWithSubnet(const QString ip) { - if (ip.count(".") != 3) return ""; + if (ip.count(".") != 3) + return ""; return ip.split("/").first(); } QStringList Utils::summarizeRoutes(const QStringList &ips, const QString cidr) { -// QMap -// QHostAddress + // QMap + // QHostAddress -// QMap subnets; // <"a.b", > + // QMap subnets; // <"a.b", > -// for (const QString &ip : ips) { -// if (ip.count(".") != 3) continue; + // for (const QString &ip : ips) { + // if (ip.count(".") != 3) continue; -// const QStringList &parts = ip.split("."); -// subnets[parts.at(0) + "." + parts.at(1)].append(ip); -// } + // const QStringList &parts = ip.split("."); + // subnets[parts.at(0) + "." + parts.at(1)].append(ip); + // } return QStringList(); } @@ -261,8 +270,7 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent) // (otherwise AttachConsole will return ERROR_ACCESS_DENIED) bool consoleDetached = (FreeConsole() != FALSE); - if (AttachConsole(dwProcessId) != FALSE) - { + if (AttachConsole(dwProcessId) != FALSE) { // Add a fake Ctrl-C handler for avoid instant kill is this console // WARNING: do not revert it or current program will be also killed SetConsoleCtrlHandler(nullptr, true); @@ -270,11 +278,9 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent) FreeConsole(); } - if (consoleDetached) - { + if (consoleDetached) { // Create a new console if previous was deleted by OS - if (AttachConsole(thisConsoleId) == FALSE) - { + if (AttachConsole(thisConsoleId) == FALSE) { int errorCode = GetLastError(); if (errorCode == 31) // 31=ERROR_GEN_FAILURE { diff --git a/client/utilities.h b/client/utilities.h index aeb06865..7ef0cd3f 100644 --- a/client/utilities.h +++ b/client/utilities.h @@ -2,43 +2,63 @@ #define UTILITIES_H #include -#include #include +#include #ifdef Q_OS_WIN -#include "Windows.h" + #include "Windows.h" #endif -class Utils : public QObject { +class Utils : public QObject +{ Q_OBJECT public: static QString getRandomString(int len); - static QString executable(const QString& baseName, bool absPath); - static QString usrExecutable(const QString& baseName); + static QString executable(const QString &baseName, bool absPath); + static QString usrExecutable(const QString &baseName); static QString systemLogPath(); - static bool createEmptyFile(const QString& path); - static bool initializePath(const QString& path); + static bool createEmptyFile(const QString &path); + static bool initializePath(const QString &path); - static QString getIPAddress(const QString& host); - static QString getStringBetween(const QString& s, const QString& a, const QString& b); - static bool checkIPv4Format(const QString& ip); - static bool checkIpSubnetFormat(const QString& ip); - static QRegularExpression ipAddressRegExp() { return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); } - static QRegularExpression ipAddressPortRegExp() { return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); } + static QString getIPAddress(const QString &host); + static QString getStringBetween(const QString &s, const QString &a, const QString &b); + static bool checkIPv4Format(const QString &ip); + static bool checkIpSubnetFormat(const QString &ip); + static QRegularExpression ipAddressRegExp() + { + return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); + } + static QRegularExpression ipAddressPortRegExp() + { + return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); + } - static QRegExp ipAddressWithSubnetRegExp() { return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}"); } + static QRegExp ipAddressWithSubnetRegExp() + { + return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}"); + } - static QRegExp ipNetwork24RegExp() { return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "0$"); } + static QRegExp ipNetwork24RegExp() + { + return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "0$"); + } - static QRegExp ipPortRegExp() { return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$"); } + static QRegExp ipPortRegExp() + { + return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$"); + } - static QRegExp domainRegExp() { return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-9\\-]{1,30})\\.[a-z]{2,}"); } - static bool processIsRunning(const QString& fileName); + static QRegExp domainRegExp() + { + return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-" + "9\\-]{1,30})\\.[a-z]{2,}"); + } + static bool processIsRunning(const QString &fileName); static void killProcessByName(const QString &name); static QString netMaskFromIpWithSubnet(const QString ip); diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 1fc0b7f5..46e8be60 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -1,45 +1,44 @@ #include "qtimer.h" -#include + #include #include #include #include -#include #include +#include #include -#include #include +#include #include #ifdef AMNEZIA_DESKTOP -#include "ipc.h" -#include "core/ipcclient.h" -#include + #include "core/ipcclient.h" + #include "ipc.h" + #include #endif #ifdef Q_OS_ANDROID -#include "../../platforms/android/android_controller.h" + #include "../../platforms/android/android_controller.h" #endif #ifdef Q_OS_IOS -#include "platforms/ios/ios_controller.h" + #include "platforms/ios/ios_controller.h" #endif #include "utilities.h" #include "vpnconnection.h" -VpnConnection::VpnConnection(std::shared_ptr settings, - std::shared_ptr configurator, QObject* parent) : QObject(parent), - m_settings(settings), - m_configurator(configurator), - m_checkTimer(new QTimer(this)) +VpnConnection::VpnConnection(std::shared_ptr settings, std::shared_ptr configurator, + QObject *parent) + : QObject(parent), m_settings(settings), m_configurator(configurator), m_checkTimer(new QTimer(this)) { m_checkTimer.setInterval(1000); #ifdef Q_OS_IOS - connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::onConnectionStateChanged); + connect(IosController::Instance(), &IosController::connectionStateChanged, this, + &VpnConnection::onConnectionStateChanged); connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged); - + #endif } @@ -55,32 +54,28 @@ void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes) emit bytesChanged(receivedBytes, sentBytes); } -void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) +void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) { #ifdef AMNEZIA_DESKTOP if (IpcClient::Interface()) { - if (state == VpnProtocol::Connected){ + if (state == Vpn::ConnectionState::Connected) { IpcClient::Interface()->resetIpStack(); IpcClient::Interface()->flushDns(); if (m_settings->routeMode() != Settings::VpnAllSites) { IpcClient::Interface()->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0"); - //qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); + // qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); } QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); QString dns2 = m_vpnConfiguration.value(config_key::dns1).toString(); - IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), - QStringList() << dns1 << dns2); - + IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2); if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { - QTimer::singleShot(1000, m_vpnProtocol.data(), [this](){ - addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); - }); - } - else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + QTimer::singleShot(1000, m_vpnProtocol.data(), + [this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); }); + } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1"); IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1"); @@ -88,9 +83,7 @@ void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState sta addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode()); } - - } - else if (state == VpnProtocol::Error) { + } else if (state == Vpn::ConnectionState::Error) { IpcClient::Interface()->flushDns(); if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { @@ -101,10 +94,9 @@ void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState sta #endif #ifdef Q_OS_IOS - if (state == VpnProtocol::Connected) { + if (state == Vpn::ConnectionState::Connected) { m_checkTimer.start(); - } - else { + } else { m_checkTimer.stop(); } #endif @@ -125,8 +117,7 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode) for (auto i = m.constBegin(); i != m.constEnd(); ++i) { if (Utils::checkIpSubnetFormat(i.key())) { ips.append(i.key()); - } - else { + } else { if (Utils::checkIpSubnetFormat(i.value().toString())) { ips.append(i.value().toString()); } @@ -139,24 +130,24 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode) IpcClient::Interface()->routeAddList(gw, ips); // re-resolve domains - for (const QString &site: sites) { - const auto &cbResolv = [this, site, gw, mode, ips](const QHostInfo &hostInfo){ - const QList &addresses = hostInfo.addresses(); - QString ipv4Addr; - for (const QHostAddress &addr: hostInfo.addresses()) { - if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { - const QString &ip = addr.toString(); - //qDebug() << "VpnConnection::addSitesRoutes updating site" << site << ip; - if (!ips.contains(ip)) { - IpcClient::Interface()->routeAddList(gw, QStringList() << ip); - m_settings->addVpnSite(mode, site, ip); - } - flushDns(); - break; + for (const QString &site : sites) { + const auto &cbResolv = [this, site, gw, mode, ips](const QHostInfo &hostInfo) { + const QList &addresses = hostInfo.addresses(); + QString ipv4Addr; + for (const QHostAddress &addr : hostInfo.addresses()) { + if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { + const QString &ip = addr.toString(); + // qDebug() << "VpnConnection::addSitesRoutes updating site" << site << ip; + if (!ips.contains(ip)) { + IpcClient::Interface()->routeAddList(gw, QStringList() << ip); + m_settings->addVpnSite(mode, site, ip); } + flushDns(); + break; } - }; - QHostInfo::lookupHost(site, this, cbResolv); + } + }; + QHostInfo::lookupHost(site, this, cbResolv); } #endif } @@ -169,11 +160,10 @@ QSharedPointer VpnConnection::vpnProtocol() const void VpnConnection::addRoutes(const QStringList &ips) { #ifdef AMNEZIA_DESKTOP - if (connectionState() == VpnProtocol::Connected && IpcClient::Interface()) { + if (connectionState() == Vpn::ConnectionState::Connected && IpcClient::Interface()) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), ips); - } - else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { IpcClient::Interface()->routeAddList(m_vpnProtocol->routeGateway(), ips); } } @@ -183,11 +173,10 @@ void VpnConnection::addRoutes(const QStringList &ips) void VpnConnection::deleteRoutes(const QStringList &ips) { #ifdef AMNEZIA_DESKTOP - if (connectionState() == VpnProtocol::Connected && IpcClient::Interface()) { + if (connectionState() == Vpn::ConnectionState::Connected && IpcClient::Interface()) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { IpcClient::Interface()->routeDeleteList(vpnProtocol()->vpnGateway(), ips); - } - else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { IpcClient::Interface()->routeDeleteList(m_vpnProtocol->routeGateway(), ips); } } @@ -197,7 +186,8 @@ void VpnConnection::deleteRoutes(const QStringList &ips) void VpnConnection::flushDns() { #ifdef AMNEZIA_DESKTOP - if (IpcClient::Interface()) IpcClient::Interface()->flushDns(); + if (IpcClient::Interface()) + IpcClient::Interface()->flushDns(); #endif } @@ -213,18 +203,22 @@ ErrorCode VpnConnection::lastError() const QMap VpnConnection::getLastVpnConfig(const QJsonObject &containerConfig) { QMap configs; - for (Proto proto: ProtocolProps::allProtocols()) { + for (Proto proto : ProtocolProps::allProtocols()) { - QString cfg = containerConfig.value(ProtocolProps::protoToString(proto)).toObject().value(config_key::last_config).toString(); + QString cfg = containerConfig.value(ProtocolProps::protoToString(proto)) + .toObject() + .value(config_key::last_config) + .toString(); - if (!cfg.isEmpty()) configs.insert(proto, cfg); + if (!cfg.isEmpty()) + configs.insert(proto, cfg); } return configs; } -QString VpnConnection::createVpnConfigurationForProto(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Proto proto, - ErrorCode *errorCode) +QString VpnConnection::createVpnConfigurationForProto(int serverIndex, const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &containerConfig, + Proto proto, ErrorCode *errorCode) { QMap lastVpnConfig = getLastVpnConfig(containerConfig); @@ -232,10 +226,8 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, if (lastVpnConfig.contains(proto)) { configData = lastVpnConfig.value(proto); configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData); - } - else { - configData = m_configurator->genVpnProtocolConfig(credentials, - container, containerConfig, proto, errorCode); + } else { + configData = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, proto, errorCode); if (errorCode && *errorCode) { return ""; @@ -246,7 +238,8 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData); if (serverIndex >= 0) { - qDebug() << "VpnConnection::createVpnConfiguration: saving config for server #" << serverIndex << container << proto; + qDebug() << "VpnConnection::createVpnConfiguration: saving config for server #" << serverIndex << container + << proto; QJsonObject protoObject = m_settings->protocolConfig(serverIndex, container, proto); protoObject.insert(config_key::last_config, configDataBeforeLocalProcessing); m_settings->setProtocolConfig(serverIndex, container, proto, protoObject); @@ -256,17 +249,18 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, return configData; } -QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode) +QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &containerConfig, + ErrorCode *errorCode) { QJsonObject vpnConfiguration; for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) { - QJsonObject vpnConfigData = QJsonDocument::fromJson( - createVpnConfigurationForProto( - serverIndex, credentials, container, containerConfig, proto, errorCode).toUtf8()). - object(); + QJsonObject vpnConfigData = + QJsonDocument::fromJson(createVpnConfigurationForProto(serverIndex, credentials, container, + containerConfig, proto, errorCode) + .toUtf8()) + .object(); if (errorCode && *errorCode) { return {}; @@ -293,12 +287,14 @@ QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, return vpnConfiguration; } -void VpnConnection::connectToVpn(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig) +void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig) { qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is") - .arg(serverIndex).arg(ContainerProps::containerToString(container)) << m_settings->routeMode(); -#if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS) + .arg(serverIndex) + .arg(ContainerProps::containerToString(container)) + << m_settings->routeMode(); +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) if (!m_IpcClient) { m_IpcClient = new IpcClient(this); } @@ -307,14 +303,14 @@ void VpnConnection::connectToVpn(int serverIndex, if (!IpcClient::init(m_IpcClient)) { qWarning() << "Error occurred when init IPC client"; emit serviceIsNotReady(); - emit connectionStateChanged(VpnProtocol::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } } #endif m_remoteAddress = credentials.hostName; - emit connectionStateChanged(VpnProtocol::Connecting); + emit connectionStateChanged(Vpn::ConnectionState::Connecting); #ifdef AMNEZIA_DESKTOP if (m_vpnProtocol) { @@ -327,15 +323,16 @@ void VpnConnection::connectToVpn(int serverIndex, ErrorCode e = ErrorCode::NoError; m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig, &e); + emit newVpnConfigurationCreated(); if (e) { - emit connectionStateChanged(VpnProtocol::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } - -#if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS) + +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration)); if (!m_vpnProtocol) { - emit VpnProtocol::Error; + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } m_vpnProtocol->prepare(); @@ -354,17 +351,21 @@ void VpnConnection::connectToVpn(int serverIndex, createProtocolConnections(); e = m_vpnProtocol.data()->start(); - if (e) emit VpnProtocol::Error; + if (e) + emit connectionStateChanged(Vpn::ConnectionState::Error); } -void VpnConnection::createProtocolConnections() { +void VpnConnection::createProtocolConnections() +{ connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); - connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::VpnConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::VpnConnectionState))); + connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(Vpn::ConnectionState)), this, + SLOT(onConnectionStateChanged(Vpn::ConnectionState))); connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64))); } #ifdef Q_OS_ANDROID -void VpnConnection::restoreConnection() { +void VpnConnection::restoreConnection() +{ createAndroidConnections(); m_vpnProtocol.reset(androidVpnProtocol); @@ -384,11 +385,13 @@ void VpnConnection::createAndroidConnections(DockerContainer container) { androidVpnProtocol = createDefaultAndroidVpnProtocol(container); - connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState); - connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated); + connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, + &AndroidVpnProtocol::setConnectionState); + connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, + &AndroidVpnProtocol::connectionDataUpdated); } -AndroidVpnProtocol* VpnConnection::createDefaultAndroidVpnProtocol(DockerContainer container) +AndroidVpnProtocol *VpnConnection::createDefaultAndroidVpnProtocol(DockerContainer container) { Proto proto = ContainerProps::defaultProtocol(container); AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration); @@ -425,7 +428,7 @@ void VpnConnection::disconnectFromVpn() #endif if (!m_vpnProtocol.data()) { - emit connectionStateChanged(VpnProtocol::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); return; } @@ -435,9 +438,10 @@ void VpnConnection::disconnectFromVpn() m_vpnProtocol = nullptr; } -VpnProtocol::VpnConnectionState VpnConnection::connectionState() +Vpn::ConnectionState VpnConnection::connectionState() { - if (!m_vpnProtocol) return VpnProtocol::Disconnected; + if (!m_vpnProtocol) + return Vpn::ConnectionState::Disconnected; return m_vpnProtocol->connectionState(); } diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 0c3c9a5f..f6b2343c 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -51,7 +51,7 @@ public: bool isConnected() const; bool isDisconnected() const; - VpnProtocol::VpnConnectionState connectionState(); + Vpn::ConnectionState connectionState(); QSharedPointer vpnProtocol() const; const QString &remoteAddress() const; @@ -74,14 +74,16 @@ public slots: signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); void vpnProtocolError(amnezia::ErrorCode error); void serviceIsNotReady(); + void newVpnConfigurationCreated(); + protected slots: void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); - void onConnectionStateChanged(VpnProtocol::VpnConnectionState state); + void onConnectionStateChanged(Vpn::ConnectionState state); protected: QSharedPointer m_vpnProtocol; diff --git a/deploy/build_macos.sh b/deploy/build_macos.sh index 700198e7..13214d6d 100755 --- a/deploy/build_macos.sh +++ b/deploy/build_macos.sh @@ -96,16 +96,16 @@ if [ "${MAC_CERT_PW+x}" ]; then security find-identity -p codesigning echo "Signing App bundle..." - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $BUNDLE_DIR + /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $BUNDLE_DIR /usr/bin/codesign --verify -vvvv $BUNDLE_DIR || true spctl -a -vvvv $BUNDLE_DIR || true if [ "${NOTARIZE_APP+x}" ]; then echo "Notarizing App bundle..." /usr/bin/ditto -c -k --keepParent $BUNDLE_DIR $PROJECT_DIR/Bundle_to_notarize.zip - xcrun altool --notarize-app -f $PROJECT_DIR/Bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD + xcrun notarytool submit $PROJECT_DIR/Bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD rm $PROJECT_DIR/Bundle_to_notarize.zip - sleep 600 + sleep 300 xcrun stapler staple $BUNDLE_DIR xcrun stapler validate $BUNDLE_DIR spctl -a -vvvv $BUNDLE_DIR || true @@ -130,15 +130,15 @@ $QIF_BIN_DIR/binarycreator --offline-only -v -c $BUILD_DIR/installer/config/maco if [ "${MAC_CERT_PW+x}" ]; then echo "Signing installer bundle..." security unlock-keychain -p $TEMP_PASS $KEYCHAIN - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $INSTALLER_BUNDLE_DIR + /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $INSTALLER_BUNDLE_DIR /usr/bin/codesign --verify -vvvv $INSTALLER_BUNDLE_DIR || true if [ "${NOTARIZE_APP+x}" ]; then echo "Notarizing installer bundle..." /usr/bin/ditto -c -k --keepParent $INSTALLER_BUNDLE_DIR $PROJECT_DIR/Installer_bundle_to_notarize.zip - xcrun altool --notarize-app -f $PROJECT_DIR/Installer_bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD + xcrun notarytool submit $PROJECT_DIR/Installer_bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD rm $PROJECT_DIR/Installer_bundle_to_notarize.zip - sleep 600 + sleep 300 xcrun stapler staple $INSTALLER_BUNDLE_DIR xcrun stapler validate $INSTALLER_BUNDLE_DIR spctl -a -vvvv $INSTALLER_BUNDLE_DIR || true @@ -151,13 +151,13 @@ hdiutil create -volname AmneziaVPN -srcfolder $BUILD_DIR/installer/$APP_NAME.app if [ "${MAC_CERT_PW+x}" ]; then echo "Signing DMG installer..." security unlock-keychain -p $TEMP_PASS $KEYCHAIN - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $DMG_FILENAME + /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $DMG_FILENAME /usr/bin/codesign --verify -vvvv $DMG_FILENAME || true if [ "${NOTARIZE_APP+x}" ]; then echo "Notarizing DMG installer..." - xcrun altool --notarize-app -f $DMG_FILENAME -t osx --primary-bundle-id $APP_DOMAIN -u $APPLE_DEV_EMAIL -p $APPLE_DEV_PASSWORD - sleep 600 + xcrun notarytool submit $DMG_FILENAME --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD + sleep 300 xcrun stapler staple $DMG_FILENAME xcrun stapler validate $DMG_FILENAME fi diff --git a/deploy/build_windows.bat b/deploy/build_windows.bat index 7ae3e9f6..7ac37f14 100644 --- a/deploy/build_windows.bat +++ b/deploy/build_windows.bat @@ -47,7 +47,7 @@ cd %PROJECT_DIR% call "%QT_BIN_DIR:"=%\qt-cmake" . -B %WORK_DIR% cd %WORK_DIR% -cmake --build . --config release +cmake --build . --config release -- /p:UseMultiToolTask=true /m if %errorlevel% neq 0 exit /b %errorlevel% cmake --build . --target clean @@ -68,7 +68,6 @@ signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.co echo "Copying deploy data..." xcopy %DEPLOY_DATA_DIR% %OUT_APP_DIR% /s /e /y /i /f xcopy %PREBILT_DEPLOY_DATA_DIR% %OUT_APP_DIR% /s /e /y /i /f -copy "%WORK_DIR:"=%\service\wireguard-service\release\wireguard-service.exe" %OUT_APP_DIR%\wireguard\ cd %SCRIPT_DIR% xcopy %SCRIPT_DIR:"=%\installer %WORK_DIR:"=%\installer /s /e /y /i /f diff --git a/deploy/data/linux/AmneziaVPN.png b/deploy/data/linux/AmneziaVPN.png new file mode 100644 index 00000000..0f104da2 Binary files /dev/null and b/deploy/data/linux/AmneziaVPN.png differ diff --git a/deploy/data/linux/client/share/applications/AmneziaVPN.desktop b/deploy/data/linux/client/share/applications/AmneziaVPN.desktop deleted file mode 100755 index d89252c0..00000000 --- a/deploy/data/linux/client/share/applications/AmneziaVPN.desktop +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env xdg-open -[Desktop Entry] -Type=Application -Name=AmneziaVPN client -Version=2.0.10 -Comment=Client of your self-hosted VPN -Exec=AmneziaVPN -Icon=/usr/share/pixmaps/AmneziaVPN_Logo.png -Categories=Network;Qt;Security; -Terminal=false diff --git a/deploy/data/linux/client/share/icons/AmneziaVPN_Logo.png b/deploy/data/linux/client/share/icons/AmneziaVPN_Logo.png deleted file mode 100755 index 0e281afb..00000000 Binary files a/deploy/data/linux/client/share/icons/AmneziaVPN_Logo.png and /dev/null differ diff --git a/deploy/data/linux/post_install.sh b/deploy/data/linux/post_install.sh index f69de3d5..b3345bac 100755 --- a/deploy/data/linux/post_install.sh +++ b/deploy/data/linux/post_install.sh @@ -37,6 +37,7 @@ sudo ln -s $APP_PATH/client/$APP_NAME.sh /usr/local/bin/$APP_NAME >> $LOG_FILE echo "user desktop creation loop started" >> $LOG_FILE sudo cp $APP_PATH/$APP_NAME.desktop /usr/share/applications/ >> $LOG_FILE +sudo cp $APP_PATH/$APP_NAME.png /usr/share/pixmaps/ >> $LOG_FILE sudo chmod 555 /usr/share/applications/$APP_NAME.desktop >> $LOG_FILE echo "user desktop creation loop ended" >> $LOG_FILE diff --git a/deploy/data/linux/post_uninstall.sh b/deploy/data/linux/post_uninstall.sh index 029bb7cf..5849a90e 100755 --- a/deploy/data/linux/post_uninstall.sh +++ b/deploy/data/linux/post_uninstall.sh @@ -54,6 +54,11 @@ if test -f /usr/share/applications/$APP_NAME.desktop; then fi +if test -f /usr/share/pixmaps/$APP_NAME.png; then + sudo rm -f /usr/share/pixmaps/$APP_NAME.png >> $LOG_FILE + +fi + date >> $LOG_FILE echo "Service after uninstall status:" >> $LOG_FILE sudo systemctl status $APP_NAME >> $LOG_FILE diff --git a/deploy/installer/config.cmake b/deploy/installer/config.cmake index 7cc75153..13f09986 100644 --- a/deploy/installer/config.cmake +++ b/deploy/installer/config.cmake @@ -15,6 +15,11 @@ elseif(LINUX) ${CMAKE_CURRENT_LIST_DIR}/config/linux.xml.in ${CMAKE_BINARY_DIR}/installer/config/linux.xml ) + + configure_file( + ${CMAKE_CURRENT_LIST_DIR}/config/AmneziaVPN.desktop.in + ${CMAKE_BINARY_DIR}/../AppDir/AmneziaVPN.desktop + ) endif() configure_file( diff --git a/deploy/data/linux/AmneziaVPN.desktop b/deploy/installer/config/AmneziaVPN.desktop.in similarity index 64% rename from deploy/data/linux/AmneziaVPN.desktop rename to deploy/installer/config/AmneziaVPN.desktop.in index d89252c0..2a53074e 100755 --- a/deploy/data/linux/AmneziaVPN.desktop +++ b/deploy/installer/config/AmneziaVPN.desktop.in @@ -1,10 +1,10 @@ #!/usr/bin/env xdg-open [Desktop Entry] Type=Application -Name=AmneziaVPN client -Version=2.0.10 +Name=AmneziaVPN +Version=@CMAKE_PROJECT_VERSION@ Comment=Client of your self-hosted VPN Exec=AmneziaVPN -Icon=/usr/share/pixmaps/AmneziaVPN_Logo.png +Icon=/usr/share/pixmaps/AmneziaVPN.png Categories=Network;Qt;Security; Terminal=false diff --git a/deploy/installer/config/linux.xml.in b/deploy/installer/config/linux.xml.in index 150c9cc5..a39edbe4 100644 --- a/deploy/installer/config/linux.xml.in +++ b/deploy/installer/config/linux.xml.in @@ -1,7 +1,7 @@ AmneziaVPN - 1.6.0.0 + @CMAKE_PROJECT_VERSION@ AmneziaVPN AmneziaVPN AmneziaVPN diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index d2629a0b..e34f5ca3 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -273,7 +273,7 @@ endif() qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_interface.rep) qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_process_interface.rep) -# deploy artifacts required to run the application to the debug build folder +# copy deploy artifacts required to run the application to the debug build folder if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") set(DEPLOY_PLATFORM_PATH "windows/x64")