mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-17 07:42:58 +03:00
Compare commits
5 Commits
feat_copy_
...
fix/async-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d1cfbaf19 | ||
|
|
594635e5cf | ||
|
|
f9b106cf5b | ||
|
|
a9861d18b7 | ||
|
|
c14138f031 |
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.1)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.2)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2122)
|
||||
set(APP_ANDROID_VERSION_CODE 2123)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -22,6 +22,7 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshExecutor.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/serversController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.h
|
||||
@@ -99,6 +100,7 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshExecutor.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/serversController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.cpp
|
||||
|
||||
@@ -49,14 +49,92 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
|
||||
{
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::isConnectionSupported(const QString &serverId) const
|
||||
{
|
||||
if (serverId.isEmpty()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
if (serverConfigUtils::isLegacyApiSubscription(m_serversRepository->serverKind(serverId))) {
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
}
|
||||
|
||||
DockerContainer container = DockerContainer::None;
|
||||
const ErrorCode errorCode = defaultContainerForServer(serverId, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
return ErrorCode::NoInstalledContainersError;
|
||||
}
|
||||
|
||||
if (ContainerUtils::isUnsupportedContainer(container)) {
|
||||
return ErrorCode::LegacyContainerNotSupportedError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
{
|
||||
ContainerConfig containerConfigModel;
|
||||
QPair<QString, QString> dns;
|
||||
QString hostName;
|
||||
@@ -120,10 +198,6 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
|
||||
containerConfigModel, container);
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ public:
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container);
|
||||
|
||||
ErrorCode isConnectionSupported(const QString &serverId) const;
|
||||
|
||||
ErrorCode openConnection(const QString &serverId);
|
||||
|
||||
void closeConnection();
|
||||
@@ -73,6 +75,8 @@ signals:
|
||||
#endif
|
||||
|
||||
private:
|
||||
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
VpnConnection* m_vpnConnection;
|
||||
|
||||
@@ -191,7 +191,7 @@ void CoreController::initControllers()
|
||||
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
|
||||
setQmlContextProperty("LanguageUiController", m_languageUiController);
|
||||
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
|
||||
setQmlContextProperty("SettingsController", m_settingsUiController);
|
||||
|
||||
m_pageController = new PageController(m_serversController, m_settingsController, this);
|
||||
@@ -203,9 +203,6 @@ void CoreController::initControllers()
|
||||
m_ipSplitTunnelingUiController = new IpSplitTunnelingUiController(m_ipSplitTunnelingController, m_ipSplitTunnelingModel, this);
|
||||
setQmlContextProperty("IpSplitTunnelingController", m_ipSplitTunnelingUiController);
|
||||
|
||||
m_serversBackupController = new ServersBackupController(m_settings, m_serversModel, m_serversUiController, m_serversController);
|
||||
setQmlContextProperty("ServersBackupController", m_serversBackupController);
|
||||
|
||||
m_allowedDnsUiController = new AllowedDnsUiController(m_allowedDnsController, m_allowedDnsModel, this);
|
||||
setQmlContextProperty("AllowedDnsController", m_allowedDnsUiController);
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "ui/controllers/settingsUiController.h"
|
||||
#include "ui/controllers/serversUiController.h"
|
||||
#include "ui/controllers/ipSplitTunnelingUiController.h"
|
||||
#include "ui/controllers/serversBackupController.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/controllers/updateUiController.h"
|
||||
@@ -175,7 +174,6 @@ private:
|
||||
AllowedDnsUiController* m_allowedDnsUiController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
UpdateUiController* m_updateUiController;
|
||||
ServersBackupController* m_serversBackupController;
|
||||
|
||||
SubscriptionUiController* m_subscriptionUiController;
|
||||
ApiNewsUiController* m_apiNewsUiController;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/selfhosted/sshExecutor.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/controllers/coreController.h"
|
||||
@@ -33,7 +34,6 @@
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/controllers/api/apiNewsUiController.h"
|
||||
#include "ui/models/api/apiCountryModel.h"
|
||||
#include "ui/models/containersModel.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
@@ -145,7 +145,9 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
|
||||
[this](const QString &serverId, int row, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverId, row, container);
|
||||
SshExecutor::instance().run(serverId, [this, serverId, row, container]() {
|
||||
m_coreController->m_usersController->revokeClient(serverId, row, container);
|
||||
});
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this,
|
||||
[this](const QString &serverId, int row, const QString &clientName, DockerContainer container) {
|
||||
@@ -156,15 +158,17 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
void CoreSignalHandlers::initImportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
|
||||
if (!m_coreController->m_connectionController->isConnected()) {
|
||||
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
if (m_coreController->m_connectionUiController->isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -176,17 +180,14 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
if (processedServerId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray availableCountries;
|
||||
QString serverCountryCode;
|
||||
|
||||
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
|
||||
if (apiV2.has_value()) {
|
||||
availableCountries = apiV2->apiConfig.availableCountries;
|
||||
serverCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
if (!apiV2.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
|
||||
apiV2->apiConfig.serverCountryCode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -204,7 +205,9 @@ void CoreSignalHandlers::initAdminConfigRevokedHandler()
|
||||
{
|
||||
connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this,
|
||||
[this](const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
SshExecutor::instance().run(serverId, [this, serverId, containerConfig, container]() {
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
});
|
||||
});
|
||||
|
||||
connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this,
|
||||
@@ -237,13 +240,16 @@ void CoreSignalHandlers::initLanguageHandler()
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
|
||||
});
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::appLanguageChanged, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->onAppLanguageChanged(m_coreController->m_settingsController->getAppLanguage());
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAutoConnectHandler()
|
||||
{
|
||||
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
|
||||
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +354,9 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
|
||||
{
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initStrictKillSwitchHandler()
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/selfhosted/sshExecutor.h"
|
||||
#include "core/installers/awgInstaller.h"
|
||||
#include "core/installers/installerBase.h"
|
||||
#include "core/installers/openvpnInstaller.h"
|
||||
@@ -72,6 +73,16 @@ namespace
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString buildRemoveContainerScript(const amnezia::ScriptVars &vars, bool removeDataVolume)
|
||||
{
|
||||
QString script = SshSession::replaceVars(amnezia::scriptData(SharedScriptType::remove_container), vars);
|
||||
if (removeDataVolume) {
|
||||
script += QLatin1String("\nsudo docker volume rm -f $CONTAINER_NAME-data 2>/dev/null || true");
|
||||
script = SshSession::replaceVars(script, vars);
|
||||
}
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
InstallController::InstallController(SecureServersRepository *serversRepository,
|
||||
@@ -93,7 +104,7 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
bool isUpdate)
|
||||
{
|
||||
qDebug().noquote() << "InstallController::setupContainer" << ContainerUtils::containerToString(container);
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
|
||||
e = isUserInSudo(credentials, sshSession);
|
||||
@@ -120,14 +131,10 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return e;
|
||||
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
|
||||
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
if (!isUpdate) {
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
}
|
||||
sshSession.runScript(credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
||||
removeContainerVars));
|
||||
const bool removeDataVolume = !isUpdate && (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
|
||||
|
||||
qDebug().noquote() << "buildContainerWorker start";
|
||||
@@ -152,8 +159,8 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return startupContainerWorker(credentials, container, config, sshSession);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
{
|
||||
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
@@ -162,11 +169,11 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
}
|
||||
if (container == DockerContainer::MtProxy) {
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
} else if (container == DockerContainer::Telemt) {
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
}
|
||||
adminConfig->updateContainerConfig(container, newConfig);
|
||||
@@ -182,10 +189,10 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
|
||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
|
||||
|
||||
bool xrayServerSettingsChanged = false;
|
||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||
@@ -213,11 +220,11 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
|
||||
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
|
||||
XrayConfigurator xrayConfigurator(&sshSession);
|
||||
qDebug() << "InstallController::updateContainer applying Xray server inbound sync, reinstall="
|
||||
qDebug() << "InstallController::updateServerConfig applying Xray server inbound sync, reinstall="
|
||||
<< reinstallRequired;
|
||||
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
qDebug() << "InstallController::updateContainer Xray inbound sync failed, error="
|
||||
qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
}
|
||||
}
|
||||
@@ -236,6 +243,41 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig)
|
||||
{
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
auto config = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
auto config = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedUser);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
auto config = m_serversRepository->nativeConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::Native);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
|
||||
{
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
||||
@@ -331,7 +373,7 @@ ErrorCode InstallController::validateAndPrepareConfig(const QString &serverId)
|
||||
|
||||
void InstallController::validateConfig(const QString &serverId)
|
||||
{
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([this, serverId]() {
|
||||
QFuture<ErrorCode> future = SshExecutor::instance().run(serverId, [this, serverId]() {
|
||||
return validateAndPrepareConfig(serverId);
|
||||
});
|
||||
|
||||
@@ -929,7 +971,7 @@ ErrorCode InstallController::rebootServer(const QString &serverId)
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
|
||||
QString script = QString("sudo reboot");
|
||||
|
||||
@@ -957,7 +999,7 @@ ErrorCode InstallController::removeAllContainers(const QString &serverId)
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
ErrorCode errorCode = sshSession.runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
@@ -979,13 +1021,12 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
SshSession sshSession;
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
ErrorCode errorCode = sshSession.runScript(
|
||||
credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars));
|
||||
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
ErrorCode errorCode =
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
|
||||
@@ -1089,7 +1130,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
@@ -1132,7 +1173,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
|
||||
ErrorCode InstallController::installServer(const ServerCredentials &credentials, DockerContainer container, int port,
|
||||
TransportProto transportProto, bool &wasContainerInstalled)
|
||||
{
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
if (errorCode) {
|
||||
@@ -1201,7 +1242,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
@@ -1243,7 +1284,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
ErrorCode InstallController::checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback)
|
||||
{
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) {
|
||||
@@ -1463,7 +1504,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = containerAndPortMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1488,7 +1529,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = torOrDnsRegMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1524,7 +1565,7 @@ ErrorCode InstallController::setDockerContainerEnabledState(const QString &serve
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
const QString script = enabled ? QStringLiteral("sudo docker start %1").arg(containerName)
|
||||
: QStringLiteral("sudo docker stop %1").arg(containerName);
|
||||
const ErrorCode runError = sshSession.runScript(credentials, script);
|
||||
@@ -1564,7 +1605,7 @@ ErrorCode InstallController::queryDockerContainerStatus(const QString &serverId,
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
const QString script = QStringLiteral(
|
||||
"sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'")
|
||||
.arg(containerName);
|
||||
@@ -1598,7 +1639,7 @@ ErrorCode InstallController::queryMtProxyDiagnostics(const QString &serverId, Do
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
return MtProxyInstaller::queryDiagnostics(sshSession, credentials, container, listenPort, out);
|
||||
}
|
||||
|
||||
@@ -1621,7 +1662,7 @@ QString InstallController::fetchDockerContainerSecret(const QString &serverId, D
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
const QString path = QStringLiteral("/data/secret");
|
||||
const QString cmd = QStringLiteral("sudo docker exec %1 cat %2").arg(containerName, path);
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, cmd, cbReadStdOut);
|
||||
|
||||
@@ -34,7 +34,12 @@ public:
|
||||
~InstallController();
|
||||
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
|
||||
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// Updates server-side container settings (admin self-hosted only): reconfigures the container over SSH.
|
||||
ErrorCode updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// Updates client-local settings only: rewrites the stored container config for any self-hosted/native server. No SSH.
|
||||
ErrorCode updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode rebootServer(const QString &serverId);
|
||||
ErrorCode removeAllContainers(const QString &serverId);
|
||||
|
||||
@@ -29,6 +29,11 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void NativeServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
|
||||
{
|
||||
QString d1 = dns1;
|
||||
|
||||
@@ -27,6 +27,8 @@ struct NativeServerConfig {
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
@@ -43,6 +43,11 @@ ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer cont
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void SelfHostedUserServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns,
|
||||
const QString &secondaryDns) const
|
||||
{
|
||||
|
||||
@@ -32,6 +32,8 @@ struct SelfHostedUserServerConfig {
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
@@ -39,33 +39,44 @@ QString OpenVpnProtocol::defaultConfigPath()
|
||||
return p;
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::stop()
|
||||
void OpenVpnProtocol::cleanupResources()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
|
||||
// TODO: need refactoring
|
||||
// sendTermSignal() will even return true while server connected ???
|
||||
if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Connected)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Reconnecting)) {
|
||||
if (m_openVpnProcess || openVpnProcessIsRunning()) {
|
||||
if (!sendTermSignal()) {
|
||||
killOpenVpnProcess();
|
||||
}
|
||||
QThread::msleep(10);
|
||||
m_managementServer.stop();
|
||||
}
|
||||
m_managementServer.stop();
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
|
||||
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
|
||||
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
|
||||
qWarning() << "OpenVpnProtocol::cleanupResources(): Failed to disable killswitch";
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
void OpenVpnProtocol::stop()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
|
||||
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
|
||||
|| m_connectionState == Vpn::ConnectionState::Connecting
|
||||
|| m_connectionState == Vpn::ConnectionState::Connected
|
||||
|| m_connectionState == Vpn::ConnectionState::Reconnecting;
|
||||
|
||||
if (wasActive) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
}
|
||||
|
||||
cleanupResources();
|
||||
|
||||
if (wasActive || m_connectionState == Vpn::ConnectionState::Disconnecting) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnProtocol::prepare()
|
||||
@@ -168,7 +179,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
|
||||
|
||||
ErrorCode OpenVpnProtocol::start()
|
||||
{
|
||||
OpenVpnProtocol::stop();
|
||||
cleanupResources();
|
||||
|
||||
if (!QFileInfo::exists(configPath())) {
|
||||
setLastError(ErrorCode::OpenVpnConfigMissing);
|
||||
|
||||
@@ -29,6 +29,7 @@ protected slots:
|
||||
void onReadyReadDataFromManagementServer();
|
||||
|
||||
private:
|
||||
void cleanupResources();
|
||||
QString configPath() const;
|
||||
bool openVpnProcessIsRunning() const;
|
||||
bool sendTermSignal();
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace amnezia
|
||||
Awg2,
|
||||
WireGuard,
|
||||
OpenVpn,
|
||||
Cloak,
|
||||
ShadowSocks,
|
||||
Ipsec,
|
||||
Xray,
|
||||
SSXray,
|
||||
|
||||
@@ -21,6 +21,8 @@ QString ContainerUtils::containerToString(DockerContainer c)
|
||||
{
|
||||
if (c == DockerContainer::None)
|
||||
return "none";
|
||||
if (c == DockerContainer::Cloak)
|
||||
return "amnezia-openvpn-cloak";
|
||||
if (c == DockerContainer::Awg)
|
||||
return "amnezia-awg";
|
||||
if (c == DockerContainer::Awg2)
|
||||
@@ -62,6 +64,8 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
|
||||
{
|
||||
return { { DockerContainer::None, "Not installed" },
|
||||
{ DockerContainer::OpenVpn, "OpenVPN" },
|
||||
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
|
||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||
{ DockerContainer::WireGuard, "WireGuard" },
|
||||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Awg2, "AmneziaWG" },
|
||||
@@ -83,6 +87,10 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
|
||||
return { { DockerContainer::OpenVpn,
|
||||
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
|
||||
"own security protocol with SSL/TLS for key exchange.") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("This protocol is no longer supported.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("This protocol is no longer supported.") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
|
||||
"consumption.") },
|
||||
@@ -194,6 +202,9 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
|
||||
|
||||
ServiceType ContainerUtils::containerService(DockerContainer c)
|
||||
{
|
||||
if (isUnsupportedContainer(c)) {
|
||||
return ServiceType::Vpn;
|
||||
}
|
||||
return ProtocolUtils::protocolService(defaultProtocol(c));
|
||||
}
|
||||
|
||||
@@ -202,6 +213,8 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
|
||||
switch (c) {
|
||||
case DockerContainer::None: return Proto::Unknown;
|
||||
case DockerContainer::OpenVpn: return Proto::OpenVpn;
|
||||
case DockerContainer::Cloak:
|
||||
case DockerContainer::ShadowSocks: return Proto::Unknown;
|
||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||
case DockerContainer::Awg2: return Proto::Awg;
|
||||
case DockerContainer::Awg: return Proto::Awg;
|
||||
@@ -252,6 +265,8 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
// macOS build using Network Extension – allow OpenVPN for parity with iOS.
|
||||
switch (c) {
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::Cloak: return false;
|
||||
case DockerContainer::ShadowSocks: return false;
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg2: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
@@ -336,6 +351,10 @@ int ContainerUtils::easySetupOrder(DockerContainer container)
|
||||
|
||||
bool ContainerUtils::isShareable(DockerContainer container)
|
||||
{
|
||||
if (isUnsupportedContainer(container)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (container) {
|
||||
case DockerContainer::TorWebSite: return false;
|
||||
case DockerContainer::Dns: return false;
|
||||
@@ -352,6 +371,11 @@ bool ContainerUtils::isAwgContainer(DockerContainer container)
|
||||
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
|
||||
}
|
||||
|
||||
bool ContainerUtils::isUnsupportedContainer(DockerContainer container)
|
||||
{
|
||||
return container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks;
|
||||
}
|
||||
|
||||
QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||
{
|
||||
QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol))
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace amnezia
|
||||
|
||||
bool isAwgContainer(DockerContainer container);
|
||||
|
||||
bool isUnsupportedContainer(DockerContainer container);
|
||||
|
||||
QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig);
|
||||
|
||||
int installPageOrder(DockerContainer container);
|
||||
|
||||
@@ -79,6 +79,7 @@ namespace amnezia
|
||||
ImportBackupFileUseRestoreInstead = 903,
|
||||
RestoreBackupInvalidError = 904,
|
||||
LegacyApiV1NotSupportedError = 905,
|
||||
LegacyContainerNotSupportedError = 906,
|
||||
|
||||
// Android errors
|
||||
AndroidError = 1000,
|
||||
|
||||
@@ -69,6 +69,7 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
|
||||
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
|
||||
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
|
||||
case (ErrorCode::LegacyContainerNotSupportedError): errorMessage = QObject::tr("This protocol is no longer supported. Please select another protocol or remove this container from the server settings."); break;
|
||||
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
|
||||
case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;
|
||||
|
||||
|
||||
@@ -290,86 +290,6 @@ namespace libssh {
|
||||
return watcher.result();
|
||||
}
|
||||
|
||||
ErrorCode Client::scpFileDownload(const QString& remotePath, const QString& localPath)
|
||||
{
|
||||
// Use full path for SCP download
|
||||
m_scpSession = ssh_scp_new(m_session, SSH_SCP_READ, remotePath.toStdString().c_str());
|
||||
|
||||
if (m_scpSession == nullptr) {
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
|
||||
if (ssh_scp_init(m_scpSession) != SSH_OK) {
|
||||
auto errorCode = fromLibsshErrorCode();
|
||||
closeScpSession();
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QFutureWatcher<ErrorCode> watcher;
|
||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::scpFileDownloadFinished);
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([this, &remotePath, &localPath]() {
|
||||
// Pull request - this gets file info
|
||||
int result = ssh_scp_pull_request(m_scpSession);
|
||||
if (result != SSH_SCP_REQUEST_NEWFILE) {
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
|
||||
// Accept the request
|
||||
ssh_scp_accept_request(m_scpSession);
|
||||
|
||||
// Get file size
|
||||
int fileSize = ssh_scp_request_get_size(m_scpSession);
|
||||
if (fileSize <= 0) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
// Open local file for writing
|
||||
QFile fout(localPath);
|
||||
if (!fout.open(QIODevice::WriteOnly)) {
|
||||
return fromFileErrorCode(fout.error());
|
||||
}
|
||||
|
||||
// Read file data in chunks
|
||||
constexpr size_t bufferSize = 16384;
|
||||
int transferred = 0;
|
||||
|
||||
while (transferred < fileSize) {
|
||||
int chunkSize = qMin(bufferSize, static_cast<size_t>(fileSize - transferred));
|
||||
QByteArray buffer(chunkSize, 0);
|
||||
|
||||
int bytesRead = ssh_scp_read(m_scpSession, buffer.data(), chunkSize);
|
||||
if (bytesRead == SSH_ERROR) {
|
||||
fout.close();
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
|
||||
if (bytesRead != chunkSize) {
|
||||
fout.close();
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
qint64 bytesWritten = fout.write(buffer);
|
||||
if (bytesWritten != chunkSize) {
|
||||
fout.close();
|
||||
return fromFileErrorCode(fout.error());
|
||||
}
|
||||
|
||||
transferred += bytesRead;
|
||||
}
|
||||
|
||||
fout.close();
|
||||
return ErrorCode::NoError;
|
||||
});
|
||||
watcher.setFuture(future);
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &Client::scpFileDownloadFinished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
closeScpSession();
|
||||
return watcher.result();
|
||||
}
|
||||
|
||||
void Client::closeScpSession()
|
||||
{
|
||||
if (m_scpSession != nullptr) {
|
||||
|
||||
@@ -38,8 +38,6 @@ namespace libssh {
|
||||
const QString &localPath,
|
||||
const QString &remotePath,
|
||||
const QString &fileDesc);
|
||||
ErrorCode scpFileDownload(const QString &remotePath,
|
||||
const QString &localPath);
|
||||
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback);
|
||||
private:
|
||||
ErrorCode closeChannel();
|
||||
@@ -56,7 +54,6 @@ namespace libssh {
|
||||
signals:
|
||||
void writeToChannelFinished();
|
||||
void scpFileCopyFinished();
|
||||
void scpFileDownloadFinished();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
33
client/core/utils/selfhosted/sshExecutor.cpp
Normal file
33
client/core/utils/selfhosted/sshExecutor.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "sshExecutor.h"
|
||||
|
||||
SshExecutor &SshExecutor::instance()
|
||||
{
|
||||
static SshExecutor executor;
|
||||
return executor;
|
||||
}
|
||||
|
||||
SshExecutor::~SshExecutor()
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
for (QThreadPool *pool : std::as_const(m_pools)) {
|
||||
pool->waitForDone();
|
||||
delete pool;
|
||||
}
|
||||
m_pools.clear();
|
||||
}
|
||||
|
||||
QThreadPool *SshExecutor::poolFor(const QString &serverId)
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
|
||||
auto it = m_pools.find(serverId);
|
||||
if (it != m_pools.end()) {
|
||||
return it.value();
|
||||
}
|
||||
|
||||
auto *pool = new QThreadPool();
|
||||
pool->setMaxThreadCount(1); // serialize all SSH ops for this server
|
||||
pool->setExpiryTimeout(-1); // keep the single worker thread alive between ops
|
||||
m_pools.insert(serverId, pool);
|
||||
return pool;
|
||||
}
|
||||
47
client/core/utils/selfhosted/sshExecutor.h
Normal file
47
client/core/utils/selfhosted/sshExecutor.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef SSHEXECUTOR_H
|
||||
#define SSHEXECUTOR_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
#include <QThreadPool>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include <utility>
|
||||
|
||||
// Per-server serial executor for long-running self-hosted SSH operations.
|
||||
//
|
||||
// All SSH work for a given serverId is queued onto a dedicated single-thread
|
||||
// pool, so operations to the same server run strictly one at a time (no
|
||||
// concurrent SSH sessions to one host => no races, and a natural guard against
|
||||
// double-run). Operations to different servers still run in parallel.
|
||||
//
|
||||
// NOTE: do NOT route nested workers that are awaited from within an already
|
||||
// queued operation (e.g. InstallController::isServerDpkgBusy) through the same
|
||||
// per-server pool — the single worker thread would block waiting on itself.
|
||||
// Such nested helpers must keep using the global QThreadPool.
|
||||
class SshExecutor
|
||||
{
|
||||
public:
|
||||
static SshExecutor &instance();
|
||||
|
||||
SshExecutor(const SshExecutor &) = delete;
|
||||
SshExecutor &operator=(const SshExecutor &) = delete;
|
||||
|
||||
template <typename Functor>
|
||||
auto run(const QString &serverId, Functor &&functor)
|
||||
{
|
||||
return QtConcurrent::run(poolFor(serverId), std::forward<Functor>(functor));
|
||||
}
|
||||
|
||||
private:
|
||||
SshExecutor() = default;
|
||||
~SshExecutor();
|
||||
|
||||
QThreadPool *poolFor(const QString &serverId);
|
||||
|
||||
QHash<QString, QThreadPool *> m_pools;
|
||||
QMutex m_mutex;
|
||||
};
|
||||
|
||||
#endif // SSHEXECUTOR_H
|
||||
@@ -44,11 +44,6 @@ SshSession::~SshSession()
|
||||
m_sshClient.disconnectFromHost();
|
||||
}
|
||||
|
||||
void SshSession::resetConnection()
|
||||
{
|
||||
m_sshClient.disconnectFromHost();
|
||||
}
|
||||
|
||||
ErrorCode SshSession::runScript(const ServerCredentials &credentials, QString script,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
||||
|
||||
@@ -47,9 +47,6 @@ public:
|
||||
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
||||
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
|
||||
/** Force-close the current SSH connection so the next operation starts fresh. */
|
||||
void resetConnection();
|
||||
|
||||
private:
|
||||
libssh::Client m_sshClient;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
|
||||
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
|
||||
sudo docker volume ls | grep amnezia | awk '{print $2}' | xargs sudo docker volume rm -f;\
|
||||
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
||||
sudo rm -frd /opt/amnezia
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
sudo docker stop $CONTAINER_NAME;\
|
||||
sudo docker rm -fv $CONTAINER_NAME;\
|
||||
sudo docker rmi $CONTAINER_NAME;\
|
||||
test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true
|
||||
sudo docker rmi $CONTAINER_NAME;
|
||||
|
||||
@@ -2880,16 +2880,6 @@ Thank you for staying with us!</source>
|
||||
<source>Add them to the application if they were not displayed</source>
|
||||
<translation>Добавить их в приложение, если они не отображаются</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="126"/>
|
||||
<source>Backup</source>
|
||||
<translation>Резервное копирование</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="127"/>
|
||||
<source>Local copy of VPN protocols, services, all server settings and users</source>
|
||||
<translation>Локальная копия VPN-протоколов, сервисов, всех настроек сервера и пользователей</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="124"/>
|
||||
<source>Reboot server</source>
|
||||
@@ -3579,34 +3569,6 @@ Thank you for staying with us!</source>
|
||||
<source>Continue</source>
|
||||
<translation>Продолжить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No containers found in backup file</source>
|
||||
<translation>В файле резервной копии не найдено контейнеров</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installing %1 (%2/%3)...</source>
|
||||
<translation>Устанавливается %1 (%2/%3)...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>backup.tgz</source>
|
||||
<translation>backup.tgz</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>RestoredServer</source>
|
||||
<translation>Восстановленный сервер</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Restore from backup</source>
|
||||
<translation>Восстановить из резервной копии</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Restoration of VPN protocols, services, all server settings and users</source>
|
||||
<translation>Восстановление VPN-протоколов, сервисов, всех настроек сервера и пользователей</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Backup to Restore</source>
|
||||
<translation>Выберите файл резервной копии</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageSetupWizardInstalling</name>
|
||||
@@ -5424,131 +5386,6 @@ FileZilla или другие SFTP-клиенты, а также смонтир
|
||||
<translation>Будет установлен протокол AmneziaWG. Он обеспечивает высокую скорость соединения и гарантирует стабильную работу даже в самых сложных условиях.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageSettingsServerBackup</name>
|
||||
<message>
|
||||
<source>Backup</source>
|
||||
<translation>Резервное копирование</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Local copy of VPN protocols, services, all server settings and users.</source>
|
||||
<translation>Локальная копия VPN-протоколов, сервисов, всех настроек сервера и пользователей.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>More about backups</source>
|
||||
<translation>Подробнее о резервных копиях</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Create backup</source>
|
||||
<translation>Создать резервную копию</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Restore from backup</source>
|
||||
<translation>Восстановить из резервной копии</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Create backup and download to device?</source>
|
||||
<translation>Создать резервную копию и скачать на устройство?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Backup will be created on server and automatically downloaded to your device</source>
|
||||
<translation>Резервная копия будет создана на сервере и автоматически скачана на ваше устройство</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Create server configuration backup?</source>
|
||||
<translation>Создать резервную копию конфигурации сервера?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>This will create a backup of your server containers configuration on the server</source>
|
||||
<translation>На сервере будет создана резервная копия конфигурации контейнеров</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Create</source>
|
||||
<translation>Создать</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel</source>
|
||||
<translation>Отмена</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Backup to Restore</source>
|
||||
<translation>Выберите файл резервной копии</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Backup files (*.tar.gz *.backup *.tgz *.gz)</source>
|
||||
<translation>Файлы резервных копий (*.tar.gz *.backup *.tgz *.gz)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Server</source>
|
||||
<translation>Сервер</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Backup created successfully: %1</source>
|
||||
<translation>Резервная копия успешно создана: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Backup downloaded successfully!\n\nSaved to:\n%1</source>
|
||||
<translation>Резервная копия успешно скачана!\n\nСохранена в:\n%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Backup error: %1</source>
|
||||
<translation>Ошибка резервного копирования: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageSettingsServerRestoreMode</name>
|
||||
<message>
|
||||
<source>%1 on %2</source>
|
||||
<translation>%1 на %2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Restore from backup</source>
|
||||
<translation>Восстановление из резервной копии</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add data from backup</source>
|
||||
<translation>Добавить данные из резервной копии</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If the same protocols are already installed on the server, they will be updated. Created users and access will be saved</source>
|
||||
<translation>Если на сервере уже установлены такие же протоколы, они будут обновлены. Созданные пользователи и доступы сохранятся</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Replace</source>
|
||||
<translation>Заменить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>All installed protocols, users and their access will not be saved</source>
|
||||
<translation>Все установленные протоколы, пользователи и их доступы не будут сохранены</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Backup restore error: %1</source>
|
||||
<translation>Ошибка восстановления из резервной копии: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Server</source>
|
||||
<translation>Сервер</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageSettingsServerBackupRestored</name>
|
||||
<message>
|
||||
<source>%1 on "%2"</source>
|
||||
<translation>%1 на "%2"</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Backup restored</source>
|
||||
<translation>Резервная копия восстановлена</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>To home</source>
|
||||
<translation>На главную</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>To server settings</source>
|
||||
<translation>К настройкам сервера</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>main2</name>
|
||||
<message>
|
||||
|
||||
@@ -475,8 +475,7 @@ bool SubscriptionUiController::deactivateExternalDevice(const QString &serverId,
|
||||
void SubscriptionUiController::validateConfig()
|
||||
{
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (!serverId.isEmpty() && m_serversController->isLegacyApiV1Server(serverId)) {
|
||||
emit unsupportedConnectDrawerRequested();
|
||||
if (serverId.isEmpty()) {
|
||||
emit configValidated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
|
||||
ServersController* serversController,
|
||||
@@ -33,7 +35,7 @@ void ConnectionUiController::openConnection()
|
||||
ErrorCode errorCode = m_connectionController->openConnection(serverId);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit connectionErrorOccurred(errorCode);
|
||||
notifyConnectionBlocked(errorCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -130,10 +132,36 @@ void ConnectionUiController::toggleConnection()
|
||||
} else if (isConnected()) {
|
||||
closeConnection();
|
||||
} else {
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ErrorCode errorCode = m_connectionController->isConnectionSupported(serverId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
notifyConnectionBlocked(errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
emit prepareConfig();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionUiController::notifyConnectionBlocked(ErrorCode errorCode)
|
||||
{
|
||||
if (errorCode == ErrorCode::LegacyApiV1NotSupportedError) {
|
||||
emit unsupportedConnectDrawerRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorCode == ErrorCode::NoInstalledContainersError) {
|
||||
emit noInstalledContainers();
|
||||
return;
|
||||
}
|
||||
|
||||
emit connectionErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
bool ConnectionUiController::isConnectionInProgress() const
|
||||
{
|
||||
return m_isConnectionInProgress;
|
||||
@@ -143,3 +171,32 @@ bool ConnectionUiController::isConnected() const
|
||||
{
|
||||
return m_isConnected;
|
||||
}
|
||||
|
||||
bool ConnectionUiController::isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex,
|
||||
const QString &clientId) const
|
||||
{
|
||||
if (clientId.isEmpty() || (!isConnected() && !isConnectionInProgress())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_serversController->getDefaultServerId() != serverId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static_cast<int>(m_serversController->getDefaultContainer(serverId)) != containerIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto adminConfig = m_serversController->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString connectionClientId =
|
||||
adminConfig->containerConfig(static_cast<DockerContainer>(containerIndex)).protocolConfig.clientId();
|
||||
if (connectionClientId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return connectionClientId == clientId || connectionClientId.contains(clientId);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ public slots:
|
||||
void openConnection();
|
||||
void closeConnection();
|
||||
|
||||
bool isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex, const QString &clientId) const;
|
||||
|
||||
ErrorCode getLastConnectionError();
|
||||
void onConnectionStateChanged(Vpn::ConnectionState state);
|
||||
|
||||
@@ -48,9 +50,12 @@ signals:
|
||||
void connectButtonClicked();
|
||||
void preparingConfig();
|
||||
void prepareConfig();
|
||||
void unsupportedConnectDrawerRequested();
|
||||
void noInstalledContainers();
|
||||
|
||||
private:
|
||||
Vpn::ConnectionState getCurrentConnectionState();
|
||||
void notifyConnectionBlocked(ErrorCode errorCode);
|
||||
|
||||
ConnectionController* m_connectionController;
|
||||
ServersController* m_serversController;
|
||||
|
||||
@@ -32,9 +32,6 @@ namespace PageLoader
|
||||
PageSettingsNewsNotifications,
|
||||
PageSettingsNewsDetail,
|
||||
PageSettingsBackup,
|
||||
PageSettingsServerBackup,
|
||||
PageSettingsServerRestoreMode,
|
||||
PageSettingsServerBackupRestored,
|
||||
PageSettingsAbout,
|
||||
PageSettingsLogging,
|
||||
PageSettingsSplitTunneling,
|
||||
@@ -154,7 +151,6 @@ signals:
|
||||
void goToPageSettings();
|
||||
void goToPageViewConfig();
|
||||
void goToPageSettingsServerServices();
|
||||
void goToPageSettingsServerManagement();
|
||||
void goToPageSettingsBackup();
|
||||
void goToShareConnectionPage(QString headerText, QString configContentHeaderText, QString configCaption, QString configExtension,
|
||||
QString configFileName);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/selfhosted/sshExecutor.h"
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
@@ -75,13 +76,7 @@ InstallUiController::InstallUiController(InstallController *installController,
|
||||
m_connectionController(connectionController)
|
||||
{
|
||||
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) {
|
||||
if (errorCode == ErrorCode::NoInstalledContainersError) {
|
||||
emit noInstalledContainers();
|
||||
} else {
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
});
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, &InstallUiController::installationErrorOccurred);
|
||||
}
|
||||
|
||||
InstallUiController::~InstallUiController()
|
||||
@@ -217,15 +212,13 @@ void InstallUiController::scanServerForInstalledContainers(const QString &server
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
|
||||
containerConfig.container = container;
|
||||
|
||||
|
||||
switch (protocolType) {
|
||||
case Proto::Awg: {
|
||||
containerConfig.protocolConfig = m_awgConfigModel->getProtocolConfig();
|
||||
@@ -271,6 +264,41 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallUiController::updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateClientConfig(serverId, container, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
m_protocolModel->updateModel(updatedConfig);
|
||||
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolType));
|
||||
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
||||
return;
|
||||
}
|
||||
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
@@ -303,15 +331,15 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
||||
ContainerConfig oldConfigCopy = oldContainerConfig;
|
||||
InstallController *installController = m_installController;
|
||||
QFuture<ErrorCode> future =
|
||||
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
|
||||
SshExecutor::instance().run(serverId, [installController, serverId, container, oldConfigCopy,
|
||||
newConfigCopy]() mutable -> ErrorCode {
|
||||
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
return installController->updateServerConfig(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateContainer(serverId, container, oldContainerConfig, containerConfig);
|
||||
ErrorCode errorCode = m_installController->updateServerConfig(serverId, container, oldContainerConfig, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
@@ -451,7 +479,7 @@ void InstallUiController::removeContainer(const QString &serverId, int container
|
||||
});
|
||||
|
||||
InstallController *installController = m_installController;
|
||||
QFuture<ErrorCode> future = QtConcurrent::run(
|
||||
QFuture<ErrorCode> future = SshExecutor::instance().run(serverId,
|
||||
[installController, serverId, container]() -> ErrorCode {
|
||||
return installController->removeContainer(serverId, container);
|
||||
});
|
||||
|
||||
@@ -64,7 +64,8 @@ public slots:
|
||||
|
||||
void scanServerForInstalledContainers(const QString &serverId);
|
||||
|
||||
void updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
|
||||
void removeServer(const QString &serverId);
|
||||
void rebootServer(const QString &serverId);
|
||||
@@ -132,7 +133,6 @@ signals:
|
||||
void cachedProfileCleared(const QString &message);
|
||||
void apiConfigRemoved(const QString &message);
|
||||
|
||||
void noInstalledContainers();
|
||||
void configValidated(bool isValid);
|
||||
|
||||
private:
|
||||
@@ -162,6 +162,8 @@ private:
|
||||
QString m_privateKeyPassphrase;
|
||||
|
||||
void updateProtocolConfigModel(const QString &serverId, int containerIndex, int protocolIndex);
|
||||
|
||||
bool buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig);
|
||||
};
|
||||
|
||||
#endif // INSTALLUICONTROLLER_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,406 +0,0 @@
|
||||
#ifndef SERVERSBACKUPCONTROLLER_H
|
||||
#define SERVERSBACKUPCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QFileInfo>
|
||||
|
||||
class QTemporaryFile;
|
||||
class ServersModel;
|
||||
class ServersUiController;
|
||||
class ServersController;
|
||||
class SecureQSettings;
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
/**
|
||||
* @brief Controller for managing Amnezia VPN configuration backups
|
||||
*
|
||||
* Uses existing ServerController and libssh::Client from Amnezia
|
||||
* Bash scripts are embedded directly in C++ code
|
||||
* Supports direct container backup via docker cp
|
||||
*
|
||||
* Fully cross-platform: Windows, macOS, Linux, iOS, Android
|
||||
*/
|
||||
class ServersBackupController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ServersBackupController(SecureQSettings *settings, ServersModel *serversModel,
|
||||
ServersUiController *serversUiController, ServersController *serversController,
|
||||
QObject *parent = nullptr);
|
||||
~ServersBackupController();
|
||||
|
||||
/**
|
||||
* @brief Backup information
|
||||
*/
|
||||
struct BackupInfo {
|
||||
QString filename;
|
||||
QString fullPath;
|
||||
QDateTime createdAt;
|
||||
qint64 size;
|
||||
bool isValid;
|
||||
QStringList containers;
|
||||
};
|
||||
|
||||
enum BackupStatus {
|
||||
Idle,
|
||||
InProgress,
|
||||
Success,
|
||||
Failed
|
||||
};
|
||||
Q_ENUM(BackupStatus)
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Create backup on server (all containers)
|
||||
* @param credentials Server credentials
|
||||
*/
|
||||
void createBackup(const ServerCredentials &credentials);
|
||||
|
||||
/**
|
||||
* @brief Create backup and automatically download to device (for QML)
|
||||
* @param downloadToDevice Download to device after creation?
|
||||
* @param deleteFromServer Delete from server after download?
|
||||
*/
|
||||
Q_INVOKABLE void createBackupWithDownload(bool downloadToDevice = true,
|
||||
bool deleteFromServer = true);
|
||||
|
||||
/**
|
||||
* @brief Create backup of specific container
|
||||
* @param credentials Server credentials
|
||||
* @param container Container type for backup
|
||||
*/
|
||||
void createContainerBackup(const ServerCredentials &credentials, DockerContainer container);
|
||||
|
||||
/**
|
||||
* @brief Create backup of specific container by name
|
||||
* @param credentials Server credentials
|
||||
* @param containerName Container name (e.g. "amnezia-awg")
|
||||
*/
|
||||
void createBackupByName(const ServerCredentials &credentials, const QString &containerName);
|
||||
|
||||
/**
|
||||
* @brief Create backup of multiple containers
|
||||
* @param credentials Server credentials
|
||||
* @param containers List of containers for backup
|
||||
*/
|
||||
void createContainersBackup(const ServerCredentials &credentials, const QList<DockerContainer> &containers);
|
||||
|
||||
/**
|
||||
* @brief Get list of backups from server
|
||||
* @param credentials Server credentials
|
||||
*/
|
||||
void fetchBackupList(const ServerCredentials &credentials);
|
||||
|
||||
/**
|
||||
* @brief Restore from backup
|
||||
* @param credentials Server credentials
|
||||
* @param backupFilename Backup file name
|
||||
* @param containers List of containers (empty = all)
|
||||
* @param replaceMode If true - clears container first, then restores. If false - adds data on top of existing
|
||||
*/
|
||||
void restoreBackup(const ServerCredentials &credentials,
|
||||
const QString &backupFilename,
|
||||
const QStringList &containers = QStringList(),
|
||||
bool replaceMode = false);
|
||||
|
||||
/**
|
||||
* @brief Check backup status on server
|
||||
* @param credentials Server credentials
|
||||
*/
|
||||
void checkBackupStatus(const ServerCredentials &credentials);
|
||||
|
||||
/**
|
||||
* @brief Download backup to local machine
|
||||
* @param credentials Server credentials
|
||||
* @param backupFilename Backup file name
|
||||
* @param localPath Save path
|
||||
*/
|
||||
void downloadBackup(const ServerCredentials &credentials,
|
||||
const QString &backupFilename,
|
||||
const QString &localPath);
|
||||
|
||||
/**
|
||||
* @brief Upload backup to server
|
||||
* @param credentials Server credentials
|
||||
* @param localPath Path to local file
|
||||
* @param replaceMode Restore mode (true = replace, false = add). Saved for later use in restoreBackup
|
||||
*/
|
||||
void uploadBackup(const ServerCredentials &credentials,
|
||||
const QString &localPath,
|
||||
bool replaceMode = false);
|
||||
|
||||
// Overloaded method for setup wizard with separate credential parameters
|
||||
Q_INVOKABLE void uploadBackupWithStrings(const QString &hostname,
|
||||
const QString &username,
|
||||
const QString &secretData,
|
||||
const QString &localPath,
|
||||
bool replaceMode = false);
|
||||
|
||||
/**
|
||||
* @brief Universal method to start restore (from QML)
|
||||
* Automatically selects correct path depending on parameters
|
||||
* @param isFromSetupWizard Restore from setup wizard?
|
||||
* @param backupFilePath Path to local backup file
|
||||
* @param replaceMode Restore mode (true = replace, false = add)
|
||||
* @param wizardHostname Hostname for setup wizard (optional)
|
||||
* @param wizardUsername Username for setup wizard (optional)
|
||||
* @param wizardSecretData Secret data for setup wizard (optional)
|
||||
*/
|
||||
Q_INVOKABLE void startRestore(bool isFromSetupWizard,
|
||||
const QString &backupFilePath,
|
||||
bool replaceMode,
|
||||
const QString &wizardHostname = QString(),
|
||||
const QString &wizardUsername = QString(),
|
||||
const QString &wizardSecretData = QString());
|
||||
|
||||
/**
|
||||
* @brief Prepare restore information from backup file
|
||||
* Parses filename, extracts IP, prepares metadata
|
||||
* @param backupFilePath Path to backup file
|
||||
* @return QVariantMap with keys: fileName, serverIp
|
||||
*/
|
||||
Q_INVOKABLE QVariantMap getBackupFileInfo(const QString &backupFilePath);
|
||||
|
||||
/**
|
||||
* @brief Scan backup file and determine which containers it contains
|
||||
* @param localPath Path to local backup file
|
||||
* @return List of container names found in backup
|
||||
*/
|
||||
Q_INVOKABLE QStringList scanBackupForContainers(const QString &localPath);
|
||||
|
||||
/**
|
||||
* @brief Set default server and container after restore (for setup wizard)
|
||||
* @param isFromSetupWizard Was restore called from setup wizard
|
||||
* @return true if successful, false if no servers or containers
|
||||
*/
|
||||
Q_INVOKABLE bool setDefaultServerAfterRestore(bool isFromSetupWizard);
|
||||
|
||||
/**
|
||||
* @brief Install containers from backup on empty server (for setup wizard)
|
||||
* Scans backup, adds empty server and sends signal to install containers
|
||||
* @param backupFilePath Path to local backup file
|
||||
* @param hostname Server hostname
|
||||
* @param username Username for SSH
|
||||
* @param secretData Password/key for SSH
|
||||
*/
|
||||
Q_INVOKABLE void prepareRestoreFromBackup(const QString &backupFilePath,
|
||||
const QString &hostname,
|
||||
const QString &username,
|
||||
const QString &secretData);
|
||||
|
||||
/**
|
||||
* @brief Delete backup from server
|
||||
* @param credentials Server credentials
|
||||
* @param backupFilename Backup file name
|
||||
*/
|
||||
void deleteBackup(const ServerCredentials &credentials,
|
||||
const QString &backupFilename);
|
||||
|
||||
/**
|
||||
* @brief Set backup directory on server
|
||||
*/
|
||||
void setBackupDirectory(const QString &directory);
|
||||
|
||||
/**
|
||||
* @brief Get backup directory
|
||||
*/
|
||||
QString backupDirectory() const { return m_backupDir; }
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Operation status changed
|
||||
*/
|
||||
void statusChanged(BackupStatus status);
|
||||
|
||||
/**
|
||||
* @brief Operation progress (0-100)
|
||||
*/
|
||||
void progressChanged(int percent, const QString &message);
|
||||
|
||||
/**
|
||||
* @brief Backup list received
|
||||
*/
|
||||
void backupListReceived(const QList<BackupInfo> &backups);
|
||||
|
||||
/**
|
||||
* @brief Backup created successfully
|
||||
*/
|
||||
void backupCreated(const QString &backupFilename);
|
||||
|
||||
/**
|
||||
* @brief Backup restored successfully
|
||||
*/
|
||||
void backupRestored();
|
||||
|
||||
/**
|
||||
* @brief Need to set default server and container (for setup wizard)
|
||||
* This signal is sent after backupRestored() if restore was from setup wizard
|
||||
*/
|
||||
void needSetDefaultServer();
|
||||
|
||||
/**
|
||||
* @brief Default server and container successfully set
|
||||
* Can navigate to result page
|
||||
*/
|
||||
void defaultServerAndContainerSet();
|
||||
|
||||
/**
|
||||
* @brief All containers from backup installed
|
||||
* Can proceed to data restore
|
||||
* @param backupFilePath Path to backup file
|
||||
* @param hostname Hostname
|
||||
* @param username Username
|
||||
* @param secretData Secret data
|
||||
* @param serverIp IP address (for display)
|
||||
* @param fileName File name (for display)
|
||||
*/
|
||||
void readyForRestore(const QString &backupFilePath,
|
||||
const QString &hostname,
|
||||
const QString &username,
|
||||
const QString &secretData,
|
||||
const QString &serverIp,
|
||||
const QString &fileName);
|
||||
|
||||
/**
|
||||
* @brief Backup downloaded
|
||||
*/
|
||||
void backupDownloaded(const QString &localPath);
|
||||
|
||||
/**
|
||||
* @brief Backup uploaded to server
|
||||
*/
|
||||
void backupUploaded(const QString &serverPath);
|
||||
|
||||
/**
|
||||
* @brief Backup status information received
|
||||
*/
|
||||
void backupStatusReceived(const QJsonObject &status);
|
||||
|
||||
/**
|
||||
* @brief Error occurred
|
||||
*/
|
||||
void errorOccurred(const QString &errorMessage, ErrorCode errorCode);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Get bash script for creating backup of all containers
|
||||
* @param ipAddress Server IP address in underscored format (e.g. "192_119_110_11")
|
||||
*/
|
||||
QString getBackupScript(const QString &ipAddress) const;
|
||||
|
||||
/**
|
||||
* @brief Get bash script for creating backup of specific container
|
||||
* @param container Container type
|
||||
* @param ipAddress Server IP address in underscored format (e.g. "192_119_110_11")
|
||||
*/
|
||||
QString getContainerBackupScript(DockerContainer container, const QString &ipAddress) const;
|
||||
|
||||
/**
|
||||
* @brief Get bash script for creating backup of multiple containers
|
||||
* @param containers List of containers
|
||||
* @param ipAddress Server IP address in underscored format (e.g. "192_119_110_11")
|
||||
*/
|
||||
QString getContainersBackupScript(const QList<DockerContainer> &containers, const QString &ipAddress) const;
|
||||
|
||||
/**
|
||||
* @brief Get bash script for restore
|
||||
* @param backupFilename Backup file name
|
||||
* @param containers List of containers
|
||||
* @param replaceMode If true - clears container first, then restores
|
||||
*/
|
||||
QString getRestoreScript(const QString &backupFilename, const QStringList &containers, bool replaceMode = false) const;
|
||||
|
||||
/**
|
||||
* @brief Get bash script for status check
|
||||
*/
|
||||
QString getCheckStatusScript() const;
|
||||
|
||||
/**
|
||||
* @brief Get bash script for backup list
|
||||
*/
|
||||
QString getListBackupsScript() const;
|
||||
|
||||
/**
|
||||
* @brief Parse backup list from output
|
||||
*/
|
||||
QList<BackupInfo> parseBackupList(const QString &output);
|
||||
|
||||
/**
|
||||
* @brief Parse status from output
|
||||
*/
|
||||
QJsonObject parseBackupStatus(const QString &output);
|
||||
|
||||
/**
|
||||
* @brief Handle standard output
|
||||
*/
|
||||
ErrorCode handleStdOut(const QString &data, QString &output);
|
||||
|
||||
/**
|
||||
* @brief Handle error output
|
||||
*/
|
||||
ErrorCode handleStdErr(const QString &data, QString &error);
|
||||
|
||||
/**
|
||||
* @brief Set status
|
||||
*/
|
||||
void setStatus(BackupStatus status);
|
||||
|
||||
/**
|
||||
* @brief Set progress
|
||||
*/
|
||||
void setProgress(int percent, const QString &message);
|
||||
|
||||
/**
|
||||
* @brief Attempt to set default container (called from timer)
|
||||
*/
|
||||
void trySetDefaultContainer();
|
||||
|
||||
ErrorCode runHostScript(const ServerCredentials &credentials, const QString &script,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbStdOut = nullptr,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbStdErr = nullptr);
|
||||
ErrorCode downloadFileFromHost(const ServerCredentials &credentials, const QString &remotePath, const QString &localPath);
|
||||
ErrorCode uploadFileToHostPublic(const ServerCredentials &credentials, const QString &localPath, const QString &remotePath,
|
||||
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
|
||||
private:
|
||||
QPointer<SecureQSettings> m_settings;
|
||||
ServersModel *m_serversModel;
|
||||
ServersUiController *m_serversUiController;
|
||||
ServersController *m_serversController;
|
||||
SshSession m_sshSession;
|
||||
BackupStatus m_status;
|
||||
QString m_backupDir;
|
||||
QString m_currentOutput;
|
||||
QString m_currentError;
|
||||
bool m_restoreReplaceMode; // Save restore mode for use after uploadBackup
|
||||
QTemporaryFile *m_tempUploadFile; // Temp file for Android URI (to prevent deletion before upload completes)
|
||||
|
||||
// For setting default container
|
||||
int m_containerRetryCount;
|
||||
static constexpr int m_maxContainerRetries = 3;
|
||||
|
||||
// For automatic restore after upload
|
||||
ServerCredentials m_pendingRestoreCredentials;
|
||||
bool m_autoRestoreAfterUpload;
|
||||
|
||||
// For automatic backup download/delete
|
||||
bool m_autoDownloadAfterCreate;
|
||||
bool m_autoDeleteAfterDownload;
|
||||
QString m_lastCreatedBackupFilename;
|
||||
};
|
||||
|
||||
#endif // SERVERSBACKUPCONTROLLER_H
|
||||
|
||||
@@ -156,7 +156,17 @@ void ServersUiController::updateModel()
|
||||
|
||||
m_serversModel->updateModel(m_orderedServerDescriptions, defaultServerId);
|
||||
|
||||
updateContainersModel();
|
||||
if (!m_processedServerId.isEmpty()) {
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
} else {
|
||||
updateContainersModel();
|
||||
}
|
||||
}
|
||||
updateDefaultServerContainersModel();
|
||||
|
||||
if (hadServersFromGatewayBefore != hasServersFromGatewayNow) {
|
||||
@@ -350,19 +360,14 @@ void ServersUiController::setProcessedServerId(const QString &serverId)
|
||||
m_processedServerId = normalizedServerId;
|
||||
|
||||
if (newIndex >= 0) {
|
||||
updateContainersModel();
|
||||
|
||||
for (const auto &description : m_orderedServerDescriptions) {
|
||||
if (description.serverId != normalizedServerId) {
|
||||
continue;
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
if (description.isApiV2) {
|
||||
if (description.isCountrySelectionAvailable && !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
emit updateApiServicesModel();
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
updateContainersModel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,6 @@ signals:
|
||||
void processedContainerIndexChanged(int index);
|
||||
void hasServersFromGatewayApiChanged();
|
||||
void updateApiCountryModel();
|
||||
void updateApiServicesModel();
|
||||
|
||||
public:
|
||||
void updateModel();
|
||||
|
||||
@@ -22,12 +22,10 @@
|
||||
|
||||
SettingsUiController::SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
m_settingsController(settingsController),
|
||||
m_serversController(serversController),
|
||||
m_languageUiController(languageUiController)
|
||||
m_serversController(serversController)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsUiController::onNotificationStateChanged);
|
||||
@@ -157,13 +155,13 @@ void SettingsUiController::restoreAppConfigFromData(const QByteArray &data)
|
||||
{
|
||||
ErrorCode errorCode = m_settingsController->restoreAppConfigFromData(data);
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
emit appLanguageChanged(
|
||||
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageUiController->getCurrentLanguageIndex()));
|
||||
emit appLanguageChanged();
|
||||
|
||||
bool amneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled();
|
||||
emit amneziaDnsToggled(amneziaDnsEnabled);
|
||||
|
||||
emit restoreBackupFinished();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
} else {
|
||||
emit errorOccurred(errorCode);
|
||||
@@ -178,6 +176,7 @@ QString SettingsUiController::getAppVersion()
|
||||
void SettingsUiController::clearSettings()
|
||||
{
|
||||
m_settingsController->clearSettings();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
emit resetLanguageToSystem();
|
||||
|
||||
@@ -206,9 +205,8 @@ bool SettingsUiController::isAutoStartEnabled()
|
||||
void SettingsUiController::toggleAutoStart(bool enable)
|
||||
{
|
||||
m_settingsController->toggleAutoStart(enable);
|
||||
if (!enable) {
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
|
||||
bool SettingsUiController::isStartMinimizedEnabled()
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "core/controllers/settingsController.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
@@ -17,7 +15,6 @@ class SettingsUiController : public QObject
|
||||
public:
|
||||
explicit SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
|
||||
@@ -32,6 +29,7 @@ public:
|
||||
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
|
||||
|
||||
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
||||
Q_PROPERTY(bool autoStartEnabled READ isAutoStartEnabled NOTIFY autoStartChanged)
|
||||
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
||||
|
||||
public slots:
|
||||
@@ -122,7 +120,7 @@ signals:
|
||||
|
||||
void loggingDisableByWatcher();
|
||||
|
||||
void appLanguageChanged(const LanguageSettings::AvailableLanguageEnum language);
|
||||
void appLanguageChanged();
|
||||
void resetLanguageToSystem();
|
||||
|
||||
void onNotificationStateChanged();
|
||||
@@ -135,12 +133,12 @@ signals:
|
||||
void activityResumed();
|
||||
|
||||
void isHomeAdLabelVisibleChanged(bool visible);
|
||||
void autoStartChanged();
|
||||
void startMinimizedChanged();
|
||||
|
||||
private:
|
||||
SettingsController* m_settingsController;
|
||||
ServersController* m_serversController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -168,42 +168,6 @@ void SystemController::setQmlRoot(QObject *qmlRoot)
|
||||
m_qmlRoot = qmlRoot;
|
||||
}
|
||||
|
||||
QString SystemController::getFileNameFromPath(const QString &filePath)
|
||||
{
|
||||
if (filePath.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
// Для Android URI используем специальный метод для получения имени файла
|
||||
if (filePath.startsWith("content://")) {
|
||||
QString fileName = AndroidController::instance()->getFileName(filePath);
|
||||
if (!fileName.isEmpty()) {
|
||||
return fileName;
|
||||
}
|
||||
// Если не удалось получить имя через ContentResolver, пытаемся извлечь из URI
|
||||
}
|
||||
#endif
|
||||
|
||||
// Для обычных путей или если Android метод не сработал
|
||||
QFileInfo fileInfo(filePath);
|
||||
QString fileName = fileInfo.fileName();
|
||||
|
||||
// Если имя файла пустое, пытаемся извлечь из пути
|
||||
if (fileName.isEmpty()) {
|
||||
QStringList parts = filePath.split('/');
|
||||
if (!parts.isEmpty()) {
|
||||
fileName = parts.last();
|
||||
// Декодируем URL-кодированные символы
|
||||
if (fileName.contains('%')) {
|
||||
fileName = QUrl::fromPercentEncoding(fileName.toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
bool SystemController::isAuthenticated()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
|
||||
@@ -18,13 +18,6 @@ public:
|
||||
public slots:
|
||||
QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "",
|
||||
const bool isSaveMode = false, const QString &defaultSuffix = "");
|
||||
|
||||
/**
|
||||
* @brief Получить имя файла из пути или URI (для Android)
|
||||
* @param filePath Путь к файлу или URI
|
||||
* @return Имя файла
|
||||
*/
|
||||
Q_INVOKABLE QString getFileNameFromPath(const QString &filePath);
|
||||
|
||||
void setQmlRoot(QObject *qmlRoot);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||
switch (role) {
|
||||
case SubscriptionStatusRole: {
|
||||
if (m_accountInfoData.configType == serverConfigUtils::ConfigType::AmneziaFreeV3) {
|
||||
return tr("Active");
|
||||
return QStringLiteral("<p><a style=\"color: #28c840;\">%1</a>").arg(tr("Active"));
|
||||
}
|
||||
|
||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate)
|
||||
|
||||
@@ -27,6 +27,7 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
|
||||
auto userData = client.value(configKey::userData).toObject();
|
||||
|
||||
switch (role) {
|
||||
case ClientIdRole: return client.value(configKey::clientId).toString();
|
||||
case ClientNameRole: return userData.value(configKey::clientName).toString();
|
||||
case CreationDateRole: return userData.value(configKey::creationDate).toString();
|
||||
case LatestHandshakeRole: return userData.value(configKey::latestHandshake).toString();
|
||||
@@ -62,6 +63,7 @@ void ClientManagementModel::updateClientName(int row, const QString &newName)
|
||||
QHash<int, QByteArray> ClientManagementModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[ClientIdRole] = "clientId";
|
||||
roles[ClientNameRole] = "clientName";
|
||||
roles[CreationDateRole] = "creationDate";
|
||||
roles[LatestHandshakeRole] = "latestHandshake";
|
||||
|
||||
@@ -10,7 +10,8 @@ class ClientManagementModel : public QAbstractListModel
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
ClientNameRole = Qt::UserRole + 1,
|
||||
ClientIdRole = Qt::UserRole + 1,
|
||||
ClientNameRole,
|
||||
CreationDateRole,
|
||||
LatestHandshakeRole,
|
||||
DataReceivedRole,
|
||||
|
||||
@@ -23,6 +23,10 @@ public:
|
||||
Q_INVOKABLE int containerFromString(const QString &container) const {
|
||||
return static_cast<int>(amnezia::ContainerUtils::containerFromString(container));
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool isUnsupportedContainer(int containerIndex) const {
|
||||
return amnezia::ContainerUtils::isUnsupportedContainer(static_cast<amnezia::DockerContainer>(containerIndex));
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CONTAINERPROPS_H
|
||||
|
||||
@@ -67,6 +67,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
|
||||
case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex);
|
||||
case IsSupportedRole: return ContainerUtils::isSupportedByCurrentPlatform(container);
|
||||
case IsShareableRole: return ContainerUtils::isShareable(container);
|
||||
case IsUnsupportedContainerRole: return ContainerUtils::isUnsupportedContainer(container);
|
||||
case IsVpnContainerRole: return ContainerUtils::containerService(container) == ServiceType::Vpn;
|
||||
case IsServiceContainerRole: return ContainerUtils::containerService(container) == ServiceType::Other;
|
||||
case IsIpsecRole: return container == DockerContainer::Ipsec;
|
||||
@@ -142,7 +143,8 @@ bool ContainersModel::hasInstalledProtocols()
|
||||
|
||||
bool ContainersModel::isInstallationAllowed(DockerContainer container)
|
||||
{
|
||||
return container != DockerContainer::Awg;
|
||||
return container != DockerContainer::Awg
|
||||
&& !ContainerUtils::isUnsupportedContainer(container);
|
||||
}
|
||||
|
||||
void ContainersModel::openContainerSettings(int containerIndex)
|
||||
@@ -176,6 +178,7 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
|
||||
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
|
||||
roles[IsSupportedRole] = "isSupported";
|
||||
roles[IsShareableRole] = "isShareable";
|
||||
roles[IsUnsupportedContainerRole] = "isUnsupportedContainer";
|
||||
roles[IsInstallationAllowedRole] = "isInstallationAllowed";
|
||||
roles[InstallPageOrderRole] = "installPageOrder";
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
IsSupportedRole,
|
||||
IsShareableRole,
|
||||
|
||||
IsUnsupportedContainerRole,
|
||||
|
||||
InstallPageOrderRole,
|
||||
|
||||
// Container type check roles
|
||||
|
||||
@@ -56,14 +56,17 @@ ListViewType {
|
||||
return
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, proxyDefaultServerContainersModel.mapToSource(index))
|
||||
} else {
|
||||
ServersUiController.processedContainerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
var containerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
|
||||
if (!isInstalled) {
|
||||
ServersUiController.processedContainerIndex = containerIndex
|
||||
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
|
||||
containersDropDown.closeTriggered()
|
||||
return
|
||||
}
|
||||
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, containerIndex)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -5,7 +5,6 @@ import QtQuick.Layouts
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import PageEnum 1.0
|
||||
import ContainerProps 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
@@ -6,8 +6,36 @@ Menu {
|
||||
|
||||
popupType: Popup.Native
|
||||
|
||||
onAboutToShow: blocker.enabled = true
|
||||
onClosed: blocker.enabled = false
|
||||
property Item inputBlocker: null
|
||||
|
||||
Component {
|
||||
id: inputBlockerComponent
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
onAboutToShow: {
|
||||
if (!textObj || !textObj.window) {
|
||||
return
|
||||
}
|
||||
|
||||
const contentItem = textObj.window.contentItem
|
||||
if (!inputBlocker) {
|
||||
inputBlocker = inputBlockerComponent.createObject(contentItem)
|
||||
} else {
|
||||
inputBlocker.parent = contentItem
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (inputBlocker) {
|
||||
inputBlocker.destroy()
|
||||
inputBlocker = null
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("C&ut")
|
||||
@@ -31,11 +59,4 @@ Menu {
|
||||
enabled: textObj.length > 0
|
||||
onTriggered: textObj.selectAll()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: blocker
|
||||
z: 2
|
||||
enabled: false
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ PageType {
|
||||
|
||||
filters: [
|
||||
ValueFilter {
|
||||
roleName: "isCurrentlyProcessed"
|
||||
value: true
|
||||
roleName: "serverId"
|
||||
value: ServersUiController.processedServerId
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -440,8 +440,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
}
|
||||
|
||||
var noButtonFunction = function() {}
|
||||
|
||||
@@ -561,7 +561,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
}
|
||||
|
||||
var noButtonFunction = function() {}
|
||||
|
||||
@@ -434,7 +434,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
|
||||
@@ -128,8 +128,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
}
|
||||
var noButtonFunction = function() {}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
|
||||
@@ -129,7 +129,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
|
||||
@@ -112,7 +112,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -279,7 +279,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -17,6 +17,10 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
enableTimer: false
|
||||
|
||||
property bool portDirty: false
|
||||
|
||||
function formatTransport(value) {
|
||||
if (value === "raw") return "RAW (TCP)"
|
||||
if (value === "xhttp") return "XHTTP"
|
||||
@@ -39,8 +43,8 @@ PageType {
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
onActiveFocusChanged: {
|
||||
if (backButton.enabled && backButton.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
@@ -60,8 +64,6 @@ PageType {
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
property alias focusItemId: textFieldWithHeaderType.textField
|
||||
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
@@ -107,13 +109,32 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
enabled: listView.enabled
|
||||
headerText: qsTr("Port")
|
||||
textField.text: port
|
||||
|
||||
Binding {
|
||||
target: textFieldWithHeaderType.textField
|
||||
property: "text"
|
||||
value: port
|
||||
when: !textFieldWithHeaderType.textField.activeFocus
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
textField.maximumLength: 5
|
||||
textField.validator: IntValidator {
|
||||
bottom: 1; top: 65535
|
||||
}
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus && textField.text === "" && port !== "") {
|
||||
textField.text = port
|
||||
}
|
||||
}
|
||||
textField.onTextChanged: {
|
||||
root.portDirty = (textField.text !== port)
|
||||
}
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== port) port = textField.text
|
||||
if (textField.text !== port) {
|
||||
port = textField.text
|
||||
}
|
||||
root.portDirty = false
|
||||
}
|
||||
checkEmptyText: true
|
||||
}
|
||||
@@ -172,9 +193,8 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
visible: listView.enabled
|
||||
&& (XrayConfigModel.hasUnsavedChanges
|
||||
|| textFieldWithHeaderType.textField.text !== port)
|
||||
enabled: visible && textFieldWithHeaderType.errorText === ""
|
||||
&& (XrayConfigModel.hasUnsavedChanges || root.portDirty)
|
||||
enabled: visible && textFieldWithHeaderType.textField.text !== ""
|
||||
text: qsTr("Save")
|
||||
onClicked: function() {
|
||||
forceActiveFocus()
|
||||
@@ -193,7 +213,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) saveButton.forceActiveFocus()
|
||||
|
||||
@@ -742,7 +742,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -95,7 +95,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -211,7 +211,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -208,7 +208,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -179,7 +179,7 @@ PageType {
|
||||
function mtProxyScheduleUpdate(closePage) {
|
||||
var cp = closePage === undefined ? false : closePage
|
||||
Qt.callLater(function () {
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
|
||||
tempPort = portTextField.textField.text
|
||||
tempUsername = usernameTextField.textField.text
|
||||
tempPassword = passwordTextField.textField.text
|
||||
|
||||
@@ -154,7 +154,7 @@ PageType {
|
||||
function telemtScheduleUpdate(closePage) {
|
||||
var cp = closePage === undefined ? false : closePage
|
||||
Qt.callLater(function () {
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,12 @@ PageType {
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
textFormat: Text.RichText
|
||||
text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this URL.")
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
|
||||
@@ -30,6 +30,16 @@ PageType {
|
||||
root.isInAppPurchase = ApiAccountInfoModel.data("isInAppPurchase")
|
||||
}
|
||||
|
||||
function selectConnectionCountry(countryIndex, countryCode, countryName) {
|
||||
if (countryIndex === ApiCountryModel.currentIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
PageController.showBusyIndicator(true)
|
||||
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.updateSubscriptionState()
|
||||
}
|
||||
@@ -83,7 +93,7 @@ PageType {
|
||||
|
||||
model: ApiCountryModel
|
||||
|
||||
currentIndex: 0
|
||||
currentIndex: ApiCountryModel.currentIndex
|
||||
|
||||
ButtonGroup {
|
||||
id: containersRadioButtonGroup
|
||||
@@ -204,15 +214,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
if (index !== ApiCountryModel.currentIndex) {
|
||||
PageController.showBusyIndicator(true)
|
||||
var prevIndex = ApiCountryModel.currentIndex
|
||||
ApiCountryModel.currentIndex = index
|
||||
if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)) {
|
||||
ApiCountryModel.currentIndex = prevIndex
|
||||
}
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
root.selectConnectionCountry(index, countryCode, countryName)
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: {
|
||||
|
||||
@@ -108,9 +108,9 @@ PageType {
|
||||
text: qsTr("Auto start")
|
||||
descriptionText: qsTr("Launch the application every time the device is starts")
|
||||
|
||||
checked: SettingsController.isAutoStartEnabled()
|
||||
checked: SettingsController.autoStartEnabled
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.isAutoStartEnabled()) {
|
||||
if (checked !== SettingsController.autoStartEnabled) {
|
||||
SettingsController.toggleAutoStart(checked)
|
||||
}
|
||||
}
|
||||
@@ -154,10 +154,10 @@ PageType {
|
||||
text: qsTr("Start minimized")
|
||||
descriptionText: qsTr("Launch application minimized (works with autostart option turned on)")
|
||||
|
||||
enabled: SettingsController.isAutoStartEnabled()
|
||||
enabled: SettingsController.autoStartEnabled
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
|
||||
checked: SettingsController.isAutoStartEnabled() && SettingsController.startMinimized
|
||||
checked: SettingsController.autoStartEnabled && SettingsController.startMinimized
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.startMinimized) {
|
||||
SettingsController.toggleStartMinimized(checked)
|
||||
@@ -166,7 +166,7 @@ PageType {
|
||||
}
|
||||
|
||||
DividerType {
|
||||
visible: !GC.isMobile()
|
||||
visible: !GC.isMobile() && ServersUiController.hasServersFromGatewayApi
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import ProtocolEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Components"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
flickable.contentY = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: flickable
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
contentHeight: contentColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
width: flickable.width
|
||||
|
||||
spacing: 16
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
|
||||
headerText: qsTr("Backup")
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("Local copy of VPN protocols, services, all server settings and users.")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: -8
|
||||
|
||||
text: qsTr("More about backups")
|
||||
color: AmneziaStyle.color.goldenApricot
|
||||
font.pixelSize: 14
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
// TODO: Open help page or show more info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
spacing: 12
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Create backup")
|
||||
|
||||
clickedFunc: function() {
|
||||
createBackup(true)
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Restore from backup")
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: AmneziaStyle.color.mutedGray
|
||||
textColor: AmneziaStyle.color.goldenApricot
|
||||
borderWidth: 1
|
||||
|
||||
clickedFunc: function() {
|
||||
restoreBackup()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createBackup(shouldDownload) {
|
||||
// Default shouldDownload = true
|
||||
var downloadAfterCreate = (shouldDownload !== undefined) ? shouldDownload : true
|
||||
|
||||
var headerText = downloadAfterCreate ?
|
||||
qsTr("Create backup and download to device?") :
|
||||
qsTr("Create server configuration backup?")
|
||||
var descriptionText = downloadAfterCreate ?
|
||||
qsTr("Backup will be created on server and automatically downloaded to your device") :
|
||||
qsTr("This will create a backup of your server containers configuration on the server")
|
||||
var yesButtonText = qsTr("Create")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
var yesButtonFunction = function() {
|
||||
PageController.showBusyIndicator(true)
|
||||
// Call C++ method that manages download and delete automatically
|
||||
ServersBackupController.createBackupWithDownload(downloadAfterCreate, true)
|
||||
}
|
||||
var noButtonFunction = function() {}
|
||||
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
|
||||
function restoreBackup() {
|
||||
var filter = GC.isMobile() ? "*.gz *.tgz *.tar.gz" : "Backup files (*.tar.gz *.backup *.tgz *.gz)"
|
||||
var localPath = SystemController.getFileName(
|
||||
qsTr("Select Backup to Restore"),
|
||||
filter,
|
||||
"",
|
||||
false,
|
||||
""
|
||||
)
|
||||
|
||||
if (!localPath || localPath.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get file information via C++
|
||||
var fileInfo = ServersBackupController.getBackupFileInfo(localPath)
|
||||
var fileName = fileInfo.fileName || "backup.tgz"
|
||||
var serverIp = fileInfo.serverIp || ""
|
||||
|
||||
// If IP not found in filename, use current server
|
||||
if (!serverIp || serverIp.length === 0) {
|
||||
serverIp = ServersUiController.serverHostName(ServersUiController.processedServerId) || ""
|
||||
}
|
||||
|
||||
var serverName = ServersUiController.serverName(ServersUiController.processedServerId) || qsTr("Server")
|
||||
|
||||
// Open restore mode selection page
|
||||
var parentItem = root.parent
|
||||
while (parentItem && parentItem.objectName !== "tabBarStackView") {
|
||||
parentItem = parentItem.parent
|
||||
}
|
||||
|
||||
if (parentItem && typeof parentItem.push === "function") {
|
||||
parentItem.push(PageController.getPagePath(PageEnum.PageSettingsServerRestoreMode), {
|
||||
"backupFilePath": localPath,
|
||||
"backupFileName": fileName,
|
||||
"serverName": serverName,
|
||||
"serverIp": serverIp
|
||||
})
|
||||
} else {
|
||||
console.warn("Could not find StackView to navigate to restore mode page")
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Backup Controller Connections ============
|
||||
|
||||
Connections {
|
||||
target: ServersBackupController
|
||||
|
||||
function onBackupCreated(backupFilename) {
|
||||
// If auto-download is not enabled, show success message
|
||||
PageController.showBusyIndicator(false)
|
||||
PageController.showNotificationMessage(qsTr("Backup created successfully: %1").arg(backupFilename))
|
||||
}
|
||||
|
||||
function onBackupDownloaded(localPath) {
|
||||
PageController.showBusyIndicator(false)
|
||||
console.log("Backup downloaded to:", localPath)
|
||||
PageController.showNotificationMessage(qsTr("Backup downloaded successfully!\n\nSaved to:\n%1").arg(localPath))
|
||||
}
|
||||
|
||||
function onProgressChanged(percent, message) {
|
||||
console.log("Backup progress:", percent, "%", message)
|
||||
}
|
||||
|
||||
function onErrorOccurred(errorMessage, errorCode) {
|
||||
PageController.showBusyIndicator(false)
|
||||
PageController.showErrorMessage(qsTr("Backup error: %1").arg(errorMessage))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import ProtocolEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Components"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property string backupFileName: ""
|
||||
property string serverName: ""
|
||||
property string serverIp: ""
|
||||
property bool isFromSetupWizard: false
|
||||
|
||||
Component.onCompleted: {
|
||||
// Убеждаемся, что все свойства инициализированы
|
||||
if (!backupFileName) backupFileName = ""
|
||||
if (!serverName) serverName = ""
|
||||
if (!serverIp) serverIp = ""
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
backButtonFunction: function() {
|
||||
// После успешного restore всегда идем на главную страницу
|
||||
PageController.goToPageHome()
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
flickable.contentY = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: flickable
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
contentHeight: contentColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
width: flickable.width
|
||||
|
||||
spacing: 16
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
|
||||
headerText: qsTr("Backup restored")
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: {
|
||||
var baseText = qsTr("%1 on \"%2\"").arg(backupFileName).arg(serverName)
|
||||
if (serverIp && serverIp.length > 0) {
|
||||
return baseText + ", " + serverIp
|
||||
}
|
||||
return baseText
|
||||
}
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 24
|
||||
spacing: 12
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("To home")
|
||||
implicitHeight: 56
|
||||
|
||||
clickedFunc: function() {
|
||||
// Переход на главную страницу (PageHome)
|
||||
PageController.goToPageHome()
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("To server settings")
|
||||
implicitHeight: 56
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
hoveredColor: AmneziaStyle.color.transparent
|
||||
pressedColor: AmneziaStyle.color.transparent
|
||||
borderWidth: 1
|
||||
borderColor: "#FFFFFF"
|
||||
textColor: "#FFFFFF"
|
||||
|
||||
clickedFunc: function() {
|
||||
// Открываем страницу настроек сервера с активной вкладкой "Управление"
|
||||
PageController.goToPage(PageEnum.PageSettingsServerInfo)
|
||||
// Устанавливаем активной вкладку "Управление" через сигнал
|
||||
PageController.goToPageSettingsServerManagement()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,17 +36,6 @@ PageType {
|
||||
function onRebootServerFinished(finishedMessage) {
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
function onRemoveAllContainersFinished(finishedMessage) {
|
||||
PageController.closePage() // close deInstalling page
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
function onRemoveContainerFinished(finishedMessage) {
|
||||
PageController.closePage() // close deInstalling page
|
||||
PageController.closePage() // close page with remove button
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -96,7 +85,6 @@ PageType {
|
||||
|
||||
property list<QtObject> serverActions: [
|
||||
check,
|
||||
backupSection,
|
||||
reboot,
|
||||
remove,
|
||||
clear,
|
||||
@@ -107,7 +95,6 @@ PageType {
|
||||
id: check
|
||||
|
||||
property bool isVisible: root.isServerWithWriteAccess
|
||||
readonly property bool isBackupSection: false
|
||||
readonly property string title: qsTr("Check the server for previously installed Amnezia services")
|
||||
readonly property string description: qsTr("Add them to the application if they were not displayed")
|
||||
readonly property var tColor: AmneziaStyle.color.paleGray
|
||||
@@ -118,25 +105,10 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: backupSection
|
||||
|
||||
property bool isVisible: root.isServerWithWriteAccess
|
||||
readonly property bool isBackupSection: false
|
||||
readonly property string title: qsTr("Backup")
|
||||
readonly property string description: qsTr("Local copy of VPN protocols, services, all server settings and users")
|
||||
readonly property var tColor: AmneziaStyle.color.paleGray
|
||||
readonly property var clickedHandler: function() {
|
||||
// Navigate to server backup page using PageController
|
||||
PageController.goToPage(PageEnum.PageSettingsServerBackup)
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: reboot
|
||||
|
||||
property bool isVisible: root.isServerWithWriteAccess
|
||||
readonly property bool isBackupSection: false
|
||||
readonly property string title: qsTr("Reboot server")
|
||||
readonly property string description: ""
|
||||
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
||||
@@ -167,7 +139,6 @@ PageType {
|
||||
id: remove
|
||||
|
||||
property bool isVisible: true
|
||||
readonly property bool isBackupSection: false
|
||||
readonly property string title: qsTr("Remove server from application")
|
||||
readonly property string description: ""
|
||||
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
||||
@@ -198,7 +169,6 @@ PageType {
|
||||
id: clear
|
||||
|
||||
property bool isVisible: root.isServerWithWriteAccess
|
||||
readonly property bool isBackupSection: false
|
||||
readonly property string title: qsTr("Clear server from Amnezia software")
|
||||
readonly property string description: ""
|
||||
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
||||
@@ -228,7 +198,6 @@ PageType {
|
||||
id: reset
|
||||
|
||||
property bool isVisible: ServersUiController.isServerFromApi(ServersUiController.processedServerId)
|
||||
readonly property bool isBackupSection: false
|
||||
readonly property string title: qsTr("Reset API config")
|
||||
readonly property string description: ""
|
||||
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
||||
@@ -255,5 +224,4 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -29,10 +29,6 @@ PageType {
|
||||
function onGoToPageSettingsServerServices() {
|
||||
tabBar.setCurrentIndex(root.pageSettingsServerServices)
|
||||
}
|
||||
|
||||
function onGoToPageSettingsServerManagement() {
|
||||
tabBar.setCurrentIndex(root.pageSettingsServerData)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -17,7 +17,8 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool isClearCacheVisible: ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex)
|
||||
property bool isUnsupportedContainer: ContainerProps.isUnsupportedContainer(ServersUiController.processedContainerIndex)
|
||||
property bool isClearCacheVisible: !isUnsupportedContainer && ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex)
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
@@ -52,10 +53,11 @@ PageType {
|
||||
Layout.bottomMargin: 32
|
||||
|
||||
headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings")
|
||||
descriptionText: root.isUnsupportedContainer ? qsTr("This protocol is no longer supported.") : ""
|
||||
}
|
||||
}
|
||||
|
||||
model: ProtocolsModel
|
||||
model: root.isUnsupportedContainer ? null : ProtocolsModel
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: delegateContent
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import ProtocolEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Components"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property string backupFilePath: ""
|
||||
property string backupFileName: ""
|
||||
property string serverName: ""
|
||||
property string serverIp: ""
|
||||
property bool isFromSetupWizard: false
|
||||
|
||||
// Credentials for setup wizard (when server is not yet added to ServersModel)
|
||||
property string wizardHostname: ""
|
||||
property string wizardUsername: ""
|
||||
property string wizardSecretData: ""
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
flickable.contentY = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: flickable
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
contentHeight: contentColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
width: flickable.width
|
||||
|
||||
spacing: 16
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
|
||||
headerText: qsTr("Restore from backup")
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: {
|
||||
// Show only filename and IP address, without server name
|
||||
if (serverIp && serverIp.length > 0) {
|
||||
return qsTr("%1 on %2").arg(backupFileName).arg(serverIp)
|
||||
}
|
||||
return backupFileName
|
||||
}
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
spacing: 0
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Add data from backup")
|
||||
descriptionText: qsTr("If the same protocols are already installed on the server, they will be updated. Created users and access will be saved")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
startRestore(false) // false = add mode
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Replace")
|
||||
descriptionText: qsTr("All installed protocols, users and their access will not be saved")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
textColor: AmneziaStyle.color.vibrantRed
|
||||
|
||||
clickedFunction: function() {
|
||||
startRestore(true) // true = replace mode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property bool restoreReplaceMode: false
|
||||
|
||||
function startRestore(replaceMode) {
|
||||
restoreReplaceMode = replaceMode
|
||||
PageController.showBusyIndicator(true)
|
||||
|
||||
// Call universal C++ method that will determine how to perform restore
|
||||
ServersBackupController.startRestore(
|
||||
isFromSetupWizard,
|
||||
backupFilePath,
|
||||
replaceMode,
|
||||
wizardHostname || "",
|
||||
wizardUsername || "",
|
||||
wizardSecretData || ""
|
||||
)
|
||||
}
|
||||
|
||||
property string lastUploadedBackupFilename: ""
|
||||
|
||||
Connections {
|
||||
target: ServersBackupController
|
||||
|
||||
function onBackupRestored() {
|
||||
console.log(" onBackupRestored, isFromSetupWizard:", isFromSetupWizard)
|
||||
|
||||
// For setup wizard, call C++ method to set default server and container
|
||||
if (isFromSetupWizard) {
|
||||
ServersBackupController.setDefaultServerAfterRestore(true)
|
||||
} else {
|
||||
// For regular mode, navigate directly
|
||||
PageController.showBusyIndicator(false)
|
||||
navigateToRestoredPage()
|
||||
}
|
||||
}
|
||||
|
||||
function onDefaultServerAndContainerSet() {
|
||||
console.log(" onDefaultServerAndContainerSet - navigating to restored page")
|
||||
// C++ has set default server and container, navigate to result page
|
||||
PageController.showBusyIndicator(false)
|
||||
navigateToRestoredPage()
|
||||
}
|
||||
|
||||
function onErrorOccurred(errorMessage, errorCode) {
|
||||
PageController.showBusyIndicator(false)
|
||||
PageController.showErrorMessage(qsTr("Backup restore error: %1").arg(errorMessage))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function navigateToRestoredPage() {
|
||||
// Navigate to successful restore page
|
||||
// Get actual server name from model
|
||||
var actualServerName = serverName
|
||||
if (root.isFromSetupWizard && ServersUiController.getServersCount() > 0) {
|
||||
var lastServerId = ServersUiController.getServerId(ServersUiController.getServersCount() - 1)
|
||||
actualServerName = ServersUiController.serverName(lastServerId) || qsTr("Server")
|
||||
} else if (!serverName || serverName.length === 0) {
|
||||
actualServerName = ServersUiController.serverName(ServersUiController.processedServerId) || qsTr("Server")
|
||||
}
|
||||
|
||||
var parentItem = root.parent
|
||||
|
||||
// For setup wizard use regular StackView
|
||||
if (root.isFromSetupWizard) {
|
||||
while (parentItem && typeof parentItem.push !== "function") {
|
||||
parentItem = parentItem.parent
|
||||
}
|
||||
if (parentItem && typeof parentItem.push === "function") {
|
||||
parentItem.push(PageController.getPagePath(PageEnum.PageSettingsServerBackupRestored), {
|
||||
"backupFileName": backupFileName,
|
||||
"serverName": actualServerName,
|
||||
"serverIp": serverIp,
|
||||
"isFromSetupWizard": true
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// For management menu, find tabBarStackView
|
||||
while (parentItem && parentItem.objectName !== "tabBarStackView") {
|
||||
parentItem = parentItem.parent
|
||||
}
|
||||
if (parentItem && typeof parentItem.push === "function") {
|
||||
parentItem.push(PageController.getPagePath(PageEnum.PageSettingsServerBackupRestored), {
|
||||
"backupFileName": backupFileName,
|
||||
"serverName": actualServerName,
|
||||
"serverIp": serverIp,
|
||||
"isFromSetupWizard": false
|
||||
})
|
||||
} else {
|
||||
console.warn("Could not find StackView to navigate to restored page")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,6 @@ import "../Controls2/TextTypes"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property var setupWizardEasy: null
|
||||
|
||||
property string savedHostname: ""
|
||||
property string savedUsername: ""
|
||||
property string savedSecretData: ""
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
@@ -136,11 +130,6 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
root.savedHostname = _hostname
|
||||
root.savedUsername = _username
|
||||
root.savedSecretData = _secretData
|
||||
console.log("Saved credentials in PageSetupWizardCredentials:", _hostname, _username)
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardEasy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,216 +15,8 @@ import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
objectName: "pageSetupWizardEasy"
|
||||
|
||||
property bool isEasySetup: true
|
||||
property bool isRestoreFromBackup: false
|
||||
property string backupFilePath: ""
|
||||
property string restoreHostname: ""
|
||||
property string restoreUsername: ""
|
||||
property string restoreSecretData: ""
|
||||
property bool waitingForServerToAdd: false
|
||||
|
||||
// For installing containers from backup
|
||||
property var containersToInstall: []
|
||||
property int currentContainerIndex: 0
|
||||
property bool isInstallingContainers: false
|
||||
|
||||
// Connections for ServersBackupController
|
||||
Connections {
|
||||
target: ServersBackupController
|
||||
|
||||
function onReadyForRestore(backupFilePath, hostname, username, secretData, serverIp, fileName) {
|
||||
console.log("onReadyForRestore received from C++")
|
||||
console.log(" backupFilePath:", backupFilePath)
|
||||
console.log(" hostname:", hostname)
|
||||
console.log(" serverIp:", serverIp)
|
||||
console.log(" fileName:", fileName)
|
||||
|
||||
// Scan backup to determine containers (C++ already did this, but needed for QML)
|
||||
var foundContainers = ServersBackupController.scanBackupForContainers(backupFilePath)
|
||||
console.log("Found containers:", foundContainers)
|
||||
|
||||
if (foundContainers.length === 0) {
|
||||
PageController.showErrorMessage(qsTr("No containers found in backup file"))
|
||||
root.isRestoreFromBackup = false
|
||||
return
|
||||
}
|
||||
|
||||
root.containersToInstall = foundContainers
|
||||
root.currentContainerIndex = 0
|
||||
|
||||
// Now add empty server with these credentials
|
||||
InstallController.setProcessedServerCredentials(hostname, username, secretData)
|
||||
|
||||
// Set waiting flag
|
||||
root.waitingForServerToAdd = true
|
||||
|
||||
console.log("Backup scanned, adding server...")
|
||||
// Add server (asynchronously)
|
||||
InstallController.addEmptyServer()
|
||||
|
||||
// Further execution will happen in onInstallServerFinished
|
||||
}
|
||||
}
|
||||
|
||||
// Connections for tracking server addition
|
||||
Connections {
|
||||
target: InstallController
|
||||
|
||||
function onInstallServerFinished(finishedMessage) {
|
||||
if (root.waitingForServerToAdd && root.isRestoreFromBackup && root.backupFilePath.length > 0) {
|
||||
console.log("Server added successfully, now installing containers from backup...")
|
||||
root.waitingForServerToAdd = false
|
||||
|
||||
// Start installing containers
|
||||
root.isInstallingContainers = true
|
||||
installNextContainer()
|
||||
}
|
||||
}
|
||||
|
||||
function onInstallContainerFinished(finishedMessage, isServiceInstall) {
|
||||
if (root.isInstallingContainers) {
|
||||
console.log("Container installed:", finishedMessage)
|
||||
|
||||
// Move to next container
|
||||
root.currentContainerIndex++
|
||||
|
||||
if (root.currentContainerIndex < root.containersToInstall.length) {
|
||||
// Install next container
|
||||
installNextContainer()
|
||||
} else {
|
||||
// All containers installed, now do restore
|
||||
console.log("All containers installed, starting restore...")
|
||||
root.isInstallingContainers = false
|
||||
|
||||
// IMPORTANT: Turn off busy indicator before navigation
|
||||
PageController.showBusyIndicator(false)
|
||||
|
||||
// Start navigation to restore mode selection page
|
||||
navigationTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to install next container from list
|
||||
function installNextContainer() {
|
||||
if (root.currentContainerIndex >= root.containersToInstall.length) {
|
||||
return
|
||||
}
|
||||
|
||||
var containerName = root.containersToInstall[root.currentContainerIndex]
|
||||
console.log("Installing container:", containerName, "(", root.currentContainerIndex + 1, "/", root.containersToInstall.length, ")")
|
||||
|
||||
// Convert container name to DockerContainer enum
|
||||
var dockerContainer = ContainerProps.containerFromString(containerName)
|
||||
|
||||
if (dockerContainer === 0) { // None
|
||||
console.log("Unknown container:", containerName, "skipping...")
|
||||
root.currentContainerIndex++
|
||||
installNextContainer()
|
||||
return
|
||||
}
|
||||
|
||||
// Get default settings for container
|
||||
var defaultProtocol = ContainerProps.defaultProtocol(dockerContainer)
|
||||
var defaultPort = InstallController.getPortForInstall(defaultProtocol)
|
||||
var defaultTransport = InstallController.defaultTransportProto(defaultProtocol)
|
||||
|
||||
// Set server index
|
||||
var serverIdx = ServersModel.getServersCount() - 1
|
||||
ServersModel.processedIndex = serverIdx
|
||||
var serverId = ServersUiController.getServerId(serverIdx)
|
||||
|
||||
// Show loading indicator with message
|
||||
PageController.showBusyIndicator(true)
|
||||
PageController.showNotificationMessage(qsTr("Installing %1 (%2/%3)...")
|
||||
.arg(containerName)
|
||||
.arg(root.currentContainerIndex + 1)
|
||||
.arg(root.containersToInstall.length))
|
||||
|
||||
// Ensure credentials are set
|
||||
InstallController.setProcessedServerCredentials(root.restoreHostname, root.restoreUsername, root.restoreSecretData)
|
||||
|
||||
// Install container
|
||||
console.log("Installing container:", containerName, "serverId:", serverId)
|
||||
ContainersModel.setProcessedContainerIndex(dockerContainer)
|
||||
InstallController.install(dockerContainer, defaultPort, defaultTransport, serverId)
|
||||
}
|
||||
|
||||
// Timer for navigating to restore mode selection page after file selection
|
||||
Timer {
|
||||
id: navigationTimer
|
||||
interval: 500
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.backupFilePath.length > 0 && root.isRestoreFromBackup) {
|
||||
console.log("Navigation timer triggered, going to restore mode page")
|
||||
console.log("Credentials available:", root.restoreHostname, root.restoreUsername, root.restoreSecretData.length > 0 ? "***" : "EMPTY")
|
||||
|
||||
// Get filename
|
||||
var fileName = SystemController.getFileNameFromPath(root.backupFilePath)
|
||||
if (!fileName || fileName === undefined || fileName.length === 0) {
|
||||
var fallbackName = root.backupFilePath.split('/').pop()
|
||||
fileName = (fallbackName && fallbackName.length > 0) ? fallbackName : qsTr("backup.tgz")
|
||||
}
|
||||
fileName = String(fileName)
|
||||
|
||||
// Extract IP address from filename
|
||||
var serverIp = ""
|
||||
var ipMatch = fileName.match(/^([\d_]+)\s*-/)
|
||||
if (ipMatch && ipMatch.length > 1) {
|
||||
serverIp = ipMatch[1].replace(/_/g, ".")
|
||||
}
|
||||
if (!serverIp || serverIp.length === 0) {
|
||||
serverIp = root.restoreHostname
|
||||
}
|
||||
|
||||
var serverName = root.restoreHostname
|
||||
if (!serverName || serverName.length === 0) {
|
||||
serverName = qsTr("RestoredServer")
|
||||
}
|
||||
|
||||
// Navigate to installation page
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
|
||||
// Immediately find StackView and navigate to restore page
|
||||
// Server already added, as we waited for onInstallServerFinished
|
||||
Qt.callLater(function() {
|
||||
var pagePath = "qrc:/ui/qml/Pages2/PageSettingsServerRestoreMode.qml"
|
||||
|
||||
// Traverse upward from root to find the containing StackView.
|
||||
// StackView has both `push` function and `depth` property.
|
||||
// This avoids a recursive downward search that causes stack overflow
|
||||
// on iOS when the component tree is large (many VPN managers).
|
||||
var stackView = root.parent
|
||||
while (stackView) {
|
||||
if (typeof stackView.push === "function" && stackView.hasOwnProperty("depth")) {
|
||||
break
|
||||
}
|
||||
stackView = stackView.parent
|
||||
}
|
||||
|
||||
if (stackView) {
|
||||
console.log("Found StackView, pushing restore mode page")
|
||||
stackView.push(pagePath, {
|
||||
"backupFilePath": root.backupFilePath,
|
||||
"backupFileName": fileName,
|
||||
"serverName": "",
|
||||
"serverIp": serverIp,
|
||||
"isFromSetupWizard": true,
|
||||
"wizardHostname": root.restoreHostname,
|
||||
"wizardUsername": root.restoreUsername,
|
||||
"wizardSecretData": root.restoreSecretData
|
||||
})
|
||||
} else {
|
||||
console.error("Could not find StackView")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SortFilterProxyModel {
|
||||
id: proxyContainersModel
|
||||
@@ -357,83 +149,6 @@ PageType {
|
||||
Keys.onReturnPressed: this.clicked()
|
||||
}
|
||||
|
||||
DividerType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
}
|
||||
|
||||
CardType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("Restore from backup")
|
||||
bodyText: qsTr("Restoration of VPN protocols, services, all server settings and users")
|
||||
|
||||
ButtonGroup.group: buttonGroup
|
||||
|
||||
onClicked: function() {
|
||||
|
||||
var filter = GC.isMobile() ? "*.gz *.tgz *.tar.gz" : "Backup files (*.tar.gz *.backup *.tgz *.gz)"
|
||||
var localPath = SystemController.getFileName(
|
||||
qsTr("Select Backup to Restore"),
|
||||
filter,
|
||||
"",
|
||||
false,
|
||||
""
|
||||
)
|
||||
|
||||
console.log("Selected file path:", localPath)
|
||||
|
||||
if (!localPath || localPath.length === 0) {
|
||||
console.log("No file selected")
|
||||
return
|
||||
}
|
||||
|
||||
// Save backup file path
|
||||
root.backupFilePath = localPath
|
||||
root.isRestoreFromBackup = true
|
||||
|
||||
// Get credentials from PageSetupWizardCredentials via StackView search
|
||||
var credentialsPage = null
|
||||
var item = root
|
||||
|
||||
// Find StackView
|
||||
while (item && !item.hasOwnProperty("depth")) {
|
||||
item = item.parent
|
||||
}
|
||||
|
||||
// If found StackView, search for PageSetupWizardCredentials in its history
|
||||
if (item && item.depth > 0) {
|
||||
for (var i = 0; i < item.depth; i++) {
|
||||
var page = item.get(i)
|
||||
if (page && page.hasOwnProperty("savedHostname")) {
|
||||
credentialsPage = page
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (credentialsPage && credentialsPage.savedHostname.length > 0) {
|
||||
root.restoreHostname = credentialsPage.savedHostname
|
||||
root.restoreUsername = credentialsPage.savedUsername
|
||||
root.restoreSecretData = credentialsPage.savedSecretData
|
||||
console.log("Got credentials from PageSetupWizardCredentials:", root.restoreHostname, root.restoreUsername)
|
||||
|
||||
// Call C++ method to prepare restore
|
||||
// It will scan backup and send readyForRestore signal
|
||||
ServersBackupController.prepareRestoreFromBackup(localPath, root.restoreHostname, root.restoreUsername, root.restoreSecretData)
|
||||
} else {
|
||||
console.log("WARNING: No credentials found")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: this.clicked()
|
||||
Keys.onReturnPressed: this.clicked()
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: continueButton
|
||||
|
||||
|
||||
@@ -29,6 +29,10 @@ PageType {
|
||||
ValueFilter {
|
||||
roleName: "isInstallationAllowed"
|
||||
value: true
|
||||
},
|
||||
ValueFilter {
|
||||
roleName: "isUnsupportedContainer"
|
||||
value: false
|
||||
}
|
||||
]
|
||||
sorters: RoleSorter {
|
||||
|
||||
@@ -382,6 +382,10 @@ PageType {
|
||||
ValueFilter {
|
||||
roleName: "isShareable"
|
||||
value: true
|
||||
},
|
||||
ValueFilter {
|
||||
roleName: "isUnsupportedContainer"
|
||||
value: false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -396,9 +400,19 @@ PageType {
|
||||
target: serverSelector
|
||||
|
||||
function onServerSelectorIndexChanged() {
|
||||
var defaultContainer = proxyContainersModel.mapFromSource(ServersUiController.serverDefaultContainer(ServersUiController.processedServerId))
|
||||
if (!proxyContainersModel.count) {
|
||||
root.shareButtonEnabled = false
|
||||
return
|
||||
}
|
||||
|
||||
var defaultContainer = proxyContainersModel.mapFromSource(
|
||||
ServersUiController.serverDefaultContainer(ServersUiController.processedServerId))
|
||||
if (defaultContainer < 0) {
|
||||
defaultContainer = 0
|
||||
}
|
||||
|
||||
containerSelectorListView.selectedIndex = defaultContainer
|
||||
containerSelectorListView.positionViewAtIndex(selectedIndex, ListView.Beginning)
|
||||
containerSelectorListView.positionViewAtIndex(defaultContainer, ListView.Beginning)
|
||||
containerSelectorListView.triggerCurrentItem()
|
||||
}
|
||||
}
|
||||
@@ -837,11 +851,10 @@ PageType {
|
||||
var noButtonFunction = function() {
|
||||
}
|
||||
|
||||
var isActiveConfigForCurrentClient = ServersUiController.isDefaultServerCurrentlyProcessed()
|
||||
&& ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex
|
||||
|
||||
if ((ConnectionController.isConnectionInProgress || ConnectionController.isConnected)
|
||||
&& isActiveConfigForCurrentClient) {
|
||||
if (ConnectionController.isRevokeBlockedDuringActiveConnection(
|
||||
ServersUiController.processedServerId,
|
||||
ServersUiController.processedContainerIndex,
|
||||
clientId)) {
|
||||
PageController.showNotificationMessage("Unable to revoke current config during active connection")
|
||||
} else {
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
|
||||
@@ -105,6 +105,19 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
objectName: "connectionControllerConnections"
|
||||
|
||||
target: ConnectionController
|
||||
|
||||
function onNoInstalledContainers() {
|
||||
PageController.setTriggeredByConnectButton(true)
|
||||
|
||||
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardEasy)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
objectName: "installControllerConnections"
|
||||
|
||||
@@ -153,11 +166,19 @@ PageType {
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
function onNoInstalledContainers() {
|
||||
PageController.setTriggeredByConnectButton(true)
|
||||
function onRemoveAllContainersFinished(finishedMessage) {
|
||||
if (tabBarStackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageDeinstalling)) {
|
||||
PageController.closePage()
|
||||
}
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardEasy)
|
||||
function onRemoveContainerFinished(finishedMessage) {
|
||||
if (tabBarStackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageDeinstalling)) {
|
||||
PageController.closePage()
|
||||
}
|
||||
PageController.closePage()
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -234,6 +234,8 @@ Window {
|
||||
DrawerType2 {
|
||||
id: privateKeyPassphraseDrawer
|
||||
|
||||
property bool isCloseByUser: false
|
||||
|
||||
anchors.fill: parent
|
||||
expandedHeight: root.height * 0.35 + PageController.safeAreaBottomMargin + PageController.imeHeight
|
||||
|
||||
@@ -253,6 +255,11 @@ Window {
|
||||
}
|
||||
|
||||
function onAboutToHide() {
|
||||
if (privateKeyPassphraseDrawer.isCloseByUser === false) {
|
||||
privateKeyPassphraseDrawer.isCloseByUser = true
|
||||
PageController.passphraseRequestDrawerClosed("")
|
||||
}
|
||||
|
||||
if (passphrase.textField.text !== "") {
|
||||
PageController.showBusyIndicator(true)
|
||||
}
|
||||
@@ -293,6 +300,7 @@ Window {
|
||||
text: qsTr("Save")
|
||||
|
||||
clickedFunc: function() {
|
||||
privateKeyPassphraseDrawer.isCloseByUser = true
|
||||
privateKeyPassphraseDrawer.closeTriggered()
|
||||
PageController.passphraseRequestDrawerClosed(passphrase.textField.text)
|
||||
}
|
||||
|
||||
@@ -106,11 +106,8 @@
|
||||
<file>Pages2/PageSettingsKillSwitch.qml</file>
|
||||
<file>Pages2/PageSettingsKillSwitchExceptions.qml</file>
|
||||
<file>Pages2/PageSettingsLogging.qml</file>
|
||||
<file>Pages2/PageSettingsServerBackup.qml</file>
|
||||
<file>Pages2/PageSettingsServerBackupRestored.qml</file>
|
||||
<file>Pages2/PageSettingsServerData.qml</file>
|
||||
<file>Pages2/PageSettingsServerInfo.qml</file>
|
||||
<file>Pages2/PageSettingsServerRestoreMode.qml</file>
|
||||
<file>Pages2/PageSettingsServerProtocol.qml</file>
|
||||
<file>Pages2/PageSettingsServerProtocols.qml</file>
|
||||
<file>Pages2/PageSettingsServerServices.qml</file>
|
||||
|
||||
Reference in New Issue
Block a user