From 75489c00c28c4c7022ede605789183eecf093042 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 14 Jul 2023 22:59:49 +0900 Subject: [PATCH] added button 'Reset settings and remove all data from the application' --- client/core/servercontroller.cpp | 486 +++++++++--------- client/core/servercontroller.h | 59 ++- client/resources.qrc | 1 + client/secure_qsettings.cpp | 55 +- client/secure_qsettings.h | 19 +- client/settings.cpp | 80 +-- client/settings.h | 2 + client/ui/controllers/installController.cpp | 17 +- client/ui/controllers/pageController.h | 1 + client/ui/controllers/settingsController.cpp | 10 +- client/ui/controllers/settingsController.h | 8 +- client/ui/models/languageModel.cpp | 5 + client/ui/models/languageModel.h | 1 + .../ui/pages_logic/ServerContainersLogic.cpp | 2 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 9 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 34 ++ client/ui/qml/Pages2/PageSettingsBackup.qml | 90 ---- client/ui/qml/Pages2/PageSettingsLogging.qml | 138 +++++ 18 files changed, 585 insertions(+), 432 deletions(-) create mode 100644 client/ui/qml/Pages2/PageSettingsLogging.qml diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 7f4690dc..409399dc 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -1,23 +1,23 @@ #include "servercontroller.h" +#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 #include @@ -25,15 +25,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 +41,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) { @@ -82,7 +81,6 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr qDebug().noquote() << "EXEC" << lineToExec; Logger::appendSshLog("Run command:" + lineToExec); - error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr); if (error != ErrorCode::NoError) { return error; @@ -93,36 +91,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 &) { @@ -131,61 +129,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 &) { @@ -197,14 +197,13 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container, qDebug().noquote() << "Copy file from container stdout : \n" << stdOut; - - qDebug().noquote() << "Copy file from container END : \n" ; + qDebug().noquote() << "Copy file from container END : \n"; 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) { @@ -218,7 +217,8 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential qDebug() << "remotePath" << remotePath; - 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; } @@ -227,41 +227,45 @@ 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, QJsonObject &config, bool isUpdate) { qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container); - //qDebug().noquote() << QJsonDocument(config).toJson(); + // qDebug().noquote() << QJsonDocument(config).toJson(); ErrorCode e = ErrorCode::NoError; e = isUserInSudo(credentials, container); - if (e) return e; + if (e) + return e; if (!isUpdate) { e = isServerPortBusy(credentials, container, config); - 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"; e = prepareHostWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer prepareHostWorker finished"; removeContainer(credentials, container); @@ -269,15 +273,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); @@ -287,46 +294,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); @@ -334,25 +320,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; @@ -374,75 +360,88 @@ 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); - if (stdOut.contains("command not found")) return ErrorCode::ServerDockerFailedError; + 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); qDebug() << "cbReadStdOut: " << stdOut; + if (stdOut.contains("docker: Error response from daemon")) + return ErrorCode::ServerDockerFailedError; - 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 &) { @@ -454,19 +453,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); @@ -475,16 +473,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(); @@ -495,85 +496,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"; } @@ -592,10 +610,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; } @@ -607,23 +626,24 @@ 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) { QString s = script; for (const QPair &var : vars) { - //qDebug() << "Replacing" << var.first << var.second; + // qDebug() << "Replacing" << var.first << var.second; s.replace(var.first, var.second); } - //qDebug().noquote() << script; + // qDebug().noquote() << script; 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; @@ -646,8 +666,10 @@ 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); QString script = QString("sudo lsof -i -P -n | grep -E ':%1 ").arg(port); @@ -660,8 +682,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; } @@ -689,9 +711,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; } @@ -718,7 +742,8 @@ 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.isEmpty() || stdOut.contains("Unable to acquire the dpkg frontend lock")) { emit serverIsBusy(true); QThread::msleep(1000); @@ -738,7 +763,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 &) { @@ -770,13 +796,10 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential QString transportProto = containerAndPortMatch.captured(3); DockerContainer container = ContainerProps::containerFromString(name); Proto mainProto = ContainerProps::defaultProtocol(container); - QJsonObject config { - { config_key::container, name }, - { ProtocolProps::protoToString(mainProto), QJsonObject { - { config_key::port, port }, - { config_key::transport_proto, transportProto }} - } - }; + QJsonObject config { { config_key::container, name }, + { ProtocolProps::protoToString(mainProto), + QJsonObject { { config_key::port, port }, + { config_key::transport_proto, transportProto } } } }; installedContainers.insert(container, config); } } @@ -784,7 +807,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..cb74d571 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,61 @@ 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); + 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 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); + 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/resources.qrc b/client/resources.qrc index df6fcb3e..3435a107 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -274,5 +274,6 @@ ui/qml/Pages2/PageProtocolShadowSocksSettings.qml ui/qml/Pages2/PageProtocolCloakSettings.qml ui/qml/Pages2/PageProtocolRaw.qml + ui/qml/Pages2/PageSettingsLogging.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 1781cb2e..fbdd63ce 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 &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); } @@ -263,8 +264,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()); } } @@ -275,7 +275,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); @@ -285,7 +286,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, ""); } @@ -297,7 +299,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); } @@ -305,9 +308,20 @@ void Settings::removeVpnSites(RouteMode mode, const QStringList &sites) setVpnSites(mode, sitesMap); } -QString Settings::primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1).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(); } +QString Settings::secondaryDns() const +{ + return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); +} + +void Settings::clearSettings() +{ + m_settings.clearSettings(); +} ServerCredentials Settings::defaultServerCredentials() const { diff --git a/client/settings.h b/client/settings.h index 9bf40ac7..00b02c15 100644 --- a/client/settings.h +++ b/client/settings.h @@ -183,6 +183,8 @@ public: m_settings.setValue("Conf/appLanguage", locale); }; + void clearSettings(); + signals: void saveLogsChanged(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 6fc5f4e9..2259721a 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -16,12 +16,19 @@ InstallController::InstallController(const QSharedPointer &servers void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { Proto mainProto = ContainerProps::defaultProtocol(container); + QJsonObject containerConfig; - QJsonObject containerConfig { { config_key::port, QString::number(port) }, - { config_key::transport_proto, - ProtocolProps::transportProtoToString(transportProto, mainProto) } }; - QJsonObject config { { config_key::container, ContainerProps::containerToString(container) }, - { ProtocolProps::protoToString(mainProto), containerConfig } }; + containerConfig.insert(config_key::port, QString::number(port)); + containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, mainProto)); + + if (container == DockerContainer::Sftp) { + containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); + containerConfig.insert(config_key::password, Utils::getRandomString(10)); + } + + QJsonObject config; + config.insert(config_key::container, ContainerProps::containerToString(container)); + config.insert(ProtocolProps::protoToString(mainProto), containerConfig); if (m_shouldCreateServer) { if (isServerAlreadyExists()) { diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 05b8fbf9..a0b9753b 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -27,6 +27,7 @@ namespace PageLoader PageSettingsApplication, PageSettingsBackup, PageSettingsAbout, + PageSettingsLogging, PageSetupWizardStart, PageSetupWizardCredentials, diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 42dd2231..b501d085 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -46,14 +46,15 @@ void SettingsController::setSecondaryDns(const QString &dns) emit secondaryDnsChanged(); } -bool SettingsController::isSaveLogsEnabled() +bool SettingsController::isLoggingEnable() { return m_settings->isSaveLogs(); } -void SettingsController::setSaveLogs(bool enable) +void SettingsController::toggleLogging(bool enable) { m_settings->setSaveLogs(enable); + emit loggingStateChanged(); } void SettingsController::openLogsFolder() @@ -101,3 +102,8 @@ QString SettingsController::getAppVersion() { return m_appVersion; } + +void SettingsController::clearSettings() +{ + m_settings->clearSettings(); +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index f961a37d..313f934d 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -16,6 +16,7 @@ public: Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) + Q_PROPERTY(bool isLoggingEnable READ isLoggingEnable WRITE toggleLogging NOTIFY loggingStateChanged) public slots: void setAmneziaDns(bool enable); @@ -27,8 +28,8 @@ public slots: QString getSecondaryDns(); void setSecondaryDns(const QString &dns); - bool isSaveLogsEnabled(); - void setSaveLogs(bool enable); + bool isLoggingEnable(); + void toggleLogging(bool enable); void openLogsFolder(); void exportLogsFile(); @@ -39,9 +40,12 @@ public slots: QString getAppVersion(); + void clearSettings(); + signals: void primaryDnsChanged(); void secondaryDnsChanged(); + void loggingStateChanged(); private: QSharedPointer m_serversModel; diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index 76eeb37d..adbbdaaa 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -54,3 +54,8 @@ int LanguageModel::getCurrentLanguageIndex() default: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; } } + +QString LanguageModel::getCurrentLanuageName() +{ + return m_availableLanguages[getCurrentLanguageIndex()].name; +} diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h index b3ff4f6e..4e8a9092 100644 --- a/client/ui/models/languageModel.h +++ b/client/ui/models/languageModel.h @@ -46,6 +46,7 @@ public: public slots: void changeLanguage(const LanguageSettings::AvailableLanguageEnum language); int getCurrentLanguageIndex(); + QString getCurrentLanuageName(); signals: void updateTranslations(const QLocale &locale); diff --git a/client/ui/pages_logic/ServerContainersLogic.cpp b/client/ui/pages_logic/ServerContainersLogic.cpp index a2971cf8..89eeb6c8 100644 --- a/client/ui/pages_logic/ServerContainersLogic.cpp +++ b/client/ui/pages_logic/ServerContainersLogic.cpp @@ -89,7 +89,7 @@ void ServerContainersLogic::onPushButtonRemoveClicked(DockerContainer container) void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int port, TransportProto tp) { ServerController serverController(m_settings); - QJsonObject config = serverController.createContainerInitialConfig(c, port, tp); + QJsonObject config; // = serverController.createContainerInitialConfig(c, port, tp); emit uiLogic()->goToPage(Page::ServerConfiguringProgress); qApp->processEvents(); diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 27ead16c..c925f801 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -17,14 +17,16 @@ Item { property string textColor: "#d7d8db" - implicitWidth: content.implicitWidth - implicitHeight: content.implicitHeight + 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 @@ -56,8 +58,6 @@ Item { color: root.textColor Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: description.visible ? 0 : 16 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter @@ -72,7 +72,6 @@ Item { visible: root.descriptionText !== "" Layout.fillWidth: true - Layout.bottomMargin: 16 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index b378a6c8..54d315b0 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -48,6 +48,7 @@ PageType { Layout.topMargin: 16 text: qsTr("Language") + descriptionText: LanguageModel.getCurrentLanuageName() rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -60,6 +61,22 @@ PageType { } + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Logging") + descriptionText: SettingsController.isLoggingEnable ? qsTr("Enabled") : qsTr("Disabled") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsLogging) + } + } + + DividerType {} + LabelWithButtonType { Layout.fillWidth: true @@ -67,10 +84,27 @@ PageType { 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() + } + 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 index ddf9f2ba..1c196aa3 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -44,96 +44,6 @@ PageType { headerText: qsTr("Backup") } - SwitcherType { - Layout.fillWidth: true - Layout.topMargin: 16 - - text: qsTr("Save logs") - - checked: SettingsController.isSaveLogsEnabled() - onCheckedChanged: { - if (checked !== SettingsController.isSaveLogsEnabled()) { - SettingsController.setSaveLogs(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: SettingsController.exportLogsFile() - } - - 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: SettingsController.clearLogs() - } - - CaptionTextType { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - - text: qsTr("Clear logs") - color: "#D7D8DB" - } - } - } - ListItemTitleType { Layout.fillWidth: true Layout.topMargin: 10 diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml new file mode 100644 index 00000000..998065e4 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -0,0 +1,138 @@ +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.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.isLoggingEnable + onCheckedChanged: { + if (checked !== SettingsController.isLoggingEnable) { + SettingsController.isLoggingEnable = 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: SettingsController.exportLogsFile() + } + + 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: SettingsController.clearLogs() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Clear logs") + color: "#D7D8DB" + } + } + } + } + } +}