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/CMakeLists.txt b/CMakeLists.txt index f6f94a23..50bd621c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 3.1.0.1 +project(${PROJECT} VERSION 4.0.7.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) 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/CMakeLists.txt b/client/CMakeLists.txt index 7e7837ac..ca5161cf 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() @@ -91,7 +95,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 +131,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 +164,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) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 491a68b8..23157468 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,94 @@ #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(); + 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); + + m_engine->load(url); + m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); if (m_settings->isSaveLogs()) { if (!Logger::init()) { @@ -124,19 +149,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 +170,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,76 +184,91 @@ 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()); + if (locale != QLocale::English) { + if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { + if (QCoreApplication::installTranslator(m_translator.get())) { + m_settings->setAppLanguage(locale); + } + } } } +void AmneziaApplication::updateTranslator(const QLocale &locale) +{ + QResource::registerResource(":/translations.qrc"); + if (!m_translator->isEmpty()) + QCoreApplication::removeTranslator(m_translator.get()); + + if (locale == QLocale::English) { + m_settings->setAppLanguage(locale); + m_engine->retranslate(); + } + + if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { + if (QCoreApplication::installTranslator(m_translator.get())) { + m_settings->setAppLanguage(locale); + } + + m_engine->retranslate(); + } + + emit translationsUpdated(); +} + bool AmneziaApplication::parseCommands() { m_parser.setApplicationDescription(APPLICATION_NAME); 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 +280,88 @@ 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()); + + 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); + + 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_sitesModel->getRouteMode() != Settings::RouteMode::VpnAllSites) { + m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites); + emit m_pageController->showNotificationMessage( + tr("Split tunneling for WireGuard is not implemented, the option was disabled")); + } + }); + + 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()); + +#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()); + + 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..2dd74fcb 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -1,27 +1,52 @@ #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/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 +57,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 +66,56 @@ 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; +#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/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/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/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 54ee320c..14059977 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -1,30 +1,27 @@ #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, QObject *parent) + : ConfiguratorBase(settings, parent) { - } WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() @@ -36,37 +33,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); if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::InternalError; + if (errorCode) + *errorCode = ErrorCode::InternalError; return connData; } @@ -96,22 +96,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 +123,60 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon } // Get keys - connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e); + connData.serverPubKey = serverController.getTextFileFromContainer( + container, credentials, amnezia::protocols::wireguard::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, + amnezia::protocols::wireguard::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) + .arg(connData.pskKey) + .arg(connData.clientIP); e = serverController.uploadTextFileToContainer(container, credentials, configPart, - protocols::wireguard::serverConfigPath, libssh::SftpOverwriteMode::SftpAppendToExisting); + protocols::wireguard::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))); + 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))); 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 config = + serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container), + serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); if (errorCode && *errorCode) { diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 92066dab..20fc59f4 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,94 @@ 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::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::FileShare, QObject::tr("SMB file sharing service")}, + { 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::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::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, + { 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 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") }, + + { DockerContainer::TorWebSite, QObject::tr("Website 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") } }; } 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; + 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; } } 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::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::FileShare : return Protocol::FileShare; + case DockerContainer::Sftp: return Proto::Sftp; + default: return Proto::Any; } } @@ -151,22 +175,23 @@ 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::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; @@ -175,7 +200,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) 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 +208,64 @@ 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::Cloak: return true; + case DockerContainer::OpenVpn: return true; + default: return false; + } +} + +QString ContainerProps::easySetupHeader(DockerContainer container) +{ + switch (container) { + case DockerContainer::WireGuard: return tr("Low"); + case DockerContainer::Cloak: return tr("High"); + case DockerContainer::OpenVpn: return tr("Medium"); + default: return ""; + } +} + +QString ContainerProps::easySetupDescription(DockerContainer container) +{ + switch (container) { + case DockerContainer::WireGuard: return tr("I just want to increase the level of privacy"); + case DockerContainer::Cloak: return tr("Many foreign websites and VPN providers are blocked"); + case DockerContainer::OpenVpn: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); + default: return ""; + } +} + +int ContainerProps::easySetupOrder(DockerContainer container) +{ + switch (container) { + case DockerContainer::WireGuard: return 1; + case DockerContainer::Cloak: return 3; + case DockerContainer::OpenVpn: return 2; + 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..9ca51a96 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, + OpenVpn, + ShadowSocks, + Cloak, + WireGuard, + Ipsec, - Q_INVOKABLE static QList allContainers(); + // non-vpn + TorWebSite, + Dns, + // FileShare, + 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/servercontroller.cpp b/client/core/servercontroller.cpp index e5505d46..b0f8146f 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,25 @@ 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; } return false; @@ -364,75 +357,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 +448,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,16 +468,19 @@ 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(); @@ -484,85 +490,102 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential 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() } }); 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 +604,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 +620,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 +634,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 +658,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 +676,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 +705,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 +737,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 +769,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 +801,19 @@ 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); + + config.insert(config_key::container, ContainerProps::containerToString(container)); } - }; + config.insert(ProtocolProps::protoToString(protocol), containerConfig); + } installedContainers.insert(container, config); } } @@ -783,7 +821,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/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/main.cpp b/client/main.cpp index 72ed6018..e78a74ff 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,14 @@ int main(int argc, char *argv[]) AllowSetForegroundWindow(ASFW_ANY); #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 @@ -63,6 +60,10 @@ int main(int argc, char *argv[]) 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/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/ios_controller.h b/client/platforms/ios/ios_controller.h index cc409f71..ea8adbc0 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,26 +35,27 @@ 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(); @@ -66,12 +69,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..57394383 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; } @@ -344,14 +345,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 +374,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/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..5f8600db 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -10,17 +10,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 +47,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 +60,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::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::FileShare, "File Sharing Service" }, + { Proto::Sftp, QObject::tr("Sftp service") } }; } QMap ProtocolProps::protocolDescriptions() @@ -81,90 +83,89 @@ 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::TorWebSite: return ServiceType::Other; + case Proto::Dns: return ServiceType::Other; + case Proto::FileShare: return ServiceType::Other; + default: return ServiceType::Other; } } 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 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::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::FileShare: return 139; + 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::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 true; + case Proto::Dns: return false; + case Proto::FileShare: return false; + default: return -1; } } 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::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::FileShare: 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::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::FileShare: 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..9472164b 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -1,206 +1,212 @@ #ifndef PROTOCOLS_DEFS_H #define PROTOCOLS_DEFS_H -#include -#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 openvpn[] = "openvpn"; + constexpr char wireguard[] = "wireguard"; + constexpr char shadowsocks[] = "shadowsocks"; + constexpr char cloak[] = "cloak"; + constexpr char sftp[] = "sftp"; + + } + + 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 + { + 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 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..841d307c 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -18,7 +18,7 @@ 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), @@ -32,7 +32,7 @@ void VpnProtocol::setLastError(ErrorCode lastError) { m_lastError = lastError; if (lastError){ - setConnectionState(VpnConnectionState::Error); + setConnectionState(Vpn::ConnectionState::Error); } qCritical().noquote() << "VpnProtocol error, code" << m_lastError << errorString(m_lastError); } @@ -60,7 +60,7 @@ void VpnProtocol::stopTimeoutTimer() m_timeoutTimer->stop(); } -VpnProtocol::VpnConnectionState VpnProtocol::connectionState() const +Vpn::ConnectionState VpnProtocol::connectionState() const { return m_connectionState; } @@ -76,19 +76,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; } @@ -124,17 +124,17 @@ 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"); + 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: ; } @@ -149,10 +149,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..a4005efb 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,7 +95,7 @@ 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) @@ -143,8 +145,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 +158,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 +193,7 @@ ErrorCode WireguardProtocol::start() return lastError(); } - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); m_wireguardStartProcess = IpcClient::CreatePrivilegedProcess(); @@ -213,16 +216,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 +253,6 @@ ErrorCode WireguardProtocol::start() void WireguardProtocol::updateVpnGateway(const QString &line) { - } QString WireguardProtocol::serviceName() const @@ -261,9 +263,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 +274,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..5b4d6ae7 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,128 @@ 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 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/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..919ca731 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,86 @@ 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); + }; + + 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..f338216b 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -2,1145 +2,2948 @@ - MainWindow + AdvancedServerSettingsLogic - - Connect to the already created VPN server - Подключиться к уже созданному серверу VPN - - - - Connection code - Код для подключения - - - - Connecting... - Подключение... - - - - - - - - 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 - - - - For all connections - Для всех соединений - - - - For selected sites - Для выбранных сайтов - - - - Error text - - - - - List of the most popular prohibited sites - Список самых популярных запрещенных сайтов - - - For example, rutor.org or 17.21.111.8 - Например, rutor.org или 17.21.111.8 - - - 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, что и вы. Чтобы создать новый код смените логин и/или пароль для подлючения в настройках вашего сервера. - - - These sites will open via VPN - These sites will open via VPN - - - Delete selected item - Удалить выбранный элемент - - - Hostname or IP address - Имя хоста или IP адрес - - - - + - + - - - - Server settings - Настройки сервера - - - - Share connection - Поделиться подключением - - - - <!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> + + + Clear server from Amnezia software - - Configure VPN protocols manually - Выбрать протоколы + + Service: + - - Run Setup Wizard - Мастер настройки - - - - If you want easily configure your server just run Wizard - Для облегченной настройки сервера запустите мастер настройки - - - - Press configure manually to choose VPN protocols you want to install - Выбрать протоколы для установки самостоятельно - - - - - - - - Setup Wizard - Мастер настройки - - - - High censorship level - Высокий уровень цензуры - - - - Medium censorship level - Средний уровень цензуры - - - - Low censorship level - Низкий уровень цензуры - - - - 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, невидимый для надзорных органов. - - - - 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 сервисы в целом работают. Я хочу установить гибкое решение. - - - - I just want to improve my privacy in internet. - Я просто хочу повысить уровень моей приватности в интернете. - - - - - - Next - Далее - - - - 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. - -Заметьте, этот протокол пока не имеет функции экспорта настроек подключения для мобильных устаройств. Следите за обновлениями. - - - - 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 - Добавить или импортировать новый сервер - - - - Add server - Добавить сервер - - - - Servers list - Список серверов - - - - Auto start - Авто старт - - - - Application Settings - Настройки приложения - - - - Auto connect - Авто соединение - - - - Check for updates - Проверить обновления - - - - Start minimized - Запускать свёрнутым - - - - Open logs folder - Открыть папки с логами - - - - DNS Servers - DNS сервера - - - - - Reset to default value - Сбросить по умолчанию - - - - Primary DNS server - Первичный DSN сервер - - - - Secondary DNS server - Вторичный DNS сервер - - - - - Clear client cached profile - Удалить закешировнный профиль - - - - - Clear server from Amnezia software - Очистить сервер от Amnezia - - - - Forget this server - Забыть этот сервер - - - - root@yourserver.org - - - - - VPN protocols - VPN протоколы - - - - VPN Protocol: - VPN протокол: - - - - Protocols - Протоколы - - - - Cloak container - Cloak контейнер - - - - - - OpenVPN settings - Настройки OpenVPN - - - - - ShadowSocks settings - Настройки ShadowSocks - - - - Cloak settings - Настройки Cloak - - - - ShadowSocks container - ShadowSocks контейнер - - - - OpenVPN container - OpenVPN контейнер - - - - Full access - Полный доступ - - - - - - <!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> - - - - - 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 - - - - 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 - - - - Auto-negotiate encryption - Авто-согласование шифрования - - - - SHA512 - - - - - SHA384 - - - - - SHA256 - - - - - SHA3-512 - - - - - SHA3-384 - - - - - SHA3-256 - - - - - whirlpool - - - - - BLAKE2b512 - - - - - BLAKE2s256 - - - - - SHA1 - - - - - Block DNS requests outside of VPN - Блокировать запросы DNS мимо VPN - - - - Enable TLS auth - Включить TLS auth - - - - ShadowSocks Settings - Настройки ShadowSocks - - - - - chacha20-poly1305 - - - - - Cloak Settings - Настройки Cloak - - - - plain - - - - - - - - - - - - Copy - Копировать - - - - Cannot open logs folder! - Невозможно открыть папку с логами! - - - - - Please fill in all fields - Пожалуйста, заполните все поля - - - - It's public key. Private key required - Это публичный ключ. Требуется приватный - - - - of - из - - - - - - Error occurred while configuring server. - Ошибка во время настройки сервера - - - - Amnezia server installed - Amnezia сервер установлен - - - - Operation finished - Операция завершена - - - + Uninstalling Amnezia software... - Удаление Amnezia... + - + + Error occurred while cleaning the server. + + + + + + Error message: + + + + + See logs for details. - Смотрите логи для подробных деталей + - + Amnezia server successfully uninstalled - Amnezia сервер удален + - - Show - Показать окно + + Error occurred while scanning the server. + - - - Disconnect - Отключиться + + All containers installed on the server are added to the GUI + - - Visit Website - Посетить сайт + + No installed containers found on the server + + + + + AppSettingsLogic + + + Software version + - - Quit - Выход + + Save log + - - Do you really want to quit? - Вы действительно хотите выйти? + + Open backup + - - Import IP addresses - Импортовать IP адреса + + Can't import config, file is corrupted. + + + + + ClientInfoLogic + + + Service: + + + + + ClientManagementLogic + + + Service: + - - Import connection - Импортировать соединение + + An error occurred while getting the list of clients. + + + + ConnectionController - - Private key - Приватный ключ - - - - Connect using SSH password - Соединиться с паролем SSH - - - - Cache cleared - Кеш очищен - - - - - - - Copied - Скопировано - - - - Save AmneziaVPN config - Сохранить конфиг AmneziaVPN - - - - - Generating... - Генерация... - - - - Error while generating connection profile - Ошибка во время генерации профиля - - - - Save OpenVPN config - Сохранить OpenVPN конфиг - - - + VPN Protocols is not installed. Please install VPN container at first - VPN протоколы ещё не установлены. Установите VPN контейнеры + - - VPN Protocol not chosen - VPN протокол не выбран + + Connection... + - - Software version - Версия программы + + Disconnect + - - Protocol: - Протокол: + + Reconnection... + - - Press Generate config - Нажмите Сгенерировать конфиг + + + + + Connect + - - Share server full access - Расшарить полный доступ + + Disconnection... + + + + + ConnectionTypeSelectionDrawer + + + Connection data + + + + + Server IP, login and password + + + + + QR code, key or configuration file + + + + + ContextMenu + + + C&ut + + + + + &Copy + + + + + &Paste + + + + + &SelectAll + + + + + ExportController + + + Access error! + + + + + Save AmneziaVPN config + + + + + ImportController + + + Open config file + + + + + InstallController + + + + installed successfully. + + + + + + is already installed on the server. + + + + + + +Already installed containers were found on the server. All installed containers have been added to the application + + + + + Server ' + + + + + ' was removed + + + + + All containers from server ' + + + + + has been removed from the server ' + + + + + Please login as the user + + + + + NotificationHandler + + + + AmneziaVPN + + + + + VPN Connected + + + + + VPN Disconnected + + + + + AmneziaVPN notification + + + + + Unsecured network detected: + + + + + PageAbout + + + About Amnezia + + + + + AmneziaVPN is opensource software, it's free forever. Our goal is to make the best VPN client in the world. +<ul> +<li>Sources on <a href="https://github.com/amnezia-vpn/desktop-client">GitHub</a></li> +<li><a href="https://amnezia.org/">Web Site</a></li> +<li><a href="https://t.me/amnezia_vpn_en">Telegram group</a></li> +<li><a href="https://signal.group/#CjQKIB2gUf8QH_IXnOJMGQWMDjYz9cNfmRQipGWLFiIgc4MwEhAKBONrSiWHvoUFbbD0xwdh">Signal group</a></li> +</ul> + + + + + + Support + + + + + Have questions? You can get support by: +<ul> +<li><a href="https://t.me/amnezia_vpn_en">Telegram group</a> (preferred way)</li> +<li>Create issue on <a href="https://github.com/amnezia-vpn/desktop-client/issues">GitHub</a></li> +<li>Email to: <a href="support@amnezia.org">support@amnezia.org</a></li> +</ul> + + + + + Donate + + + + + Please support Amnezia project by donation, we really need it now more than ever. +<ul> +<li>By credit card on <a href="https://www.patreon.com/amneziavpn">Patreon</a> (starting from $1)</li> +<li>Send some coins to addresses listed <a href="https://github.com/amnezia-vpn/desktop-client/blob/master/README.md">on GitHub page</a></li> +</ul> + + + + + + PageAdvancedServerSettings + + + Advanced server settings + + + + + Clients Management + + + + + PageAppSetting + + + Application Settings + + + + + Auto connect + + + + + Auto start + + + + + Start minimized + + + + + Check for updates + + + + + Keep logs + + + + + Open logs folder + + + + + Export logs + + + + + Clear logs + + + + + Cleared + + + + + Backup and restore configuration + + + + + Backup app config + + + + + Restore app config + + + + + PageClientInfoOpenVPN + + + Client Info + + + + + Client name + + + + + Certificate id + + + + + Certificate + + + + + Revoke Certificate + + + + + PageClientInfoWireGuard + + + Client Info + + + + + Client name + + + + + Public Key + + + + + Revoke Key + + + + + PageClientManagement + + + Clients Management + + + + + PageDeinstalling + + + Removing services from + + + + + Usually it takes no more than 5 minutes + + + + + PageGeneralSettings + + + App settings + + + + + Network settings + + + + + Server Settings + + + + + Share connection + + + + + Servers + + + + + Add server + + + + + Exit + + + + + PageHome + + + VPN protocol + + + + + Servers + + + + + PageNetworkSetting + + + DNS Servers + + + + + Use AmneziaDNS service (recommended) + + + + + Use AmneziaDNS container on your server, when it installed. + +Your AmneziaDNS server available only when it installed and VPN connected, it has internal IP address 172.29.172.254 + +If AmneziaDNS service is not installed on the same server, or this option is unchecked, the following DNS servers will be used: + + + + + Primary DNS server + + + + + + Reset to default + + + + + Secondary DNS server + + + + + PageNewServer + + + Setup your server to use VPN + + + + + If you want easily configure your server just run Wizard + + + + + Run Setup Wizard + + + + + Press configure manually to choose VPN protocols you want to install + + + + + Configure + + + + + PageNewServerProtocols + + + Select VPN protocols + + + + + Setup server + + + + + Select protocol container + + + + + Port + + + + + Network Protocol + + + + + udp + + + + + tcp + + + + + PageProtoCloak + + + Cloak Settings + + + + + Cipher + + + + + chacha20-poly1305 + + + + + aes-256-gcm + + + + + aes-192-gcm + + + + + aes-128-gcm + + + + + Fake Web Site + + + + + Port + + + + + Save and restart VPN + + + + + Cancel + + + + + PageProtoOpenVPN + + + OpenVPN Settings + + + + + VPN Addresses Subnet + + + + + Network protocol + + + + + TCP + + + + + UDP + + + + + Port + + + + + Auto-negotiate encryption + + + + + 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 + + + + + Hash + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + Enable TLS auth + + + + + Block DNS requests outside of VPN + + + + + Additional client config commands → + + + + + Additional server config commands → + + + + + Save and restart VPN + + + + + Cancel + + + + + PageProtoSftp + + + SFTP settings + + + + + Port + + + + + User Name + + + + + Password + + + + + Restore drive when client starts + + + + + Mount drive + + + + + PageProtoShadowSocks + + + ShadowSocks Settings + + + + + Cipher + + + + + chacha20-ietf-poly1305 + + + + + xchacha20-ietf-poly1305 + + + + + aes-256-gcm + + + + + aes-192-gcm + + + + + aes-128-gcm + + + + + Port + + + + + Save and restart VPN + + + + + Cancel + + + + + PageProtoTorWebSite + + + Tor Web Site settings + + + + + Web site onion address + + + + + Notes:<ul> +<li>Use <a href="https://www.torproject.org/download/">Tor Browser</a> to open this url.</li> +<li>After installation it takes several minutes while your onion site will become available in the Tor Network.</li> +<li>When configuring WordPress set the domain as this onion address.</li> +</ul> + + + + + + PageProtoWireGuard + + + WireGuard Settings + + + + + PageProtocolCloakSettings + + + Settings updated successfully + + + + + Cloak settings + + + + + Disguised as traffic from + + + + + Port + + + + + + Cipher + + + + + Save and Restart Amnezia + + + + + PageProtocolOpenVpnSettings + + + Settings updated successfully + + + + + OpenVPN settings + + + + + VPN Addresses Subnet + + + + + 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 + + + + + Block DNS requests outside of VPN + + + + + Additional client configuration commands + + + + + + Commands: + + + + + Additional server configuration commands + + + + + Remove OpenVPN + + + + + Remove OpenVpn from server? + + + + + Continue + Продолжить + + + + Cancel + + + + + Save and Restart Amnezia + + + + + PageProtocolRaw + + + settings + + + + + Show connection options + + + + + Connection options + + + + + + Remove + + + + + from server? + + + + + Continue + Продолжить + + + + Cancel + + + + + PageProtocolShadowSocksSettings + + + Settings updated successfully + + + + + ShadowSocks settings + + + + + Port + + + + + + Cipher + + + + + Save and Restart Amnezia + + + + + PageQrDecoderIos + + + Import configuration + + + + + PageServerConfiguringProgress + + + Configuring... + + + + + Please wait. + + + + + Cancel + + + + + PageServerContainers + + + + Install new service + + + + + Installed services + + + + + Default + + + + + Port + + + + + Network Protocol + + + + + udp + + + + + tcp + + + + + Cancel + + + + + Continue + Продолжить + + + + Installed Protocols and Services + + + + + Remove container + + + + + This action will erase all data of this container on the server. + + + + + PageServerList + + + Servers + + + + + PageServerSettings + + + Server settings + + + + + Protocols and Services + + + + + Share Server (FULL ACCESS) + + + + + Advanced server settings + + + + + Forget this server + + + + + PageServiceSftpSettings + + + Settings updated successfully + + + + + SFTP settings + + + + + Host + + + + + Port + + + + + Login + + + + + Password + + + + + Mount folder on device + + + + + In order to mount remote SFTP folder as local drive, perform following steps: <br> + + + + + + <br>1. Install the latest version of + + + + + + <br>2. Install the latest version of + + + + + Detailed instructions + + + + + Remove SFTP and all data stored there + + + + + Some description + + + + + Continue + Продолжить + + + + Cancel + + + + + PageServiceTorWebsiteSettings + + + Settings updated successfully + + + + + Tor website settings + + + + + Website address + + + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> 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. + + + + + Remove website + + + + + Some description + + + + + Continue + Продолжить + + + + Cancel + + + + + PageSettings + + + Settings + + + + + Servers + + + + + Connection + + + + + Application + + + + + Backup + + + + + About AmneziaVPN + + + + + 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. + + + + + Card on Patreon + + + + + Show other methods on Github + + + + + Contacts + + + + + Telegram group + + + + + To discuss features + + + + + Mail + + + + + For reviews and bug reports + + + + + Github + + + + + Website + + + + + Check for updates + + + + + PageSettingsApplication + + + Application + + + + + 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. + + + + + Continue + Продолжить + + + + Cancel + + + + + PageSettingsBackup + + + Backup + + + + + Settings restored from backup file + + + + + Configuration backup + + + + + It will help you instantly restore connection settings at the next installation + + + + + Make a backup + + + + + Restore from backup + + + + + PageSettingsConnection + + + Connection + + + + + Use AmneziaDNS if installed on the server + + + + + Internal IP address 172.29.172.254 + + + + + DNS servers + + + + + If AmneziaDNS is not used or installed + + + + + Split site tunneling + + + + + Allows you to connect to some sites through a secure connection, and to others bypassing it + + + + + Separate application tunneling + + + + + Allows you to use the VPN only for certain applications + + + + + PageSettingsDns + + + DNS servers + + + + + If AmneziaDNS is not used or installed + + + + + Save + + + + + PageSettingsLogging + + + Logging + + + + + Save logs + + + + + Open folder with logs + + + + + Save logs to file + + + + + Clear logs + + + + + PageSettingsServerData + + + All installed containers have been added to the application + + + + + No installed containers found + + + + + Clear Amnezia cache + + + + + May be needed when changing other settings + + + + + Clear cached profiles? + Очистить закешированные профили + + + + some description + + + + + + + Continue + Продолжить + + + + + + Cancel + + + + + Check the server for previously installed Amnezia services + + + + + 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. + + + + + Clear server from Amnezia software + + + + + Clear server from Amnezia software? + + + + + 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 + + + + + from server? + + + + + Continue + Продолжить + + + + Cancel + + + + + PageSettingsServersList + + + Servers + + + + + PageSetupWizard + + + Setup your server to use VPN + + + + + High censorship level + + + + + 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. + + + + + + Medium censorship level + + + + + 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. + + + + + + Low censorship level + + + + + I want to improve my privacy on the internet. +OpenVPN profile will be installed. + + + + + + Next + + + + + PageSetupWizardConfigSource + + + Server connection + + + + + Do not use connection code from public sources. It may have been created to intercept your data. + +It's okay if a friend passed the code. + + + + + What do you have? + + + + + File with connection settings + + + + + QR-code + + + + + Key as text + + + + + PageSetupWizardCredentials + + + Server connection + + + + + Server IP address [:port] + + + + + 255.255.255.255:88 + + + + + + Insert + + + + + Password / SSH private key + + + + + Continue + Продолжить + + + + Enter the address in the format 255.255.255.255:88 + + + + + Login to connect via SSH + + + + + Ip address cannot be empty + + + + + 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 + + + + + I want to choose a VPN protocol + + + + + Continue + Продолжить + + + + PageSetupWizardHighLevel + + + Setup Wizard + + + + + 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. + + + + + 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. + + + + + 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). + + + + + Next + + + + + PageSetupWizardInstalling + + + The server has already been added to the application + + + + + Installing + + + + + Usually it takes no more than 5 minutes + + + + + PageSetupWizardLowLevel + + + Setup Wizard + + + + + 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. + + + + + OpenVPN profile will be installed + + + + + Start configuring + + + + + PageSetupWizardMediumLevel + + + Setup Wizard + + + + + 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". + +This protocol supports exporting connection profiles to mobile devices by using QR codes (you should launch the 3rd party open source VPN client - ShadowSocks VPN). + + + + + OpenVPN over ShadowSocks profile will be installed + + + + + Next + + + + + PageSetupWizardProtocolSettings + + + Installing + + + + + protocol description + + + + + More detailed + + + + + detailed protocol description + + + + + Close + + + + + Network protocol + + + + + Port + + + + + Install + + + + + PageSetupWizardProtocols + + + VPN protocol + + + + + Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. + + + + + PageSetupWizardQrReader + + + Point the camera at the QR code and hold for a couple of seconds. + + + + + PageSetupWizardStart + + + Free service for creating a personal VPN on your server. + + + + + Helps you access blocked content without revealing your privacy, even to VPN providers. + + + + + I have the data to connect + + + + + I have nothing + + + + + PageSetupWizardTextKey + + + Connection key + + + + + A line that starts with vpn://... + + + + + Key + + + + + Insert + + + + + Continue + Продолжить + + + + PageSetupWizardVPNMode + + + Setup Wizard + + + + + Optional. + +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. + +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. + + + + + Turn on mode "VPN for selected sites" + + + + + Start configuring + + + + + 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 + + + + + WireGuard native format + + + + + VPN Access + + + + + Connection + + + + + VPN access without the ability to manage the server + + + + + Full access to server + + + + + Server and service + + + + + Server + + + + + Accessing + + + + + Protocols and services + + + + + Connection to + + + + + + File with connection settings to + + + + + For the AmneziaVPN app + + + + + Full access + + + + + + Connection format + + + + + Share + + + + + PageShareConnection + + + Share protocol config + + + + + Share for Amnezia + + + + + Share for + + + + + PageShareProtoAmnezia + + + Share for Amnezia + + + + + 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. + + + + + Anyone who logs in with this code will be able to connect to this VPN server. + +This code does not include server credentials. + +New encryption keys pair will be generated. + + + + + Share + + + + + Save to file + + + + + Save AmneziaVPN config + + + + + Scan QR code using AmneziaVPN mobile + + + + + PageShareProtoCloak + + + Share Cloak Settings + + + + + Note: Cloak protocol using same password for all connections + + + + + Share + + + + + Save to file + + + + + Save AmneziaVPN config + + + + + PageShareProtoIkev2 + + + Share IKEv2 Settings + + + + + + Export p12 certificate + + + + + + Export config for Apple + + + + + + Export config for StrongSwan + + + + + PageShareProtoOpenVPN + + + Share OpenVPN Settings + + + + + New encryption keys pair will be generated. + + + + + Share + + + + + Save to file + + + + + Save OpenVPN config + + + + + PageShareProtoSftp + + + Share SFTP settings + + + + + PageShareProtoShadowSocks + + + Share ShadowSocks Settings + + + + + Note: ShadowSocks protocol using same password for all connections + + + + + Copy config + + + + + Connection string + + + + + Copy string + + + + + PageShareProtoTorWebSite + + + Share Tor Web site + + + + + PageShareProtoWireGuard + + + Share WireGuard Settings + + + + + New encryption keys pair will be generated. + + + + + Share + + + + + Save to file + + + + + Save OpenVPN config + + + + + PageShareProtocolBase + + + Generate config + + + + + Generating config... + + + + + Show config + + + + + PageSites + + + Web site/Hostname/IP address/Subnet + + + + + yousite.com or IP address + + + + + Import IP addresses + + + + + Delete selected + + + + + Select all + + + + + Export all + + + + + PageStart + + + Setup your server to use VPN + + + + + Connect to the already created VPN server + + + + + + Set up your own server + + + + + Import connection + + + + + Connection code + + + + + Connect + + + + + Open file + + + + + Scan QR code + + + + + Restore app config + + + + + How to get own server? → + + + + + Server IP address [:port] + + + + + Login to connect via SSH + + + + + + Password + + + + + + Connect using SSH key + + + + + Private key + + + + + Connect using SSH password + + + + + PageVPN + + + Donate + + + + + Server + + + + + Profile + + + + + Proto + + + + + DNS + + + + + How to use VPN + + + + + For all connections + + + + + Except selected sites + + + + + For selected sites + + + + + + Add site + + + + + PageViewConfig + + + Check config + + + + + 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. + + + + + Suspicious string: + + + + + Cached connection profile: + + + + + Cancel + + + + + Import config + + + + + PopupType + + + Close + QObject - - AmneziaVPN is already running. - Приложение AmneziaVPN уже запущено. - No error @@ -1166,656 +2969,650 @@ This code does not include server credentials. Server port already used. Check for another software + + + Server error: Docker container missing + + + + + Server error: Docker failed + + - Ssh connection error + Installation canceled by user - Ssh connection timeout - - - - - Ssh protocol error - - - - - Ssh server ket check failed + The user does not have permission to use sudo - Ssh key file error + Ssh request was denied - Ssh authentication error + Ssh request was interrupted - Ssh session closed - - - - Ssh internal error - - Failed to create remote process on server + + Invalid private key or invalid passphrase entered - - Failed to start remote process on server + + The selected private key format is not supported, use openssh ED25519 key types or PEM key types + + + + + Timeout connecting to server - Remote process on server crashed + Sftp error: End-of-file encountered + + + + + Sftp error: File does not exist + + + + + Sftp error: Permission denied - Failed to save config to disk + Sftp error: Generic failure - OpenVPN config missing + Sftp error: Garbage received from server - OpenVPN management server error + Sftp error: No connection has been set up - EasyRSA runtime error + Sftp error: There was a connection, but we lost it + + + + + Sftp error: Operation not supported by libssh yet + + + + + Sftp error: Invalid file handle - OpenVPN executable missing + 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 - Amnezia helper service error - Ошибка локального сервиса Amnezia + Sftp error: Write-protected filesystem + - + + Sftp error: No media was in remote drive + + + + + Failed to save config to disk + + + + + OpenVPN config missing + + + + + OpenVPN management server error + + + + + OpenVPN executable missing + + + + + ShadowSocks (ss-local) executable missing + + + + + Cloak (ck-client) executable missing + + + + + Amnezia helper service error + + + + + OpenSSL failed + + + + Can't connect: another VPN connection is active - + + Can't setup OpenVPN TAP network adapter + + + + + VPN pool error: no available addresses + + + + + The config does not contain any containers and credentiaks for connecting to the server + + + + Internal error - - - QSsh::Internal::SftpChannelPrivate - - Server could not start SFTP subsystem. + + IPsec - - The SFTP server finished unexpectedly with exit code %1. + + + Web site in Tor network - - The SFTP server crashed: %1. + + DNS Service - - Unexpected packet of type %1. + + Sftp file sharing service - - Protocol version mismatch: Expected %1, got %2 + + Amnezia DNS - - Unknown error. + + OpenVPN container - - Created remote directory "%1". + + Container with OpenVpn and ShadowSocks - - Remote directory "%1" already exists. + + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - - Error creating directory "%1": %2 + + WireGuard container - - Could not open local file "%1": %2 + + IPsec container - - Remote directory could not be opened for reading. + + Sftp file sharing service - is secure FTP service - - Failed to list remote directory contents. + + Sftp service - - 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 + + An error occurred while saving the list of clients. - QSsh::Internal::SshChannelManager + SelectContainer - - Unexpected request success packet. + + VPN containers - - Unexpected request failure packet. - - - - - Invalid channel id %1 + + Other containers - QSsh::Internal::SshConnectionPrivate + SelectLanguageDrawer - - 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. + + Choose language - QSsh::Internal::SshRemoteProcessPrivate + ServerConfiguringProgressLogic - - Process killed by signal + + + Please wait, configuring process may take up to 5 minutes - - Server sent invalid signal "%1" + + Configuring... + + + + + Operation finished - QSsh::SftpFileSystemModel + ServerContainersLogic - - File Type + + Error occurred while configuring server. - - File Name + + Error message: - - Error getting "stat" info about "%1": %2 - - - - - Error listing contents of directory "%1": %2 + + See logs for details. - QSsh::Ssh + ServerSettingsLogic - - Failed to open key file "%1" for reading: %2 + + + Clear client cached profile - - Failed to open key file "%1" for writing: %2 + + Service: - - Password Required + + Cache cleared - - - 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 - Настройки сервера - Settings - + Server #1 - - + + Server - SshConnection + SettingsController - - Server and client capabilities don't match. Client list was: %1. -Server list was %2. + + Software version + + + + + Save log + + + + + Backup application config + + + + + Open backup + + + + + Backup file is empty + + + + + Backup file is corrupted - SshKeyGenerator + ShareConnectionButtonCopyType - - Error generating key: %1 + + Copy - - Password for Private Key + + Copied + + + + + ShareConnectionDrawer + + + Share - - It is recommended that you secure your private key -with a password, which you can enter below. + + Copy - - Encrypt Key File + + Show content - - Do Not Encrypt Key File + + To read the QR code in the Amnezia app, select "Add Server" → "I have connection details" + + + + + ShareConnectionLogic + + + Error while generating connection profile + + + + + Error occurred while generating the config. + + + + + Error message: + + + + + See logs for details. + + + + + SitesLogic + + + These sites will be opened using VPN + + + + + These sites will be excepted from VPN + + + + + StartPageLogic + + + + Connect + + + + + + Please fill in all fields + + + + + Connecting... + + + + + Open config file + + + + + SystemTrayNotificationHandler + + + Show + + + + + Connect + + + + + Disconnect + + + + + Visit Website + + + + + Quit + + + + + UiLogic + + + Error occurred while configuring server. + + + + + Error message: + + + + + See logs for details. VpnConnection - + Mbps + + VpnLogic + + + + 0 Mbps + + + + + AmneziaVPN not supporting selected protocol on this device. Select another protocol. + + + + + VPN Protocols is not installed. + Please install VPN container at first + + + + + VPN Protocol not chosen + + + VpnProtocol - + Unknown - Неизвестно + - + Disconnected - Отключено + - + Preparing - Подготовка + - + Connecting... - Подключение... + - + Connected - Подключено + - + Disconnecting... - Отключение... + - + Reconnecting... - Переподключение... + - + Error - Ошибка + + + + + amnezia::ContainerProps + + + Low + + + + + High + + + + + Medium + + + + + Many foreign websites and VPN providers are blocked + + + + + Some foreign sites are blocked, but VPN providers are not blocked + + + + + I just want to increase the level of privacy + + + + + main + + + It's public key. Private key required + + + + + Ssh log + + + + + App log + + + + + Wrap words + 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..77ca0f5f --- /dev/null +++ b/client/ui/controllers/connectionController.cpp @@ -0,0 +1,142 @@ +#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); +} + +ConnectionController::~ConnectionController() +{ +// todo use ConnectionController instead of using m_vpnConnection directly +#ifdef AMNEZIA_DESKTOP + if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { + m_vpnConnection->disconnectFromVpn(); + for (int i = 0; i < 50; i++) { + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); + QThread::msleep(100); + if (m_vpnConnection->isDisconnected()) { + break; + } + } + } +#endif +} + +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_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(); + } +} + +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..7bfe0fac --- /dev/null +++ b/client/ui/controllers/connectionController.h @@ -0,0 +1,57 @@ +#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(); + + bool isConnected() const; + bool isConnectionInProgress() const; + QString connectionStateText() const; + +public slots: + void openConnection(); + void closeConnection(); + + QString getLastConnectionError(); + void onConnectionStateChanged(Vpn::ConnectionState state); + + void onCurrentContainerUpdated(); + +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: + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + + QSharedPointer m_vpnConnection; + + bool m_isConnected = false; + bool m_isConnectionInProgress = false; + QString m_connectionStateText = tr("Connect"); +}; + +#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..d9278ece --- /dev/null +++ b/client/ui/controllers/importController.cpp @@ -0,0 +1,386 @@ +#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; + // 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); + } + }); + + 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); + + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); + + 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) +{ + 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() << "send error?" + }*/ + + if (hostNameAndPortMatch.hasCaptured(2)) { + port = hostNameAndPortMatch.captured(2); + } + + 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 config; + config[config_key::containers] = arr; + config[config_key::defaultContainer] = "amnezia-wireguard"; + 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..63510d1a --- /dev/null +++ b/client/ui/controllers/installController.cpp @@ -0,0 +1,482 @@ +#include "installController.h" + +#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::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 = ContainerProps::containerHumanNames().value(container) + tr(" installed successfully. "); + } else { + finishMessage = + ContainerProps::containerHumanNames().value(container) + tr(" is already installed on the server. "); + } + if (installedContainers.size() > 1) { + finishMessage += tr("\nAlready installed containers were found on the server. " + "All installed containers have been added to the application"); + } + + 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); + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); + + 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 = ContainerProps::containerHumanNames().value(container) + tr(" installed successfully. "); + } else { + finishMessage = + ContainerProps::containerHumanNames().value(container) + tr(" is already installed on the server. "); + } + + 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 '") + serverName + tr("' was removed")); +} + +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 '") + serverName + ("' have been removed")); + 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(containerName + tr(" has been removed from the server '") + + 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()); + + m_serversModel->addServer(server); + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); + + 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..7b8f74ab --- /dev/null +++ b/client/ui/controllers/pageController.cpp @@ -0,0 +1,147 @@ +#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 +} + +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; + } +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h new file mode 100644 index 00000000..a8f883fe --- /dev/null +++ b/client/ui/controllers/pageController.h @@ -0,0 +1,125 @@ +#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, + 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(); + +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; +}; + +#endif // PAGECONTROLLER_H diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp new file mode 100644 index 00000000..46993f6a --- /dev/null +++ b/client/ui/controllers/settingsController.cpp @@ -0,0 +1,154 @@ +#include "settingsController.h" + +#include + +#include "logger.h" +#include "systemController.h" +#include "ui/qautostart.h" +#include "version.h" + +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_MAJOR_VERSION), __DATE__); +} + +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); +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h new file mode 100644 index 00000000..5a51a3b4 --- /dev/null +++ b/client/ui/controllers/settingsController.h @@ -0,0 +1,79 @@ +#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); + +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..a27e91d0 --- /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: ") + 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: ") + hostname); +} + +void SitesController::importSites(const QString &fileName, bool replaceExisting) +{ + QFile file(fileName); + + if (!file.open(QIODevice::ReadOnly)) { + emit errorOccurred(tr("Can't open file: ") + fileName); + return; + } + + QByteArray jsonData = file.readAll(); + QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); + if (jsonDocument.isNull()) { + emit errorOccurred(tr("Failed to parse JSON data from file: ") + fileName); + return; + } + + if (!jsonDocument.isArray()) { + emit errorOccurred(tr("The JSON data is not an array in file: ") + 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..afff44b6 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,226 @@ 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: { + 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); +} + +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); +} + +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..741a0620 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -3,36 +3,81 @@ #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 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 isOnlyServicesInstalled(const int serverIndex); 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..5135f348 --- /dev/null +++ b/client/ui/models/languageModel.cpp @@ -0,0 +1,61 @@ +#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 { metaEnum.valueToKey(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; +} + +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; + 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; + 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..b64862dd --- /dev/null +++ b/client/ui/models/languageModel.h @@ -0,0 +1,67 @@ +#ifndef LANGUAGEMODEL_H +#define LANGUAGEMODEL_H + +#include +#include + +#include "settings.h" + +namespace LanguageSettings +{ + Q_NAMESPACE + enum class AvailableLanguageEnum { + English, + Russian + }; + 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: + QVector m_availableLanguages; + + std::shared_ptr m_settings; +}; + +#endif // LANGUAGEMODEL_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..8c999470 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -1,62 +1,89 @@ #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::PageProtocolOpenVpnSettings; + // non-vpn + case Proto::TorWebSite: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::Dns: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::FileShare: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::Sftp: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + 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..7eea94e5 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(); +} + +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..d7b15844 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(); + 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..5fd9a38b 100644 --- a/client/ui/models/sites_model.cpp +++ b/client/ui/models/sites_model.cpp @@ -1,87 +1,118 @@ #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(); + m_currentRouteMode = m_settings->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(); +} + +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..70def0ec 100644 --- a/client/ui/models/sites_model.h +++ b/client/ui/models/sites_model.h @@ -10,32 +10,43 @@ 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); + + 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; + Settings::RouteMode m_currentRouteMode; + + QVector> m_sites; }; #endif // SITESMODEL_H diff --git a/client/ui/notificationhandler.cpp b/client/ui/notificationhandler.cpp index 779569d7..f932eb17 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"); diff --git a/client/ui/notificationhandler.h b/client/ui/notificationhandler.h index b87e9575..9a2182de 100644 --- a/client/ui/notificationhandler.h +++ b/client/ui/notificationhandler.h @@ -31,7 +31,7 @@ public: void messageClickHandle(); public slots: - virtual void setConnectionState(VpnProtocol::VpnConnectionState state); + virtual void setConnectionState(Vpn::ConnectionState state); 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..ab28d0d0 --- /dev/null +++ b/client/ui/qml/Components/ConnectButton.qml @@ -0,0 +1,148 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Shapes +import Qt5Compat.GraphicalEffects + +import ConnectionState 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 (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..ecde1554 --- /dev/null +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -0,0 +1,65 @@ +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 + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 32 + Layout.alignment: Qt.AlignHCenter + + text: qsTr("Connection data") + wrapMode: Text.WordWrap + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Server IP, login and password") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSetupWizardCredentials) + root.visible = false + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("QR code, key or configuration file") + 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..4708128f --- /dev/null +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -0,0 +1,103 @@ +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 + checked: isDefault + + onPressed: function(mouse) { + if (!isSupported) { + PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) + } + } + + onClicked: { + if (checked) { + var needReconnected = false + if (!isDefault) { + needReconnected = true + } + + isDefault = true + + menuContent.currentIndex = index + containersDropDown.menuVisible = false + + + if (needReconnected && + (ConnectionController.isConnected || ConnectionController.isConnectionInProgress)) { + PageController.showNotificationMessage(qsTr("Reconnect via VPN Procotol: ") + name) + PageController.goToPageHome() + menu.visible = false + ConnectionController.openConnection() + } + } else { + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) + InstallController.setShouldCreateServer(false) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + containersDropDown.menuVisible = false + menu.visible = 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..a79f9140 --- /dev/null +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -0,0 +1,77 @@ +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: parent.height * 0.5 + + ColumnLayout { + 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..edd96bd7 --- /dev/null +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -0,0 +1,110 @@ +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.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..3a8a4175 --- /dev/null +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -0,0 +1,255 @@ +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: 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("Show content") + + 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 + 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..a9edd543 100644 --- a/client/ui/qml/Config/GlobalConfig.qml +++ b/client/ui/qml/Config/GlobalConfig.qml @@ -11,17 +11,17 @@ 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 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..67ffbd9c --- /dev/null +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -0,0 +1,42 @@ +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" + + 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..b0c39ddc --- /dev/null +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -0,0 +1,114 @@ +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 { + 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..4b94cb1a --- /dev/null +++ b/client/ui/qml/Controls2/CardType.qml @@ -0,0 +1,126 @@ +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 + 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 + 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 + } + } +} 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..c22d00c2 --- /dev/null +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -0,0 +1,100 @@ +import QtQuick +import QtQuick.Controls + +Drawer { + id: drawer + property bool needCloseButton: true + property bool isOpened: false + + 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 + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + onAboutToShow: { + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + + if (needCloseButton) { + PageController.drawerOpen() + } + } + + onAboutToHide: { + if (needCloseButton) { + PageController.drawerClose() + } + } + + onOpened: { + isOpened = true + } + + onClosed: { + isOpened = false + + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + } + + + onPositionChanged: { + if (isOpened && (position <= 0.99 && position >= 0.95)) { + mouseArea.canceled() + drawer.close() + mouseArea.exited() + dropArea.exited() + } + } + + DropArea { + id: dropArea + } + + MouseArea { + id: mouseArea + anchors.fill: parent + + onPressed: { + isOpened = true + } + } +} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml new file mode 100644 index 00000000..2feb5e17 --- /dev/null +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -0,0 +1,200 @@ +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 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 } + } + } + + 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 + + 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 + } + } + + onExited: { + if (menu.visible === false) { + rootButtonBackground.border.color = 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..9433f52a --- /dev/null +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -0,0 +1,60 @@ +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 + + 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..b4af3784 --- /dev/null +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -0,0 +1,67 @@ +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 + + 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..843599a4 --- /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" + + implicitWidth: 40 + implicitHeight: 40 + + 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 + radius: 12 + color: { + if (root.enabled) { + if(root.pressed) { + return pressedColor + } + return hovered ? hoveredColor : defaultColor + } + return defaultColor + } + 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..7a1489c0 --- /dev/null +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -0,0 +1,191 @@ +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 string textColor: "#d7d8db" + property string descriptionColor: "#878B91" + 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 ? leftImage.implicitHeight : 56 + Layout.preferredWidth: rightImageSource ? leftImage.implicitWidth : 56 + Layout.rightMargin: rightImageSource ? 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: root.descriptionOnTop ? root.descriptionColor : root.textColor + 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: root.descriptionOnTop ? root.textColor : root.descriptionColor + + 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 + + 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: true + + 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..ee7372f5 --- /dev/null +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -0,0 +1,119 @@ +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" + + implicitWidth: content.implicitWidth + switcher.implicitWidth + implicitHeight: content.implicitHeight + + hoverEnabled: enabled ? true : false + + indicator: Rectangle { + id: switcher + + anchors.left: content.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 4 + + 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.fill: parent + anchors.rightMargin: switcher.implicitWidth + + ListItemTitleType { + Layout.fillWidth: true + + text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor + } + + CaptionTextType { + id: description + + Layout.fillWidth: true + + 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..4b70c274 --- /dev/null +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -0,0 +1,79 @@ +import QtQuick +import QtQuick.Controls + +Rectangle { + id: root + + property string placeholderText + property string text + property alias textArea: textArea + property alias textAreaText: textArea.text + + height: 148 + color: "#1C1D21" + border.width: 1 + border.color: "#2C2D30" + radius: 16 + + 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 { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: { + fl.interactive = true + contextMenu.open() + } + } + + ContextMenuType { + id: contextMenu + textObj: textArea + } + } + } + + //todo make whole background clickable, with code below we lose ability to select text by mouse +// MouseArea { +// anchors.fill: parent +// cursorShape: Qt.IBeamCursor +// onClicked: textArea.forceActiveFocus() +// } +} diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml new file mode 100644 index 00000000..3f80428e --- /dev/null +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -0,0 +1,159 @@ +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 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" + + 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: textField.focus ? root.borderFocusedColor : 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 = "" + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: contextMenu.open() + } + + ContextMenuType { + id: contextMenu + textObj: textField + } + } + } + + 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.PointingHandCursor + + onPressed: function(mouse) { + textField.forceActiveFocus() + mouse.accepted = false + } + } +} diff --git a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml new file mode 100644 index 00000000..e3b14e63 --- /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: 500 + 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..ed89b5a6 --- /dev/null +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -0,0 +1,30 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes + +Popup { + id: root + + 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 { + image: "qrc:/images/svg/close_black_24dp.svg" + imageColor: "#D7D8DB" + + 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..243b1205 --- /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 ") + 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..d8796524 --- /dev/null +++ b/client/ui/qml/Pages2/PageHome.qml @@ -0,0 +1,363 @@ +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 + + Item { + anchors.top: parent.top + anchors.bottom: buttonBackground.top + anchors.right: parent.right + anchors.left: parent.left + + ConnectButton { + anchors.centerIn: parent + } + } + + Connections { + target: PageController + + function onRestorePageHomeState(isContainerInstalled) { + menu.visible = true + if (isContainerInstalled) { + containersDropDown.menuVisible = true + } + } + } + + Rectangle { + id: buttonBackground + anchors.fill: buttonContent + + 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 + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + RowLayout { + Layout.topMargin: 24 + Layout.leftMargin: 24 + Layout.rightMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + spacing: 0 + + Header1TextType { + Layout.maximumWidth: buttonContent.width - 48 - 18 - 12 // todo + + maximumLineCount: 2 + elide: Qt.ElideRight + + text: root.defaultServerName + horizontalAlignment: Qt.AlignHCenter + } + + Image { + Layout.preferredWidth: 18 + Layout.preferredHeight: 18 + + Layout.leftMargin: 12 + + source: "qrc:/images/controls/chevron-down.svg" + } + } + + LabelTextType { + Layout.bottomMargin: 44 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + text: { + var description = "" + if (ServersModel.isDefaultServerHasWriteAccess()) { + if (SettingsController.isAmneziaDnsEnabled() + && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { + description += "Amnezia DNS | " + } + } else { + if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { + description += "Amnezia DNS | " + } + } + + description += root.defaultContainerName + " | " + root.defaultServerHostName + return description + } + } + } + + MouseArea { + anchors.fill: buttonBackground + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onClicked: { + menu.visible = true + } + } + + DrawerType { + id: menu + + interactive: { + if (stackView && stackView.currentItem) { + return (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) ? true : false + } else { + return false + } + } + dragMargin: buttonBackground.height + 56 // page start tabBar height + + width: parent.width + height: parent.height * 0.9 + + ColumnLayout { + id: serversMenuHeader + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + + Header1TextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: root.defaultServerName + horizontalAlignment: Qt.AlignHCenter + maximumLineCount: 2 + elide: Qt.ElideRight + } + + LabelTextType { + Layout.bottomMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + text: root.defaultServerHostName + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + spacing: 8 + + DropDownType { + id: containersDropDown + + rootButtonImageColor: "#0E0E11" + rootButtonBackgroundColor: "#D7D8DB" + rootButtonHoveredBorderColor: "transparent" + rootButtonPressedBorderColor: "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 + + actionButtonImage: "qrc:/images/controls/plus.svg" + + headerText: qsTr("Servers") + + actionButtonFunction: function() { + menu.visible = false + connectionTypeSelection.visible = true + } + } + + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection + } + } + + FlickableType { + anchors.top: serversMenuHeader.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight + + 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 += "AmneziaDNS | " + } + } else { + if (containsAmneziaDns) { + description += "AmneziaDNS | " + } + } + + return description += hostName + } + + checked: index === serversMenuContent.currentIndex + + ButtonGroup.group: serversRadioButtonGroup + + onClicked: { + 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) + menu.visible = false + } + } + } + + DividerType { + Layout.fillWidth: true + } + } + } + } + } + } + } +} 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..491bdf31 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -0,0 +1,402 @@ +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: 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 + 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 + + 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..8bbfab14 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -0,0 +1,206 @@ +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 ") + 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 + + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + textColor: "#EB5757" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" 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 + } + + 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..016a7c88 --- /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 ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + 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..61ba663d --- /dev/null +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -0,0 +1,287 @@ +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() { + col.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() { + col.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() { + col.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() { + col.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + TextEdit{ + id: clipboard + visible: false + } + + function copyToClipBoard(text) { + clipboard.text = text + clipboard.selectAll() + clipboard.copy() + clipboard.select(0, 0) + } + + 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..04d7076c --- /dev/null +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -0,0 +1,162 @@ +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() { + content.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + TextEdit{ + id: clipboard + visible: false + } + + function copyToClipBoard(text) { + clipboard.text = text + clipboard.selectAll() + clipboard.copy() + clipboard.select(0, 0) + } + + 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 domain as this onion address.") + } + + 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..e020dc2c --- /dev/null +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -0,0 +1,112 @@ +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 { + 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 {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml new file mode 100644 index 00000000..e73ef88f --- /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. +And if you don't like the app, all the more support it - the donation will be used to improve the app.") + 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..6f5e48a2 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -0,0 +1,152 @@ +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("Auto start") + descriptionText: qsTr("Launch the application every time ") + Qt.platform.os + qsTr(" 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..7a556dfb --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -0,0 +1,155 @@ +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("It will help you instantly restore connection settings at the next installation") + 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) + } + } + } + + 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..78d4a681 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -0,0 +1,123 @@ +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 if 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 { + Layout.fillWidth: true + + text: qsTr("Split site tunneling") + descriptionText: qsTr("Allows you to connect to some sites through a secure connection, and to others bypassing it") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsSplitTunneling) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Separate application tunneling") + descriptionText: qsTr("Allows you to use the VPN only for certain applications") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml new file mode 100644 index 00000000..0bc13eae --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -0,0 +1,128 @@ +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 { + text: qsTr("If AmneziaDNS is not used or installed") + } + + TextFieldWithHeaderType { + id: primaryDns + + Layout.fillWidth: true + headerText: "Primary DNS" + + textFieldText: SettingsController.primaryDns + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressRegExp() + } + } + + TextFieldWithHeaderType { + id: secondaryDns + + Layout.fillWidth: true + headerText: "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..42f33901 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -0,0 +1,177 @@ +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 logs"), + qsTr("Logs files (*.log)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", + true, + ".log") + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + SettingsController.exportLogsFile(fileName) + PageController.showBusyIndicator(false) + } + } + } + + 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..14d34590 --- /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 ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" 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 + } + + 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..c0807f35 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -0,0 +1,111 @@ +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 + + actionButtonImage: "qrc:/images/controls/plus.svg" + + headerText: qsTr("Servers") + + actionButtonFunction: function() { + connectionTypeSelection.visible = true + } + } + } + + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection + } + + 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..b79d5d22 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -0,0 +1,409 @@ +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 + + 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("Only the addresses in the list must be opened via VPN") + property int type: routeMode.onlyForwardSites + } + QtObject { + id: allExceptSites + property string name: qsTr("Addresses from the list should never be opened 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 { + Layout.fillWidth: true + Layout.leftMargin: 16 + + headerText: qsTr("Split site tunneling") + } + + SwitcherType { + id: switcher + + property int lastActiveRouteMode: routeMode.onlyForwardSites + + Layout.fillWidth: true + Layout.rightMargin: 16 + + checked: SitesModel.routeMode !== routeMode.allSites + onToggled: { + if (checked) { + SitesModel.routeMode = lastActiveRouteMode + } else { + lastActiveRouteMode = SitesModel.routeMode + selector.text = root.routeModesModel[getRouteModesModelIndex()].name + SitesModel.routeMode = routeMode.allSites + } + } + } + } + + DropDownType { + id: selector + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + drawerHeight: 0.4375 + + enabled: switcher.checked + + 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 + connectButton.implicitHeight + connectButton.anchors.bottomMargin + connectButton.anchors.topMargin + + enabled: switcher.checked + + 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 + } + } + } + } + } + } + + RowLayout { + id: connectButton + + 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..bc24c196 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -0,0 +1,133 @@ +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("Server connection") + } + + 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) + } + } + } + } + + 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..b228a7a3 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -0,0 +1,195 @@ +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 + + 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: 1 + } + + BasicButtonType { + id: continueButton + + implicitWidth: parent.width + anchors.topMargin: 24 + + 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 + + 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..f2919398 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -0,0 +1,160 @@ +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) { + 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) { + PageController.goToStartPage() + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { + PageController.restorePageHomeState() + } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { + PageController.goToPage(PageEnum.PageSettingsServersList, false) + } else { + 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("Amnesia has detected that your server is currently ") + + qsTr("busy installing other software. Amnesia 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..07eef177 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -0,0 +1,258 @@ +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 ") + 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 + } + + TextField { + implicitWidth: parent.width + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + padding: 0 + leftPadding: 0 + height: 24 + + color: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + text: detailedDescription + + wrapMode: Text.WordWrap + + readOnly: true + background: Rectangle { + anchors.fill: parent + color: "transparent" + } + } + + 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.defaultPort(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..9f5e57a5 --- /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 = tabBarStackView.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://ru-docs.amnezia.org/guides/hosting-instructions") + } + } + + 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..2f1fc392 --- /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() { + PageController.goToStartPage() + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { + PageController.restorePageHomeState() + } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { + PageController.goToPage(PageEnum.PageSettingsServersList, false) + } else { + 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..a03b3717 --- /dev/null +++ b/client/ui/qml/Pages2/PageShare.qml @@ -0,0 +1,392 @@ +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("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("VPN access without the ability to manage the server") : + qsTr("Full access to server") + color: "#878B91" + } + + DropDownType { + id: serverSelector + + signal severSelectorIndexChanged + property int currentIndex: 0 + + Layout.fillWidth: true + Layout.topMargin: 16 + + drawerHeight: 0.4375 + + descriptionText: qsTr("Servers") + 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 connection settings to ") + serverSelector.text + } + serverSelector.menuVisible = false + } + + Component.onCompleted: { + handler() + } + + function handler() { + serverSelector.text = selectedText + root.fullConfigServerSelectorText = selectedText + root.connectionServerSelectorText = selectedText + ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex) + } + } + } + + DropDownType { + id: protocolSelector + + Layout.fillWidth: true + Layout.topMargin: 16 + + drawerHeight: 0.5 + + descriptionText: qsTr("Protocols") + 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..43366af7 --- /dev/null +++ b/client/ui/qml/Pages2/PageStart.qml @@ -0,0 +1,218 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Shapes + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + Connections { + target: PageController + + function onGoToPageHome() { + tabBar.currentIndex = 0 + tabBarStackView.goToTabBarPage(PageEnum.PageHome) + + PageController.updateDrawerRootPage(PageEnum.PageHome) + } + + function onGoToPageSettings() { + tabBar.currentIndex = 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() { + 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) { + 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 }) + } + } + + TabBar { + id: tabBar + + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + topPadding: 8 + bottomPadding: 8 + leftPadding: shareTabButton.visible ? 96 : 128 + rightPadding: shareTabButton.visible ? 96 : 128 + + 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 + } + } + 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) + } + } + TabImageButtonType { + isSelected: tabBar.currentIndex === 2 + image: "qrc:/images/controls/settings-2.svg" + onClicked: { + tabBarStackView.goToTabBarPage(PageEnum.PageSettings) + } + } + } + + BusyIndicatorType { + id: busyIndicator + anchors.centerIn: parent + z: 1 + } + + TopCloseButtonType { + id: topCloseButton + x: tabBarStackView.width - topCloseButton.width + z: 1 + } +} 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; isetEnabled(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..96134f14 100644 --- a/client/ui/systemtray_notificationhandler.h +++ b/client/ui/systemtray_notificationhandler.h @@ -17,7 +17,7 @@ public: explicit SystemTrayNotificationHandler(QObject* parent); ~SystemTrayNotificationHandler(); - void setConnectionState(VpnProtocol::VpnConnectionState state) override; + void setConnectionState(Vpn::ConnectionState state) override; protected: virtual void notify(Message type, const QString& title, @@ -26,7 +26,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); 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..1cff01e6 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) { @@ -328,14 +324,14 @@ void VpnConnection::connectToVpn(int serverIndex, m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig, &e); 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 +350,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 +384,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 +427,7 @@ void VpnConnection::disconnectFromVpn() #endif if (!m_vpnProtocol.data()) { - emit connectionStateChanged(VpnProtocol::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); return; } @@ -435,9 +437,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..20ee14fa 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,14 @@ 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(); 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/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")