Compare commits

..

4 Commits

Author SHA1 Message Date
lunardunno
d194e01fc0 Adding extended descriptions of new errors 2026-05-27 18:15:08 +04:00
lunardunno
7931317dec Error Codes added
Error Codes added for ServerContainerizationNotSupported & DockerServiceNotActive
2026-05-27 17:26:22 +04:00
lunardunno
66103dac5a adding message handling to install controller
Adding handling for "Containerization app is not supported" and "Service status not active" messages to the controller.
2026-05-27 17:19:42 +04:00
lunardunno
15669b3f98 Updating install_docker.sh script
Implementing a Docker service status check.
The Docker reinstall step has been removed due to the implementation of Docker service checking.
Implementing locale checking and assignment.
Implementation of execution of some actions through commands with sudo, to reduce delays caused by differences in the values ​​of the PATH variable for the root user and the user included in the sudo group.
Implementation of a verification step for the install containerization app to avoid installing unsupported podman-docker applications.
2026-05-27 16:06:54 +04:00
114 changed files with 3429 additions and 2383 deletions

View File

@@ -917,4 +917,3 @@ jobs:
run: |
echo "Pull request:" >> $GITHUB_STEP_SUMMARY
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY

View File

@@ -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.0)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES

View File

@@ -193,6 +193,10 @@ elseif(APPLE)
include(cmake/macos.cmake)
endif()
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(tests)
endif()
list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
target_link_libraries(${PROJECT} PRIVATE ${LIBS})

View File

@@ -4,7 +4,6 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QThread>
#include <QUuid>
#include "logger.h"
@@ -138,324 +137,115 @@ amnezia::ProtocolConfig XrayConfigurator::processConfigWithLocalSettings(const a
return protocolConfig;
}
ErrorCode XrayConfigurator::uploadServerConfigJson(const ServerCredentials &credentials, DockerContainer container,
const DnsSettings &dnsSettings, const QJsonObject &serverConfig) const
{
const QString updatedConfig = QJsonDocument(serverConfig).toJson();
ErrorCode errorCode = m_sshSession->uploadTextFileToContainer(
container, credentials, updatedConfig, amnezia::protocols::xray::serverConfigPath,
libssh::ScpOverwriteMode::ScpOverwriteExisting);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to upload updated config";
return errorCode;
}
const QString restartScript = QStringLiteral("sudo docker restart $CONTAINER_NAME");
errorCode = m_sshSession->runScript(
credentials,
m_sshSession->replaceVars(restartScript,
amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns,
dnsSettings.secondaryDns)));
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to restart container";
}
return errorCode;
}
ErrorCode XrayConfigurator::readRealityKeyFiles(const DockerContainer container, const ServerCredentials &credentials,
QString &outPublicKey, QString &outShortId) const
{
outPublicKey.clear();
outShortId.clear();
auto readKeyFile = [&](const QString &path, QString &out) -> ErrorCode {
for (int attempt = 0; attempt < 3; ++attempt) {
ErrorCode fileError = ErrorCode::NoError;
out = QString::fromUtf8(m_sshSession->getTextFileFromContainer(container, credentials, path, fileError));
out.replace(QLatin1Char('\n'), QString());
out.replace(QLatin1Char('\r'), QString());
if (fileError == ErrorCode::NoError && !out.isEmpty()) {
return ErrorCode::NoError;
}
if (attempt < 2) {
QThread::msleep(500);
}
}
logger.error() << "Xray readRealityKeyFiles: failed path=" << path;
return ErrorCode::XrayRealityKeysReadFailed;
};
ErrorCode errorCode = readKeyFile(QString::fromLatin1(amnezia::protocols::xray::PublicKeyPath), outPublicKey);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
return readKeyFile(QString::fromLatin1(amnezia::protocols::xray::shortidPath), outShortId);
}
QJsonObject XrayConfigurator::mergeStreamSettingsForServerInbound(const XrayServerConfig &srv,
const QJsonObject &existingStreamSettings) const
{
QJsonObject streamSettings = buildStreamSettings(srv, QString());
if (srv.security != QLatin1String("reality")) {
return streamSettings;
}
const QJsonObject newRs = streamSettings[amnezia::protocols::xray::realitySettings].toObject();
QJsonObject oldRs = existingStreamSettings[amnezia::protocols::xray::realitySettings].toObject();
QJsonObject merged = oldRs.isEmpty() ? newRs : oldRs;
const QString siteEff = srv.site.isEmpty() ? QString::fromLatin1(amnezia::protocols::xray::defaultSite) : srv.site;
const QString sniEff = srv.sni.isEmpty() ? siteEff : srv.sni;
if (newRs.contains(amnezia::protocols::xray::fingerprint)) {
merged[amnezia::protocols::xray::fingerprint] = newRs[amnezia::protocols::xray::fingerprint];
}
merged[amnezia::protocols::xray::serverNames] = QJsonArray { sniEff };
if (!merged.contains(QStringLiteral("dest"))) {
merged[QStringLiteral("dest")] = siteEff + QStringLiteral(":443");
}
streamSettings[amnezia::protocols::xray::realitySettings] = merged;
return streamSettings;
}
ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials &credentials, DockerContainer container,
ContainerConfig &containerConfig, const DnsSettings &dnsSettings,
bool appendNewClient, QString *outClientId)
{
ErrorCode errorCode = ErrorCode::NoError;
const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>();
if (!xrayCfg) {
logger.error() << "Xray applyServerSettings: missing XrayProtocolConfig";
return ErrorCode::InternalError;
}
const XrayServerConfig &srv = xrayCfg->serverConfig;
if (srv.isThirdPartyConfig) {
logger.info() << "Xray applyServerSettings: skipped (third-party/native profile)";
if (outClientId && xrayCfg->hasClientConfig()) {
*outClientId = xrayCfg->clientConfig->id;
}
return ErrorCode::NoError;
}
logger.info() << "Xray applyServerSettings: start"
<< "container=" << static_cast<int>(container) << "host=" << credentials.hostName
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port
<< "appendClient=" << appendNewClient;
QString flowValue = srv.flow;
if (flowValue.isEmpty() && srv.security == QLatin1String("reality")) {
flowValue = QStringLiteral("xtls-rprx-vision");
}
QString realityPublicKey;
QString realityShortId;
if (srv.security == QLatin1String("reality")) {
errorCode = readRealityKeyFiles(container, credentials, realityPublicKey, realityShortId);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Xray applyServerSettings: readRealityKeyFiles failed, error="
<< static_cast<int>(errorCode);
return errorCode;
}
}
QString currentConfig = m_sshSession->getTextFileFromContainer(
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Xray applyServerSettings: getTextFileFromContainer failed, error="
<< static_cast<int>(errorCode) << "path=" << amnezia::protocols::xray::serverConfigPath;
return errorCode;
}
logger.info() << "Xray applyServerSettings: read server config, bytes=" << currentConfig.size();
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
if (doc.isNull() || !doc.isObject()) {
logger.error() << "Failed to parse server config JSON";
return ErrorCode::XrayServerConfigInvalid;
}
QJsonObject serverConfig = doc.object();
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
logger.error() << "Server config missing 'inbounds' field";
return ErrorCode::XrayServerConfigInvalid;
}
QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray();
if (inbounds.isEmpty()) {
logger.error() << "Server config has empty 'inbounds' array";
return ErrorCode::XrayServerConfigInvalid;
}
QJsonObject inbound = inbounds[0].toObject();
if (!inbound.contains(amnezia::protocols::xray::settings)) {
logger.error() << "Inbound missing 'settings' field";
return ErrorCode::XrayServerConfigInvalid;
}
const QJsonObject existingStream = inbound[amnezia::protocols::xray::streamSettings].toObject();
inbound[amnezia::protocols::xray::streamSettings] = mergeStreamSettingsForServerInbound(srv, existingStream);
if (!srv.port.isEmpty()) {
inbound[amnezia::protocols::xray::port] = srv.port.toInt();
}
QJsonObject settings = inbound[amnezia::protocols::xray::settings].toObject();
if (!settings.contains(amnezia::protocols::xray::clients)) {
settings[amnezia::protocols::xray::clients] = QJsonArray {};
}
QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray();
QString clientId;
if (appendNewClient) {
clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
QJsonObject clientEntry;
clientEntry[amnezia::protocols::xray::id] = clientId;
if (!flowValue.isEmpty()) {
clientEntry[amnezia::protocols::xray::flow] = flowValue;
}
clients.append(clientEntry);
} else {
if (clients.isEmpty()) {
logger.error() << "Server config has no VLESS clients";
return ErrorCode::XrayServerNoVlessClients;
}
clientId = clients[0].toObject()[amnezia::protocols::xray::id].toString();
if (clientId.isEmpty()) {
logger.error() << "Server config VLESS client has empty id";
return ErrorCode::XrayServerNoVlessClients;
}
QJsonArray updatedClients;
for (const QJsonValue &v : clients) {
QJsonObject c = v.toObject();
if (flowValue.isEmpty()) {
c.remove(amnezia::protocols::xray::flow);
} else {
c[amnezia::protocols::xray::flow] = flowValue;
}
updatedClients.append(c);
}
clients = updatedClients;
}
settings[amnezia::protocols::xray::clients] = clients;
inbound[amnezia::protocols::xray::settings] = settings;
inbounds[0] = inbound;
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
errorCode = uploadServerConfigJson(credentials, container, dnsSettings, serverConfig);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Xray applyServerSettings: upload/restart failed, error=" << static_cast<int>(errorCode);
return errorCode;
}
logger.info() << "Xray applyServerSettings: server config uploaded and container restarted";
if (outClientId) {
*outClientId = clientId;
}
XrayProtocolConfig updated =
buildClientProtocolConfig(credentials, container, srv, clientId, errorCode, realityPublicKey, realityShortId);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Xray applyServerSettings: buildClientProtocolConfig failed, error="
<< static_cast<int>(errorCode);
return errorCode;
}
containerConfig.protocolConfig = updated;
logger.info() << "Xray applyServerSettings: done, clientId=" << clientId;
return ErrorCode::NoError;
}
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
ContainerConfig mutableConfig = containerConfig;
QString clientId;
const ErrorCode applyError =
applyServerSettingsToRemote(credentials, container, mutableConfig, dnsSettings, true, &clientId);
errorCode = applyError;
if (applyError != ErrorCode::NoError || clientId.isEmpty()) {
return QString();
}
return clientId;
}
// Generate new UUID for client
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
XrayProtocolConfig XrayConfigurator::buildClientProtocolConfig(const ServerCredentials &credentials,
DockerContainer container,
const XrayServerConfig &srv, const QString &clientId,
ErrorCode &errorCode,
const QString &prefetchedRealityPublicKey,
const QString &prefetchedRealityShortId) const
{
QString xrayPublicKey = prefetchedRealityPublicKey;
QString xrayShortId = prefetchedRealityShortId;
if (srv.security == QLatin1String("reality")) {
if (xrayPublicKey.isEmpty() || xrayShortId.isEmpty()) {
errorCode = readRealityKeyFiles(container, credentials, xrayPublicKey, xrayShortId);
if (errorCode != ErrorCode::NoError) {
return {};
}
// Get flow value from settings (default xtls-rprx-vision)
QString flowValue = "xtls-rprx-vision";
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
if (!xrayCfg->serverConfig.flow.isEmpty()) {
flowValue = xrayCfg->serverConfig.flow;
}
}
QJsonObject userObj;
userObj[amnezia::protocols::xray::id] = clientId;
userObj[amnezia::protocols::xray::encryption] = QStringLiteral("none");
if (!srv.flow.isEmpty()) {
userObj[amnezia::protocols::xray::flow] = srv.flow;
// Get current server config
QString currentConfig = m_sshSession->getTextFileFromContainer(
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to get server config file";
return "";
}
QJsonObject vnextEntry;
vnextEntry[amnezia::protocols::xray::address] = credentials.hostName;
vnextEntry[amnezia::protocols::xray::port] =
srv.port.isEmpty() ? QString(amnezia::protocols::xray::defaultPort).toInt() : srv.port.toInt();
vnextEntry[amnezia::protocols::xray::users] = QJsonArray { userObj };
QJsonObject outboundSettings;
outboundSettings[amnezia::protocols::xray::vnext] = QJsonArray { vnextEntry };
QJsonObject outbound;
outbound[QStringLiteral("protocol")] = QStringLiteral("vless");
outbound[amnezia::protocols::xray::settings] = outboundSettings;
QJsonObject streamObj = buildStreamSettings(srv, clientId);
if (srv.security == QLatin1String("reality")) {
QJsonObject rs = streamObj[amnezia::protocols::xray::realitySettings].toObject();
rs[amnezia::protocols::xray::publicKey] = xrayPublicKey;
rs[amnezia::protocols::xray::shortId] = xrayShortId;
rs[amnezia::protocols::xray::spiderX] = QString();
streamObj[amnezia::protocols::xray::realitySettings] = rs;
// Parse current config as JSON
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
if (doc.isNull() || !doc.isObject()) {
logger.error() << "Failed to parse server config JSON";
errorCode = ErrorCode::InternalError;
return "";
}
outbound[amnezia::protocols::xray::streamSettings] = streamObj;
QJsonObject serverConfig = doc.object();
QJsonObject inboundObj;
inboundObj[QStringLiteral("listen")] = amnezia::protocols::xray::defaultLocalListenAddr;
inboundObj[amnezia::protocols::xray::port] = amnezia::protocols::xray::defaultLocalProxyPort;
inboundObj[QStringLiteral("protocol")] = QStringLiteral("socks");
inboundObj[amnezia::protocols::xray::settings] = QJsonObject { { QStringLiteral("udp"), true } };
// Validate server config structure
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
logger.error() << "Server config missing 'inbounds' field";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonObject clientJson;
clientJson[QStringLiteral("log")] = QJsonObject { { QStringLiteral("loglevel"), QStringLiteral("error") } };
clientJson[amnezia::protocols::xray::inbounds] = QJsonArray { inboundObj };
clientJson[amnezia::protocols::xray::outbounds] = QJsonArray { outbound };
QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray();
if (inbounds.isEmpty()) {
logger.error() << "Server config has empty 'inbounds' array";
errorCode = ErrorCode::InternalError;
return "";
}
const QString config = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
QJsonObject inbound = inbounds[0].toObject();
if (!inbound.contains(amnezia::protocols::xray::settings)) {
logger.error() << "Inbound missing 'settings' field";
errorCode = ErrorCode::InternalError;
return "";
}
XrayProtocolConfig protocolConfig;
protocolConfig.serverConfig = srv;
QJsonObject settings = inbound[amnezia::protocols::xray::settings].toObject();
if (!settings.contains(amnezia::protocols::xray::clients)) {
logger.error() << "Settings missing 'clients' field";
errorCode = ErrorCode::InternalError;
return "";
}
XrayClientConfig clientConfig;
clientConfig.nativeConfig = config;
clientConfig.localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort);
clientConfig.id = clientId;
protocolConfig.setClientConfig(clientConfig);
QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray();
return protocolConfig;
// Create configuration for new client
QJsonObject clientConfig {
{amnezia::protocols::xray::id, clientId},
};
clientConfig[amnezia::protocols::xray::id] = clientId;
if (!flowValue.isEmpty()) {
clientConfig[amnezia::protocols::xray::flow] = flowValue;
}
clients.append(clientConfig);
// Update config
settings[amnezia::protocols::xray::clients] = clients;
inbound[amnezia::protocols::xray::settings] = settings;
inbounds[0] = inbound;
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
// Save updated config to server
QString updatedConfig = QJsonDocument(serverConfig).toJson();
errorCode = m_sshSession->uploadTextFileToContainer(
container,
credentials,
updatedConfig,
amnezia::protocols::xray::serverConfigPath,
libssh::ScpOverwriteMode::ScpOverwriteExisting
);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to upload updated config";
return "";
}
// Restart container
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
errorCode = m_sshSession->runScript(
credentials,
m_sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns))
);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to restart container";
return "";
}
return clientId;
}
QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, const QString &clientId) const
@@ -629,13 +419,6 @@ ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentia
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
if (xrayCfg->serverConfig.isThirdPartyConfig && xrayCfg->hasClientConfig()) {
logger.info() << "Xray createConfig: returning existing third-party client config without server SSH";
return *xrayCfg;
}
}
const XrayServerConfig *serverConfig = nullptr;
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
serverConfig = &xrayCfg->serverConfig;
@@ -658,5 +441,93 @@ ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentia
return XrayProtocolConfig{};
}
return buildClientProtocolConfig(credentials, container, srv, xrayClientId, errorCode);
// Fetch server keys (Reality only)
QString xrayPublicKey;
QString xrayShortId;
if (srv.security == "reality") {
xrayPublicKey = m_sshSession->getTextFileFromContainer(container, credentials,
amnezia::protocols::xray::PublicKeyPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
logger.error() << "Failed to get public key";
if (errorCode == ErrorCode::NoError) {
errorCode = ErrorCode::InternalError;
}
return XrayProtocolConfig{};
}
xrayPublicKey.replace("\n", "");
xrayShortId = m_sshSession->getTextFileFromContainer(container, credentials,
amnezia::protocols::xray::shortidPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
logger.error() << "Failed to get short ID";
if (errorCode == ErrorCode::NoError) {
errorCode = ErrorCode::InternalError;
}
return XrayProtocolConfig{};
}
xrayShortId.replace("\n", "");
}
// Build outbound
QJsonObject userObj;
userObj[amnezia::protocols::xray::id] = xrayClientId;
userObj[amnezia::protocols::xray::encryption] = "none";
if (!srv.flow.isEmpty()) {
userObj[amnezia::protocols::xray::flow] = srv.flow;
}
QJsonObject vnextEntry;
vnextEntry[amnezia::protocols::xray::address] = credentials.hostName;
vnextEntry[amnezia::protocols::xray::port] = srv.port.toInt();
vnextEntry[amnezia::protocols::xray::users] = QJsonArray { userObj };
QJsonObject outboundSettings;
outboundSettings[amnezia::protocols::xray::vnext] = QJsonArray { vnextEntry };
QJsonObject outbound;
outbound["protocol"] = "vless";
outbound[amnezia::protocols::xray::settings] = outboundSettings;
// Build streamSettings
QJsonObject streamObj = buildStreamSettings(srv, xrayClientId);
// Inject Reality keys
if (srv.security == "reality") {
QJsonObject rs = streamObj[amnezia::protocols::xray::realitySettings].toObject();
rs[amnezia::protocols::xray::publicKey] = xrayPublicKey;
rs[amnezia::protocols::xray::shortId] = xrayShortId;
rs[amnezia::protocols::xray::spiderX] = "";
streamObj[amnezia::protocols::xray::realitySettings] = rs;
}
outbound[amnezia::protocols::xray::streamSettings] = streamObj;
// Build full client config
QJsonObject inboundObj;
inboundObj["listen"] = amnezia::protocols::xray::defaultLocalListenAddr;
inboundObj[amnezia::protocols::xray::port] = amnezia::protocols::xray::defaultLocalProxyPort;
inboundObj["protocol"] = "socks";
inboundObj[amnezia::protocols::xray::settings] = QJsonObject { { "udp", true } };
QJsonObject clientJson;
clientJson["log"] = QJsonObject { { "loglevel", "error" } };
clientJson[amnezia::protocols::xray::inbounds] = QJsonArray { inboundObj };
clientJson[amnezia::protocols::xray::outbounds] = QJsonArray { outbound };
QString config = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
// Return
XrayProtocolConfig protocolConfig;
protocolConfig.serverConfig = srv;
XrayClientConfig clientConfig;
clientConfig.nativeConfig = config;
qDebug() << "config:" << config;
clientConfig.localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort);
clientConfig.id = xrayClientId;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
}

View File

@@ -23,37 +23,12 @@ public:
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
amnezia::ProtocolConfig protocolConfig) override;
amnezia::ErrorCode applyServerSettingsToRemote(const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container,
amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
bool appendNewClient,
QString *outClientId = nullptr);
private:
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode);
amnezia::ErrorCode uploadServerConfigJson(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::DnsSettings &dnsSettings, const QJsonObject &serverConfig) const;
amnezia::XrayProtocolConfig buildClientProtocolConfig(const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container,
const amnezia::XrayServerConfig &srv,
const QString &clientId,
amnezia::ErrorCode &errorCode,
const QString &prefetchedRealityPublicKey = {},
const QString &prefetchedRealityShortId = {}) const;
amnezia::ErrorCode readRealityKeyFiles(amnezia::DockerContainer container,
const amnezia::ServerCredentials &credentials,
QString &outPublicKey,
QString &outShortId) const;
QJsonObject mergeStreamSettingsForServerInbound(const amnezia::XrayServerConfig &srv,
const QJsonObject &existingStreamSettings) const;
// Builds the native xray "streamSettings" JSON object from XrayServerConfig
QJsonObject buildStreamSettings(const amnezia::XrayServerConfig &srv,
const QString &clientId) const;
};

View File

@@ -5,7 +5,6 @@
#include <QEventLoop>
#include <QFutureWatcher>
#include <QJsonDocument>
#include <QJsonObject>
#include <QPromise>
#include <QSet>
#include <QSysInfo>
@@ -217,8 +216,7 @@ ErrorCode SubscriptionController::executeRequest(const QString &endpoint, const
}
ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const ProtocolData &protocolData,
CaptchaInfo &captchaInfo)
const QString &serviceProtocol, const ProtocolData &protocolData)
{
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
@@ -235,19 +233,6 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
if (errorCode == ErrorCode::ApiCaptchaRequiredError) {
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
captchaInfo.captchaId = jsonObj.value("captcha_id").toString();
captchaInfo.captchaImageBase64 = jsonObj.value("captcha_image").toString();
captchaInfo.hint = jsonObj.value("hint").toString();
captchaInfo.isRequired = true;
}
return errorCode;
}
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
@@ -257,9 +242,9 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody);
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
return ErrorCode::InternalError;
}
@@ -971,74 +956,3 @@ QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(const
return promise->future();
}
ErrorCode SubscriptionController::resolveImportServiceCaptcha(const QString &userCountryCode,
const QString &serviceType,
const QString &serviceProtocol,
const ProtocolData &protocolData,
const QString &captchaId,
const QString &captchaSolution,
CaptchaInfo *retryCaptchaOut)
{
GatewayRequestData gatewayRequestData{QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
userCountryCode,
"",
serviceType,
serviceProtocol,
QJsonObject()};
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
apiPayload["captcha_id"] = captchaId;
QString normalizedSolution;
normalizedSolution.reserve(captchaSolution.size());
for (const QChar &ch : captchaSolution) {
const ushort u = ch.unicode();
if (u >= '0' && u <= '9') {
normalizedSolution += ch;
} else if (u >= 0xFF10 && u <= 0xFF19) {
normalizedSolution += QChar(static_cast<char16_t>(u - 0xFF10 + '0'));
}
}
apiPayload["captcha_solution"] = normalizedSolution.isEmpty() ? captchaSolution.trimmed() : normalizedSolution;
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
if (errorCode != ErrorCode::NoError) {
if (retryCaptchaOut
&& (errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|| errorCode == ErrorCode::ApiCaptchaRequiredError)) {
const QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
const QJsonObject jsonObj = jsonDoc.object();
if (jsonObj.contains(QStringLiteral("captcha_id")) && jsonObj.contains(QStringLiteral("captcha_image"))) {
retryCaptchaOut->captchaId = jsonObj.value(QStringLiteral("captcha_id")).toString();
retryCaptchaOut->captchaImageBase64 = jsonObj.value(QStringLiteral("captcha_image")).toString();
retryCaptchaOut->hint = jsonObj.value(QStringLiteral("hint")).toString();
retryCaptchaOut->isRequired = true;
}
}
}
return errorCode;
}
QJsonObject serverConfigJson;
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody);
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
return ErrorCode::InternalError;
}
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(serverConfigJson);
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
return ErrorCode::NoError;
}

View File

@@ -42,13 +42,6 @@ public:
QJsonObject toJsonObject() const;
};
struct CaptchaInfo {
QString captchaId;
QString captchaImageBase64;
QString hint;
bool isRequired = false;
};
explicit SubscriptionController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository);
@@ -56,8 +49,7 @@ public:
void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload);
ErrorCode importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const ProtocolData &protocolData,
CaptchaInfo &captchaInfo);
const QString &serviceProtocol, const ProtocolData &protocolData);
ErrorCode importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const QString &email);
@@ -106,11 +98,6 @@ public:
AppStoreRestoreResult processAppStoreRestore(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol);
ErrorCode resolveImportServiceCaptcha(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const ProtocolData &protocolData,
const QString &captchaId, const QString &captchaSolution,
CaptchaInfo *retryCaptchaOut = nullptr);
private:
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
bool isApiKeyExpired(const QString &serverId) const;

View File

@@ -6,7 +6,9 @@
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/utilities.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/serverConfigUtils.h"
#include "version.h"
#include "core/utils/containerEnum.h"
@@ -65,15 +67,13 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
bool isApiConfig = false;
const auto kind = m_serversRepository->serverKind(serverId);
const QString primaryDns = m_appSettingsRepository->primaryDns();
const QString secondaryDns = m_appSettingsRepository->secondaryDns();
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = cfg->getDnsPair(m_appSettingsRepository->useAmneziaDns(), primaryDns, secondaryDns);
dns = { cfg->dns1, cfg->dns2 };
hostName = cfg->hostName;
description = cfg->description;
break;
@@ -83,7 +83,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = cfg->getDnsPair(primaryDns, secondaryDns);
dns = { cfg->dns1, cfg->dns2 };
hostName = cfg->hostName;
description = cfg->description;
break;
@@ -93,7 +93,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = cfg->getDnsPair(primaryDns, secondaryDns);
dns = { cfg->dns1, cfg->dns2 };
hostName = cfg->hostName;
description = cfg->description;
break;
@@ -105,7 +105,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = cfg->getDnsPair(primaryDns, secondaryDns);
dns = { cfg->dns1, cfg->dns2 };
hostName = cfg->hostName;
description = cfg->description;
configVersion = serverConfigUtils::ConfigSource::AmneziaGateway;
@@ -123,6 +123,16 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) {
if (m_appSettingsRepository->useAmneziaDns()) {
dns.first = protocols::dns::amneziaDnsIp;
} else {
dns.first = m_appSettingsRepository->primaryDns();
}
}
if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) {
dns.second = m_appSettingsRepository->secondaryDns();
}
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
containerConfigModel, container);

View File

@@ -178,8 +178,7 @@ void CoreController::initControllers()
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel,
#endif
m_sftpConfigModel, m_socks5ConfigModel, m_mtProxyConfigModel, m_telemtConfigModel,
m_connectionController, this);
m_sftpConfigModel, m_socks5ConfigModel, m_mtProxyConfigModel, m_telemtConfigModel, this);
setQmlContextProperty("InstallController", m_installUiController);
m_importController = new ImportUiController(m_importCoreController, this);
@@ -213,16 +212,15 @@ void CoreController::initControllers()
setQmlContextProperty("SystemController", m_systemController);
m_networkReachabilityController = new NetworkReachabilityController(this);
setQmlContextProperty("NetworkReachabilityController", m_networkReachabilityController);
setQmlContextProperty("NetworkReachability", m_networkReachabilityController);
m_engine->rootContext()->setContextProperty("NetworkReachabilityController", m_networkReachabilityController);
m_engine->rootContext()->setContextProperty("NetworkReachability", m_networkReachabilityController);
m_servicesCatalogUiController = new ServicesCatalogUiController(m_servicesCatalogController, m_apiServicesModel, this);
setQmlContextProperty("ServicesCatalogUiController", m_servicesCatalogUiController);
m_subscriptionUiController = new SubscriptionUiController(m_serversController, m_apiServicesModel, m_servicesCatalogController, m_subscriptionController,
m_apiSubscriptionPlansModel, m_apiBenefitsModel, m_apiAccountInfoModel,
m_apiCountryModel, m_apiDevicesModel, m_settingsController,
m_connectionController, this);
m_apiCountryModel, m_apiDevicesModel, m_settingsController, this);
setQmlContextProperty("SubscriptionUiController", m_subscriptionUiController);
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
@@ -344,6 +342,9 @@ void CoreController::openConnectionByIndex(int serverIndex)
if (serverId.isEmpty()) {
return;
}
if (m_serversModel) {
m_serversModel->setProcessedServerIndex(serverIndex);
}
if (m_serversController) {
m_serversController->setDefaultServer(serverId);
}

View File

@@ -82,11 +82,33 @@
#endif
class CoreSignalHandlers;
class TestMultipleImports;
class TestAdminSelfHostedExport;
class TestServerEdit;
class TestDefaultServerChange;
class TestServerEdgeCases;
class TestSignalOrder;
class TestServersModelSync;
class TestComplexOperations;
class TestSettingsSignals;
class TestUiServersModelAndController;
class TestSelfHostedServerSetup;
class CoreController : public QObject
{
Q_OBJECT
friend class CoreSignalHandlers;
friend class TestMultipleImports;
friend class TestAdminSelfHostedExport;
friend class TestServerEdit;
friend class TestDefaultServerChange;
friend class TestServerEdgeCases;
friend class TestSignalOrder;
friend class TestServersModelSync;
friend class TestComplexOperations;
friend class TestSettingsSignals;
friend class TestUiServersModelAndController;
friend class TestSelfHostedServerSetup;
public:
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
@@ -103,36 +125,6 @@ signals:
void translationsUpdated();
void websiteUrlChanged(const QString &newUrl);
protected:
SecureServersRepository* serversRepositoryProtected() const { return m_serversRepository; }
SecureAppSettingsRepository* appSettingsRepositoryProtected() const { return m_appSettingsRepository; }
ServersModel* serversModelProtected() const { return m_serversModel; }
ContainersModel* containersModelProtected() const { return m_containersModel; }
ApiServicesModel* apiServicesModelProtected() const { return m_apiServicesModel; }
NewsModel* newsModelProtected() const { return m_newsModel; }
AllowedDnsModel* allowedDnsModelProtected() const { return m_allowedDnsModel; }
AppSplitTunnelingModel* appSplitTunnelingModelProtected() const { return m_appSplitTunnelingModel; }
IpSplitTunnelingModel* ipSplitTunnelingModelProtected() const { return m_ipSplitTunnelingModel; }
LanguageModel* languageModelProtected() const { return m_languageModel; }
InstallUiController* installUiControllerProtected() const { return m_installUiController; }
ImportController* importCoreControllerProtected() const { return m_importCoreController; }
ExportController* exportControllerProtected() const { return m_exportController; }
InstallController* installControllerProtected() const { return m_installController; }
ServersController* serversControllerProtected() const { return m_serversController; }
SettingsUiController* settingsUiControllerProtected() const { return m_settingsUiController; }
SettingsController* settingsControllerProtected() const { return m_settingsController; }
AllowedDnsUiController* allowedDnsUiControllerProtected() const { return m_allowedDnsUiController; }
AllowedDnsController* allowedDnsControllerProtected() const { return m_allowedDnsController; }
LanguageUiController* languageUiControllerProtected() const { return m_languageUiController; }
IpSplitTunnelingController* ipSplitTunnelingControllerProtected() const { return m_ipSplitTunnelingController; }
IpSplitTunnelingUiController* ipSplitTunnelingUiControllerProtected() const { return m_ipSplitTunnelingUiController; }
AppSplitTunnelingController* appSplitTunnelingControllerProtected() const { return m_appSplitTunnelingController; }
AppSplitTunnelingUiController* appSplitTunnelingUiControllerProtected() const { return m_appSplitTunnelingUiController; }
ServersUiController* serversUiControllerProtected() const { return m_serversUiController; }
ServicesCatalogUiController* servicesCatalogUiControllerProtected() const { return m_servicesCatalogUiController; }
ApiNewsUiController* apiNewsUiControllerProtected() const { return m_apiNewsUiController; }
private:
void initRepositories();
void initCoreControllers();

View File

@@ -125,9 +125,9 @@ void CoreSignalHandlers::initInstallControllerHandler()
{
connect(m_coreController->m_installController, &InstallController::serverIsBusy, m_coreController->m_installUiController, &InstallUiController::serverIsBusy);
connect(m_coreController->m_installUiController, &InstallUiController::cancelInstallation, m_coreController->m_installController, &InstallController::cancelInstallation);
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIdChanged,
m_coreController->m_installUiController, [this](const QString &serverId) {
if (!serverId.isEmpty()) {
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIndexChanged,
m_coreController->m_installUiController, [this](int serverIndex) {
if (serverIndex >= 0) {
m_coreController->m_installUiController->clearProcessedServerCredentials();
}
});

View File

@@ -30,8 +30,6 @@ namespace
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
constexpr QLatin1String errorResponsePattern3("Account not found.");
constexpr QLatin1String errorResponsePatternQrSessionNotFound("QR session not found");
constexpr QLatin1String errorResponsePatternSessionNotFound("Session not found");
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
@@ -39,7 +37,6 @@ namespace
constexpr int httpStatusCodeConflict = 409;
constexpr int httpStatusCodeNotImplemented = 501;
constexpr int httpStatusCodePaymentRequired = 402;
constexpr int httpStatusCodeRequestTimeout = 408;
constexpr int httpStatusCodeUnprocessableEntity = 422;
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
@@ -209,9 +206,8 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
}
responseBody = decryptionResult.decryptedBody;
const auto errorCode =
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, responseBody);
auto errorCode =
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
if (errorCode) {
return errorCode;
}
@@ -221,6 +217,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
return ErrorCode::ApiConfigDecryptionError;
}
responseBody = decryptionResult.decryptedBody;
return ErrorCode::NoError;
}
@@ -259,7 +256,7 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode,
decryptionResult.decryptedBody);
if (errorCode) {
promise->addResult(qMakePair(errorCode, decryptionResult.decryptedBody));
promise->addResult(qMakePair(errorCode, QByteArray()));
promise->finish();
return;
}
@@ -462,19 +459,15 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
qDebug() << "the response contains an html tag";
return true;
}
if (apiHttpStatus == httpStatusCodeRequestTimeout) {
return false;
}
if (apiHttpStatus == httpStatusCodeNotFound) {
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|| responseBody.contains(errorResponsePattern3) || responseBody.contains(errorResponsePatternQrSessionNotFound)
|| responseBody.contains(errorResponsePatternSessionNotFound)) {
|| responseBody.contains(errorResponsePattern3)) {
return false;
} else {
qDebug() << replyError;
return true;
}
}
}
if (apiHttpStatus == httpStatusCodeNotImplemented) {
if (responseBody.contains(updateRequestResponsePattern)) {
return false;

View File

@@ -20,7 +20,6 @@
#include "core/installers/sftpInstaller.h"
#include "core/installers/socks5Installer.h"
#include "core/installers/mtProxyInstaller.h"
#include "core/configurators/xrayConfigurator.h"
#include "core/installers/telemtInstaller.h"
#include "core/installers/torInstaller.h"
#include "core/installers/wireguardInstaller.h"
@@ -120,14 +119,9 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return e;
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
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));
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
amnezia::genBaseVars(credentials, container, QString(), QString())));
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
qDebug().noquote() << "buildContainerWorker start";
@@ -187,16 +181,6 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
bool xrayServerSettingsChanged = false;
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
const auto *oldXrayConfig = oldConfig.getXrayProtocolConfig();
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
if (oldXrayConfig && newXrayConfig) {
xrayServerSettingsChanged =
!oldXrayConfig->serverConfig.hasEqualServerSettings(newXrayConfig->serverConfig);
}
}
ErrorCode errorCode = ErrorCode::NoError;
if (reinstallRequired) {
errorCode = setupContainer(credentials, container, newConfig, true);
@@ -207,21 +191,6 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
}
}
const bool skipXrayInboundSync =
newConfig.getXrayProtocolConfig() && newConfig.getXrayProtocolConfig()->serverConfig.isThirdPartyConfig;
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="
<< reinstallRequired;
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
if (errorCode != ErrorCode::NoError) {
qDebug() << "InstallController::updateContainer Xray inbound sync failed, error="
<< static_cast<int>(errorCode);
}
}
if (errorCode == ErrorCode::NoError) {
if (container == DockerContainer::MtProxy) {
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
@@ -247,9 +216,9 @@ void InstallController::clearCachedProfile(const QString &serverId, DockerContai
return;
}
adminConfig->clearCachedClientProfile(container);
const ContainerConfig containerConfigModel = adminConfig->containerConfig(container);
adminConfig->clearCachedClientProfile(container);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
emit clientRevocationRequested(serverId, containerConfigModel, container);
@@ -669,19 +638,12 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
}
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
const auto *oldXrayConfig = oldConfig.getXrayProtocolConfig();
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
const auto* oldXrayConfig = oldConfig.getXrayProtocolConfig();
const auto* newXrayConfig = newConfig.getXrayProtocolConfig();
if (oldXrayConfig && newXrayConfig) {
const QString oldPort = oldXrayConfig->serverConfig.port.isEmpty()
? QString(protocols::xray::defaultPort)
: oldXrayConfig->serverConfig.port;
const QString newPort = newXrayConfig->serverConfig.port.isEmpty()
? QString(protocols::xray::defaultPort)
: newXrayConfig->serverConfig.port;
if (oldPort != newPort) {
if (oldXrayConfig->serverConfig.port != newXrayConfig->serverConfig.port)
return true;
}
}
}
@@ -809,8 +771,12 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("Containerization app is not supported"))
return ErrorCode::ServerContainerizationNotSupported;
if (stdOut.contains("command not found"))
return ErrorCode::ServerDockerFailedError;
if (stdOut.contains("Service status not active"))
return ErrorCode::DockerServiceNotActive;
return error;
}
@@ -980,12 +946,10 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
return ErrorCode::InternalError;
}
SshSession sshSession(this);
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));
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
amnezia::genBaseVars(credentials, container, QString(), QString())));
if (errorCode == ErrorCode::NoError) {
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;

View File

@@ -217,11 +217,6 @@ void SettingsController::toggleAutoStart(bool enable)
bool SettingsController::isStartMinimizedEnabled() const
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
if (!isAutoStartEnabled()) {
return false;
}
#endif
return m_appSettingsRepository->isStartMinimized();
}

View File

@@ -1,17 +1,15 @@
#include "socks5Installer.h"
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/utilities.h"
#include <QRegularExpression>
using namespace amnezia;
using namespace ProtocolUtils;
@@ -35,29 +33,10 @@ ContainerConfig Socks5Installer::generateConfig(DockerContainer container, int p
ErrorCode Socks5Installer::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, ContainerConfig &config)
{
if (container != DockerContainer::Socks5Proxy || !sshSession) {
return ErrorCode::NoError;
}
Socks5ProxyProtocolConfig *socks5Config = config.getSocks5ProxyProtocolConfig();
if (!socks5Config) {
return ErrorCode::NoError;
}
ErrorCode readError = ErrorCode::NoError;
const QByteArray configRaw = sshSession->getTextFileFromContainer(
container, credentials, QString::fromUtf8(protocols::socks5Proxy::proxyConfigPath), readError);
if (readError != ErrorCode::NoError || configRaw.trimmed().isEmpty()) {
return ErrorCode::NoError;
}
const QString proxyConfig = QString::fromUtf8(configRaw);
static const QRegularExpression usernameAndPasswordRegExp(QStringLiteral("users (\\w+):CL:(\\w+)"));
const QRegularExpressionMatch usernameAndPasswordMatch = usernameAndPasswordRegExp.match(proxyConfig);
if (usernameAndPasswordMatch.hasMatch()) {
socks5Config->userName = usernameAndPasswordMatch.captured(1);
socks5Config->password = usernameAndPasswordMatch.captured(2);
}
Q_UNUSED(container);
Q_UNUSED(credentials);
Q_UNUSED(sshSession);
Q_UNUSED(config);
return ErrorCode::NoError;
}

View File

@@ -13,7 +13,6 @@
#include "core/utils/api/apiUtils.h"
#include "core/models/api/apiConfig.h"
#include "core/models/api/authData.h"
#include "core/utils/networkUtilities.h"
namespace amnezia
{
@@ -68,20 +67,6 @@ ContainerConfig ApiV2ServerConfig::containerConfig(DockerContainer container) co
return containers.value(container);
}
QPair<QString, QString> ApiV2ServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
{
QString d1 = dns1;
QString d2 = dns2;
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
d1 = primaryDns;
}
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
d2 = secondaryDns;
}
return { d1, d2 };
}
QJsonObject ApiV2ServerConfig::toJson() const
{
QJsonObject obj;
@@ -95,6 +80,9 @@ QJsonObject ApiV2ServerConfig::toJson() const
if (!description.isEmpty()) {
obj[configKey::description] = description;
}
if (!displayName.isEmpty()) {
obj[configKey::displayName] = displayName;
}
obj[configKey::configVersion] = configVersion;
@@ -146,6 +134,7 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
config.name = json.value(configKey::name).toString();
config.nameOverriddenByUser = json.value(configKey::nameOverriddenByUser).toBool(false);
config.description = json.value(configKey::description).toString();
config.displayName = json.value(configKey::displayName).toString();
config.configVersion = json.value(configKey::configVersion).toInt(2);
config.hostName = json.value(configKey::hostName).toString();

View File

@@ -3,7 +3,6 @@
#include <QJsonObject>
#include <QMap>
#include <QPair>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
@@ -44,9 +43,6 @@ struct ApiV2ServerConfig {
bool isExternalPremium() const;
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const;
static ApiV2ServerConfig fromJson(const QJsonObject& json);
};

View File

@@ -23,7 +23,9 @@ LegacyApiServerConfig LegacyApiServerConfig::fromJson(const QJsonObject &json)
{
LegacyApiServerConfig config;
config.name = json.value(configKey::name).toString();
config.description = json.value(configKey::description).toString();
config.displayName = json.value(configKey::displayName).toString();
config.hostName = json.value(configKey::hostName).toString();
config.crc = json.value(configKey::crc).toInt(0);

View File

@@ -108,114 +108,35 @@ QJsonObject XrayXhttpConfig::toJson() const
return obj;
}
namespace
{
XrayXhttpConfig clearedXhttpConfig()
{
XrayXhttpConfig c;
c.mode = QString();
c.host = QString();
c.path = QString();
c.headersTemplate = QString();
c.uplinkMethod = QString();
c.disableGrpc = false;
c.disableSse = false;
c.sessionPlacement = QString();
c.sessionKey = QString();
c.seqPlacement = QString();
c.seqKey = QString();
c.uplinkDataPlacement = QString();
c.uplinkDataKey = QString();
c.uplinkChunkSize = QString();
c.scMaxBufferedPosts = QString();
c.scMaxEachPostBytesMin = QString();
c.scMaxEachPostBytesMax = QString();
c.scMinPostsIntervalMsMin = QString();
c.scMinPostsIntervalMsMax = QString();
c.scStreamUpServerSecsMin = QString();
c.scStreamUpServerSecsMax = QString();
return c;
}
} // namespace
XrayXhttpConfig XrayXhttpConfig::fromJson(const QJsonObject &json)
{
if (json.isEmpty()) {
return clearedXhttpConfig();
}
XrayXhttpConfig c;
c.mode = json.value(configKey::xhttpMode).toString(protocols::xray::defaultXhttpMode);
c.host = json.value(configKey::xhttpHost).toString(protocols::xray::defaultSite);
c.path = json.value(configKey::xhttpPath).toString();
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString(protocols::xray::defaultXhttpHeadersTemplate);
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString(protocols::xray::defaultXhttpUplinkMethod);
c.disableGrpc = json.value(configKey::xhttpDisableGrpc).toBool(true);
c.disableSse = json.value(configKey::xhttpDisableSse).toBool(true);
XrayXhttpConfig c = clearedXhttpConfig();
c.sessionPlacement = json.value(configKey::xhttpSessionPlacement).toString(protocols::xray::defaultXhttpSessionPlacement);
c.sessionKey = json.value(configKey::xhttpSessionKey).toString();
c.seqPlacement = json.value(configKey::xhttpSeqPlacement).toString(protocols::xray::defaultXhttpSessionPlacement);
c.seqKey = json.value(configKey::xhttpSeqKey).toString();
c.uplinkDataPlacement = json.value(configKey::xhttpUplinkDataPlacement).toString(protocols::xray::defaultXhttpUplinkDataPlacement);
c.uplinkDataKey = json.value(configKey::xhttpUplinkDataKey).toString();
if (json.contains(configKey::xhttpMode)) {
c.mode = json.value(configKey::xhttpMode).toString();
}
if (json.contains(configKey::xhttpHost)) {
c.host = json.value(configKey::xhttpHost).toString();
}
if (json.contains(configKey::xhttpPath)) {
c.path = json.value(configKey::xhttpPath).toString();
}
if (json.contains(configKey::xhttpHeadersTemplate)) {
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString();
}
if (json.contains(configKey::xhttpUplinkMethod)) {
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString();
}
if (json.contains(configKey::xhttpDisableGrpc)) {
c.disableGrpc = json.value(configKey::xhttpDisableGrpc).toBool();
}
if (json.contains(configKey::xhttpDisableSse)) {
c.disableSse = json.value(configKey::xhttpDisableSse).toBool();
}
if (json.contains(configKey::xhttpSessionPlacement)) {
c.sessionPlacement = json.value(configKey::xhttpSessionPlacement).toString();
}
if (json.contains(configKey::xhttpSessionKey)) {
c.sessionKey = json.value(configKey::xhttpSessionKey).toString();
}
if (json.contains(configKey::xhttpSeqPlacement)) {
c.seqPlacement = json.value(configKey::xhttpSeqPlacement).toString();
}
if (json.contains(configKey::xhttpSeqKey)) {
c.seqKey = json.value(configKey::xhttpSeqKey).toString();
}
if (json.contains(configKey::xhttpUplinkDataPlacement)) {
c.uplinkDataPlacement = json.value(configKey::xhttpUplinkDataPlacement).toString();
}
if (json.contains(configKey::xhttpUplinkDataKey)) {
c.uplinkDataKey = json.value(configKey::xhttpUplinkDataKey).toString();
}
if (json.contains(configKey::xhttpUplinkChunkSize)) {
c.uplinkChunkSize = json.value(configKey::xhttpUplinkChunkSize).toString();
}
if (json.contains(configKey::xhttpScMaxBufferedPosts)) {
c.scMaxBufferedPosts = json.value(configKey::xhttpScMaxBufferedPosts).toString();
}
if (json.contains(configKey::xhttpScMaxEachPostBytesMin)) {
c.scMaxEachPostBytesMin = json.value(configKey::xhttpScMaxEachPostBytesMin).toString();
}
if (json.contains(configKey::xhttpScMaxEachPostBytesMax)) {
c.scMaxEachPostBytesMax = json.value(configKey::xhttpScMaxEachPostBytesMax).toString();
}
if (json.contains(configKey::xhttpScMinPostsIntervalMsMin)) {
c.scMinPostsIntervalMsMin = json.value(configKey::xhttpScMinPostsIntervalMsMin).toString();
}
if (json.contains(configKey::xhttpScMinPostsIntervalMsMax)) {
c.scMinPostsIntervalMsMax = json.value(configKey::xhttpScMinPostsIntervalMsMax).toString();
}
if (json.contains(configKey::xhttpScStreamUpServerSecsMin)) {
c.scStreamUpServerSecsMin = json.value(configKey::xhttpScStreamUpServerSecsMin).toString();
}
if (json.contains(configKey::xhttpScStreamUpServerSecsMax)) {
c.scStreamUpServerSecsMax = json.value(configKey::xhttpScStreamUpServerSecsMax).toString();
}
c.uplinkChunkSize = json.value(configKey::xhttpUplinkChunkSize).toString("0");
c.scMaxBufferedPosts = json.value(configKey::xhttpScMaxBufferedPosts).toString();
c.scMaxEachPostBytesMin = json.value(configKey::xhttpScMaxEachPostBytesMin).toString("1");
c.scMaxEachPostBytesMax = json.value(configKey::xhttpScMaxEachPostBytesMax).toString("100");
c.scMinPostsIntervalMsMin = json.value(configKey::xhttpScMinPostsIntervalMsMin).toString("100");
c.scMinPostsIntervalMsMax = json.value(configKey::xhttpScMinPostsIntervalMsMax).toString("800");
c.scStreamUpServerSecsMin = json.value(configKey::xhttpScStreamUpServerSecsMin).toString("1");
c.scStreamUpServerSecsMax = json.value(configKey::xhttpScStreamUpServerSecsMax).toString("100");
if (json.contains(QLatin1String("xPadding"))) {
c.xPadding = XrayXPaddingConfig::fromJson(json.value(QLatin1String("xPadding")).toObject());
}
if (json.contains(QLatin1String("xmux"))) {
c.xmux = XrayXmuxConfig::fromJson(json.value(QLatin1String("xmux")).toObject());
}
c.xPadding = XrayXPaddingConfig::fromJson(json.value("xPadding").toObject());
c.xmux = XrayXmuxConfig::fromJson(json.value("xmux").toObject());
return c;
}
@@ -235,27 +156,12 @@ QJsonObject XrayMkcpConfig::toJson() const
XrayMkcpConfig XrayMkcpConfig::fromJson(const QJsonObject &json)
{
XrayMkcpConfig c;
if (json.isEmpty()) {
return c;
}
if (json.contains(configKey::mkcpTti)) {
c.tti = json.value(configKey::mkcpTti).toString();
}
if (json.contains(configKey::mkcpUplinkCapacity)) {
c.uplinkCapacity = json.value(configKey::mkcpUplinkCapacity).toString();
}
if (json.contains(configKey::mkcpDownlinkCapacity)) {
c.downlinkCapacity = json.value(configKey::mkcpDownlinkCapacity).toString();
}
if (json.contains(configKey::mkcpReadBufferSize)) {
c.readBufferSize = json.value(configKey::mkcpReadBufferSize).toString();
}
if (json.contains(configKey::mkcpWriteBufferSize)) {
c.writeBufferSize = json.value(configKey::mkcpWriteBufferSize).toString();
}
if (json.contains(configKey::mkcpCongestion)) {
c.congestion = json.value(configKey::mkcpCongestion).toBool();
}
c.tti = json.value(configKey::mkcpTti).toString();
c.uplinkCapacity = json.value(configKey::mkcpUplinkCapacity).toString();
c.downlinkCapacity = json.value(configKey::mkcpDownlinkCapacity).toString();
c.readBufferSize = json.value(configKey::mkcpReadBufferSize).toString();
c.writeBufferSize = json.value(configKey::mkcpWriteBufferSize).toString();
c.congestion = json.value(configKey::mkcpCongestion).toBool(true);
return c;
}
@@ -302,14 +208,8 @@ QJsonObject XrayServerConfig::toJson() const
if (!transport.isEmpty()) {
obj[configKey::xrayTransport] = transport;
}
const QJsonObject xhttpObj = xhttp.toJson();
if (!xhttpObj.isEmpty()) {
obj[QStringLiteral("xhttp")] = xhttpObj;
}
const QJsonObject mkcpObj = mkcp.toJson();
if (!mkcpObj.isEmpty()) {
obj[QStringLiteral("mkcp")] = mkcpObj;
}
obj["xhttp"] = xhttp.toJson();
obj["mkcp"] = mkcp.toJson();
return obj;
}
@@ -325,39 +225,20 @@ XrayServerConfig XrayServerConfig::fromJson(const QJsonObject &json)
c.site = json.value(configKey::site).toString();
c.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false);
if (json.contains(configKey::xraySecurity)) {
c.security = json.value(configKey::xraySecurity).toString();
}
if (json.contains(configKey::xrayFlow)) {
c.flow = json.value(configKey::xrayFlow).toString();
}
if (json.contains(configKey::xrayFingerprint)) {
c.fingerprint = json.value(configKey::xrayFingerprint).toString();
if (c.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
c.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
}
}
if (json.contains(configKey::xraySni)) {
c.sni = json.value(configKey::xraySni).toString();
}
if (json.contains(configKey::xrayAlpn)) {
c.alpn = json.value(configKey::xrayAlpn).toString();
}
if (json.contains(configKey::xrayTransport)) {
c.transport = json.value(configKey::xrayTransport).toString();
}
if (json.contains(QLatin1String("xhttp"))) {
const QJsonObject xhttpJson = json.value(QLatin1String("xhttp")).toObject();
if (!xhttpJson.isEmpty()) {
c.xhttp = XrayXhttpConfig::fromJson(xhttpJson);
}
}
if (json.contains(QLatin1String("mkcp"))) {
const QJsonObject mkcpJson = json.value(QLatin1String("mkcp")).toObject();
if (!mkcpJson.isEmpty()) {
c.mkcp = XrayMkcpConfig::fromJson(mkcpJson);
}
// New: Security
c.security = json.value(configKey::xraySecurity).toString(protocols::xray::defaultSecurity);
c.flow = json.value(configKey::xrayFlow).toString(protocols::xray::defaultFlow);
c.fingerprint = json.value(configKey::xrayFingerprint).toString(protocols::xray::defaultFingerprint);
if (c.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
c.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
}
c.sni = json.value(configKey::xraySni).toString(protocols::xray::defaultSni);
c.alpn = json.value(configKey::xrayAlpn).toString(protocols::xray::defaultAlpn);
// New: Transport
c.transport = json.value(configKey::xrayTransport).toString(protocols::xray::defaultTransport);
c.xhttp = XrayXhttpConfig::fromJson(json.value("xhttp").toObject());
c.mkcp = XrayMkcpConfig::fromJson(json.value("mkcp").toObject());
return c;
}
@@ -370,10 +251,7 @@ bool XrayServerConfig::hasEqualServerSettings(const XrayServerConfig &other) con
&& flow == other.flow
&& transport == other.transport
&& fingerprint == other.fingerprint
&& sni == other.sni
&& alpn == other.alpn
&& xhttp.toJson() == other.xhttp.toJson()
&& mkcp.toJson() == other.mkcp.toJson();
&& sni == other.sni;
}
QJsonObject XrayClientConfig::toJson() const
@@ -473,154 +351,9 @@ XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject &json)
}
}
c.needsClientHydration =
c.hasClientConfig()
&& (!json.contains(configKey::xrayTransport) || c.serverConfig.isThirdPartyConfig);
if (c.needsClientHydration) {
c.hydrateServerConfigFromClientNative();
}
return c;
}
bool XrayProtocolConfig::hydrateServerConfigFromClientNative()
{
if (!clientConfig.has_value() || clientConfig->nativeConfig.isEmpty()) {
return false;
}
QJsonDocument doc = QJsonDocument::fromJson(clientConfig->nativeConfig.toUtf8());
if (doc.isNull() || !doc.isObject()) {
return false;
}
const QJsonObject root = doc.object();
const QJsonArray outbounds = root.value(protocols::xray::outbounds).toArray();
if (outbounds.isEmpty()) {
return false;
}
const QJsonObject outbound = outbounds[0].toObject();
const QJsonObject streamSettings = outbound.value(protocols::xray::streamSettings).toObject();
if (streamSettings.isEmpty()) {
return false;
}
XrayServerConfig &srv = serverConfig;
const QJsonObject settings = outbound.value(protocols::xray::settings).toObject();
const QJsonArray vnext = settings.value(protocols::xray::vnext).toArray();
if (!vnext.isEmpty()) {
const QJsonObject vnextEntry = vnext[0].toObject();
if (vnextEntry.contains(protocols::xray::port)) {
srv.port = QString::number(vnextEntry.value(protocols::xray::port).toInt());
}
const QJsonArray users = vnextEntry.value(protocols::xray::users).toArray();
if (!users.isEmpty()) {
srv.flow = users[0].toObject().value(protocols::xray::flow).toString();
}
}
const QString networkVal = streamSettings.value(protocols::xray::network).toString(QStringLiteral("tcp"));
if (networkVal == QLatin1String("xhttp")) {
srv.transport = QStringLiteral("xhttp");
} else if (networkVal == QLatin1String("kcp")) {
srv.transport = QStringLiteral("mkcp");
} else {
srv.transport = QStringLiteral("raw");
}
if (streamSettings.contains(protocols::xray::security)) {
srv.security = streamSettings.value(protocols::xray::security).toString();
}
if (srv.security == QLatin1String("reality")) {
const QJsonObject rs = streamSettings.value(protocols::xray::realitySettings).toObject();
srv.sni = rs.value(protocols::xray::serverName).toString();
srv.site = srv.sni.isEmpty() ? srv.site : srv.sni;
const QString fp = rs.value(protocols::xray::fingerprint).toString();
if (!fp.isEmpty()) {
srv.fingerprint = fp.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)
? QString::fromLatin1(protocols::xray::defaultFingerprint)
: fp;
}
}
if (srv.security == QLatin1String("tls")) {
const QJsonObject tls = streamSettings.value(QStringLiteral("tlsSettings")).toObject();
srv.sni = tls.value(protocols::xray::serverName).toString();
const QString fp = tls.value(protocols::xray::fingerprint).toString();
if (!fp.isEmpty()) {
srv.fingerprint = fp;
}
QStringList alpnList;
for (const QJsonValue &v : tls.value(QStringLiteral("alpn")).toArray()) {
alpnList << v.toString();
}
if (!alpnList.isEmpty()) {
srv.alpn = alpnList.join(QLatin1Char(','));
}
}
if (srv.transport == QLatin1String("xhttp")) {
const QJsonObject xhttpObj = streamSettings.value(QStringLiteral("xhttpSettings")).toObject();
QJsonObject xhttpJson;
const QString mode = xhttpObj.value(QStringLiteral("mode")).toString();
if (!mode.isEmpty()) {
if (mode == QLatin1String("auto")) {
xhttpJson[configKey::xhttpMode] = QStringLiteral("Auto");
} else if (mode == QLatin1String("packet-up")) {
xhttpJson[configKey::xhttpMode] = QStringLiteral("Packet-up");
} else if (mode == QLatin1String("stream-up")) {
xhttpJson[configKey::xhttpMode] = QStringLiteral("Stream-up");
} else if (mode == QLatin1String("stream-one")) {
xhttpJson[configKey::xhttpMode] = QStringLiteral("Stream-one");
} else {
xhttpJson[configKey::xhttpMode] = mode;
}
}
if (xhttpObj.contains(QStringLiteral("host"))) {
xhttpJson[configKey::xhttpHost] = xhttpObj.value(QStringLiteral("host")).toString();
}
if (xhttpObj.contains(QStringLiteral("path"))) {
xhttpJson[configKey::xhttpPath] = xhttpObj.value(QStringLiteral("path")).toString();
}
if (xhttpObj.contains(QStringLiteral("uplinkHTTPMethod"))) {
xhttpJson[configKey::xhttpUplinkMethod] = xhttpObj.value(QStringLiteral("uplinkHTTPMethod")).toString();
}
xhttpJson[configKey::xhttpDisableGrpc] = xhttpObj.value(QStringLiteral("noGRPCHeader")).toBool(true);
xhttpJson[configKey::xhttpDisableSse] = xhttpObj.value(QStringLiteral("noSSEHeader")).toBool(true);
srv.xhttp = XrayXhttpConfig::fromJson(xhttpJson);
}
if (srv.transport == QLatin1String("mkcp")) {
const QJsonObject kcpObj = streamSettings.value(QStringLiteral("kcpSettings")).toObject();
XrayMkcpConfig mk;
if (kcpObj.contains(QStringLiteral("tti"))) {
mk.tti = QString::number(kcpObj.value(QStringLiteral("tti")).toInt());
}
if (kcpObj.contains(QStringLiteral("uplinkCapacity"))) {
mk.uplinkCapacity = QString::number(kcpObj.value(QStringLiteral("uplinkCapacity")).toInt());
}
if (kcpObj.contains(QStringLiteral("downlinkCapacity"))) {
mk.downlinkCapacity = QString::number(kcpObj.value(QStringLiteral("downlinkCapacity")).toInt());
}
if (kcpObj.contains(QStringLiteral("readBufferSize"))) {
mk.readBufferSize = QString::number(kcpObj.value(QStringLiteral("readBufferSize")).toInt());
}
if (kcpObj.contains(QStringLiteral("writeBufferSize"))) {
mk.writeBufferSize = QString::number(kcpObj.value(QStringLiteral("writeBufferSize")).toInt());
}
if (kcpObj.contains(QStringLiteral("congestion"))) {
mk.congestion = kcpObj.value(QStringLiteral("congestion")).toBool(true);
}
srv.mkcp = mk;
}
needsClientHydration = false;
return true;
}
bool XrayProtocolConfig::hasClientConfig() const
{
return clientConfig.has_value();

View File

@@ -75,7 +75,6 @@ struct XrayXhttpConfig {
XrayXmuxConfig xmux;
QJsonObject toJson() const;
/// Reads only keys present in JSON (no Amnezia UI defaults). Use XrayConfigModel::applyDefaultsToServerConfig for UI.
static XrayXhttpConfig fromJson(const QJsonObject &json);
};
@@ -100,13 +99,15 @@ struct XrayServerConfig {
QString site;
bool isThirdPartyConfig = false;
QString security;
QString flow;
QString fingerprint;
QString sni;
QString alpn;
// New: Security
QString security = protocols::xray::defaultSecurity;
QString flow = protocols::xray::defaultFlow;
QString fingerprint = protocols::xray::defaultFingerprint;
QString sni = protocols::xray::defaultSni;
QString alpn = protocols::xray::defaultAlpn;
QString transport;
// New: Transport
QString transport = protocols::xray::defaultTransport;
XrayXhttpConfig xhttp;
XrayMkcpConfig mkcp;
@@ -138,10 +139,6 @@ struct XrayProtocolConfig {
bool hasClientConfig() const;
void setClientConfig(const XrayClientConfig &config);
void clearClientConfig();
bool needsClientHydration = false;
bool hydrateServerConfigFromClientNative();
};
} // namespace amnezia

View File

@@ -9,7 +9,6 @@
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/networkUtilities.h"
namespace amnezia
{
@@ -29,20 +28,6 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
return containers.value(container);
}
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
{
QString d1 = dns1;
QString d2 = dns2;
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
d1 = primaryDns;
}
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
d2 = secondaryDns;
}
return { d1, d2 };
}
QJsonObject NativeServerConfig::toJson() const
{
QJsonObject obj;
@@ -50,6 +35,9 @@ QJsonObject NativeServerConfig::toJson() const
if (!description.isEmpty()) {
obj[configKey::description] = this->description;
}
if (!displayName.isEmpty()) {
obj[configKey::displayName] = displayName;
}
if (!hostName.isEmpty()) {
obj[configKey::hostName] = hostName;
}
@@ -82,6 +70,7 @@ NativeServerConfig NativeServerConfig::fromJson(const QJsonObject& json)
NativeServerConfig config;
config.description = json.value(configKey::description).toString();
config.displayName = json.value(configKey::displayName).toString();
config.hostName = json.value(configKey::hostName).toString();
QJsonArray containersArray = json.value(configKey::containers).toArray();

View File

@@ -3,7 +3,6 @@
#include <QJsonObject>
#include <QMap>
#include <QPair>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
@@ -26,9 +25,6 @@ struct NativeServerConfig {
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const;
static NativeServerConfig fromJson(const QJsonObject& json);
};

View File

@@ -87,6 +87,9 @@ QJsonObject SelfHostedAdminServerConfig::toJson() const
if (!description.isEmpty()) {
obj[configKey::description] = this->description;
}
if (!displayName.isEmpty()) {
obj[configKey::displayName] = displayName;
}
if (!hostName.isEmpty()) {
obj[configKey::hostName] = hostName;
}
@@ -129,6 +132,7 @@ SelfHostedAdminServerConfig SelfHostedAdminServerConfig::fromJson(const QJsonObj
SelfHostedAdminServerConfig config;
config.description = json.value(configKey::description).toString();
config.displayName = json.value(configKey::displayName).toString();
config.hostName = json.value(configKey::hostName).toString();
QJsonArray containersArray = json.value(configKey::containers).toArray();

View File

@@ -8,7 +8,6 @@
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/networkUtilities.h"
namespace amnezia
{
@@ -43,21 +42,6 @@ ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer cont
return containers.value(container);
}
QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns,
const QString &secondaryDns) const
{
QString d1 = dns1;
QString d2 = dns2;
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
d1 = primaryDns;
}
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
d2 = secondaryDns;
}
return { d1, d2 };
}
QJsonObject SelfHostedUserServerConfig::toJson() const
{
QJsonObject obj;
@@ -65,6 +49,9 @@ QJsonObject SelfHostedUserServerConfig::toJson() const
if (!description.isEmpty()) {
obj[configKey::description] = this->description;
}
if (!displayName.isEmpty()) {
obj[configKey::displayName] = displayName;
}
if (!hostName.isEmpty()) {
obj[configKey::hostName] = hostName;
}
@@ -97,6 +84,7 @@ SelfHostedUserServerConfig SelfHostedUserServerConfig::fromJson(const QJsonObjec
SelfHostedUserServerConfig config;
config.description = json.value(configKey::description).toString();
config.displayName = json.value(configKey::displayName).toString();
config.hostName = json.value(configKey::hostName).toString();
QJsonArray containersArray = json.value(configKey::containers).toArray();

View File

@@ -3,7 +3,6 @@
#include <QJsonObject>
#include <QMap>
#include <QPair>
#include <optional>
#include "core/utils/containerEnum.h"
@@ -31,9 +30,6 @@ struct SelfHostedUserServerConfig {
std::optional<ServerCredentials> credentials() const;
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const;
static SelfHostedUserServerConfig fromJson(const QJsonObject &json);
};

View File

@@ -84,14 +84,15 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
const int httpStatusCodeNotFound = 404;
const int httpStatusCodeNotImplemented = 501;
const int httpStatusCodePaymentRequired = 402;
const int httpStatusCodeTooManyRequests = 429;
const int httpStatusCodeRequestTimeout = 408;
const int httpStatusCodeUnprocessableEntity = 422;
if (!sslErrors.empty()) {
qDebug().noquote() << sslErrors;
return amnezia::ErrorCode::ApiConfigSslError;
}
if (replyError == QNetworkReply::NoError) {
return amnezia::ErrorCode::NoError;
}
if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << replyError;
@@ -106,10 +107,6 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
const int httpStatusFromBody = jsonObj.value(QStringLiteral("http_status")).toInt(-1);
if (httpStatusFromBody == httpStatusCodeTooManyRequests) {
return amnezia::ErrorCode::ApiRateLimitError;
}
if (httpStatusFromBody == httpStatusCodeConflict) {
if (apiErrorMessageFromJson(jsonObj).contains(trialAlreadyUsedMessage, Qt::CaseInsensitive)) {
return amnezia::ErrorCode::ApiTrialAlreadyUsedError;
@@ -119,9 +116,6 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
if (httpStatusFromBody == httpStatusCodeNotFound) {
return amnezia::ErrorCode::ApiNotFoundError;
}
if (httpStatusFromBody == httpStatusCodeRequestTimeout) {
return amnezia::ErrorCode::ApiConfigTimeoutError;
}
if (httpStatusFromBody == httpStatusCodeNotImplemented) {
return amnezia::ErrorCode::ApiUpdateRequestError;
}
@@ -132,28 +126,9 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
return amnezia::ErrorCode::ApiConfigDownloadError;
}
if (httpStatusFromBody == httpStatusCodePaymentRequired) {
const QString message = apiErrorMessageFromJson(jsonObj);
if (message.contains(QLatin1String("refresh_captcha"), Qt::CaseInsensitive)) {
return amnezia::ErrorCode::ApiCaptchaRefreshError;
}
if (message.contains(QLatin1String("invalid_captcha"), Qt::CaseInsensitive)) {
return amnezia::ErrorCode::ApiCaptchaInvalidError;
}
if (jsonObj.contains(QStringLiteral("captcha_id")) || jsonObj.contains(QStringLiteral("captcha_image"))
|| message.compare(QLatin1String("rate_limit_exceeded"), Qt::CaseInsensitive) == 0
|| message.contains(QLatin1String("rate_limit_exceeded"), Qt::CaseInsensitive)) {
return amnezia::ErrorCode::ApiCaptchaRequiredError;
}
return amnezia::ErrorCode::ApiSubscriptionNotActiveError;
}
if (httpStatusFromBody >= 300) {
return amnezia::ErrorCode::ApiConfigDownloadError;
}
}
if (replyError == QNetworkReply::NoError) {
return amnezia::ErrorCode::NoError;
return amnezia::ErrorCode::ApiConfigDownloadError;
}
qDebug() << "something went wrong";

View File

@@ -35,9 +35,8 @@ namespace amnezia
ServerCgroupMountpoint = 212,
DockerPullRateLimit = 213,
ServerLinuxKernelTooOld = 214,
XrayServerConfigInvalid = 215,
XrayServerNoVlessClients = 216,
XrayRealityKeysReadFailed = 217,
ServerContainerizationNotSupported = 215,
DockerServiceNotActive = 216,
// Ssh connection errors
SshRequestDeniedError = 300,
@@ -101,10 +100,6 @@ namespace amnezia
ApiSubscriptionNotActiveError = 1114,
ApiNoPurchasedSubscriptionsError = 1115,
ApiTrialAlreadyUsedError = 1116,
ApiCaptchaRequiredError = 1117,
ApiCaptchaInvalidError = 1118,
ApiCaptchaRefreshError = 1119,
ApiRateLimitError = 1120,
// QFile errors
OpenError = 1200,
@@ -123,5 +118,3 @@ namespace amnezia
Q_DECLARE_METATYPE(amnezia::ErrorCode)
#endif // ERRORCODES_H

View File

@@ -30,15 +30,8 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
case(ErrorCode::XrayServerConfigInvalid):
errorMessage = QObject::tr("Server error: invalid or unreadable XRay server configuration");
break;
case(ErrorCode::XrayServerNoVlessClients):
errorMessage = QObject::tr("Server error: XRay server has no VLESS clients");
break;
case(ErrorCode::XrayRealityKeysReadFailed):
errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server");
break;
case(ErrorCode::ServerContainerizationNotSupported): errorMessage = QObject::tr("Server error: The default containerization app offered for installation on the server is not supported.\n Try installing Docker Engine on the server manually."); break;
case(ErrorCode::DockerServiceNotActive): errorMessage = QObject::tr("Docker error: The Docker service is not active.\n Check the status of the docker service on the server or try again in a minute."); break;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
@@ -93,10 +86,6 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiSubscriptionNotActiveError): errorMessage = QObject::tr("No active subscription found"); break;
case (ErrorCode::ApiNoPurchasedSubscriptionsError): errorMessage = QObject::tr("No purchased subscriptions found. Please purchase a subscription first"); break;
case (ErrorCode::ApiTrialAlreadyUsedError): errorMessage = QObject::tr("This email address has already been used to activate a trial"); break;
case (ErrorCode::ApiCaptchaRequiredError): errorMessage = QObject::tr("CAPTCHA verification is required"); break;
case (ErrorCode::ApiCaptchaInvalidError): errorMessage = QObject::tr("CAPTCHA was incorrect. Please try again"); break;
case (ErrorCode::ApiCaptchaRefreshError): errorMessage = QObject::tr("CAPTCHA refreshed. Please try again"); break;
case (ErrorCode::ApiRateLimitError): errorMessage = QObject::tr("Too many requests. Please try again later"); break;
// QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;

View File

@@ -295,8 +295,6 @@ amnezia::ScriptVars amnezia::genMtProxyVars(const ContainerConfig &containerConf
vars.append({{"$MTPROXY_PORT", c.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : c.port}});
vars.append({{"$MTPROXY_SECRET", c.secret}});
vars.append({{"$MTPROXY_REGENERATE_SECRET",
c.secret.isEmpty() ? QStringLiteral("1") : QStringLiteral("0")}});
vars.append({{"$MTPROXY_TAG", c.tag}});
vars.append({{"$MTPROXY_TRANSPORT_MODE",
c.transportMode.isEmpty() ? QString(protocols::mtProxy::transportModeStandard)
@@ -352,8 +350,6 @@ amnezia::ScriptVars amnezia::genTelemtVars(const ContainerConfig &containerConfi
vars.append({ { "$TELEMT_TOML_TLS", faketls ? QLatin1String("true") : QLatin1String("false") } });
vars.append({ { "$TELEMT_PORT", c.port.isEmpty() ? QString(protocols::telemt::defaultPort) : c.port } });
vars.append({ { "$TELEMT_SECRET", c.secret } });
vars.append({ { "$TELEMT_REGENERATE_SECRET",
c.secret.isEmpty() ? QStringLiteral("1") : QStringLiteral("0") } });
vars.append({ { "$TELEMT_TAG", c.tag } });
QString tlsDomain = c.tlsDomain;
if (tlsDomain.isEmpty()) {

View File

@@ -1,8 +1,7 @@
if which apt-get > /dev/null 2>&1 || command -v apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1 || command -v dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
elif which yum > /dev/null 2>&1 || command -v yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
elif which zypper > /dev/null 2>&1 || command -v zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
elif which pacman > /dev/null 2>&1 || command -v pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1;\
fi;\
if which $LOCK_CMD > /dev/null 2>&1 || command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
if which apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
elif which yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
elif which zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
elif which pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
if command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi

View File

@@ -1,8 +1,8 @@
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then opt="--version";\
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then opt="--version";\
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then opt="--version";\
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then opt="--version";\
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then opt="--version";\
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\
else pm="uname"; opt="-a";\
fi;\
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\

View File

@@ -1,25 +1,33 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; what_pkg="-s install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; what_pkg="--dry-run install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="suse";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; what_pkg="-Sp"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
else echo "Packet manager not found"; exit 1; fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, What pkg command: $what_pkg, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg, Language: $LANG";\
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\
if ! command -v docker > /dev/null 2>&1; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl enable --now docker; sleep 5;\
if ! sudo sh -c 'command -v fuser > /dev/null 2>&1'; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
if ! sudo sh -c 'command -v lsof > /dev/null 2>&1'; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\
if ! sudo sh -c 'command -v docker > /dev/null 2>&1'; then \
sudo $pm $check_pkgs;\
if ! sudo $pm $what_pkg $docker_pkg 2>/dev/null | grep -qi podman; then \
sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl enable --now docker; sleep 5;\
else \
echo "Containerization app is not supported";\
exit 1;\
fi;\
fi;\
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! command -v apparmor_parser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor; fi;\
if [ "$(sudo cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! sudo sh -c 'command -v apparmor_parser > /dev/null 2>&1'; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor;\
fi;\
fi;\
if [ "$(systemctl is-active docker)" != "active" ]; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
if [ "$(sudo systemctl is-active docker)" != "active" ]; then \
sleep 5; sudo systemctl start docker; sleep 5;\
if [ "$(sudo systemctl is-active docker)" != "active" ]; then echo "Service status not active"; fi;\
fi;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
docker --version;\
sudo docker --version;\
uname -sr

View File

@@ -4,10 +4,8 @@
curl -s https://core.telegram.org/getProxySecret -o /data/proxy-secret
curl -s https://core.telegram.org/getProxyConfig -o /data/proxy-multi.conf
# Determine secret: regenerate (fresh install) -> env var -> saved file -> generate new
if [ "$MTPROXY_REGENERATE_SECRET" = "1" ]; then
SECRET=$(openssl rand -hex 16)
elif [ -n "$MTPROXY_SECRET" ]; then
# Determine secret: env var -> saved file -> generate new
if [ -n "$MTPROXY_SECRET" ]; then
SECRET="$MTPROXY_SECRET"
elif [ -f /data/secret ]; then
SECRET=$(cat /data/secret)

View File

@@ -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

View File

@@ -4,10 +4,8 @@
echo "[*] Amnezia Telemt: configure script start"
mkdir -p /data/tlsfront
# Secret: regenerate (fresh install) -> env var -> saved file -> openssl
if [ "$TELEMT_REGENERATE_SECRET" = "1" ]; then
SECRET=$(openssl rand -hex 16)
elif [ -n "$TELEMT_SECRET" ]; then
# Secret: substituted $TELEMT_SECRET -> saved file -> openssl (same rules as MTProxy configure)
if [ -n "$TELEMT_SECRET" ]; then
SECRET="$TELEMT_SECRET"
elif [ -f /data/secret ]; then
SECRET=$(cat /data/secret)

145
client/tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,145 @@
cmake_minimum_required(VERSION 3.25.0)
project(AmneziaVPN_Tests)
find_package(Qt6 REQUIRED COMPONENTS Test)
set(CMAKE_AUTORCC ON)
qt6_add_resources(TEST_QRC
${CLIENT_ROOT_DIR}/server_scripts/serverScripts.qrc
)
add_library(test_common OBJECT
${SOURCES}
${HEADERS}
${TEST_QRC}
)
qt_add_repc_replicas(test_common
${CLIENT_ROOT_DIR}/../ipc/ipc_interface.rep
${CLIENT_ROOT_DIR}/../ipc/ipc_process_interface.rep
)
target_link_libraries(test_common PUBLIC
${LIBS}
)
target_include_directories(test_common PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/..
${CMAKE_CURRENT_BINARY_DIR}/..
${CMAKE_CURRENT_BINARY_DIR}
)
add_executable(test_import_export
testAdminSelfHostedExport.cpp
)
target_link_libraries(test_import_export PRIVATE
Qt6::Test
test_common
)
add_executable(test_multiple_imports
testMultipleImports.cpp
)
target_link_libraries(test_multiple_imports PRIVATE
Qt6::Test
test_common
)
add_executable(test_server_edit
testServerEdit.cpp
)
target_link_libraries(test_server_edit PRIVATE
Qt6::Test
test_common
)
add_executable(test_default_server_change
testDefaultServerChange.cpp
)
target_link_libraries(test_default_server_change PRIVATE
Qt6::Test
test_common
)
add_executable(test_server_edge_cases
testServerEdgeCases.cpp
)
target_link_libraries(test_server_edge_cases PRIVATE
Qt6::Test
test_common
)
add_executable(test_signal_order
testSignalOrder.cpp
)
target_link_libraries(test_signal_order PRIVATE
Qt6::Test
test_common
)
add_executable(test_servers_model_sync
testServersModelSync.cpp
)
target_link_libraries(test_servers_model_sync PRIVATE
Qt6::Test
test_common
)
add_executable(test_complex_operations
testComplexOperations.cpp
)
target_link_libraries(test_complex_operations PRIVATE
Qt6::Test
test_common
)
add_executable(test_settings_signals
testSettingsSignals.cpp
)
target_link_libraries(test_settings_signals PRIVATE
Qt6::Test
test_common
)
add_executable(test_ui_servers_model_and_controller
testUiServersModelAndController.cpp
)
target_link_libraries(test_ui_servers_model_and_controller PRIVATE
Qt6::Test
test_common
)
add_executable(test_self_hosted_server_setup
testSelfHostedServerSetup.cpp
)
target_link_libraries(test_self_hosted_server_setup PRIVATE
Qt6::Test
test_common
)
enable_testing()
add_test(NAME ImportExportTest COMMAND test_import_export)
add_test(NAME MultipleImportsTest COMMAND test_multiple_imports)
add_test(NAME ServerEditTest COMMAND test_server_edit)
add_test(NAME DefaultServerChangeTest COMMAND test_default_server_change)
add_test(NAME ServerEdgeCasesTest COMMAND test_server_edge_cases)
add_test(NAME SignalOrderTest COMMAND test_signal_order)
add_test(NAME ServersModelSyncTest COMMAND test_servers_model_sync)
add_test(NAME ComplexOperationsTest COMMAND test_complex_operations)
add_test(NAME SettingsSignalsTest COMMAND test_settings_signals)
add_test(NAME UiServersModelAndControllerTest COMMAND test_ui_servers_model_and_controller)
add_test(NAME SelfHostedServerSetupTest COMMAND test_self_hosted_server_setup)

View File

@@ -0,0 +1,147 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QFile>
#include <QDebug>
#include <QUuid>
#include <QSignalSpy>
#include "core/controllers/coreController.h"
#include "core/utils/constants/configKeys.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
class TestAdminSelfHostedExport : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
QJsonObject decodeVpnKey(const QString &vpnKey) {
QString key = vpnKey;
key.replace("vpn://", "");
QByteArray ba = QByteArray::fromBase64(
key.toUtf8(),
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals
);
qDebug() << "Base64 decoded size:" << ba.size();
QJsonDocument testDoc = QJsonDocument::fromJson(ba);
if (!testDoc.isNull()) {
qDebug() << "Data is not compressed, using as-is";
return testDoc.object();
}
QByteArray baUncompressed = qUncompress(ba);
if (!baUncompressed.isEmpty()) {
qDebug() << "Data was compressed, uncompressed size:" << baUncompressed.size();
ba = baUncompressed;
} else {
qDebug() << "qUncompress failed or data is not compressed";
}
return QJsonDocument::fromJson(ba).object();
}
QJsonObject sortContainers(const QJsonObject &config) {
QJsonObject sorted = config;
if (!config.contains("containers")) {
return sorted;
}
QJsonArray containers = config["containers"].toArray();
QVector<QJsonObject> containerVec;
for (const QJsonValue &val : containers) {
containerVec.append(val.toObject());
}
std::sort(containerVec.begin(), containerVec.end(), [](const QJsonObject &a, const QJsonObject &b) {
return a["container"].toString() < b["container"].toString();
});
QJsonArray sortedContainers;
for (const QJsonObject &obj : containerVec) {
sortedContainers.append(obj);
}
sorted["containers"] = sortedContainers;
return sorted;
}
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
}
void testAdminSelfHostedExport() {
QString vpnKey = "vpn://AAABTXjarZIxT8MwEIX_Cro5jbDjQunKUhhYyoZQZZKjRGpsy3baQtT_zp2bJh3oACLLPfvz3bOe00FpTdS1QR9g_tKB3q1h3sFCwBzEdf9N5ElBBgtJqBiQOkcFoemAbs6RInQ7oNkZemAvrrKvRV9VX6fH-lhSVSwavU9GSdcmXZX0UqSbseJRMqlioDxuSsJZH1mKWTrhvI22tJvVljKoLU-TtB3aN4NxpavKYwhpSD7LRc4t0WsTeMwqNRNsKweHbAyTtnRj8KvWE0pUEut-hNah2TpDM0-Kwu8vKMSd-ttFLrntao_rVvuKWkc9OnIk4n8t915_Ulcqo5FSxa9tYsk2rxlU-K7bTby_lDWfCKWvXTy-5jOGeLVET-9L7MOG-KQbJEBx57jXjdtgXtqG_wUdws5yJhCpa1iefhopM2gD-n4An-ElHL4BvzD6nw";
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
qDebug() << "IMPORTED KEY:" << vpnKey;
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(vpnKey);
QVERIFY2(importResult.errorCode == ErrorCode::NoError, "Import should succeed");
QVERIFY2(!importResult.config.isEmpty(), "Config should not be empty");
QJsonObject importedConfig = importResult.config;
m_coreController->m_importCoreController->importConfig(importedConfig);
QVERIFY2(importFinishedSpy.count() == 1, "importFinished signal should be emitted");
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged signal should NOT be emitted (default is already 0)");
QVERIFY2(m_coreController->m_serversRepository->serversCount() > 0, "Server should be added");
const QString serverId = m_coreController->m_serversRepository->defaultServerId();
auto exportResult = m_coreController->m_exportController->generateFullAccessConfig(serverId);
QVERIFY2(exportResult.errorCode == ErrorCode::NoError, "Export should succeed");
QVERIFY2(!exportResult.config.isEmpty(), "Exported config should not be empty");
qDebug() << "EXPORTED KEY:" << exportResult.config;
QJsonObject exportedConfig = decodeVpnKey(exportResult.config);
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(exportResult.config);
QVERIFY2(importResult2.errorCode == ErrorCode::NoError, "Re-import should succeed");
QJsonObject sortedImported = sortContainers(importedConfig);
QJsonObject sortedExported = sortContainers(importResult2.config);
QString importedJson = QJsonDocument(sortedImported).toJson(QJsonDocument::Compact);
QString exportedJson = QJsonDocument(sortedExported).toJson(QJsonDocument::Compact);
qDebug() << "IMPORTED JSON:" << importedJson;
qDebug() << "EXPORTED JSON:" << exportedJson;
QCOMPARE(exportedJson, importedJson);
}
};
QTEST_MAIN(TestAdminSelfHostedExport)
#include "testAdminSelfHostedExport.moc"

View File

@@ -0,0 +1,111 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "tests/testServerRepositoryHelpers.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
using namespace amnezia;
class TestComplexOperations : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testComplexOperationSequence() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
QSignalSpy serverEditedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited);
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult1.config);
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
m_coreController->m_importCoreController->importConfig(importResult2.config);
auto importResult3 = m_coreController->m_importCoreController->extractConfigFromData(wgKey);
m_coreController->m_importCoreController->importConfig(importResult3.config);
QVERIFY2(importFinishedSpy.count() == 3, "importFinished should be emitted 3 times");
QVERIFY2(serverAddedSpy.count() == 3, "serverAdded should be emitted 3 times");
QVERIFY2(defaultServerChangedSpy.count() == 2, "defaultServerChanged should be emitted 2 times (0->1, 1->2, first import doesn't emit as default is already 0)");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 3, "Should have 3 servers");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default should be index 2");
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
m_coreController->m_serversController->getServerId(0),
QStringLiteral("Edited First Server"));
QVERIFY2(serverEditedSpy.count() == 1, "serverEdited should be emitted");
QString editedDesc0 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(0));
QVERIFY2(editedDesc0 == "Edited First Server", "First server should be edited");
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(1));
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved should be emitted");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "Should have 2 servers");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default should be index 1 (was 2, removed 1)");
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(0));
QVERIFY2(defaultServerChangedSpy.count() == 4, "defaultServerChanged should be emitted again");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default should be index 0");
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
m_coreController->m_serversController->getServerId(0),
QStringLiteral("Final Edited Server"));
QVERIFY2(serverEditedSpy.count() == 2, "serverEdited should be emitted again");
QString finalDesc0 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(0));
QVERIFY2(finalDesc0 == "Final Edited Server", "First server should be edited again");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "Final servers count should be 2");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Final default index should be 0");
if (m_coreController->m_serversModel) {
QVERIFY2(m_coreController->m_serversModel->rowCount() == 2, "Model should have 2 rows");
QString modelDesc0 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
QVERIFY2(modelDesc0 == "Final Edited Server", "Model should reflect final edited name");
}
}
};
QTEST_MAIN(TestComplexOperations)
#include "testComplexOperations.moc"

View File

@@ -0,0 +1,128 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "tests/testServerRepositoryHelpers.h"
#include "ui/models/serversModel.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
using namespace amnezia;
class TestDefaultServerChange : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
m_coreController->m_serversRepository->invalidateCache();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testSetDefaultServerIndex() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult1.config);
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
m_coreController->m_importCoreController->importConfig(importResult2.config);
auto importResult3 = m_coreController->m_importCoreController->extractConfigFromData(wgKey);
m_coreController->m_importCoreController->importConfig(importResult3.config);
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 3, "Should have 3 servers");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default should be index 2");
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(0));
QVERIFY2(defaultServerChangedSpy.count() == 1, "defaultServerChanged signal should be emitted");
QVERIFY2(defaultServerChangedSpy.at(0).at(0).toString() == m_coreController->m_serversController->getServerId(0),
"defaultServerChanged should emit new default server id");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default server index should be 0");
if (m_coreController->m_serversModel) {
int modelDefaultIndex = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::IsDefaultRole).toBool() ? 0 : -1;
QVERIFY2(modelDefaultIndex == 0, "Model should reflect default server");
}
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(2));
QVERIFY2(defaultServerChangedSpy.count() == 2, "defaultServerChanged signal should be emitted again");
QVERIFY2(defaultServerChangedSpy.at(1).at(0).toString() == m_coreController->m_serversController->getServerId(2),
"defaultServerChanged should emit new default server id");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default server index should be 2");
}
void testDefaultServerChangeOnRemoveEdgeCases() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult1.config);
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
m_coreController->m_importCoreController->importConfig(importResult2.config);
auto importResult3 = m_coreController->m_importCoreController->extractConfigFromData(wgKey);
m_coreController->m_importCoreController->importConfig(importResult3.config);
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 3, "Should have 3 servers");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default should be index 2");
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "Should have 2 servers");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default should be index 1 (was 2, removed 0)");
QString desc1 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(0));
QString desc2 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(1));
QVERIFY2(desc1 == "Xray Server", "First remaining server should be Xray");
QVERIFY2(desc2 == "WireGuard Server", "Second remaining server should be WireGuard");
defaultServerChangedSpy.clear();
serverRemovedSpy.clear();
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "Should have 1 server");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default should be index 0 (was 1, removed 0)");
QString lastDesc = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(0));
QVERIFY2(lastDesc == "WireGuard Server", "Last server should be WireGuard");
}
};
QTEST_MAIN(TestDefaultServerChange)
#include "testDefaultServerChange.moc"

View File

@@ -0,0 +1,195 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
#include <QUuid>
#include <QSignalSpy>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "tests/testServerRepositoryHelpers.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
using namespace amnezia;
class TestMultipleImports : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
m_coreController->m_serversRepository->invalidateCache();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testMultipleImports() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 0, "Initial servers count should be 0");
if (m_coreController->m_serversModel) {
QVERIFY2(m_coreController->m_serversModel->rowCount() == 0, "Initial model row count should be 0");
}
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
QVERIFY2(importResult1.errorCode == ErrorCode::NoError, "First import should succeed");
m_coreController->m_importCoreController->importConfig(importResult1.config);
QVERIFY2(importFinishedSpy.count() == 1, "importFinished signal should be emitted once");
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged signal should NOT be emitted (default is already 0)");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "After first import servers count should be 1");
if (m_coreController->m_serversModel) {
QVERIFY2(m_coreController->m_serversModel->rowCount() == 1, "After first import model row count should be 1");
}
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "First server should be default");
QString desc1 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(0));
QVERIFY2(desc1 == "AWG Server", "First server description should match");
if (m_coreController->m_serversModel) {
QString modelDesc1 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
QVERIFY2(modelDesc1 == "AWG Server", "First server description in model should match");
}
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
QVERIFY2(importResult2.errorCode == ErrorCode::NoError, "Second import should succeed");
m_coreController->m_importCoreController->importConfig(importResult2.config);
QVERIFY2(importFinishedSpy.count() == 2, "importFinished signal should be emitted twice");
QVERIFY2(defaultServerChangedSpy.count() == 1, "defaultServerChanged signal should be emitted once (0->1, first import doesn't emit)");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "After second import servers count should be 2");
if (m_coreController->m_serversModel) {
QVERIFY2(m_coreController->m_serversModel->rowCount() == 2, "After second import model row count should be 2");
}
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Second server should be default");
QString desc2 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(1));
QVERIFY2(desc2 == "Xray Server", "Second server description should match");
if (m_coreController->m_serversModel) {
QString modelDesc2 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(1, 0), ServersModel::NameRole).toString();
QVERIFY2(modelDesc2 == "Xray Server", "Second server description in model should match");
}
auto importResult3 = m_coreController->m_importCoreController->extractConfigFromData(wgKey);
QVERIFY2(importResult3.errorCode == ErrorCode::NoError, "Third import should succeed");
m_coreController->m_importCoreController->importConfig(importResult3.config);
QVERIFY2(importFinishedSpy.count() == 3, "importFinished signal should be emitted three times");
QVERIFY2(defaultServerChangedSpy.count() == 2, "defaultServerChanged signal should be emitted twice (0->1, 1->2, first import doesn't emit)");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 3, "After third import servers count should be 3");
if (m_coreController->m_serversModel) {
QVERIFY2(m_coreController->m_serversModel->rowCount() == 3, "After third import model row count should be 3");
}
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Third server should be default");
QString desc3 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(2));
QVERIFY2(desc3 == "WireGuard Server", "Third server description should match");
if (m_coreController->m_serversModel) {
QString modelDesc3 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(2, 0), ServersModel::NameRole).toString();
QVERIFY2(modelDesc3 == "WireGuard Server", "Third server description in model should match");
}
}
void testMultipleImportsRemoval() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 0, "Initial servers count should be 0");
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
QVERIFY2(importResult1.errorCode == ErrorCode::NoError, "First import should succeed");
m_coreController->m_importCoreController->importConfig(importResult1.config);
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
QVERIFY2(importResult2.errorCode == ErrorCode::NoError, "Second import should succeed");
m_coreController->m_importCoreController->importConfig(importResult2.config);
QVERIFY2(importFinishedSpy.count() == 2, "importFinished signal should be emitted twice");
QVERIFY2(defaultServerChangedSpy.count() == 1, "defaultServerChanged signal should be emitted once (0->1, first import doesn't emit)");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "After two imports servers count should be 2");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Second server should be default");
QString desc0 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(0));
QString desc1 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(1));
QVERIFY2(desc0 == "AWG Server", "First server description should match");
QVERIFY2(desc1 == "Xray Server", "Second server description should match");
defaultServerChangedSpy.clear();
serverRemovedSpy.clear();
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
QVERIFY2(serverRemovedSpy.at(0).at(1).toInt() == 0, "serverRemoved should emit removed index 0");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "After removing first server, servers count should be 1");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "After removing first server, default index should be 0");
QString remainingDesc = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(0));
QVERIFY2(remainingDesc == "Xray Server", "Remaining server should be Xray Server");
if (m_coreController->m_serversModel) {
QVERIFY2(m_coreController->m_serversModel->rowCount() == 1, "After removing first server, model row count should be 1");
QString modelDesc = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
QVERIFY2(modelDesc == "Xray Server", "Remaining server description in model should match");
}
defaultServerChangedSpy.clear();
serverRemovedSpy.clear();
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
QVERIFY2(serverRemovedSpy.at(0).at(1).toInt() == 0, "serverRemoved should emit removed index 0");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 0, "After removing last server, servers count should be 0");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "After removing last server, default index should be 0");
if (m_coreController->m_serversModel) {
QVERIFY2(m_coreController->m_serversModel->rowCount() == 0, "After removing last server, model row count should be 0");
}
}
};
QTEST_MAIN(TestMultipleImports)
#include "testMultipleImports.moc"

View File

@@ -0,0 +1,385 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include <QProcessEnvironment>
#include <QDebug>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "core/models/protocols/dnsProtocolConfig.h"
#include "core/utils/commonStructs.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/errorCodes.h"
#include "ui/models/serversModel.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
using namespace amnezia;
class TestSelfHostedServerSetup : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
ServerCredentials getCredentialsFromEnv() {
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString hostName = env.value("TEST_SERVER_HOST");
QString userName = env.value("TEST_SERVER_USER");
QString password = env.value("TEST_SERVER_PASSWORD");
QString portStr = env.value("TEST_SERVER_PORT", "22");
int port = portStr.toInt();
ServerCredentials credentials;
credentials.hostName = hostName;
credentials.userName = userName;
credentials.secretData = password;
credentials.port = port;
return credentials;
}
void verifySshConnection(const ServerCredentials& credentials) {
QString sshOutput;
ErrorCode sshError = m_coreController->m_installController->checkSshConnection(credentials, sshOutput);
QVERIFY2(sshError == ErrorCode::NoError,
QString("SSH connection should succeed. Error: %1, Output: %2")
.arg(static_cast<int>(sshError))
.arg(sshOutput)
.toUtf8().constData());
qDebug() << "SSH connection successful. Output:" << sshOutput;
}
void verifyAdminAccess(int serverIndex)
{
const QString serverId = m_coreController->m_serversRepository->serverIdAt(serverIndex);
const auto adminCfg = m_coreController->m_serversRepository->selfHostedAdminConfig(serverId);
QVERIFY2(adminCfg.has_value(), "Server config should be SelfHostedAdminServerConfig");
const SelfHostedAdminServerConfig &selfHosted = *adminCfg;
QVERIFY2(selfHosted.hasCredentials(),
"Server should have credentials (admin access)");
QVERIFY2(!selfHosted.userName.isEmpty(),
"Server should have userName for admin access");
QVERIFY2(!selfHosted.password.isEmpty(),
"Server should have password for admin access");
QVERIFY2(!selfHosted.isReadOnly(),
"Server should not be read-only (should have admin access)");
if (m_coreController->m_serversModel) {
bool hasWriteAccess = m_coreController->m_serversModel->data(
m_coreController->m_serversModel->index(serverIndex, 0),
ServersModel::HasWriteAccessRole
).toBool();
QVERIFY2(hasWriteAccess,
"Server should have write access (admin access) according to ServersModel");
}
qDebug() << "Admin access verified for server at index:" << serverIndex;
}
void verifyClientConfig(const ContainerConfig& containerConfig, DockerContainer container) {
QString containerName = ContainerUtils::containerToString(container);
qDebug() << "Checking container:" << containerName;
if (ContainerUtils::containerService(container) != ServiceType::Other) {
bool hasClientConfig = containerConfig.protocolConfig.hasClientConfig();
QVERIFY2(hasClientConfig,
QString("Container %1 should have client config initialized")
.arg(containerName)
.toUtf8().constData());
if (container == DockerContainer::Awg) {
const AwgProtocolConfig* awgProtocolConfig = containerConfig.protocolConfig.as<AwgProtocolConfig>();
QVERIFY2(awgProtocolConfig != nullptr, "Protocol config should be AwgProtocolConfig");
QVERIFY2(awgProtocolConfig->hasClientConfig(), "AwgProtocolConfig should have client config");
const std::optional<AwgClientConfig>& clientCfgOpt = awgProtocolConfig->clientConfig;
QVERIFY2(clientCfgOpt.has_value(), "Awg client config should exist");
const AwgClientConfig& awgClientConfig = *clientCfgOpt;
QVERIFY2(!awgClientConfig.hostName.isEmpty(), "Awg client config should have hostName");
QVERIFY2(awgClientConfig.port > 0, "Awg client config should have valid port");
QVERIFY2(!awgClientConfig.clientPrivateKey.isEmpty(), "Awg client config should have clientPrivateKey");
QVERIFY2(!awgClientConfig.clientPublicKey.isEmpty(), "Awg client config should have clientPublicKey");
QVERIFY2(!awgClientConfig.serverPublicKey.isEmpty(), "Awg client config should have serverPublicKey");
QVERIFY2(!awgClientConfig.clientId.isEmpty(), "Awg client config should have clientId");
QVERIFY2(!awgClientConfig.nativeConfig.isEmpty(), "Awg client config should have nativeConfig");
}
qDebug() << "Container" << containerName << "has valid client config initialized";
} else {
qDebug() << "Container" << containerName << "is service type Other, skipping client config check";
}
}
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testSelfHostedServerSetup() {
ServerCredentials credentials = getCredentialsFromEnv();
if (credentials.hostName.isEmpty() || credentials.userName.isEmpty() || credentials.secretData.isEmpty()) {
QSKIP("Test requires TEST_SERVER_HOST, TEST_SERVER_USER, TEST_SERVER_PASSWORD environment variables");
}
QVERIFY2(credentials.isValid(), "Server credentials should be valid");
qDebug() << "Using server:" << credentials.hostName << "user:" << credentials.userName << "port:" << credentials.port;
verifySshConnection(credentials);
int awgPort = 55424;
TransportProto awgTransportProto = TransportProto::Udp;
bool wasAwgInstalled = false;
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
ErrorCode installServerError = m_coreController->m_installController->installServer(
credentials, DockerContainer::Awg, awgPort, awgTransportProto, wasAwgInstalled);
QVERIFY2(installServerError == ErrorCode::NoError,
QString("installServer for Awg should succeed. Error: %1")
.arg(static_cast<int>(installServerError))
.toUtf8().constData());
QVERIFY2(serverAddedSpy.count() == 1, "serverAdded signal should be emitted");
QVERIFY2(m_coreController->m_serversRepository->serversCount() > 0, "Server should be added");
int serverIndex = m_coreController->m_serversRepository->serversCount() - 1;
qDebug() << "Server with Awg container added at index:" << serverIndex;
const auto adminAfterAwg = m_coreController->m_serversRepository->selfHostedAdminConfig(
m_coreController->m_serversRepository->serverIdAt(serverIndex));
QVERIFY2(adminAfterAwg.has_value(), "Server should be self-hosted (admin)");
const SelfHostedAdminServerConfig *selfHostedAfterAwg = &(*adminAfterAwg);
QVERIFY2(selfHostedAfterAwg->defaultContainer == DockerContainer::Awg, "Default container should be Awg");
QVERIFY2(selfHostedAfterAwg->containers.contains(DockerContainer::Awg), "Server should have Awg container");
ContainerConfig awgConfig = selfHostedAfterAwg->containers.value(DockerContainer::Awg);
QVERIFY2(awgConfig.container == DockerContainer::Awg, "Awg container config should be valid");
QVERIFY2(selfHostedAfterAwg->containers.size() == 1,
QString("Server should have exactly 1 container after Awg installation, but has %1")
.arg(selfHostedAfterAwg->containers.size())
.toUtf8().constData());
verifyClientConfig(awgConfig, DockerContainer::Awg);
qDebug() << "Awg container installed and configured successfully with valid client config";
int dnsPort = 53;
TransportProto dnsTransportProto = TransportProto::Udp;
bool wasDnsInstalled = false;
const QString serverIdForOps = m_coreController->m_serversRepository->serverIdAt(serverIndex);
ErrorCode installContainerError = m_coreController->m_installController->installContainer(
serverIdForOps, DockerContainer::Dns, dnsPort, dnsTransportProto, wasDnsInstalled);
QVERIFY2(installContainerError == ErrorCode::NoError,
QString("installContainer for Dns should succeed. Error: %1")
.arg(static_cast<int>(installContainerError))
.toUtf8().constData());
qDebug() << "Dns container installed:" << wasDnsInstalled;
const auto adminAfterDns = m_coreController->m_serversRepository->selfHostedAdminConfig(
m_coreController->m_serversRepository->serverIdAt(serverIndex));
QVERIFY2(adminAfterDns.has_value(), "Server config should be SelfHostedAdminServerConfig");
const SelfHostedAdminServerConfig *selfHostedAfterDns = &(*adminAfterDns);
QVERIFY2(selfHostedAfterDns->containers.contains(DockerContainer::Awg), "Server should still have Awg container");
QVERIFY2(selfHostedAfterDns->containers.contains(DockerContainer::Dns), "Server should have Dns container");
QVERIFY2(selfHostedAfterDns->containers.size() == 2,
QString("Server should have exactly 2 containers after Dns installation, but has %1")
.arg(selfHostedAfterDns->containers.size())
.toUtf8().constData());
ContainerConfig dnsConfig = selfHostedAfterDns->containers.value(DockerContainer::Dns);
QVERIFY2(dnsConfig.container == DockerContainer::Dns, "Dns container config should be valid");
const DnsProtocolConfig* dnsProtocolConfig = dnsConfig.protocolConfig.as<DnsProtocolConfig>();
QVERIFY2(dnsProtocolConfig != nullptr, "Protocol config should be DnsProtocolConfig");
qDebug() << "Dns container installed and configured successfully";
verifyAdminAccess(serverIndex);
qDebug() << "Test completed successfully. Server setup with Awg and Dns containers is complete.";
}
void testSelfHostedServerEmptyRecover() {
ServerCredentials credentials = getCredentialsFromEnv();
if (credentials.hostName.isEmpty() || credentials.userName.isEmpty() || credentials.secretData.isEmpty()) {
QSKIP("Test requires TEST_SERVER_HOST, TEST_SERVER_USER, TEST_SERVER_PASSWORD environment variables");
}
QVERIFY2(credentials.isValid(), "Server credentials should be valid");
qDebug() << "Using server:" << credentials.hostName << "user:" << credentials.userName << "port:" << credentials.port;
verifySshConnection(credentials);
SelfHostedAdminServerConfig serverConfig;
serverConfig.hostName = credentials.hostName;
serverConfig.userName = credentials.userName;
serverConfig.password = credentials.secretData;
serverConfig.port = credentials.port;
serverConfig.description = m_coreController->m_appSettingsRepository->nextAvailableServerName();
serverConfig.displayName = serverConfig.description.isEmpty() ? serverConfig.hostName : serverConfig.description;
serverConfig.defaultContainer = DockerContainer::None;
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
m_coreController->m_serversRepository->addServer(QString(), serverConfig.toJson(),
serverConfigUtils::ConfigType::SelfHostedAdmin);
QVERIFY2(serverAddedSpy.count() == 1, "serverAdded signal should be emitted");
QVERIFY2(m_coreController->m_serversRepository->serversCount() > 0, "Server should be added");
int serverIndex = m_coreController->m_serversRepository->serversCount() - 1;
qDebug() << "Empty server added at index:" << serverIndex;
const auto addedAdmin = m_coreController->m_serversRepository->selfHostedAdminConfig(
m_coreController->m_serversRepository->serverIdAt(serverIndex));
QVERIFY2(addedAdmin.has_value(), "Added server should be self-hosted admin");
const SelfHostedAdminServerConfig *selfHosted = &(*addedAdmin);
QVERIFY2(selfHosted->containers.isEmpty(), "Server should have no containers initially");
QVERIFY2(selfHosted->defaultContainer == DockerContainer::None, "Default container should be None");
const QString scanServerId = m_coreController->m_serversRepository->serverIdAt(serverIndex);
ErrorCode scanError = m_coreController->m_installController->scanServerForInstalledContainers(scanServerId);
QVERIFY2(scanError == ErrorCode::NoError,
QString("Server scan should succeed. Error: %1")
.arg(static_cast<int>(scanError))
.toUtf8().constData());
qDebug() << "Server scan completed successfully";
const auto scannedAdmin = m_coreController->m_serversRepository->selfHostedAdminConfig(
m_coreController->m_serversRepository->serverIdAt(serverIndex));
QVERIFY2(scannedAdmin.has_value(), "Scanned server config should be SelfHostedAdminServerConfig");
const SelfHostedAdminServerConfig *scannedSelfHosted = &(*scannedAdmin);
QMap<DockerContainer, ContainerConfig> containers = scannedSelfHosted->containers;
int containersCount = containers.size();
qDebug() << "Found containers count:" << containersCount;
QVERIFY2(containersCount >= 0,
QString("Containers count should be non-negative, but got %1")
.arg(containersCount)
.toUtf8().constData());
if (containersCount > 0) {
qDebug() << "Server has" << containersCount << "installed container(s)";
} else {
qDebug() << "Server has no installed containers";
}
for (auto it = containers.begin(); it != containers.end(); ++it) {
verifyClientConfig(it.value(), it.key());
}
QVERIFY2(scannedSelfHosted->containers.size() == containersCount,
QString("Scanned containers count should match. Expected: %1, Actual: %2")
.arg(containersCount)
.arg(scannedSelfHosted->containers.size())
.toUtf8().constData());
verifyAdminAccess(serverIndex);
qDebug() << "Test completed successfully. Server has admin access and all containers are initialized.";
}
void testRemoveAllContainers() {
ServerCredentials credentials = getCredentialsFromEnv();
if (credentials.hostName.isEmpty() || credentials.userName.isEmpty() || credentials.secretData.isEmpty()) {
QSKIP("Test requires TEST_SERVER_HOST, TEST_SERVER_USER, TEST_SERVER_PASSWORD environment variables");
}
QVERIFY2(credentials.isValid(), "Server credentials should be valid");
qDebug() << "Using server:" << credentials.hostName << "user:" << credentials.userName << "port:" << credentials.port;
verifySshConnection(credentials);
int awgPort = 55424;
TransportProto awgTransportProto = TransportProto::Udp;
bool wasAwgInstalled = false;
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
ErrorCode installServerError = m_coreController->m_installController->installServer(
credentials, DockerContainer::Awg, awgPort, awgTransportProto, wasAwgInstalled);
QVERIFY2(installServerError == ErrorCode::NoError,
QString("installServer for Awg should succeed. Error: %1")
.arg(static_cast<int>(installServerError))
.toUtf8().constData());
QVERIFY2(serverAddedSpy.count() == 1, "serverAdded signal should be emitted");
int serverIndex = m_coreController->m_serversRepository->serversCount() - 1;
qDebug() << "Server with Awg container added at index:" << serverIndex;
const auto adminBeforeRemoval = m_coreController->m_serversRepository->selfHostedAdminConfig(
m_coreController->m_serversRepository->serverIdAt(serverIndex));
QVERIFY2(adminBeforeRemoval.has_value(), "Server config should be SelfHostedAdminServerConfig");
const SelfHostedAdminServerConfig *selfHostedBeforeRemoval = &(*adminBeforeRemoval);
QVERIFY2(!selfHostedBeforeRemoval->containers.isEmpty(), "Server should have containers before removal");
QVERIFY2(selfHostedBeforeRemoval->defaultContainer != DockerContainer::None, "Server should have default container before removal");
qDebug() << "Containers before removal:" << selfHostedBeforeRemoval->containers.size();
const QString removeServerId = m_coreController->m_serversRepository->serverIdAt(serverIndex);
ErrorCode removeError = m_coreController->m_installController->removeAllContainers(removeServerId);
QVERIFY2(removeError == ErrorCode::NoError,
QString("removeAllContainers should succeed. Error: %1")
.arg(static_cast<int>(removeError))
.toUtf8().constData());
qDebug() << "All containers removed successfully";
const auto adminAfterRemoval = m_coreController->m_serversRepository->selfHostedAdminConfig(
m_coreController->m_serversRepository->serverIdAt(serverIndex));
QVERIFY2(adminAfterRemoval.has_value(), "Server config should be SelfHostedAdminServerConfig");
const SelfHostedAdminServerConfig *selfHostedAfterRemoval = &(*adminAfterRemoval);
QVERIFY2(selfHostedAfterRemoval->containers.isEmpty(),
"Server should have no containers after removal");
QVERIFY2(selfHostedAfterRemoval->defaultContainer == DockerContainer::None,
"Default container should be None after removal");
qDebug() << "Containers after removal:" << selfHostedAfterRemoval->containers.size();
verifyAdminAccess(serverIndex);
qDebug() << "Test completed successfully. All containers removed and server is empty.";
}
};
QTEST_MAIN(TestSelfHostedServerSetup)
#include "testSelfHostedServerSetup.moc"

View File

@@ -0,0 +1,114 @@
#include <QTest>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include "core/controllers/coreController.h"
#include "core/repositories/secureServersRepository.h"
#include "core/models/serverDescription.h"
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
#include "core/utils/serverConfigUtils.h"
using namespace amnezia;
class TestServerEdgeCases : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
m_coreController->m_serversRepository->invalidateCache();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testInvalidIndexOperations() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult.config);
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "Should have 1 server");
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
QSignalSpy serverEditedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited);
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(-1));
QVERIFY2(serverRemovedSpy.count() == 0, "serverRemoved should NOT be emitted for invalid index");
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(10));
QVERIFY2(serverRemovedSpy.count() == 0, "serverRemoved should NOT be emitted for invalid index");
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(100));
QVERIFY2(serverRemovedSpy.count() == 0, "serverRemoved should NOT be emitted for invalid index");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "Server count should remain 1");
const QString validServerId = m_coreController->m_serversController->getServerId(0);
const serverConfigUtils::ConfigType editKind =
m_coreController->m_serversRepository->serverKind(validServerId);
m_coreController->m_serversRepository->editServer(m_coreController->m_serversController->getServerId(-1),
QJsonObject(), editKind);
QVERIFY2(serverEditedSpy.count() == 0, "serverEdited should NOT be emitted for invalid index");
m_coreController->m_serversRepository->editServer(m_coreController->m_serversController->getServerId(10),
QJsonObject(), editKind);
QVERIFY2(serverEditedSpy.count() == 0, "serverEdited should NOT be emitted for invalid index");
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(-1));
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged should NOT be emitted for invalid index");
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(10));
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged should NOT be emitted for invalid index");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default server index should remain 0");
}
void testEmptyRepositoryOperations() {
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
QSignalSpy serverEditedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited);
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 0, "Should start with 0 servers");
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
QVERIFY2(serverRemovedSpy.count() == 0, "serverRemoved should NOT be emitted for empty repository");
m_coreController->m_serversRepository->editServer(m_coreController->m_serversController->getServerId(0),
SelfHostedAdminServerConfig {}.toJson(),
serverConfigUtils::ConfigType::SelfHostedAdmin);
QVERIFY2(serverEditedSpy.count() == 0, "serverEdited should NOT be emitted for empty repository");
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(0));
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged should NOT be emitted for empty repository");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default server index should be 0 for empty repository");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 0, "Server count should remain 0");
}
};
QTEST_MAIN(TestServerEdgeCases)
#include "testServerEdgeCases.moc"

View File

@@ -0,0 +1,106 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "tests/testServerRepositoryHelpers.h"
#include "ui/models/serversModel.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
using namespace amnezia;
class TestServerEdit : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
m_coreController->m_serversRepository->invalidateCache();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testServerEditTriggersHandlers() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult.config);
QVERIFY2(importFinishedSpy.count() == 1, "Import should succeed");
QSignalSpy serverEditedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited);
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
m_coreController->m_serversController->getServerId(0),
QStringLiteral("Edited AWG Server"));
QVERIFY2(serverEditedSpy.count() == 1, "serverEdited signal should be emitted");
QVERIFY2(serverEditedSpy.at(0).at(0).toString() == m_coreController->m_serversRepository->serverIdAt(0),
"serverEdited should emit edited server id");
const QString editedDesc = amnezia::test::serverDescription(m_coreController->m_serversRepository,
m_coreController->m_serversRepository->serverIdAt(0));
QVERIFY2(editedDesc == "Edited AWG Server", "Server description should be updated");
if (m_coreController->m_serversModel) {
QString modelDesc = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
QVERIFY2(modelDesc == "Edited AWG Server", "Server description in model should be updated");
}
}
void testServerEditPreservesDefault() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult1.config);
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
m_coreController->m_importCoreController->importConfig(importResult2.config);
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default server should be index 1");
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
m_coreController->m_serversController->getServerId(1),
QStringLiteral("Edited Default Server"));
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged should NOT be emitted when editing default server");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default server index should remain 1");
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
m_coreController->m_serversController->getServerId(0),
QStringLiteral("Edited Non-Default Server"));
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged should NOT be emitted when editing non-default server");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default server index should remain 1");
}
};
QTEST_MAIN(TestServerEdit)
#include "testServerEdit.moc"

View File

@@ -0,0 +1,93 @@
#ifndef TESTSERVERREPOSITORYHELPERS_H
#define TESTSERVERREPOSITORYHELPERS_H
#include <QString>
#include <QJsonObject>
#include "core/repositories/secureServersRepository.h"
#include "core/utils/serverConfigUtils.h"
namespace amnezia::test
{
inline QString serverDescription(SecureServersRepository *repo, const QString &serverId)
{
switch (repo->serverKind(serverId)) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = repo->selfHostedAdminConfig(serverId);
return cfg.has_value() ? cfg->description : QString();
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
const auto cfg = repo->selfHostedUserConfig(serverId);
return cfg.has_value() ? cfg->description : QString();
}
case serverConfigUtils::ConfigType::Native: {
const auto cfg = repo->nativeConfig(serverId);
return cfg.has_value() ? cfg->description : QString();
}
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
case serverConfigUtils::ConfigType::AmneziaFreeV3:
case serverConfigUtils::ConfigType::ExternalPremium: {
const auto cfg = repo->apiV2Config(serverId);
return cfg.has_value() ? cfg->description : QString();
}
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
case serverConfigUtils::ConfigType::AmneziaFreeV2: {
const auto cfg = repo->legacyApiConfig(serverId);
return cfg.has_value() ? cfg->description : QString();
}
case serverConfigUtils::ConfigType::Invalid:
default:
return {};
}
}
inline void setServerDescription(SecureServersRepository *repo, const QString &serverId, const QString &description)
{
const serverConfigUtils::ConfigType kind = repo->serverKind(serverId);
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
auto cfg = repo->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) return;
cfg->description = description;
cfg->displayName = description;
repo->editServer(serverId, cfg->toJson(), kind);
return;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
auto cfg = repo->selfHostedUserConfig(serverId);
if (!cfg.has_value()) return;
cfg->description = description;
cfg->displayName = description;
repo->editServer(serverId, cfg->toJson(), kind);
return;
}
case serverConfigUtils::ConfigType::Native: {
auto cfg = repo->nativeConfig(serverId);
if (!cfg.has_value()) return;
cfg->description = description;
cfg->displayName = description;
repo->editServer(serverId, cfg->toJson(), kind);
return;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
case serverConfigUtils::ConfigType::AmneziaFreeV3:
case serverConfigUtils::ConfigType::ExternalPremium: {
auto cfg = repo->apiV2Config(serverId);
if (!cfg.has_value()) return;
cfg->description = description;
cfg->displayName = description;
repo->editServer(serverId, cfg->toJson(), kind);
return;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
case serverConfigUtils::ConfigType::AmneziaFreeV2:
case serverConfigUtils::ConfigType::Invalid:
default:
return;
}
}
} // namespace amnezia::test
#endif

View File

@@ -0,0 +1,112 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "tests/testServerRepositoryHelpers.h"
#include "ui/models/serversModel.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
using namespace amnezia;
class TestServersModelSync : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testServersModelSyncOnOperations() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
if (!m_coreController->m_serversModel) {
QSKIP("ServersModel not available");
}
QVERIFY2(m_coreController->m_serversModel->rowCount() == 0, "Initial model row count should be 0");
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult.config);
QVERIFY2(m_coreController->m_serversModel->rowCount() == 1, "Model should have 1 row after import");
QString modelDesc1 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
QVERIFY2(modelDesc1 == "AWG Server", "Model should have correct server name");
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
m_coreController->m_serversController->getServerId(0),
QStringLiteral("Edited AWG Server"));
QString modelDesc2 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
QVERIFY2(modelDesc2 == "Edited AWG Server", "Model should be updated after edit");
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
QVERIFY2(m_coreController->m_serversModel->rowCount() == 0, "Model should have 0 rows after removal");
}
void testServersModelDefaultIndexSync() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
if (!m_coreController->m_serversModel) {
QSKIP("ServersModel not available");
}
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult1.config);
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
m_coreController->m_importCoreController->importConfig(importResult2.config);
auto importResult3 = m_coreController->m_importCoreController->extractConfigFromData(wgKey);
m_coreController->m_importCoreController->importConfig(importResult3.config);
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default should be index 2");
QVERIFY2(m_coreController->m_serversModel->rowCount() == 3, "Model should have 3 rows");
bool isDefault0 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::IsDefaultRole).toBool();
bool isDefault1 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(1, 0), ServersModel::IsDefaultRole).toBool();
bool isDefault2 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(2, 0), ServersModel::IsDefaultRole).toBool();
QVERIFY2(!isDefault0, "Server 0 should not be default");
QVERIFY2(!isDefault1, "Server 1 should not be default");
QVERIFY2(isDefault2, "Server 2 should be default");
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(0));
isDefault0 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::IsDefaultRole).toBool();
isDefault2 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(2, 0), ServersModel::IsDefaultRole).toBool();
QVERIFY2(isDefault0, "Server 0 should be default after change");
QVERIFY2(!isDefault2, "Server 2 should not be default after change");
}
};
QTEST_MAIN(TestServersModelSync)
#include "testServersModelSync.moc"

View File

@@ -0,0 +1,265 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include <QLocale>
#include "core/controllers/coreController.h"
#include "ui/controllers/settingsUiController.h"
#include "ui/controllers/languageUiController.h"
#include "ui/models/allowedDnsModel.h"
#include "ui/models/ipSplitTunnelingModel.h"
#include "ui/models/appSplitTunnelingModel.h"
#include "ui/models/languageModel.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
class TestSettingsSignals : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
}
void testDnsSettingsSignals() {
QSignalSpy primaryDnsChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::primaryDnsChanged);
QSignalSpy secondaryDnsChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::secondaryDnsChanged);
QSignalSpy allowedDnsServersChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::allowedDnsServersChanged);
QString primaryDns = "8.8.8.8";
QString secondaryDns = "8.8.4.4";
m_coreController->m_settingsUiController->setPrimaryDns(primaryDns);
QVERIFY2(primaryDnsChangedSpy.count() == 1, "primaryDnsChanged signal should be emitted");
QVERIFY2(m_coreController->m_settingsController->getPrimaryDns() == primaryDns, "Primary DNS should be updated in SettingsController");
QVERIFY2(m_coreController->m_settingsUiController->getPrimaryDns() == primaryDns, "Primary DNS should be available in SettingsUiController");
QVERIFY2(m_coreController->m_appSettingsRepository->primaryDns() == primaryDns, "Primary DNS should be available in SecureAppSettingsRepository");
m_coreController->m_settingsUiController->setSecondaryDns(secondaryDns);
QVERIFY2(secondaryDnsChangedSpy.count() == 1, "secondaryDnsChanged signal should be emitted");
QVERIFY2(m_coreController->m_settingsController->getSecondaryDns() == secondaryDns, "Secondary DNS should be updated in SettingsController");
QVERIFY2(m_coreController->m_settingsUiController->getSecondaryDns() == secondaryDns, "Secondary DNS should be available in SettingsUiController");
QVERIFY2(m_coreController->m_appSettingsRepository->secondaryDns() == secondaryDns, "Secondary DNS should be available in SecureAppSettingsRepository");
QStringList dnsList = {"1.1.1.1", "1.0.0.1"};
m_coreController->m_allowedDnsController->addDnsList(dnsList, true);
QVERIFY2(allowedDnsServersChangedSpy.count() == 1, "allowedDnsServersChanged signal should be emitted");
QVERIFY2(m_coreController->m_appSettingsRepository->getAllowedDnsServers() == dnsList, "Allowed DNS servers should be updated in SecureAppSettingsRepository");
QVERIFY2(m_coreController->m_allowedDnsController->getCurrentDnsServers() == dnsList, "Allowed DNS servers should be available in AllowedDnsController");
QVERIFY2(m_coreController->m_allowedDnsUiController != nullptr, "AllowedDnsUiController should exist");
QVERIFY2(m_coreController->m_allowedDnsModel != nullptr, "AllowedDnsModel should exist");
QStringList modelDnsList;
for (int i = 0; i < m_coreController->m_allowedDnsModel->rowCount(); ++i) {
modelDnsList.append(m_coreController->m_allowedDnsModel->data(m_coreController->m_allowedDnsModel->index(i, 0), AllowedDnsModel::IpRole).toString());
}
QVERIFY2(modelDnsList == dnsList, "Allowed DNS servers should be available in AllowedDnsModel");
}
void testAmneziaDnsToggleSignal() {
QSignalSpy amneziaDnsToggledSpy(m_coreController->m_settingsUiController, &SettingsUiController::amneziaDnsToggled);
QSignalSpy useAmneziaDnsChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::useAmneziaDnsChanged);
bool initialValue = m_coreController->m_settingsController->isAmneziaDnsEnabled();
m_coreController->m_settingsUiController->toggleAmneziaDns(!initialValue);
QVERIFY2(amneziaDnsToggledSpy.count() == 1, "amneziaDnsToggled signal should be emitted");
QVERIFY2(amneziaDnsToggledSpy.at(0).at(0).toBool() == !initialValue, "amneziaDnsToggled should emit correct value");
QVERIFY2(useAmneziaDnsChangedSpy.count() == 1, "useAmneziaDnsChanged signal should be emitted");
QVERIFY2(m_coreController->m_settingsController->isAmneziaDnsEnabled() == !initialValue, "Amnezia DNS state should be updated in SettingsController");
QVERIFY2(m_coreController->m_settingsUiController->isAmneziaDnsEnabled() == !initialValue, "Amnezia DNS state should be available in SettingsUiController");
QVERIFY2(m_coreController->m_appSettingsRepository->useAmneziaDns() == !initialValue, "Amnezia DNS state should be available in SecureAppSettingsRepository");
m_coreController->m_settingsUiController->toggleAmneziaDns(initialValue);
QVERIFY2(amneziaDnsToggledSpy.count() == 2, "amneziaDnsToggled signal should be emitted again");
QVERIFY2(useAmneziaDnsChangedSpy.count() == 2, "useAmneziaDnsChanged signal should be emitted again");
QVERIFY2(m_coreController->m_settingsUiController->isAmneziaDnsEnabled() == initialValue, "Amnezia DNS state should be restored in SettingsUiController");
}
void testLoggingSignals() {
QSignalSpy loggingStateChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::loggingStateChanged);
QSignalSpy saveLogsChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::saveLogsChanged);
bool initialLogging = m_coreController->m_settingsController->isLoggingEnabled();
m_coreController->m_settingsUiController->toggleLogging(!initialLogging);
QVERIFY2(loggingStateChangedSpy.count() == 1, "loggingStateChanged signal should be emitted");
QVERIFY2(saveLogsChangedSpy.count() == 1, "saveLogsChanged signal should be emitted");
QVERIFY2(m_coreController->m_settingsController->isLoggingEnabled() == !initialLogging, "Logging state should be updated in SettingsController");
QVERIFY2(m_coreController->m_settingsUiController->isLoggingEnabled() == !initialLogging, "Logging state should be available in SettingsUiController");
QVERIFY2(m_coreController->m_appSettingsRepository->isSaveLogs() == !initialLogging, "Logging state should be available in SecureAppSettingsRepository");
m_coreController->m_settingsUiController->toggleLogging(initialLogging);
QVERIFY2(loggingStateChangedSpy.count() == 2, "loggingStateChanged signal should be emitted again");
QVERIFY2(saveLogsChangedSpy.count() == 2, "saveLogsChanged signal should be emitted again");
QVERIFY2(m_coreController->m_settingsUiController->isLoggingEnabled() == initialLogging, "Logging state should be restored in SettingsUiController");
}
void testScreenshotsSignals() {
QSignalSpy screenshotsEnabledChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged);
bool initialScreenshots = m_coreController->m_settingsController->isScreenshotsEnabled();
m_coreController->m_settingsUiController->toggleScreenshotsEnabled(!initialScreenshots);
QVERIFY2(screenshotsEnabledChangedSpy.count() == 1, "screenshotsEnabledChanged signal should be emitted");
QVERIFY2(screenshotsEnabledChangedSpy.at(0).at(0).toBool() == !initialScreenshots, "screenshotsEnabledChanged should emit correct value");
QVERIFY2(m_coreController->m_settingsController->isScreenshotsEnabled() == !initialScreenshots, "Screenshots state should be updated in SettingsController");
QVERIFY2(m_coreController->m_settingsUiController->isScreenshotsEnabled() == !initialScreenshots, "Screenshots state should be available in SettingsUiController");
QVERIFY2(m_coreController->m_appSettingsRepository->isScreenshotsEnabled() == !initialScreenshots, "Screenshots state should be available in SecureAppSettingsRepository");
}
void testStartMinimizedSignals() {
QSignalSpy startMinimizedChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::startMinimizedChanged);
bool initialStartMinimized = m_coreController->m_settingsController->isStartMinimizedEnabled();
m_coreController->m_settingsUiController->toggleStartMinimized(!initialStartMinimized);
QVERIFY2(startMinimizedChangedSpy.count() == 1, "startMinimizedChanged signal should be emitted");
QVERIFY2(m_coreController->m_settingsController->isStartMinimizedEnabled() == !initialStartMinimized, "Start minimized state should be updated in SettingsController");
QVERIFY2(m_coreController->m_settingsUiController->isStartMinimizedEnabled() == !initialStartMinimized, "Start minimized state should be available in SettingsUiController");
QVERIFY2(m_coreController->m_appSettingsRepository->isStartMinimized() == !initialStartMinimized, "Start minimized state should be available in SecureAppSettingsRepository");
}
void testAutoConnectSignals() {
bool initialAutoConnect = m_coreController->m_settingsController->isAutoConnectEnabled();
m_coreController->m_settingsUiController->toggleAutoConnect(!initialAutoConnect);
QVERIFY2(m_coreController->m_settingsController->isAutoConnectEnabled() == !initialAutoConnect, "Auto connect state should be updated in SettingsController");
QVERIFY2(m_coreController->m_settingsUiController->isAutoConnectEnabled() == !initialAutoConnect, "Auto connect state should be available in SettingsUiController");
QVERIFY2(m_coreController->m_appSettingsRepository->isAutoConnect() == !initialAutoConnect, "Auto connect state should be available in SecureAppSettingsRepository");
m_coreController->m_settingsUiController->toggleAutoConnect(initialAutoConnect);
QVERIFY2(m_coreController->m_settingsController->isAutoConnectEnabled() == initialAutoConnect, "Auto connect state should be restored in SettingsController");
QVERIFY2(m_coreController->m_settingsUiController->isAutoConnectEnabled() == initialAutoConnect, "Auto connect state should be restored in SettingsUiController");
}
void testLanguageChangeSignals() {
QSignalSpy appLanguageChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appLanguageChanged);
QSignalSpy translationsUpdatedSpy(m_coreController->m_languageUiController, &LanguageUiController::translationsUpdated);
QLocale initialLocale = m_coreController->m_settingsController->getAppLanguage();
QLocale newLocale = (initialLocale.language() == QLocale::English) ? QLocale::Russian : QLocale::English;
m_coreController->m_settingsController->setAppLanguage(newLocale);
QVERIFY2(appLanguageChangedSpy.count() == 1, "appLanguageChanged signal should be emitted");
QVERIFY2(appLanguageChangedSpy.at(0).at(0).value<QLocale>() == newLocale, "appLanguageChanged should emit correct locale");
QVERIFY2(m_coreController->m_settingsController->getAppLanguage() == newLocale, "App language should be updated in SettingsController");
QVERIFY2(m_coreController->m_appSettingsRepository->getAppLanguage() == newLocale, "App language should be available in SecureAppSettingsRepository");
if (m_coreController->m_languageModel) {
QString newLanguageName = m_coreController->m_languageUiController->getCurrentLanguageName();
QVERIFY2(!newLanguageName.isEmpty(), "Language name should be available in LanguageUiController");
}
}
void testGatewayEndpointSignals() {
QSignalSpy gatewayEndpointChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::gatewayEndpointChanged);
QSignalSpy devGatewayEnvChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::devGatewayEnvChanged);
QString initialEndpoint = m_coreController->m_settingsController->getGatewayEndpoint();
QString newEndpoint = "https://test-gateway.example.com";
m_coreController->m_settingsUiController->setGatewayEndpoint(newEndpoint);
QVERIFY2(gatewayEndpointChangedSpy.count() == 1, "gatewayEndpointChanged signal should be emitted");
QVERIFY2(gatewayEndpointChangedSpy.at(0).at(0).toString() == newEndpoint, "gatewayEndpointChanged should emit correct endpoint");
QVERIFY2(m_coreController->m_settingsController->getGatewayEndpoint() == newEndpoint, "Gateway endpoint should be updated in SettingsController");
QVERIFY2(m_coreController->m_settingsUiController->getGatewayEndpoint() == newEndpoint, "Gateway endpoint should be available in SettingsUiController");
QVERIFY2(m_coreController->m_appSettingsRepository->getGatewayEndpoint() == newEndpoint, "Gateway endpoint should be available in SecureAppSettingsRepository");
bool initialDevEnv = m_coreController->m_settingsController->isDevGatewayEnv();
m_coreController->m_settingsUiController->toggleDevGatewayEnv(!initialDevEnv);
QVERIFY2(devGatewayEnvChangedSpy.count() == 1, "devGatewayEnvChanged signal should be emitted");
QVERIFY2(devGatewayEnvChangedSpy.at(0).at(0).toBool() == !initialDevEnv, "devGatewayEnvChanged should emit correct value");
QVERIFY2(m_coreController->m_settingsController->isDevGatewayEnv() == !initialDevEnv, "Dev gateway env state should be updated in SettingsController");
QVERIFY2(m_coreController->m_settingsUiController->isDevGatewayEnv() == !initialDevEnv, "Dev gateway env state should be available in SettingsUiController");
QVERIFY2(m_coreController->m_appSettingsRepository->isDevGatewayEnv() == !initialDevEnv, "Dev gateway env state should be available in SecureAppSettingsRepository");
}
void testSettingsClearedSignal() {
QSignalSpy settingsClearedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::settingsCleared);
m_coreController->m_settingsController->clearSettings();
QVERIFY2(settingsClearedSpy.count() == 1, "settingsCleared signal should be emitted");
}
void testSplitTunnelingSignals() {
QSignalSpy siteSplitTunnelingToggledSpy(m_coreController->m_settingsController, &SettingsController::siteSplitTunnelingToggled);
QSignalSpy appSplitTunnelingToggledSpy(m_coreController->m_settingsController, &SettingsController::appSplitTunnelingToggled);
QSignalSpy sitesSplitTunnelingEnabledChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::sitesSplitTunnelingEnabledChanged);
QSignalSpy appsSplitTunnelingEnabledChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsSplitTunnelingEnabledChanged);
QSignalSpy routeModeChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::routeModeChanged);
QSignalSpy appsRouteModeChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsRouteModeChanged);
QSignalSpy sitesChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::sitesChanged);
QSignalSpy appsChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsChanged);
bool initialSitesSplitTunneling = m_coreController->m_ipSplitTunnelingController->isSplitTunnelingEnabled();
m_coreController->m_ipSplitTunnelingController->toggleSplitTunneling(!initialSitesSplitTunneling);
QVERIFY2(sitesSplitTunnelingEnabledChangedSpy.count() == 1, "sitesSplitTunnelingEnabledChanged signal should be emitted");
QVERIFY2(m_coreController->m_ipSplitTunnelingController->isSplitTunnelingEnabled() == !initialSitesSplitTunneling, "Sites split tunneling should be updated in IpSplitTunnelingController");
QVERIFY2(m_coreController->m_appSettingsRepository->isSitesSplitTunnelingEnabled() == !initialSitesSplitTunneling, "Sites split tunneling should be available in SecureAppSettingsRepository");
bool initialAppsSplitTunneling = m_coreController->m_appSplitTunnelingController->isSplitTunnelingEnabled();
m_coreController->m_appSplitTunnelingController->toggleSplitTunneling(!initialAppsSplitTunneling);
QVERIFY2(appsSplitTunnelingEnabledChangedSpy.count() == 1, "appsSplitTunnelingEnabledChanged signal should be emitted");
QVERIFY2(m_coreController->m_appSplitTunnelingController->isSplitTunnelingEnabled() == !initialAppsSplitTunneling, "Apps split tunneling should be updated in AppSplitTunnelingController");
QVERIFY2(m_coreController->m_appSettingsRepository->isAppsSplitTunnelingEnabled() == !initialAppsSplitTunneling, "Apps split tunneling should be available in SecureAppSettingsRepository");
RouteMode initialRouteMode = m_coreController->m_ipSplitTunnelingController->getRouteMode();
RouteMode newRouteMode = (initialRouteMode == RouteMode::VpnOnlyForwardSites)
? RouteMode::VpnAllExceptSites
: RouteMode::VpnOnlyForwardSites;
m_coreController->m_ipSplitTunnelingController->setRouteMode(newRouteMode);
QVERIFY2(routeModeChangedSpy.count() == 1, "routeModeChanged signal should be emitted");
QVERIFY2(m_coreController->m_ipSplitTunnelingController->getRouteMode() == newRouteMode, "Route mode should be updated in IpSplitTunnelingController");
QVERIFY2(m_coreController->m_appSettingsRepository->routeMode() == newRouteMode, "Route mode should be available in SecureAppSettingsRepository");
AppsRouteMode initialAppsRouteMode = m_coreController->m_appSplitTunnelingController->getRouteMode();
AppsRouteMode newAppsRouteMode = (initialAppsRouteMode == AppsRouteMode::VpnAllExceptApps)
? AppsRouteMode::VpnAllApps
: AppsRouteMode::VpnAllExceptApps;
m_coreController->m_appSplitTunnelingController->setRouteMode(newAppsRouteMode);
QVERIFY2(appsRouteModeChangedSpy.count() == 1, "appsRouteModeChanged signal should be emitted");
QVERIFY2(m_coreController->m_appSplitTunnelingController->getRouteMode() == newAppsRouteMode, "Apps route mode should be updated in AppSplitTunnelingController");
QVERIFY2(m_coreController->m_appSettingsRepository->appsRouteMode() == newAppsRouteMode, "Apps route mode should be available in SecureAppSettingsRepository");
QMap<QString, QString> sitesMap{{"example.com", "1.2.3.4"}};
m_coreController->m_ipSplitTunnelingController->addSites(sitesMap, true);
QVERIFY2(sitesChangedSpy.count() >= 1, "sitesChanged signal should be emitted");
QVector<QPair<QString, QString>> currentSites = m_coreController->m_ipSplitTunnelingController->getCurrentSites();
QVERIFY2(currentSites.size() >= 1, "Sites should be available in IpSplitTunnelingController");
QVERIFY2(m_coreController->m_ipSplitTunnelingUiController != nullptr, "IpSplitTunnelingUiController should exist");
QVERIFY2(m_coreController->m_ipSplitTunnelingModel != nullptr, "IpSplitTunnelingModel should exist");
m_coreController->m_ipSplitTunnelingUiController->updateModel();
QVERIFY2(m_coreController->m_ipSplitTunnelingModel->rowCount() >= 1, "Sites should be available in IpSplitTunnelingModel");
QString modelUrl = m_coreController->m_ipSplitTunnelingModel->data(m_coreController->m_ipSplitTunnelingModel->index(0, 0), IpSplitTunnelingModel::UrlRole).toString();
QVERIFY2(modelUrl == "example.com", "Site URL should be available in IpSplitTunnelingModel");
}
};
QTEST_MAIN(TestSettingsSignals)
#include "testSettingsSignals.moc"

View File

@@ -0,0 +1,88 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
using namespace amnezia;
class TestSignalOrder : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
m_coreController->m_serversRepository->invalidateCache();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testSignalOrderOnImport() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult.config);
QVERIFY2(importFinishedSpy.count() == 1, "importFinished signal should be emitted");
QVERIFY2(serverAddedSpy.count() == 1, "serverAdded signal should be emitted");
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged signal should NOT be emitted (default is already 0)");
QVERIFY2(serverAddedSpy.at(0).count() > 0, "serverAdded should have arguments");
}
void testSignalOrderOnRemoveDefault() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult1.config);
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
m_coreController->m_importCoreController->importConfig(importResult2.config);
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default should be index 1");
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(1));
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
QVERIFY2(defaultServerChangedSpy.count() == 1, "defaultServerChanged signal should be emitted when removing default server");
QVERIFY2(defaultServerChangedSpy.at(0).at(0).toString() == m_coreController->m_serversRepository->defaultServerId(),
"defaultServerChanged should emit new default server id");
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default server index should be 0");
}
};
QTEST_MAIN(TestSignalOrder)
#include "testSignalOrder.moc"

View File

@@ -0,0 +1,306 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <QUuid>
#include <QSignalSpy>
#include <QModelIndex>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "core/controllers/selfhosted/importController.h"
#include "ui/models/serversModel.h"
#include "ui/models/containersModel.h"
#include "core/utils/constants/configKeys.h"
using namespace amnezia;
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/containerEnum.h"
#include "core/utils/protocolEnum.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
using namespace amnezia;
namespace {
int defaultServerRow(const QVector<ServerDescription> &descriptions, const QString &defaultServerId)
{
for (int i = 0; i < descriptions.size(); ++i) {
if (descriptions.at(i).serverId == defaultServerId) {
return i;
}
}
return -1;
}
} // namespace
class TestUiServersModelAndController : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
QJsonObject createAwg2Config()
{
QJsonObject clientConfig;
clientConfig[configKey::mtu] = protocols::awg::defaultMtu;
clientConfig[configKey::junkPacketCount] = protocols::awg::defaultJunkPacketCount;
clientConfig[configKey::junkPacketMinSize] = protocols::awg::defaultJunkPacketMinSize;
clientConfig[configKey::junkPacketMaxSize] = protocols::awg::defaultJunkPacketMaxSize;
clientConfig[configKey::specialJunk1] = protocols::awg::defaultSpecialJunk1;
clientConfig[configKey::specialJunk2] = protocols::awg::defaultSpecialJunk2;
clientConfig[configKey::specialJunk3] = protocols::awg::defaultSpecialJunk3;
clientConfig[configKey::specialJunk4] = protocols::awg::defaultSpecialJunk4;
clientConfig[configKey::specialJunk5] = protocols::awg::defaultSpecialJunk5;
clientConfig[configKey::clientPrivKey] = "test_client_private_key";
clientConfig[configKey::clientPubKey] = "test_client_public_key";
clientConfig[configKey::serverPubKey] = "test_server_public_key";
clientConfig[configKey::pskKey] = "test_psk_key";
clientConfig[configKey::clientIp] = "10.8.1.2";
clientConfig[configKey::allowedIps] = QJsonArray::fromStringList({"0.0.0.0/0"});
QJsonObject awgConfig;
awgConfig[configKey::lastConfig] = QString(QJsonDocument(clientConfig).toJson());
awgConfig[configKey::port] = protocols::awg::defaultPort;
awgConfig[configKey::transportProto] = "udp";
awgConfig[configKey::protocolVersion] = protocols::awg::awgV2;
awgConfig[configKey::subnetAddress] = protocols::wireguard::defaultSubnetAddress;
awgConfig[configKey::junkPacketCount] = protocols::awg::defaultJunkPacketCount;
awgConfig[configKey::junkPacketMinSize] = protocols::awg::defaultJunkPacketMinSize;
awgConfig[configKey::junkPacketMaxSize] = protocols::awg::defaultJunkPacketMaxSize;
awgConfig[configKey::initPacketJunkSize] = protocols::awg::defaultInitPacketJunkSize;
awgConfig[configKey::responsePacketJunkSize] = protocols::awg::defaultResponsePacketJunkSize;
awgConfig[configKey::cookieReplyPacketJunkSize] = protocols::awg::defaultCookieReplyPacketJunkSize;
awgConfig[configKey::transportPacketJunkSize] = protocols::awg::defaultTransportPacketJunkSize;
awgConfig[configKey::initPacketMagicHeader] = protocols::awg::defaultInitPacketMagicHeader;
awgConfig[configKey::responsePacketMagicHeader] = protocols::awg::defaultResponsePacketMagicHeader;
awgConfig[configKey::underloadPacketMagicHeader] = protocols::awg::defaultUnderloadPacketMagicHeader;
awgConfig[configKey::transportPacketMagicHeader] = protocols::awg::defaultTransportPacketMagicHeader;
awgConfig[configKey::specialJunk1] = protocols::awg::defaultSpecialJunk1;
awgConfig[configKey::specialJunk2] = protocols::awg::defaultSpecialJunk2;
awgConfig[configKey::specialJunk3] = protocols::awg::defaultSpecialJunk3;
awgConfig[configKey::specialJunk4] = protocols::awg::defaultSpecialJunk4;
awgConfig[configKey::specialJunk5] = protocols::awg::defaultSpecialJunk5;
awgConfig[configKey::isThirdPartyConfig] = true;
QJsonObject container;
container[configKey::container] = "amnezia-awg";
container[configKey::awg] = awgConfig;
QJsonArray containers;
containers.append(container);
QJsonObject config;
config[configKey::containers] = containers;
config[configKey::defaultContainer] = "amnezia-awg";
config[configKey::description] = "AWG2 Test Server";
config[configKey::hostName] = "test.example.com";
return config;
}
QJsonObject createServerDescriptionTestConfig(bool withAmneziaDns)
{
QJsonObject config = createAwg2Config();
config[configKey::description] = "Server 1";
if (withAmneziaDns) {
config[configKey::dns1] = protocols::dns::amneziaDnsIp;
}
return config;
}
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testUiServersModelAndControllerRoles() {
QJsonObject testConfig = createAwg2Config();
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
m_coreController->m_importCoreController->importConfig(testConfig);
QVERIFY2(importFinishedSpy.count() == 1, "importFinished signal should be emitted");
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "Server should be imported");
int serverIndex = m_coreController->m_serversRepository->defaultServerIndex();
QVERIFY2(serverIndex == 0, "Default server index should be 0");
if (m_coreController->m_serversModel) {
QVERIFY2(m_coreController->m_serversModel->rowCount() == 1, "ServersModel should have 1 row");
QModelIndex serverModelIndex = m_coreController->m_serversModel->index(0, 0);
QVERIFY2(serverModelIndex.isValid(), "Server model index should be valid");
QString serverName = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::NameRole).toString();
QVERIFY2(serverName == "AWG2 Test Server", QString("Server name should be 'AWG2 Test Server', got '%1'").arg(serverName).toUtf8().constData());
QString serverDescription = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::ServerDescriptionRole).toString();
QVERIFY2(serverDescription.contains("test.example.com"), QString("Server description should contain hostname, got '%1'").arg(serverDescription).toUtf8().constData());
QString hostName = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::HostNameRole).toString();
QVERIFY2(hostName == "test.example.com", "Host name should match");
bool isDefault = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::IsDefaultRole).toBool();
QVERIFY2(isDefault == true, "Server should be default");
bool hasInstalledContainers = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::HasInstalledContainers).toBool();
QVERIFY2(hasInstalledContainers == true, "Server should have installed containers");
bool hasWriteAccess = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::HasWriteAccessRole).toBool();
QVERIFY2(hasWriteAccess == false, "Server should not have write access for imported config");
int defaultContainerRole = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::DefaultContainerRole).toInt();
DockerContainer expectedContainer = DockerContainer::Awg;
QVERIFY2(defaultContainerRole == static_cast<int>(expectedContainer), "Default container should be Awg");
}
if (m_coreController->m_serversUiController) {
m_coreController->m_serversUiController->setProcessedServerId(
m_coreController->m_serversUiController->getServerId(0));
QString hostName = "test.example.com";
QString collapsedDescription = m_coreController->m_serversUiController->getDefaultServerDescriptionCollapsed();
QString expectedCollapsed = "AmneziaWG (version 2) | " + hostName;
QVERIFY2(collapsedDescription == expectedCollapsed,
QString("Collapsed description should be '%1', got '%2'").arg(expectedCollapsed, collapsedDescription).toUtf8().constData());
QString expandedDescription = m_coreController->m_serversUiController->getDefaultServerDescriptionExpanded();
QString expectedExpanded = hostName;
QVERIFY2(expandedDescription == expectedExpanded,
QString("Expanded description should be '%1', got '%2'").arg(expectedExpanded, expandedDescription).toUtf8().constData());
}
if (m_coreController->m_containersModel) {
int awgContainerIndex = -1;
for (int i = 0; i < ContainerUtils::allContainers().size(); ++i) {
DockerContainer container = ContainerUtils::allContainers().at(i);
if (container == DockerContainer::Awg) {
awgContainerIndex = i;
break;
}
}
QVERIFY2(awgContainerIndex >= 0, "Awg container index should be found");
QModelIndex containerModelIndex = m_coreController->m_containersModel->index(awgContainerIndex, 0);
QVERIFY2(containerModelIndex.isValid(), "Container model index should be valid");
bool isInstalled = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::IsInstalledRole).toBool();
QVERIFY2(isInstalled == true, "Awg container should be installed");
bool isVpnContainer = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::IsVpnContainerRole).toBool();
QVERIFY2(isVpnContainer == true, "Awg container should be VPN container");
QString containerName = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::NameRole).toString();
QString expectedContainerName = ContainerUtils::containerHumanNames().value(DockerContainer::Awg);
QVERIFY2(containerName == expectedContainerName, QString("Container name should be '%1', got '%2'").arg(expectedContainerName, containerName).toUtf8().constData());
QString containerDescription = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::DescriptionRole).toString();
QString expectedDescription = ContainerUtils::containerDescriptions().value(DockerContainer::Awg);
QVERIFY2(containerDescription == expectedDescription, QString("Container description should match, got '%1'").arg(containerDescription).toUtf8().constData());
QString detailedDescription = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::DetailedDescriptionRole).toString();
QString expectedDetailedDescription = ContainerUtils::containerDetailedDescriptions().value(DockerContainer::Awg);
QVERIFY2(detailedDescription == expectedDetailedDescription, QString("Container detailed description should match, got '%1'").arg(detailedDescription).toUtf8().constData());
int serviceType = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::ServiceTypeRole).toInt();
QVERIFY2(serviceType == static_cast<int>(ProtocolEnumNS::ServiceType::Vpn), "Service type should be Vpn");
bool isSupported = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::IsSupportedRole).toBool();
QVERIFY2(isSupported == true, "Container should be supported");
bool isShareable = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::IsShareableRole).toBool();
QVERIFY2(isShareable == true, "Container should be shareable");
QJsonObject containerConfig = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::ConfigRole).toJsonObject();
QVERIFY2(!containerConfig.isEmpty(), "Container config should not be empty");
QVERIFY2(containerConfig.value(configKey::container).toString() == "amnezia-awg", "Container config should have correct container type");
QJsonObject awgProtocolConfig = containerConfig.value(configKey::awg).toObject();
QVERIFY2(!awgProtocolConfig.isEmpty(), "AWG protocol config should not be empty");
QString protocolVersion = awgProtocolConfig.value(configKey::protocolVersion).toString();
QVERIFY2(protocolVersion == protocols::awg::awgV2, QString("Protocol version should be '%1', got '%2'").arg(protocols::awg::awgV2, protocolVersion).toUtf8().constData());
QString port = awgProtocolConfig.value(configKey::port).toString();
QVERIFY2(port == protocols::awg::defaultPort, QString("Port should be '%1', got '%2'").arg(protocols::awg::defaultPort, port).toUtf8().constData());
QString subnetAddress = awgProtocolConfig.value(configKey::subnetAddress).toString();
QVERIFY2(subnetAddress == protocols::wireguard::defaultSubnetAddress, QString("Subnet address should be '%1', got '%2'").arg(protocols::wireguard::defaultSubnetAddress, subnetAddress).toUtf8().constData());
bool isThirdParty = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::IsThirdPartyConfigRole).toBool();
QVERIFY2(isThirdParty == true, "Imported config should be third party config");
DockerContainer dockerContainer = static_cast<DockerContainer>(m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::DockerContainerRole).toInt());
QVERIFY2(dockerContainer == DockerContainer::Awg, "Docker container should be Awg");
QString containerString = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::ContainerStringRole).toString();
QVERIFY2(containerString == "amnezia-awg", "Container string should be amnezia-awg");
}
}
void testServerDescriptionFormat() {
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
QJsonObject configNoDns = createServerDescriptionTestConfig(false);
m_coreController->m_importCoreController->importConfig(configNoDns);
QVERIFY2(importFinishedSpy.count() == 1, "importFinished should be emitted");
m_coreController->m_appSettingsRepository->setUseAmneziaDns(false);
QVector<ServerDescription> descriptionsNoDns = m_coreController->m_serversController->buildServerDescriptions(
m_coreController->m_appSettingsRepository->useAmneziaDns());
const QString defIdNoDns = m_coreController->m_serversRepository->defaultServerId();
m_coreController->m_serversModel->updateModel(descriptionsNoDns, defaultServerRow(descriptionsNoDns, defIdNoDns));
QString descNoDns = m_coreController->m_serversModel->data(
m_coreController->m_serversModel->index(0, 0), ServersModel::ServerDescriptionRole).toString();
QVERIFY2(descNoDns == "test.example.com",
QString("Without Amnezia DNS expected 'test.example.com', got '%1'").arg(descNoDns).toUtf8().constData());
m_coreController->m_serversRepository->clearServers();
if (m_coreController->m_serversRepository->serversCount() > 0) {
m_coreController->m_serversRepository->setDefaultServer(m_coreController->m_serversRepository->serverIdAt(0));
}
QJsonObject configWithDns = createServerDescriptionTestConfig(true);
m_coreController->m_importCoreController->importConfig(configWithDns);
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "Server should be imported");
m_coreController->m_appSettingsRepository->setUseAmneziaDns(true);
QVector<ServerDescription> descriptionsWithDns = m_coreController->m_serversController->buildServerDescriptions(
m_coreController->m_appSettingsRepository->useAmneziaDns());
const QString defIdWithDns = m_coreController->m_serversRepository->defaultServerId();
m_coreController->m_serversModel->updateModel(descriptionsWithDns, defaultServerRow(descriptionsWithDns, defIdWithDns));
QString descWithDns = m_coreController->m_serversModel->data(
m_coreController->m_serversModel->index(0, 0), ServersModel::ServerDescriptionRole).toString();
QVERIFY2(descWithDns == "Amnezia DNS | test.example.com",
QString("With Amnezia DNS expected 'Amnezia DNS | test.example.com', got '%1'").arg(descWithDns).toUtf8().constData());
}
};
QTEST_MAIN(TestUiServersModelAndController)
#include "testUiServersModelAndController.moc"

View File

@@ -65,7 +65,6 @@ SubscriptionUiController::SubscriptionUiController(ServersController* serversCon
ApiCountryModel* apiCountryModel,
ApiDevicesModel* apiDevicesModel,
SettingsController* settingsController,
ConnectionController* connectionController,
QObject *parent)
: QObject(parent),
m_serversController(serversController),
@@ -77,34 +76,13 @@ SubscriptionUiController::SubscriptionUiController(ServersController* serversCon
m_apiAccountInfoModel(apiAccountInfoModel),
m_apiCountryModel(apiCountryModel),
m_apiDevicesModel(apiDevicesModel),
m_settingsController(settingsController),
m_connectionController(connectionController)
m_settingsController(settingsController)
{
connect(m_apiServicesModel, &ApiServicesModel::serviceSelectionChanged, this, [this]() {
ApiServicesModel::ApiServicesData selectedServiceData = m_apiServicesModel->selectedServiceData();
m_apiSubscriptionPlansModel->updateModel(selectedServiceData.subscriptionPlansJson);
m_apiBenefitsModel->updateModel(selectedServiceData.benefits);
});
connect(this, &SubscriptionUiController::installServerFromApiFinished, this,
[this](const QString &, int preferredDefaultServerIndex) {
if (m_connectionController->isConnected()) {
return;
}
const int selectedServerIndex = preferredDefaultServerIndex >= 0
? preferredDefaultServerIndex
: (m_serversController->getServersCount() - 1);
const QString serverId = m_serversController->getServerId(selectedServerIndex);
if (!serverId.isEmpty()) {
m_serversController->setDefaultServer(serverId);
}
});
}
bool SubscriptionUiController::isCaptchaAwaitingUser() const
{
return m_captchaState.isPending;
}
bool SubscriptionUiController::exportVpnKey(const QString &serverId, const QString &fileName)
@@ -293,105 +271,18 @@ bool SubscriptionUiController::importFreeFromGateway()
}
SubscriptionController::ProtocolData protocolData = m_subscriptionController->generateProtocolData(serviceProtocol);
SubscriptionController::CaptchaInfo captchaInfo;
ErrorCode errorCode = m_subscriptionController->importServiceFromGateway(userCountryCode, serviceType,
serviceProtocol, protocolData,
captchaInfo);
serviceProtocol, protocolData);
if (errorCode == ErrorCode::NoError) {
emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName()));
return true;
} else if (errorCode == ErrorCode::ApiCaptchaRequiredError && captchaInfo.isRequired) {
m_captchaState.userCountryCode = userCountryCode;
m_captchaState.serviceType = serviceType;
m_captchaState.serviceProtocol = serviceProtocol;
m_captchaState.openvpnPrivKey = protocolData.certPrivKey;
m_captchaState.wireguardClientPrivKey = protocolData.wireGuardClientPrivKey;
m_captchaState.wireguardClientPubKey = protocolData.wireGuardClientPubKey;
m_captchaState.xrayUuid = protocolData.xrayUuid;
m_captchaState.isPending = true;
emit captchaRequired(captchaInfo.captchaId, captchaInfo.captchaImageBase64,
captchaInfo.hint.isEmpty() ? tr("Enter the digits from the image to continue") : captchaInfo.hint);
return false;
} else {
emit errorOccurred(errorCode);
return false;
}
}
void SubscriptionUiController::onCaptchaSolved(const QString &captchaId, const QString &solution)
{
if (!m_captchaState.isPending) {
return;
}
SubscriptionController::ProtocolData protocolData;
protocolData.certPrivKey = m_captchaState.openvpnPrivKey;
protocolData.wireGuardClientPrivKey = m_captchaState.wireguardClientPrivKey;
protocolData.wireGuardClientPubKey = m_captchaState.wireguardClientPubKey;
protocolData.xrayUuid = m_captchaState.xrayUuid;
SubscriptionController::CaptchaInfo retryCaptcha;
ErrorCode errorCode = m_subscriptionController->resolveImportServiceCaptcha(
m_captchaState.userCountryCode,
m_captchaState.serviceType,
m_captchaState.serviceProtocol,
protocolData,
captchaId,
solution,
&retryCaptcha);
if (errorCode == ErrorCode::NoError) {
m_captchaState.isPending = false;
emit captchaFlowDismissRequested();
emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName()));
return;
}
if ((errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|| errorCode == ErrorCode::ApiCaptchaRequiredError)
&& retryCaptcha.isRequired) {
emit captchaRequired(retryCaptcha.captchaId, retryCaptcha.captchaImageBase64,
retryCaptcha.hint.isEmpty() ? tr("Enter the digits from the image to continue") : retryCaptcha.hint);
return;
}
m_captchaState.isPending = false;
emit errorOccurred(errorCode);
}
void SubscriptionUiController::onRefreshCaptchaRequested()
{
if (!m_captchaState.isPending) {
return;
}
SubscriptionController::ProtocolData protocolData;
protocolData.certPrivKey = m_captchaState.openvpnPrivKey;
protocolData.wireGuardClientPrivKey = m_captchaState.wireguardClientPrivKey;
protocolData.wireGuardClientPubKey = m_captchaState.wireguardClientPubKey;
protocolData.xrayUuid = m_captchaState.xrayUuid;
SubscriptionController::CaptchaInfo captchaInfo;
ErrorCode errorCode = m_subscriptionController->importServiceFromGateway(
m_captchaState.userCountryCode,
m_captchaState.serviceType,
m_captchaState.serviceProtocol,
protocolData,
captchaInfo);
if (errorCode == ErrorCode::ApiCaptchaRequiredError && captchaInfo.isRequired) {
emit captchaRequired(captchaInfo.captchaId, captchaInfo.captchaImageBase64,
captchaInfo.hint.isEmpty() ? tr("Enter the digits from the image to continue") : captchaInfo.hint);
} else if (errorCode != ErrorCode::NoError) {
m_captchaState.isPending = false;
emit errorOccurred(errorCode);
}
}
bool SubscriptionUiController::importTrialFromGateway(const QString &email)
{
emit trialEmailError(QString());

View File

@@ -5,7 +5,6 @@
#include "core/controllers/serversController.h"
#include "core/controllers/settingsController.h"
#include "core/controllers/connectionController.h"
#include "core/controllers/api/servicesCatalogController.h"
#include "core/controllers/api/subscriptionController.h"
#include "ui/models/api/apiSubscriptionPlansModel.h"
@@ -29,7 +28,6 @@ public:
ApiCountryModel* apiCountryModel,
ApiDevicesModel* apiDevicesModel,
SettingsController* settingsController,
ConnectionController* connectionController,
QObject *parent = nullptr);
Q_PROPERTY(QList<QString> qrCodes READ getQrCodes NOTIFY vpnKeyExportReady)
@@ -58,10 +56,6 @@ public slots:
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
bool isVlessProtocol(const QString &serverId);
bool isCaptchaAwaitingUser() const;
void onCaptchaSolved(const QString &captchaId, const QString &solution);
void onRefreshCaptchaRequested();
void removeApiConfig(const QString &serverId);
void removeServer(const QString &serverId);
@@ -89,23 +83,9 @@ signals:
void apiServerRemoved(const QString &message);
void vpnKeyExportReady();
void captchaRequired(const QString &captchaId, const QString &captchaImageBase64, const QString &hint);
void captchaFlowDismissRequested();
void unsupportedConnectDrawerRequested();
private:
struct CaptchaState {
QString userCountryCode;
QString serviceType;
QString serviceProtocol;
QString openvpnPrivKey;
QString wireguardClientPrivKey;
QString wireguardClientPubKey;
QString xrayUuid;
bool isPending = false;
} m_captchaState;
private:
QList<QString> getQrCodes();
int getQrCodesCount();
@@ -124,7 +104,6 @@ private:
ApiCountryModel* m_apiCountryModel;
ApiDevicesModel* m_apiDevicesModel;
SettingsController* m_settingsController;
ConnectionController* m_connectionController;
};
#endif // SUBSCRIPTIONUICONTROLLER_H

View File

@@ -44,6 +44,7 @@ signals:
void connectionStateChanged();
void connectionErrorOccurred(ErrorCode errorCode);
void reconnectWithUpdatedContainer(const QString &message);
void connectButtonClicked();
void preparingConfig();

View File

@@ -12,6 +12,7 @@ LanguageUiController::LanguageUiController(SettingsController* settingsControlle
void LanguageUiController::onAppLanguageChanged(const QLocale &locale)
{
emit updateTranslations(locale);
emit translationsUpdated();
}
void LanguageUiController::changeLanguage(const LanguageSettings::AvailableLanguageEnum language)

View File

@@ -12,7 +12,6 @@
#include "core/utils/api/apiUtils.h"
#include "core/controllers/selfhosted/installController.h"
#include "core/controllers/connectionController.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
@@ -52,7 +51,6 @@ InstallUiController::InstallUiController(InstallController *installController,
Socks5ProxyConfigModel *socks5ConfigModel,
MtProxyConfigModel* mtConfigModel,
TelemtConfigModel *telemtConfigModel,
ConnectionController *connectionController,
QObject *parent)
: QObject(parent),
m_installController(installController),
@@ -71,8 +69,7 @@ InstallUiController::InstallUiController(InstallController *installController,
m_sftpConfigModel(sftpConfigModel),
m_socks5ConfigModel(socks5ConfigModel),
m_mtProxyConfigModel(mtConfigModel),
m_telemtConfigModel(telemtConfigModel),
m_connectionController(connectionController)
m_telemtConfigModel(telemtConfigModel)
{
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) {
@@ -136,10 +133,6 @@ void InstallUiController::install(DockerContainer container, int port, Transport
finishMessage += tr("\nAdded containers that were already installed on the server");
}
if (!m_connectionController->isConnected()) {
m_serversController->setDefaultServer(newServerId);
}
emit installServerFinished(finishMessage);
} else {
const auto adminBefore = m_serversController->selfHostedAdminConfig(serverId);
@@ -179,12 +172,7 @@ void InstallUiController::install(DockerContainer container, int port, Transport
"All installed containers have been added to the application");
}
const bool isServiceInstall = ContainerUtils::containerService(container) == ServiceType::Other;
if (!m_connectionController->isConnected() && !isServiceInstall) {
m_serversController->setDefaultContainer(serverId, container);
}
emit installContainerFinished(finishMessage, isServiceInstall);
emit installContainerFinished(finishMessage, ContainerUtils::containerService(container) == ServiceType::Other);
}
}
@@ -275,15 +263,11 @@ void InstallUiController::updateContainer(const QString &serverId, int container
}
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
const bool asyncUpdate = container == DockerContainer::MtProxy || container == DockerContainer::Telemt
|| container == DockerContainer::Xray || container == DockerContainer::SSXray;
if (asyncUpdate) {
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
emit serverIsBusy(true);
auto *watcher = new QFutureWatcher<ErrorCode>(this);
const Proto protocolTypeCopy = protocolType;
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
[this, watcher, serverId, container, closePage, protocolTypeCopy]() {
[this, watcher, serverId, container, closePage]() {
const ErrorCode errorCode = watcher->result();
watcher->deleteLater();
emit serverIsBusy(false);
@@ -292,8 +276,15 @@ void InstallUiController::updateContainer(const QString &serverId, int container
const ContainerConfig updatedConfig =
m_serversController->getContainerConfig(serverId, container);
m_protocolModel->updateModel(updatedConfig);
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolTypeCopy));
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
const auto defaultContainer =
m_serversController->getDefaultContainer(serverId);
if ((serverId == m_serversController->getDefaultServerId())
&& (container == defaultContainer)) {
emit currentContainerUpdated();
} else {
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
}
} else {
emit installationErrorOccurred(errorCode);
}
@@ -304,7 +295,7 @@ void InstallUiController::updateContainer(const QString &serverId, int container
InstallController *installController = m_installController;
QFuture<ErrorCode> future =
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
newConfigCopy]() mutable -> ErrorCode {
newConfigCopy]() mutable -> ErrorCode {
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
});
watcher->setFuture(future);
@@ -316,8 +307,13 @@ void InstallUiController::updateContainer(const QString &serverId, int container
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);
const auto defaultContainer = m_serversController->getDefaultContainer(serverId);
if ((serverId == m_serversController->getDefaultServerId()) && (container == defaultContainer)) {
emit currentContainerUpdated();
} else {
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
}
return;
}
@@ -431,34 +427,6 @@ void InstallUiController::removeContainer(const QString &serverId, int container
DockerContainer container = static_cast<DockerContainer>(containerIndex);
QString containerName = ContainerUtils::containerHumanNames().value(container);
const bool asyncRemove = container == DockerContainer::Xray || container == DockerContainer::SSXray;
if (asyncRemove) {
emit serverIsBusy(true);
auto *watcher = new QFutureWatcher<ErrorCode>(this);
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
[this, watcher, serverId, container, containerName, serverName]() {
const ErrorCode errorCode = watcher->result();
watcher->deleteLater();
emit serverIsBusy(false);
if (errorCode == ErrorCode::NoError) {
emit removeContainerFinished(
tr("%1 has been removed from the server '%2'").arg(containerName, serverName));
} else {
emit installationErrorOccurred(errorCode);
}
});
InstallController *installController = m_installController;
QFuture<ErrorCode> future = QtConcurrent::run(
[installController, serverId, container]() -> ErrorCode {
return installController->removeContainer(serverId, container);
});
watcher->setFuture(future);
return;
}
ErrorCode errorCode = m_installController->removeContainer(serverId, container);
if (errorCode == ErrorCode::NoError) {
@@ -549,12 +517,6 @@ void InstallUiController::setEncryptedPassphrase(QString passphrase)
void InstallUiController::addEmptyServer()
{
m_installController->addEmptyServer(m_processedServerCredentials);
if (!m_connectionController->isConnected()) {
const QString newServerId = m_serversController->getServerId(m_serversController->getServersCount() - 1);
if (!newServerId.isEmpty()) {
m_serversController->setDefaultServer(newServerId);
}
}
emit installServerFinished(tr("Server added successfully"));
}

View File

@@ -9,7 +9,6 @@
#include "core/utils/protocolEnum.h"
#include "core/controllers/serversController.h"
#include "core/controllers/settingsController.h"
#include "core/controllers/connectionController.h"
#include "core/controllers/selfhosted/usersController.h"
#include "core/controllers/selfhosted/installController.h"
#include "core/utils/errorCodes.h"
@@ -53,7 +52,6 @@ public:
Socks5ProxyConfigModel* socks5ConfigModel,
MtProxyConfigModel* mtConfigModel,
TelemtConfigModel* telemtConfigModel,
ConnectionController* connectionController,
QObject *parent = nullptr);
~InstallUiController();
@@ -129,6 +127,8 @@ signals:
void serverIsBusy(const bool isBusy);
void cancelInstallation();
void currentContainerUpdated();
void cachedProfileCleared(const QString &message);
void apiConfigRemoved(const QString &message);
@@ -155,7 +155,6 @@ private:
Socks5ProxyConfigModel* m_socks5ConfigModel;
MtProxyConfigModel* m_mtProxyConfigModel;
TelemtConfigModel* m_telemtConfigModel;
ConnectionController* m_connectionController;
ServerCredentials m_processedServerCredentials;

View File

@@ -31,12 +31,6 @@ bool descriptionsHaveGatewayServers(const QVector<ServerDescription> &list)
}
return false;
}
const ServerDescription &emptyServerDescription()
{
static const ServerDescription s_emptyDescription;
return s_emptyDescription;
}
} // namespace
ServersUiController::ServersUiController(ServersController* serversController,
SettingsController* settingsController,
@@ -106,6 +100,8 @@ void ServersUiController::setDefaultServer(const QString &serverId)
return;
}
m_serversController->setDefaultServer(serverId);
updateModel();
emit defaultServerIdChanged(serverId);
}
void ServersUiController::setDefaultContainer(const QString &serverId, int containerIndex)
@@ -124,12 +120,12 @@ void ServersUiController::toggleAmneziaDns(bool enabled)
updateModel();
}
void ServersUiController::onDefaultServerChanged(const QString &defaultServerId)
void ServersUiController::onDefaultServerChanged(const QString &/*defaultServerId*/)
{
m_serversModel->setDefaultServerId(defaultServerId);
updateModel();
setProcessedServerId(m_serversController->getDefaultServerId());
updateDefaultServerContainersModel();
emit defaultServerIdChanged(defaultServerId);
emit defaultServerIdChanged(m_serversController->getDefaultServerId());
}
void ServersUiController::updateModel()
@@ -140,21 +136,27 @@ void ServersUiController::updateModel()
const QString defaultServerId = m_serversController->getDefaultServerId();
const bool hadServersFromGatewayBefore = descriptionsHaveGatewayServers(m_orderedServerDescriptions);
const bool hasServersFromGatewayNow = descriptionsHaveGatewayServers(descriptions);
const int listCount = descriptions.size();
const int defaultRowInDescriptions = rowForServerId(descriptions, defaultServerId);
m_orderedServerDescriptions = descriptions;
if (m_orderedServerDescriptions.isEmpty()) {
if (!m_processedServerId.isEmpty()) {
setProcessedServerId(QString());
}
if (listCount == 0) {
setProcessedServerId(QString());
} else if (m_processedServerIndex >= listCount) {
setProcessedServerId(defaultServerId);
} else if (!m_processedServerId.isEmpty()) {
const int row = rowForServerId(m_orderedServerDescriptions, m_processedServerId);
if (row < 0) {
setProcessedServerId(QString());
setProcessedServerId(defaultServerId);
} else {
setProcessedServerId(m_processedServerId);
}
} else if (defaultRowInDescriptions >= 0) {
setProcessedServerId(defaultServerId);
}
m_serversModel->updateModel(m_orderedServerDescriptions, defaultServerId);
m_serversModel->updateModel(m_orderedServerDescriptions, defaultRowInDescriptions);
updateContainersModel();
updateDefaultServerContainersModel();
@@ -164,6 +166,7 @@ void ServersUiController::updateModel()
}
emit defaultServerIdChanged(defaultServerId);
emit defaultServerIndexChanged(defaultServerIndex());
}
QString ServersUiController::getDefaultServerId() const
@@ -173,35 +176,60 @@ QString ServersUiController::getDefaultServerId() const
QString ServersUiController::getDefaultServerName() const
{
return serverName(getDefaultServerId());
const QString defaultServerId = m_serversController->getDefaultServerId();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == defaultServerId) {
return description.serverName;
}
}
return QString();
}
QString ServersUiController::getDefaultServerDefaultContainerName() const
{
const auto &description = serverDescriptionById(getDefaultServerId());
if (description.serverId.isEmpty()) {
return QString();
const QString defaultServerId = m_serversController->getDefaultServerId();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == defaultServerId) {
return ContainerUtils::containerHumanNames().value(description.defaultContainer);
}
}
return ContainerUtils::containerHumanNames().value(description.defaultContainer);
return QString();
}
QString ServersUiController::getDefaultServerDescriptionCollapsed() const
{
return serverDescriptionById(getDefaultServerId()).collapsedServerDescription;
const QString defaultServerId = m_serversController->getDefaultServerId();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == defaultServerId) {
return description.collapsedServerDescription;
}
}
return QString();
}
QString ServersUiController::getDefaultServerImagePathCollapsed() const
{
const auto &description = serverDescriptionById(getDefaultServerId());
if (!description.isApiV2 || description.apiServerCountryCode.isEmpty()) {
return "";
const QString defaultServerId = m_serversController->getDefaultServerId();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == defaultServerId) {
if (!description.isApiV2 || description.apiServerCountryCode.isEmpty()) {
return "";
}
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(description.apiServerCountryCode.toUpper());
}
}
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(description.apiServerCountryCode.toUpper());
return "";
}
QString ServersUiController::getDefaultServerDescriptionExpanded() const
{
return serverDescriptionById(getDefaultServerId()).expandedServerDescription;
const QString defaultServerId = m_serversController->getDefaultServerId();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == defaultServerId) {
return description.expandedServerDescription;
}
}
return QString();
}
bool ServersUiController::isDefaultServerDefaultContainerHasSplitTunneling() const
@@ -253,75 +281,15 @@ bool ServersUiController::isDefaultServerDefaultContainerHasSplitTunneling() con
bool ServersUiController::isDefaultServerFromApi() const
{
return isServerFromApi(getDefaultServerId());
}
bool ServersUiController::hasServerWithWriteAccess() const
{
const QString defaultServerId = m_serversController->getDefaultServerId();
for (const auto &description : m_orderedServerDescriptions) {
if (description.hasWriteAccess) {
return true;
if (description.serverId == defaultServerId) {
return description.isApiV2;
}
}
return false;
}
QString ServersUiController::serverName(const QString &serverId) const
{
return serverDescriptionById(serverId).serverName;
}
QString ServersUiController::serverHostName(const QString &serverId) const
{
return serverDescriptionById(serverId).hostName;
}
int ServersUiController::serverDefaultContainer(const QString &serverId) const
{
const auto &description = serverDescriptionById(serverId);
return description.serverId.isEmpty() ? -1 : static_cast<int>(description.defaultContainer);
}
bool ServersUiController::isServerFromApi(const QString &serverId) const
{
return serverDescriptionById(serverId).isServerFromGatewayApi;
}
bool ServersUiController::isServerCountrySelectionAvailable(const QString &serverId) const
{
return serverDescriptionById(serverId).isCountrySelectionAvailable;
}
bool ServersUiController::isServerHasWriteAccess(const QString &serverId) const
{
return serverDescriptionById(serverId).hasWriteAccess;
}
bool ServersUiController::serverHasInstalledContainers(const QString &serverId) const
{
return serverDescriptionById(serverId).hasInstalledVpnContainers;
}
QString ServersUiController::serverAdEndpoint(const QString &serverId) const
{
return serverDescriptionById(serverId).adEndpoint;
}
bool ServersUiController::isServerRenewalAvailable(const QString &serverId) const
{
return serverDescriptionById(serverId).isRenewalAvailable;
}
bool ServersUiController::isServerSubscriptionExpired(const QString &serverId) const
{
return serverDescriptionById(serverId).isSubscriptionExpired;
}
bool ServersUiController::isServerSubscriptionExpiringSoon(const QString &serverId) const
{
return serverDescriptionById(serverId).isSubscriptionExpiringSoon;
}
int ServersUiController::getProcessedContainerIndex() const
{
return m_processedContainerIndex;
@@ -343,17 +311,27 @@ QString ServersUiController::getProcessedServerId() const
void ServersUiController::setProcessedServerId(const QString &serverId)
{
const int newIndex = serverId.isEmpty() ? -1 : serverIndexForId(serverId);
const QString normalizedServerId = newIndex >= 0 ? serverId : QString();
const int index = serverId.isEmpty() ? -1 : serverIndexForId(serverId);
if (!serverId.isEmpty() && index < 0) {
return;
}
if (m_processedServerId != normalizedServerId) {
m_processedServerId = normalizedServerId;
if (m_processedServerIndex != index || m_processedServerId != serverId) {
m_processedServerIndex = index;
m_processedServerId = serverId;
m_serversModel->setProcessedServerIndex(index);
if (newIndex >= 0) {
if (index >= 0) {
updateContainersModel();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == serverId) {
setProcessedContainerIndex(static_cast<int>(description.defaultContainer));
break;
}
}
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId != normalizedServerId) {
if (description.serverId != serverId) {
continue;
}
if (description.isApiV2) {
@@ -367,12 +345,45 @@ void ServersUiController::setProcessedServerId(const QString &serverId)
}
emit processedServerIdChanged(m_processedServerId);
emit processedServerIndexChanged(m_processedServerIndex);
}
}
int ServersUiController::getProcessedServerIndex() const
{
return m_processedServerIndex;
}
void ServersUiController::setProcessedServerIndex(int index)
{
if (index < 0) {
setProcessedServerId(QString());
return;
}
const QString id = getServerId(index);
if (!id.isEmpty()) {
setProcessedServerId(id);
}
}
int ServersUiController::defaultServerIndex() const
{
return rowForServerId(m_orderedServerDescriptions, getDefaultServerId());
}
bool ServersUiController::processedServerIsPremium() const
{
return processedServerDescription().isPremium;
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == m_processedServerId) {
return description.isPremium;
}
}
return false;
}
const ServerCredentials ServersUiController::getProcessedServerCredentials() const
{
return m_serversController->getServerCredentials(m_processedServerId);
}
bool ServersUiController::isDefaultServerCurrentlyProcessed() const
@@ -382,22 +393,18 @@ bool ServersUiController::isDefaultServerCurrentlyProcessed() const
bool ServersUiController::isProcessedServerHasWriteAccess() const
{
return isServerHasWriteAccess(m_processedServerId);
ServerCredentials credentials = m_serversController->getServerCredentials(m_processedServerId);
return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty());
}
const ServerDescription &ServersUiController::processedServerDescription() const
{
return serverDescriptionById(m_processedServerId);
}
const ServerDescription &ServersUiController::serverDescriptionById(const QString &serverId) const
QString ServersUiController::getDefaultServerDescription(const QString &serverId) const
{
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == serverId) {
return description;
return description.baseDescription;
}
}
return emptyServerDescription();
return QString();
}
bool ServersUiController::hasServersFromGatewayApi() const
@@ -460,11 +467,6 @@ int ServersUiController::getServerIndexById(const QString &serverId) const
return rowForServerId(m_orderedServerDescriptions, serverId);
}
int ServersUiController::getServersCount() const
{
return m_orderedServerDescriptions.size();
}
void ServersUiController::updateContainersModel()
{
if (m_processedServerId.isEmpty()) {

View File

@@ -19,6 +19,7 @@ class ServersUiController : public QObject
Q_OBJECT
Q_PROPERTY(QString defaultServerId READ getDefaultServerId NOTIFY defaultServerIdChanged)
Q_PROPERTY(int defaultServerIndex READ defaultServerIndex NOTIFY defaultServerIndexChanged)
Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerIdChanged)
Q_PROPERTY(QString defaultServerDefaultContainerName READ getDefaultServerDefaultContainerName NOTIFY defaultServerIdChanged)
@@ -29,8 +30,9 @@ class ServersUiController : public QObject
Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIdChanged)
Q_PROPERTY(QString processedServerId READ getProcessedServerId WRITE setProcessedServerId NOTIFY processedServerIdChanged)
Q_PROPERTY(int processedServerIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged)
Q_PROPERTY(int processedContainerIndex READ getProcessedContainerIndex WRITE setProcessedContainerIndex NOTIFY processedContainerIndexChanged)
Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerIdChanged)
Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerIndexChanged)
Q_PROPERTY(bool hasServersFromGatewayApi READ hasServersFromGatewayApi NOTIFY hasServersFromGatewayApiChanged)
@@ -70,27 +72,20 @@ public slots:
QString getDefaultServerDescriptionExpanded() const;
bool isDefaultServerDefaultContainerHasSplitTunneling() const;
bool isDefaultServerFromApi() const;
bool hasServerWithWriteAccess() const;
QString serverName(const QString &serverId) const;
QString serverHostName(const QString &serverId) const;
int serverDefaultContainer(const QString &serverId) const;
bool isServerFromApi(const QString &serverId) const;
bool isServerCountrySelectionAvailable(const QString &serverId) const;
bool isServerHasWriteAccess(const QString &serverId) const;
bool serverHasInstalledContainers(const QString &serverId) const;
QString serverAdEndpoint(const QString &serverId) const;
bool isServerRenewalAvailable(const QString &serverId) const;
bool isServerSubscriptionExpired(const QString &serverId) const;
bool isServerSubscriptionExpiringSoon(const QString &serverId) const;
QString getProcessedServerId() const;
void setProcessedServerId(const QString &serverId);
int getProcessedServerIndex() const;
void setProcessedServerIndex(int index);
int defaultServerIndex() const;
int getProcessedContainerIndex() const;
void setProcessedContainerIndex(int index);
bool processedServerIsPremium() const;
const ServerCredentials getProcessedServerCredentials() const;
bool isDefaultServerCurrentlyProcessed() const;
bool isProcessedServerHasWriteAccess() const;
@@ -102,14 +97,15 @@ public slots:
QString getServerId(int index) const;
int getServerIndexById(const QString &serverId) const;
int getServersCount() const;
QStringList getAllInstalledServicesName(int serverIndex) const;
signals:
void errorOccurred(const QString &errorMessage);
void finished(const QString &message);
void defaultServerIdChanged(const QString &serverId);
void defaultServerIndexChanged(int index);
void processedServerIdChanged(const QString &serverId);
void processedServerIndexChanged(int index);
void processedContainerIndexChanged(int index);
void hasServersFromGatewayApiChanged();
void updateApiCountryModel();
@@ -119,8 +115,7 @@ public:
void updateModel();
private:
const ServerDescription &serverDescriptionById(const QString &serverId) const;
const ServerDescription &processedServerDescription() const;
QString getDefaultServerDescription(const QString &serverId) const;
int serverIndexForId(const QString &serverId) const;
bool listHasServersFromGatewayApi() const;
@@ -135,6 +130,7 @@ private:
QVector<amnezia::ServerDescription> m_orderedServerDescriptions;
int m_processedServerIndex = -1;
QString m_processedServerId;
int m_processedContainerIndex = -1;
};

View File

@@ -164,7 +164,6 @@ void SettingsUiController::restoreAppConfigFromData(const QByteArray &data)
emit amneziaDnsToggled(amneziaDnsEnabled);
emit restoreBackupFinished();
emit startMinimizedChanged();
} else {
emit errorOccurred(errorCode);
}
@@ -178,7 +177,6 @@ QString SettingsUiController::getAppVersion()
void SettingsUiController::clearSettings()
{
m_settingsController->clearSettings();
emit startMinimizedChanged();
emit resetLanguageToSystem();
emit changeSettingsFinished(tr("All settings have been reset to default values"));
@@ -206,9 +204,6 @@ bool SettingsUiController::isAutoStartEnabled()
void SettingsUiController::toggleAutoStart(bool enable)
{
m_settingsController->toggleAutoStart(enable);
if (!enable) {
emit startMinimizedChanged();
}
}
bool SettingsUiController::isStartMinimizedEnabled()

View File

@@ -267,13 +267,8 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amne
m_container = container;
m_protocolConfig = protocolConfig;
if (m_protocolConfig.needsClientHydration) {
m_protocolConfig.hydrateServerConfigFromClientNative();
}
if (!m_protocolConfig.serverConfig.isThirdPartyConfig) {
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
}
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
m_originalProtocolConfig = m_protocolConfig;

View File

@@ -20,25 +20,20 @@
using namespace amnezia;
namespace {
int rowForServerId(const QVector<ServerDescription> &descriptions, const QString &serverId)
{
if (serverId.isEmpty()) {
return -1;
}
for (int i = 0; i < descriptions.size(); ++i) {
if (descriptions.at(i).serverId == serverId) {
return i;
}
}
return -1;
}
} // namespace
ServersModel::ServersModel(QObject *parent) : QAbstractListModel(parent)
{
connect(this, &ServersModel::defaultServerIndexChanged, this, &ServersModel::defaultServerNameChanged);
connect(this, &ServersModel::defaultServerIndexChanged, this, [this](const int serverIndex) {
if (serverIndex < 0 || serverIndex >= m_descriptions.size()) {
return;
}
auto defaultContainer = m_descriptions.at(serverIndex).defaultContainer;
emit ServersModel::defaultServerDefaultContainerChanged(defaultContainer);
emit ServersModel::defaultServerNameChanged();
});
connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged);
}
int ServersModel::rowCount(const QModelIndex &parent) const
@@ -61,22 +56,52 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
return row.serverName;
case ServerDescriptionRole:
return configVersion ? row.baseDescription : (row.baseDescription + row.hostName);
case CollapsedServerDescriptionRole:
return row.collapsedServerDescription;
case ExpandedServerDescriptionRole:
return row.expandedServerDescription;
case HostNameRole:
return row.hostName;
case ServerIdRole:
return row.serverId;
case CredentialsRole:
return QVariant::fromValue(serverCredentials(index.row()));
case CredentialsLoginRole:
return serverCredentials(index.row()).userName;
case IsDefaultRole:
return row.serverId == m_defaultServerId;
return index.row() == m_defaultServerIndex;
case IsCurrentlyProcessedRole:
return index.row() == m_processedServerIndex;
case HasWriteAccessRole:
return row.hasWriteAccess;
case ContainsAmneziaDnsRole:
return row.primaryDnsIsAmnezia;
case DefaultContainerRole:
return QVariant::fromValue(row.defaultContainer);
case HasInstalledContainers:
return row.hasInstalledVpnContainers;
case IsServerFromTelegramApiRole:
return false;
case IsServerFromGatewayApiRole:
return row.isServerFromGatewayApi;
case ApiConfigRole:
return QVariant();
case IsCountrySelectionAvailableRole:
return row.isCountrySelectionAvailable;
case ApiAvailableCountriesRole:
return row.apiAvailableCountries;
case ApiServerCountryCodeRole:
return row.apiServerCountryCode;
case HasAmneziaDns:
return row.primaryDnsIsAmnezia;
case IsAdVisibleRole:
return row.isAdVisible;
case AdHeaderRole:
return row.adHeader;
case AdDescriptionRole:
return row.adDescription;
case AdEndpointRole:
return row.adEndpoint;
case IsRenewalAvailableRole:
return row.isRenewalAvailable;
case IsSubscriptionExpiredRole:
return row.isSubscriptionExpired;
case IsSubscriptionExpiringSoonRole:
@@ -92,56 +117,111 @@ QVariant ServersModel::data(const int index, int role) const
return data(modelIndex, role);
}
void ServersModel::updateModel(const QVector<ServerDescription> &descriptions,
const QString &defaultServerId)
void ServersModel::updateModel(const QVector<ServerDescription> &descriptions, int defaultServerIndex)
{
beginResetModel();
m_descriptions = descriptions;
m_defaultServerId = defaultServerId;
m_defaultServerIndex = defaultServerIndex;
endResetModel();
emit defaultServerIndexChanged(m_defaultServerIndex);
emit processedServerChanged();
}
void ServersModel::setDefaultServerId(const QString &serverId)
const int ServersModel::getDefaultServerIndex()
{
if (m_defaultServerId == serverId) {
return;
}
return m_defaultServerIndex;
}
const int oldIndex = rowForServerId(m_descriptions, m_defaultServerId);
const int newIndex = rowForServerId(m_descriptions, serverId);
m_defaultServerId = serverId;
const int ServersModel::getServersCount()
{
return m_descriptions.size();
}
const QVector<int> roles = { IsDefaultRole };
if (oldIndex >= 0 && oldIndex < m_descriptions.size()) {
emit dataChanged(this->index(oldIndex), this->index(oldIndex), roles);
bool ServersModel::hasServerWithWriteAccess()
{
for (size_t i = 0; i < getServersCount(); i++) {
if (qvariant_cast<bool>(data(static_cast<int>(i), HasWriteAccessRole))) {
return true;
}
}
if (newIndex >= 0 && newIndex < m_descriptions.size()) {
emit dataChanged(this->index(newIndex), this->index(newIndex), roles);
return false;
}
void ServersModel::setProcessedServerIndex(const int index)
{
if (m_processedServerIndex != index) {
m_processedServerIndex = index;
emit processedServerIndexChanged(m_processedServerIndex);
}
}
const ServerCredentials ServersModel::getProcessedServerCredentials()
{
return serverCredentials(m_processedServerIndex);
}
bool ServersModel::isDefaultServerCurrentlyProcessed()
{
return m_defaultServerIndex == m_processedServerIndex;
}
bool ServersModel::isDefaultServerFromApi()
{
return data(m_defaultServerIndex, IsServerFromTelegramApiRole).toBool()
|| data(m_defaultServerIndex, IsServerFromGatewayApiRole).toBool();
}
bool ServersModel::isProcessedServerHasWriteAccess()
{
return qvariant_cast<bool>(data(m_processedServerIndex, HasWriteAccessRole));
}
bool ServersModel::isDefaultServerHasWriteAccess()
{
return qvariant_cast<bool>(data(m_defaultServerIndex, HasWriteAccessRole));
}
QHash<int, QByteArray> ServersModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[ServerDescriptionRole] = "serverDescription";
roles[CollapsedServerDescriptionRole] = "collapsedServerDescription";
roles[ExpandedServerDescriptionRole] = "expandedServerDescription";
roles[HostNameRole] = "hostName";
roles[ServerIdRole] = "serverId";
roles[CredentialsRole] = "credentials";
roles[CredentialsLoginRole] = "credentialsLogin";
roles[IsDefaultRole] = "isDefault";
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
roles[HasWriteAccessRole] = "hasWriteAccess";
roles[ContainsAmneziaDnsRole] = "containsAmneziaDns";
roles[DefaultContainerRole] = "defaultContainer";
roles[HasInstalledContainers] = "hasInstalledContainers";
roles[IsServerFromTelegramApiRole] = "isServerFromTelegramApi";
roles[IsServerFromGatewayApiRole] = "isServerFromGatewayApi";
roles[ApiConfigRole] = "apiConfig";
roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable";
roles[ApiAvailableCountriesRole] = "apiAvailableCountries";
roles[ApiServerCountryCodeRole] = "apiServerCountryCode";
roles[IsAdVisibleRole] = "isAdVisible";
roles[AdHeaderRole] = "adHeader";
roles[AdDescriptionRole] = "adDescription";
roles[AdEndpointRole] = "adEndpoint";
roles[IsRenewalAvailableRole] = "isRenewalAvailable";
roles[IsSubscriptionExpiredRole] = "isSubscriptionExpired";
roles[IsSubscriptionExpiringSoonRole] = "isSubscriptionExpiringSoon";
roles[HasAmneziaDns] = "hasAmneziaDns";
return roles;
}
@@ -153,3 +233,40 @@ ServerCredentials ServersModel::serverCredentials(int index) const
return m_descriptions.at(index).selfHostedSshCredentials;
}
bool ServersModel::isServerFromApi(const int serverIndex)
{
return data(serverIndex, IsServerFromTelegramApiRole).toBool()
|| data(serverIndex, IsServerFromGatewayApiRole).toBool();
}
QVariant ServersModel::getDefaultServerData(const QString roleString)
{
auto roles = roleNames();
for (auto it = roles.begin(); it != roles.end(); it++) {
if (QString(it.value()) == roleString) {
return data(m_defaultServerIndex, it.key());
}
}
return {};
}
QVariant ServersModel::getProcessedServerData(const QString &roleString)
{
auto roles = roleNames();
for (auto it = roles.begin(); it != roles.end(); it++) {
if (QString(it.value()) == roleString) {
return data(m_processedServerIndex, it.key());
}
}
return {};
}
bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
{
if (serverIndex < 0 || serverIndex >= m_descriptions.size()) {
return false;
}
return m_descriptions.at(serverIndex).hasInstalledVpnContainers;
}

View File

@@ -14,22 +14,39 @@ public:
enum Roles {
NameRole = Qt::UserRole + 1,
ServerDescriptionRole,
CollapsedServerDescriptionRole,
ExpandedServerDescriptionRole,
HostNameRole,
ServerIdRole,
CredentialsRole,
CredentialsLoginRole,
IsDefaultRole,
IsCurrentlyProcessedRole,
HasWriteAccessRole,
ContainsAmneziaDnsRole,
DefaultContainerRole,
HasInstalledContainers,
IsServerFromTelegramApiRole,
IsServerFromGatewayApiRole,
ApiConfigRole,
IsCountrySelectionAvailableRole,
ApiAvailableCountriesRole,
ApiServerCountryCodeRole,
IsAdVisibleRole,
AdHeaderRole,
AdDescriptionRole,
AdEndpointRole,
IsRenewalAvailableRole,
IsSubscriptionExpiredRole,
IsSubscriptionExpiringSoonRole,
HasAmneziaDns
};
ServersModel(QObject *parent = nullptr);
@@ -39,19 +56,52 @@ public:
QVariant data(const int index, int role = Qt::DisplayRole) const;
public slots:
void updateModel(const QVector<amnezia::ServerDescription> &descriptions,
const QString &defaultServerId);
void setDefaultServerId(const QString &serverId);
const int getDefaultServerIndex();
bool isDefaultServerCurrentlyProcessed();
bool isDefaultServerFromApi();
bool isProcessedServerHasWriteAccess();
bool isDefaultServerHasWriteAccess();
bool hasServerWithWriteAccess();
const int getServersCount();
void setProcessedServerIndex(const int index);
const ServerCredentials getProcessedServerCredentials();
QVariant getProcessedServerData(const QString &roleString);
QVariant getDefaultServerData(const QString roleString);
bool isServerFromApi(const int serverIndex);
void updateModel(const QVector<amnezia::ServerDescription> &descriptions, int defaultServerIndex);
protected:
QHash<int, QByteArray> roleNames() const override;
signals:
void processedServerIndexChanged(const int index);
void processedServerChanged();
void defaultServerIndexChanged(const int index);
void defaultServerNameChanged();
void defaultServerDescriptionChanged();
void defaultServerDefaultContainerChanged(const int containerIndex);
void updateApiCountryModel();
void updateApiServicesModel();
private:
ServerCredentials serverCredentials(int index) const;
bool serverHasInstalledContainers(const int serverIndex) const;
QVector<amnezia::ServerDescription> m_descriptions;
QString m_defaultServerId;
int m_defaultServerIndex = -1;
int m_processedServerIndex = -1;
};
#endif // SERVERSMODEL_H

View File

@@ -51,11 +51,11 @@ Rectangle {
}
Keys.onEnterPressed: {
Qt.openUrlExternally(ServersUiController.serverAdEndpoint(ServersUiController.defaultServerId))
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
}
Keys.onReturnPressed: {
Qt.openUrlExternally(ServersUiController.serverAdEndpoint(ServersUiController.defaultServerId))
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
}
RowLayout {
@@ -144,7 +144,7 @@ Rectangle {
onClicked: function() {
root.forceActiveFocus()
Qt.openUrlExternally(ServersUiController.serverAdEndpoint(ServersUiController.defaultServerId))
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
}
}
}

View File

@@ -182,6 +182,7 @@ Button {
}
onClicked: {
ServersUiController.setProcessedServerIndex(ServersUiController.defaultServerIndex)
ConnectionController.connectButtonClicked()
}

View File

@@ -13,6 +13,7 @@ Item {
onButtonStartChanged: {
if (buttonStart) {
ServersUiController.setProcessedServerIndex(ServersUiController.defaultServerIndex)
ConnectionController.connectButtonClicked()
}
}

View File

@@ -48,7 +48,7 @@ ListViewType {
showImage: !isInstalled
checkable: isInstalled && !ConnectionController.isConnected
checked: proxyDefaultServerContainersModel.mapToSource(index) === ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId)
checked: proxyDefaultServerContainersModel.mapToSource(index) === ServersModel.getDefaultServerData("defaultContainer")
onClicked: {
if (ConnectionController.isConnected && isInstalled) {
@@ -58,7 +58,7 @@ ListViewType {
if (checked) {
containersDropDown.closeTriggered()
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, proxyDefaultServerContainersModel.mapToSource(index))
ServersUiController.setDefaultContainer(ServersUiController.getServerId(ServersUiController.defaultServerIndex), proxyDefaultServerContainersModel.mapToSource(index))
} else {
ServersUiController.processedContainerIndex = proxyDefaultServerContainersModel.mapToSource(index)
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)

View File

@@ -46,7 +46,7 @@ DrawerType2 {
}
if (serverName.textField.text !== root.serverNameText) {
ServersUiController.editServerName(ServersUiController.processedServerId, serverName.textField.text);
ServersUiController.editServerName(ServersUiController.getServerId(ServersUiController.processedServerIndex), serverName.textField.text);
}
root.closeTriggered()
}

View File

@@ -17,7 +17,7 @@ import "../Config"
ListViewType {
id: root
property int selectedIndex: ServersUiController.getServerIndexById(ServersUiController.defaultServerId)
property int selectedIndex: ServersUiController.defaultServerIndex
anchors.top: serversMenuHeader.bottom
anchors.right: parent.right
@@ -29,8 +29,8 @@ ListViewType {
Connections {
target: ServersUiController
function onDefaultServerIdChanged() {
root.selectedIndex = ServersUiController.getServerIndexById(ServersUiController.defaultServerId)
function onDefaultServerIndexChanged() {
root.selectedIndex = ServersUiController.defaultServerIndex
}
}
@@ -106,14 +106,14 @@ ListViewType {
z: 1
onClicked: function() {
ServersUiController.setProcessedServerId(serverId)
ServersUiController.processedServerIndex = index
if (ServersUiController.isServerFromApi(ServersUiController.processedServerId)) {
if (ServersUiController.isServerCountrySelectionAvailable(ServersUiController.processedServerId)) {
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
} else {
PageController.showBusyIndicator(true)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.processedServerId, false)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.getServerId(ServersUiController.processedServerIndex), false)
PageController.showBusyIndicator(false)
if (!result) {
return

View File

@@ -34,25 +34,25 @@ ListViewType {
if (isVpnContainer) {
// var isThirdPartyConfig = root.model.data(index, ContainersModel.IsThirdPartyConfigRole)
if (isThirdPartyConfig) {
InstallController.updateProtocols(ServersUiController.processedServerId, containerIndex)
InstallController.updateProtocols(ServersUiController.getServerId(ServersUiController.processedServerIndex), containerIndex)
PageController.goToPage(PageEnum.PageProtocolRaw)
return
}
}
if (isIpsec) {
InstallController.updateProtocols(ServersUiController.processedServerId, containerIndex)
InstallController.updateProtocols(ServersUiController.getServerId(ServersUiController.processedServerIndex), containerIndex)
PageController.goToPage(PageEnum.PageProtocolRaw)
} else if (isDns) {
PageController.goToPage(PageEnum.PageServiceDnsSettings)
} else if (isMtProxy) {
MtProxyConfigModel.updateModel(config)
PageController.goToPage(PageEnum.PageServiceMtProxySettings, false)
PageController.goToPage(PageEnum.PageServiceMtProxySettings)
} else if (isTelemt) {
TelemtConfigModel.updateModel(config)
PageController.goToPage(PageEnum.PageServiceTelemtSettings, false)
PageController.goToPage(PageEnum.PageServiceTelemtSettings)
} else {
InstallController.updateProtocols(ServersUiController.processedServerId, containerIndex)
InstallController.updateProtocols(ServersUiController.getServerId(ServersUiController.processedServerIndex), containerIndex)
PageController.goToPage(PageEnum.PageSettingsServerProtocol)
}

View File

@@ -15,7 +15,7 @@ DrawerType2 {
property bool isRenewalAvailable: false
onOpened: {
isRenewalAvailable = ServersUiController.isServerRenewalAvailable(ServersUiController.defaultServerId) && !ApiAccountInfoModel.data("isInAppPurchase")
isRenewalAvailable = ServersModel.getDefaultServerData("isRenewalAvailable") && !ApiAccountInfoModel.data("isInAppPurchase")
}
expandedStateContent: ColumnLayout {
@@ -43,7 +43,7 @@ DrawerType2 {
anchors.left: parent.left
anchors.right: parent.right
text: ServersUiController.serverName(ServersUiController.defaultServerId) + qsTr(" subscription has expired")
text: ServersModel.getDefaultServerData("name") + qsTr(" subscription has expired")
horizontalAlignment: Text.AlignLeft
}
}
@@ -76,7 +76,7 @@ DrawerType2 {
textColor: AmneziaStyle.color.midnightBlack
clickedFunc: function() {
SubscriptionUiController.getRenewalLink(ServersUiController.defaultServerId)
SubscriptionUiController.getRenewalLink(ServersUiController.getServerId(ServersUiController.defaultServerIndex))
}
}
@@ -96,7 +96,7 @@ DrawerType2 {
clickedFunc: function() {
PageController.showBusyIndicator(true)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.defaultServerId, false)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.getServerId(ServersUiController.defaultServerIndex), false)
PageController.showBusyIndicator(false)
if (result) {
root.closeTriggered()

View File

@@ -12,8 +12,6 @@ Item {
property int headerTextMaximumLineCount: 2
property int headerTextElide: Qt.ElideRight
property string descriptionText
property string descriptionLinkText
property string descriptionLinkUrl
property alias headerRow: headerRow
implicitWidth: content.implicitWidth
@@ -45,26 +43,5 @@ Item {
color: AmneziaStyle.color.mutedGray
visible: root.descriptionText !== ""
}
ParagraphTextType {
id: descriptionLink
Layout.topMargin: 16
Layout.fillWidth: true
text: root.descriptionLinkText !== "" && root.descriptionLinkUrl !== ""
? ("<a href=\"" + root.descriptionLinkUrl + "\" style=\"color: " + AmneziaStyle.color.goldenApricotString + ";\">" + root.descriptionLinkText + "</a>")
: ""
textFormat: Text.RichText
visible: root.descriptionLinkText !== ""
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
}
}

View File

@@ -1,263 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import Qt5Compat.GraphicalEffects
import Style 1.0
import "."
import "TextTypes"
import "../Config"
Popup {
id: root
property string captchaId
property string captchaImageBase64
property string hint: qsTr("Enter the digits from the image to continue")
signal captchaSolved(string captchaId, string solution)
signal refreshCaptchaRequested()
leftMargin: 25
rightMargin: 25
bottomMargin: 70 + SettingsController.safeAreaBottomMargin
width: parent.width - leftMargin - rightMargin
anchors.centerIn: parent
modal: true
closePolicy: Popup.NoAutoClose
Overlay.modal: Rectangle {
color: AmneziaStyle.color.translucentMidnightBlack
}
onOpened: {
timer.start()
solutionField.textField.text = ""
solutionField.textField.focus = true
}
onCaptchaIdChanged: {
if (opened) {
solutionField.textField.text = ""
}
}
onCaptchaImageBase64Changed: {
if (opened) {
solutionField.textField.text = ""
}
}
onClosed: {
FocusController.dropRootObject(root)
}
background: Rectangle {
anchors.fill: parent
color: AmneziaStyle.color.slateGray
radius: 22
}
Timer {
id: timer
interval: 200
onTriggered: {
FocusController.pushRootObject(root)
FocusController.setFocusItem(solutionField.textField)
}
repeat: false
running: true
}
contentItem: Item {
implicitWidth: contentLayout.implicitWidth
implicitHeight: contentLayout.implicitHeight
anchors.fill: parent
ColumnLayout {
id: contentLayout
anchors.fill: parent
anchors.leftMargin: 20
anchors.rightMargin: 20
anchors.topMargin: 20
anchors.bottomMargin: 20
spacing: 16
Text {
id: titleText
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
text: root.hint
wrapMode: Text.WordWrap
color: AmneziaStyle.color.paleGray
font.pixelSize: 18
font.weight: Font.Bold
font.family: "PT Root UI VF"
lineHeight: 24 + LanguageUiController.getLineHeightAppend()
lineHeightMode: Text.FixedHeight
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignTop
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: 200
Rectangle {
id: imagePanel
anchors.fill: parent
color: AmneziaStyle.color.pearlGray
radius: 16
Image {
id: captchaImage
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
cache: false
Component.onCompleted: {
if (captchaImageBase64 !== "") {
source = "data:image/png;base64," + captchaImageBase64
}
}
Connections {
target: root
function onCaptchaImageBase64Changed() {
captchaImage.source = "data:image/png;base64," + root.captchaImageBase64
}
}
}
BusyIndicator {
anchors.centerIn: parent
running: captchaImage.status === Image.Loading
}
Rectangle {
id: refreshHit
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 10
width: 44
height: 44
radius: width / 2
color: AmneziaStyle.color.charcoalGray
Image {
id: refreshIcon
anchors.centerIn: parent
width: 26
height: 26
fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true
antialiasing: true
source: "qrc:/images/controls/refresh-cw.svg"
// Rasterize SVG at high resolution, then scale down — avoids blocky edges on HiDPI.
readonly property real _dpr: (Window.window && Window.window.screen)
? Window.window.screen.devicePixelRatio : 2.0
readonly property int _raster: Math.ceil(64 * Math.min(Math.max(_dpr, 1.0), 4.0))
sourceSize: Qt.size(_raster, _raster)
layer.enabled: true
layer.smooth: true
layer.textureSize: Qt.size(_raster, _raster)
layer.effect: ColorOverlay {
color: AmneziaStyle.color.goldenApricot
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.refreshCaptchaRequested()
}
}
}
}
TextFieldWithHeaderType {
id: solutionField
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
headerText: qsTr("Digits from the image")
headerTextColor: AmneziaStyle.color.mutedGray
textField.placeholderText: qsTr("_ _ _ _ _ _")
textField.placeholderTextColor: AmneziaStyle.color.mutedGray
textField.inputMethodHints: Qt.ImhDigitsOnly | Qt.ImhNoPredictiveText
textField.maximumLength: 6
textField.font.letterSpacing: 2
textField.onAccepted: {
submitIfNonEmpty()
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: 8
}
BasicButtonType {
id: continueButton
Layout.fillWidth: true
implicitHeight: 52
text: qsTr("Continue")
defaultColor: AmneziaStyle.color.paleGray
hoveredColor: AmneziaStyle.color.lightGray
pressedColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.midnightBlack
clickedFunc: function() {
submitIfNonEmpty()
}
}
BasicButtonType {
id: closeButton
Layout.fillWidth: true
implicitHeight: 52
text: qsTr("Close")
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
borderColor: AmneziaStyle.color.mutedGray
borderFocusedColor: AmneziaStyle.color.paleGray
clickedFunc: function() {
root.close()
}
}
}
}
function submitIfNonEmpty() {
const t = solutionField.textField.text.trim()
if (t !== "") {
root.captchaSolved(root.captchaId, t)
}
}
}

View File

@@ -344,14 +344,14 @@ PageType {
Keys.onReturnPressed: this.clicked()
onClicked: {
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
ServersUiController.setProcessedServerIndex(ServersUiController.defaultServerIndex)
if (ServersUiController.isServerFromApi(ServersUiController.processedServerId)) {
if (ServersUiController.isServerCountrySelectionAvailable(ServersUiController.processedServerId)) {
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
} else {
PageController.showBusyIndicator(true)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.processedServerId, false)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.getServerId(ServersUiController.processedServerIndex), false)
PageController.showBusyIndicator(false)
if (!result) {
return
@@ -420,13 +420,13 @@ PageType {
target: ServersUiController
function onDefaultServerIdChanged() {
function onDefaultServerIndexChanged() {
updateContainersModelFilters()
}
}
function updateContainersModelFilters() {
if (ServersUiController.isServerHasWriteAccess(ServersUiController.defaultServerId)) {
if (ServersModel.isDefaultServerHasWriteAccess()) {
proxyDefaultServerContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters()
} else {
proxyDefaultServerContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters()

View File

@@ -435,13 +435,13 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
}
var noButtonFunction = function() {}

View File

@@ -555,13 +555,13 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
}
var noButtonFunction = function() {}

View File

@@ -428,13 +428,13 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
}
var noButtonFunction = function() {
if (!GC.isMobile()) {

View File

@@ -184,7 +184,7 @@ PageType {
var yesButtonFunction = function() {
PageController.goToPage(PageEnum.PageDeinstalling)
InstallController.removeContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
InstallController.removeContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
}
var noButtonFunction = function() {}

View File

@@ -123,13 +123,13 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
}
var noButtonFunction = function() {}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)

View File

@@ -123,13 +123,13 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
}
var noButtonFunction = function() {
if (!GC.isMobile()) {

View File

@@ -107,7 +107,7 @@ PageType {
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function () {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}

View File

@@ -274,7 +274,7 @@ PageType {
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function () {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}

View File

@@ -64,18 +64,6 @@ PageType {
spacing: 0
Text {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
visible: !listView.enabled
wrapMode: Text.WordWrap
color: AmneziaStyle.color.paleGray
font.pixelSize: 14
text: qsTr("You have read-only access to this server. XRay settings cannot be edited.")
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: 16
@@ -85,8 +73,6 @@ PageType {
BaseHeaderType {
Layout.fillWidth: true
headerText: qsTr("XRay VLESS settings")
descriptionLinkText: qsTr("More about settings")
descriptionLinkUrl: "https://docs.amnezia.org"
}
ImageButtonType {
@@ -99,6 +85,22 @@ PageType {
}
}
LabelTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 4
text: qsTr("More about settings")
color: AmneziaStyle.color.goldenApricot
font.pixelSize: 16
lineHeight: 24 + LanguageUiController.getLineHeightAppend()
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: Qt.openUrlExternally("https://docs.amnezia.org")
}
}
TextFieldWithHeaderType {
id: textFieldWithHeaderType
Layout.fillWidth: true
@@ -171,9 +173,8 @@ PageType {
Layout.bottomMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: listView.enabled
&& (XrayConfigModel.hasUnsavedChanges
|| textFieldWithHeaderType.textField.text !== port)
// Show Save immediately while user edits port, even before focus loss.
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || textFieldWithHeaderType.textField.text !== port)
enabled: visible && textFieldWithHeaderType.errorText === ""
text: qsTr("Save")
onClicked: function() {
@@ -183,17 +184,13 @@ PageType {
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
if (textFieldWithHeaderType.textField.text !== port) {
port = textFieldWithHeaderType.textField.text
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function() {
if (!GC.isMobile()) saveButton.forceActiveFocus()
@@ -212,8 +209,6 @@ PageType {
clickedFunction: function() {
var yesButtonFunction = function() {
XrayConfigModel.resetToDefaults()
PageController.showNotificationMessage(
qsTr("Settings were reset to defaults. Tap Save to apply them on the server."))
}
showQuestionDrawer(qsTr("Reset settings?"), qsTr("All XRay settings will be restored to defaults."),
qsTr("Reset"), qsTr("Cancel"), yesButtonFunction, function() {

View File

@@ -737,7 +737,7 @@ PageType {
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function () {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}

View File

@@ -90,7 +90,7 @@ PageType {
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function () {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}

View File

@@ -206,7 +206,7 @@ PageType {
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function () {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}

View File

@@ -203,7 +203,7 @@ PageType {
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function () {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}

View File

@@ -79,7 +79,7 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot remove AmneziaDNS from running server"))
} else {
PageController.goToPage(PageEnum.PageDeinstalling)
InstallController.removeContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
InstallController.removeContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
}
}
var noButtonFunction = function() {}

View File

@@ -20,10 +20,15 @@ import "../Components"
PageType {
id: root
Rectangle {
anchors.fill: parent
z: -1
color: AmneziaStyle.color.onyxBlack
}
property int containerStatus: 1
property bool isUpdating: false
property bool isCheckingStatus: false
property bool isFetchingSecret: false
property bool previousEnabled: true
property int previousContainerStatus: 1
@@ -45,7 +50,7 @@ PageType {
onSavedTransportModeChanged: {
if (savedTransportMode === "faketls") {
root.syncedSecretTabIndex = 1
root.syncedSecretTabIndex = 2
} else if (savedTransportMode !== "") {
root.syncedSecretTabIndex = 0
}
@@ -63,96 +68,9 @@ PageType {
readonly property bool mtProxyNetworkBlocked: !NetworkReachabilityController.hasInternetAccess
property bool remoteOperationBusy: false
readonly property bool operationInProgress: isCheckingStatus || isFetchingSecret || isUpdating || diagLoading
readonly property bool pageBusy: operationInProgress || remoteOperationBusy
readonly property bool navigationBlockedWhileBusy: pageBusy
property bool pageOpenHandled: false
property bool busyIndicatorShown: false
function syncPageBusyIndicator() {
if (!root.pageOpenHandled) {
return
}
var wantBusy = root.pageBusy
if (wantBusy === root.busyIndicatorShown) {
return
}
root.busyIndicatorShown = wantBusy
PageController.showBusyIndicator(wantBusy)
}
onPageBusyChanged: syncPageBusyIndicator()
function mtProxyDomainToHex(domain) {
var hex = ""
for (var i = 0; i < domain.length; i++) {
var code = domain.charCodeAt(i).toString(16)
hex += (code.length < 2 ? "0" : "") + code
}
return hex
}
function mtProxyClientSecret(baseHex32, mode, tlsDomain) {
if (baseHex32 === "") {
return ""
}
if (mode === "faketls") {
return "ee" + baseHex32 + mtProxyDomainToHex(tlsDomain)
}
return "dd" + baseHex32
}
function mtProxyClientSecretForTabIndex(baseHex32, tabIndex, tlsDomain, defaultTlsDomain) {
var domain = tlsDomain !== "" ? tlsDomain : defaultTlsDomain
if (tabIndex === 1) {
return mtProxyClientSecret(baseHex32, "faketls", domain)
}
return mtProxyClientSecret(baseHex32, "standard", domain)
}
property bool containerStatusRefreshCallPending: false
function mtProxyRequestContainerStatusRefresh() {
if (!NetworkReachabilityController.hasInternetAccess) {
isCheckingStatus = false
syncPageBusyIndicator()
return
}
isCheckingStatus = true
syncPageBusyIndicator()
InstallController.refreshContainerStatus(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
}
function mtProxyScheduleContainerStatusRefresh() {
if (containerStatusRefreshCallPending) {
return
}
containerStatusRefreshCallPending = true
Qt.callLater(function () {
containerStatusRefreshCallPending = false
root.mtProxyRequestContainerStatusRefresh()
})
}
function mtProxyOnPageShown() {
if (root.pageOpenHandled) {
return
}
root.pageOpenHandled = true
PageController.disableControls(navigationBlockedWhileBusy)
if (!NetworkReachabilityController.hasInternetAccess) {
isCheckingStatus = false
} else {
isCheckingStatus = true
}
syncPageBusyIndicator()
root.mtProxyScheduleContainerStatusRefresh()
}
readonly property bool navigationBlockedWhileBusy: isUpdating || diagLoading
// Hex values that exist in last loaded / last successfully saved config — show link panel only for these.
property var mtProxyPersistedAdditionalHex: []
function mtProxyRefreshPersistedAdditionalSecrets() {
@@ -174,15 +92,19 @@ PageType {
return false
}
// Rejects garbage like "123123123123"; only dotted IPv4 shape (≤3 digits per octet, ≤4 octets).
readonly property var natIpv4InputFormat: /^(\d{1,3}\.){0,3}\d{0,3}$/
// Defer SSH/updateContainer so QML control handlers return before nested event loops run;
// avoids "Object destroyed while one of its QML signal handlers is in progress".
function mtProxyScheduleUpdate(closePage) {
var cp = closePage === undefined ? false : closePage
Qt.callLater(function () {
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
})
}
// Optional IPv4: show invalid while typing only when the string looks complete (four octets), so partial entry is not nagged.
function natIpv4FieldShowInvalidError(text) {
var t = text ? String(text).replace(/^\s+|\s+$/g, '') : ""
if (t === "")
@@ -245,9 +167,15 @@ PageType {
root.mtProxyRefreshPersistedAdditionalSecrets()
})
Qt.callLater(root.mtProxyOnPageShown)
if (!NetworkReachabilityController.hasInternetAccess) {
isCheckingStatus = false
return
}
isCheckingStatus = true
InstallController.refreshContainerStatus(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
}
// Block back navigation and Escape (via PageStart.isControlsDisabled) while SSH/update or diagnostics refresh runs.
onNavigationBlockedWhileBusyChanged: {
if (root.visible) {
PageController.disableControls(navigationBlockedWhileBusy)
@@ -256,16 +184,10 @@ PageType {
onVisibleChanged: {
if (!visible) {
root.pageOpenHandled = false
containerStatusRefreshCallPending = false
isCheckingStatus = false
isFetchingSecret = false
busyIndicatorShown = false
PageController.disableControls(false)
PageController.showBusyIndicator(false)
diagLoading = false
} else {
root.mtProxyOnPageShown()
PageController.disableControls(navigationBlockedWhileBusy)
}
}
@@ -277,7 +199,8 @@ PageType {
return
}
if (NetworkReachabilityController.hasInternetAccess) {
root.mtProxyScheduleContainerStatusRefresh()
isCheckingStatus = true
InstallController.refreshContainerStatus(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
}
}
}
@@ -285,15 +208,10 @@ PageType {
Connections {
target: InstallController
function onServerIsBusy(busy) {
remoteOperationBusy = busy
}
function onUpdateContainerFinished(message, closePage) {
if (!root.visible) {
isUpdating = false
isCheckingStatus = false
isFetchingSecret = false
return
}
isUpdating = false
@@ -309,11 +227,9 @@ PageType {
if (!root.visible) {
isUpdating = false
isCheckingStatus = false
isFetchingSecret = false
return
}
isUpdating = false
isFetchingSecret = false
containerStatus = previousContainerStatus
MtProxyConfigModel.setEnabled(previousEnabled)
MtProxyConfigModel.setPort(previousPort)
@@ -338,7 +254,6 @@ PageType {
}
if (enabled && pendingUpdateAfterEnable) {
pendingUpdateAfterEnable = false
isUpdating = true
root.mtProxyScheduleUpdate(false)
return
}
@@ -351,9 +266,9 @@ PageType {
function onContainerStatusRefreshed(status) {
if (!root.visible) {
isCheckingStatus = false
isFetchingSecret = false
return
}
isCheckingStatus = false
containerStatus = status
root.savedTransportMode = MtProxyConfigModel.getTransportMode()
@@ -361,17 +276,10 @@ PageType {
root.savedPublicHost = MtProxyConfigModel.getPublicHost()
if (status === 1) {
MtProxyConfigModel.setEnabled(true)
isFetchingSecret = true
isCheckingStatus = false
InstallController.fetchContainerSecret(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
} else {
isFetchingSecret = false
isCheckingStatus = false
if (status === 2) {
MtProxyConfigModel.setEnabled(false)
}
InstallController.fetchContainerSecret(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
} else if (status === 2) {
MtProxyConfigModel.setEnabled(false)
}
syncPageBusyIndicator()
}
function onContainerDiagnosticsRefreshed(portReachable, upstreamReachable, clientsConnected, lastConfigRefresh, statsEndpoint) {
@@ -388,35 +296,20 @@ PageType {
function onContainerSecretFetched(secret) {
if (!root.visible) {
isFetchingSecret = false
return
}
isFetchingSecret = false
syncPageBusyIndicator()
MtProxyConfigModel.validateAndSetSecret(secret)
}
}
Item {
id: contentLayer
anchors.fill: parent
enabled: !root.pageBusy
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20 + PageController.safeAreaTopMargin
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: {
if (this.activeFocus) {
if (mainTabBar.currentIndex === 0) {
connectionListView.positionViewAtBeginning()
} else {
settingsListView.positionViewAtBeginning()
}
}
if (this.activeFocus) connectionListView.positionViewAtBeginning()
}
}
@@ -425,62 +318,57 @@ PageType {
anchors.top: backButton.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 8
spacing: 0
BaseHeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 24
headerText: qsTr("MTProxy settings")
descriptionLinkText: qsTr("Read more about this settings")
descriptionLinkUrl: "https://core.telegram.org/proxy"
}
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
visible: root.mtProxyNetworkBlocked
text: qsTr("No internet connection. Connect to the internet to change MTProxy settings.")
color: AmneziaStyle.color.mutedGray
wrapMode: Text.WordWrap
font.pixelSize: 14
headerText: qsTr("MTProxy settings")
}
}
TabBar {
id: mainTabBar
anchors.top: pageHeader.bottom
anchors.left: parent.left
anchors.right: parent.right
width: parent.width
background: Rectangle {
color: AmneziaStyle.color.transparent
Rectangle {
width: parent.width
height: 1
anchors.bottom: parent.bottom
color: AmneziaStyle.color.slateGray
LabelWithButtonType {
Layout.fillWidth: true
Layout.leftMargin: 0
Layout.rightMargin: 16
text: qsTr("Read more about this settings")
textColor: AmneziaStyle.color.goldenApricot
clickedFunction: function () {
Qt.openUrlExternally("https://core.telegram.org/proxy")
}
}
TabButtonType {
text: qsTr("Connection")
isSelected: mainTabBar.currentIndex === 0
}
TabButtonType {
text: qsTr("Settings")
isSelected: mainTabBar.currentIndex === 1
TabBar {
id: mainTabBar
Layout.fillWidth: true
Layout.topMargin: 4
background: Rectangle {
color: AmneziaStyle.color.transparent
Rectangle {
width: parent.width
height: 1
anchors.bottom: parent.bottom
color: AmneziaStyle.color.slateGray
}
}
TabButtonType {
text: qsTr("Connection")
isSelected: mainTabBar.currentIndex === 0
}
TabButtonType {
text: qsTr("Settings")
isSelected: mainTabBar.currentIndex === 1
}
}
}
StackLayout {
id: tabContent
anchors.top: mainTabBar.bottom
anchors.top: pageHeader.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
@@ -494,11 +382,35 @@ PageType {
width: connectionListView.width
spacing: 0
function domainToHex(domain) {
var hex = ""
for (var i = 0; i < domain.length; i++) {
var code = domain.charCodeAt(i).toString(16)
hex += (code.length < 2 ? "0" : "") + code
}
return hex
}
function secretForMode(mode) {
if (mode === "faketls") {
var domain = root.savedTlsDomain !== "" ? root.savedTlsDomain : MtProxyConfigModel.defaultTlsDomain()
return "ee" + secret + domainToHex(domain)
} else if (mode === "padded") {
return "dd" + secret
}
return secret
}
property int secretTabIndex: root.syncedSecretTabIndex
function activeSecret() {
return root.mtProxyClientSecretForTabIndex(secret, root.syncedSecretTabIndex,
root.savedTlsDomain, MtProxyConfigModel.defaultTlsDomain())
if (root.syncedSecretTabIndex === 0) {
return secretForMode("standard")
}
if (root.syncedSecretTabIndex === 1) {
return secretForMode("padded")
}
return secretForMode("faketls")
}
function effectiveSecret() {
@@ -506,7 +418,7 @@ PageType {
}
function effectiveHost() {
return root.savedPublicHost !== "" ? root.savedPublicHost : ServersUiController.serverHostName(ServersUiController.processedServerId)
return root.savedPublicHost !== "" ? root.savedPublicHost : ServersModel.getProcessedServerData("hostName")
}
function tmeLink() {
@@ -808,7 +720,7 @@ PageType {
Layout.bottomMargin: 24
Layout.leftMargin: 0
Layout.rightMargin: 16
visible: ServersUiController.isProcessedServerHasWriteAccess()
visible: ServersModel.isProcessedServerHasWriteAccess()
text: qsTr("Delete MTProxy")
textColor: AmneziaStyle.color.vibrantRed
clickedFunction: function () {
@@ -818,7 +730,7 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function () {
PageController.goToPage(PageEnum.PageDeinstalling)
InstallController.removeContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
InstallController.removeContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, function () {
})
@@ -842,13 +754,37 @@ PageType {
width: settingsListView.width
spacing: 0
function mtProxyDomainToHex(domain) {
var hex = ""
for (var i = 0; i < domain.length; i++) {
var code = domain.charCodeAt(i).toString(16)
hex += (code.length < 2 ? "0" : "") + code
}
return hex
}
function mtProxySecretForBaseHex(baseHex, mode) {
if (mode === "faketls") {
var domain = root.savedTlsDomain !== "" ? root.savedTlsDomain : MtProxyConfigModel.defaultTlsDomain()
return "ee" + baseHex + mtProxyDomainToHex(domain)
} else if (mode === "padded") {
return "dd" + baseHex
}
return baseHex
}
function mtProxyActiveSecretForBaseHex(baseHex) {
return root.mtProxyClientSecretForTabIndex(baseHex, root.syncedSecretTabIndex,
root.savedTlsDomain, MtProxyConfigModel.defaultTlsDomain())
if (root.syncedSecretTabIndex === 0) {
return mtProxySecretForBaseHex(baseHex, "standard")
}
if (root.syncedSecretTabIndex === 1) {
return mtProxySecretForBaseHex(baseHex, "padded")
}
return mtProxySecretForBaseHex(baseHex, "faketls")
}
function mtProxyEffectiveHostForLinks() {
return root.savedPublicHost !== "" ? root.savedPublicHost : ServersUiController.serverHostName(ServersUiController.processedServerId)
return root.savedPublicHost !== "" ? root.savedPublicHost : ServersModel.getProcessedServerData("hostName")
}
function mtProxyTmeLinkForAdditional(baseHex) {
@@ -868,7 +804,7 @@ PageType {
Layout.bottomMargin: 16
text: qsTr("Enable MTProxy")
checked: isEnabled
enabled: containerStatus !== 0 && containerStatus !== 3 && !root.pageBusy
enabled: !isCheckingStatus && containerStatus !== 0 && containerStatus !== 3 && !isUpdating
&& !root.mtProxyNetworkBlocked
onToggled: function () {
if (checked !== isEnabled) {
@@ -879,9 +815,9 @@ PageType {
isUpdating = true
if (checked) {
root.pendingUpdateAfterEnable = true
InstallController.setContainerEnabled(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, true)
InstallController.setContainerEnabled(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, true)
} else {
InstallController.setContainerEnabled(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, false)
InstallController.setContainerEnabled(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, false)
}
}
}
@@ -907,20 +843,19 @@ PageType {
CaptionTextType {
Layout.fillWidth: true
text: secret !== "" ? mtProxyActiveSecretForBaseHex(secret) : qsTr("Not generated")
text: secret !== "" ? secret : qsTr("Not generated")
color: secret !== "" ? AmneziaStyle.color.paleGray : AmneziaStyle.color.mutedGray
wrapMode: Text.WrapAnywhere
elide: Text.ElideMiddle
font.pixelSize: 14
}
ImageButtonType {
Layout.alignment: Qt.AlignTop
implicitWidth: 36
implicitHeight: 36
hoverEnabled: true
image: "qrc:/images/controls/refresh-cw.svg"
imageColor: AmneziaStyle.color.paleGray
visible: ServersUiController.isProcessedServerHasWriteAccess()
visible: ServersModel.isProcessedServerHasWriteAccess()
onClicked: {
var secretSnapshot = secret
showQuestionDrawer(
@@ -954,7 +889,7 @@ PageType {
Layout.rightMargin: 16
Layout.bottomMargin: 4
headerText: qsTr("Public host / IP")
textField.placeholderText: ServersUiController.serverHostName(ServersUiController.processedServerId)
textField.placeholderText: ServersModel.getProcessedServerData("hostName")
textField.text: publicHost
textField.maximumLength: 253
textField.validator: PublicHostInputValidator {
@@ -1001,7 +936,7 @@ PageType {
Layout.rightMargin: 16
Layout.bottomMargin: 12
visible: publicHostTextField.textField.text !== "" &&
publicHostTextField.textField.text !== ServersUiController.serverHostName(ServersUiController.processedServerId)
publicHostTextField.textField.text !== ServersModel.getProcessedServerData("hostName")
text: qsTr("⚠ This overrides the server IP in connection links. Make sure this host/domain points to your server.")
color: AmneziaStyle.color.goldenApricot
font.pixelSize: 12
@@ -1163,7 +1098,6 @@ PageType {
clickedFunction: function () {
transportMode = (index === 0) ? "standard" : "faketls"
MtProxyConfigModel.setTransportMode(transportMode)
root.syncedSecretTabIndex = transportMode === "faketls" ? 1 : 0
transportModeDropDown.closeTriggered()
}
}
@@ -1347,14 +1281,11 @@ PageType {
implicitWidth: 32
implicitHeight: 32
hoverEnabled: true
visible: ServersUiController.isProcessedServerHasWriteAccess()
visible: ServersModel.isProcessedServerHasWriteAccess()
image: "qrc:/images/controls/trash.svg"
imageColor: AmneziaStyle.color.vibrantRed
onClicked: {
MtProxyConfigModel.removeAdditionalSecret(index)
if (containerStatus === 1) {
root.mtProxyScheduleUpdate(false)
}
}
}
}
@@ -1697,7 +1628,7 @@ PageType {
enabled: !diagLoading
onClicked: {
diagLoading = true
InstallController.refreshContainerDiagnostics(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, parseInt(port))
InstallController.refreshContainerDiagnostics(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, parseInt(port))
}
}
}
@@ -1823,7 +1754,7 @@ PageType {
Layout.bottomMargin: 32
Layout.rightMargin: 16
Layout.leftMargin: 16
visible: ServersUiController.isProcessedServerHasWriteAccess()
visible: ServersModel.isProcessedServerHasWriteAccess()
enabled: !root.mtProxyNetworkBlocked
text: qsTr("Save")
clickedFunc: function () {
@@ -1921,5 +1852,34 @@ PageType {
}
}
Rectangle {
anchors.fill: parent
visible: isCheckingStatus || isUpdating || root.mtProxyNetworkBlocked
color: AmneziaStyle.color.midnightBlack
opacity: 0.6
z: 1
MouseArea {
anchors.fill: parent
}
BusyIndicator {
anchors.centerIn: parent
visible: isCheckingStatus || isUpdating
running: isCheckingStatus || isUpdating
width: 48
height: 48
}
CaptionTextType {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 24
anchors.rightMargin: 24
visible: root.mtProxyNetworkBlocked && !isCheckingStatus && !isUpdating
horizontalAlignment: Text.AlignHCenter
text: qsTr("No internet connection. Connect to the internet to change MTProxy settings.")
color: AmneziaStyle.color.paleGray
wrapMode: Text.WordWrap
font.pixelSize: 14
}
}
}

View File

@@ -73,7 +73,7 @@ PageType {
Layout.rightMargin: 16
text: qsTr("Host")
descriptionText: ServersUiController.serverHostName(ServersUiController.processedServerId)
descriptionText: ServersModel.getProcessedServerData("hostName")
descriptionOnTop: true
@@ -173,7 +173,7 @@ PageType {
clickedFunc: function() {
PageController.showBusyIndicator(true)
InstallController.mountSftpDrive(ServersUiController.processedServerId, port, password, username)
InstallController.mountSftpDrive(ServersUiController.getServerId(ServersUiController.processedServerIndex), port, password, username)
PageController.showBusyIndicator(false)
}
}

View File

@@ -71,7 +71,7 @@ PageType {
Layout.bottomMargin: 16
text: qsTr("Host")
descriptionText: ServersUiController.serverHostName(ServersUiController.processedServerId)
descriptionText: ServersModel.getProcessedServerData("hostName")
descriptionOnTop: true
@@ -285,7 +285,7 @@ PageType {
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
tempPort = portTextField.textField.text
tempUsername = usernameTextField.textField.text
tempPassword = passwordTextField.textField.text

View File

@@ -18,10 +18,15 @@ import "../Components"
PageType {
id: root
Rectangle {
anchors.fill: parent
z: -1
color: AmneziaStyle.color.onyxBlack
}
property int containerStatus: 1
property bool isUpdating: false
property bool isCheckingStatus: false
property bool isFetchingSecret: false
property bool previousEnabled: true
property int previousContainerStatus: 1
@@ -35,7 +40,6 @@ PageType {
property bool previousNatEnabled: false
property string previousNatInternalIp: ""
property string previousNatExternalIp: ""
property string previousSecret: ""
property string savedTransportMode: ""
property string savedTlsDomain: ""
@@ -43,7 +47,7 @@ PageType {
onSavedTransportModeChanged: {
if (savedTransportMode === "faketls") {
root.syncedSecretTabIndex = 1
root.syncedSecretTabIndex = 2
} else if (savedTransportMode !== "") {
root.syncedSecretTabIndex = 0
}
@@ -60,101 +64,13 @@ PageType {
property string diagStatsEndpoint: ""
readonly property bool telemtNetworkBlocked: !NetworkReachabilityController.hasInternetAccess
readonly property bool navigationBlockedWhileBusy: isUpdating || diagLoading
property bool remoteOperationBusy: false
readonly property bool operationInProgress: isCheckingStatus || isFetchingSecret || isUpdating || diagLoading
readonly property bool pageBusy: operationInProgress || remoteOperationBusy
readonly property bool navigationBlockedWhileBusy: pageBusy
property bool pageOpenHandled: false
property bool busyIndicatorShown: false
function syncPageBusyIndicator() {
if (!root.pageOpenHandled) {
return
}
var wantBusy = root.pageBusy
if (wantBusy === root.busyIndicatorShown) {
return
}
root.busyIndicatorShown = wantBusy
PageController.showBusyIndicator(wantBusy)
}
onPageBusyChanged: syncPageBusyIndicator()
function telemtDomainToHex(domain) {
var hex = ""
for (var i = 0; i < domain.length; i++) {
var code = domain.charCodeAt(i).toString(16)
hex += (code.length < 2 ? "0" : "") + code
}
return hex
}
function telemtClientSecret(baseHex32, mode, tlsDomain) {
if (baseHex32 === "") {
return ""
}
if (mode === "faketls") {
return "ee" + baseHex32 + telemtDomainToHex(tlsDomain)
}
return "dd" + baseHex32
}
function telemtClientSecretForTabIndex(baseHex32, tabIndex, tlsDomain, defaultTlsDomain) {
var domain = tlsDomain !== "" ? tlsDomain : defaultTlsDomain
if (tabIndex === 1) {
return telemtClientSecret(baseHex32, "faketls", domain)
}
return telemtClientSecret(baseHex32, "standard", domain)
}
property bool containerStatusRefreshCallPending: false
function telemtRequestContainerStatusRefresh() {
if (!NetworkReachabilityController.hasInternetAccess) {
isCheckingStatus = false
syncPageBusyIndicator()
return
}
isCheckingStatus = true
syncPageBusyIndicator()
InstallController.refreshContainerStatus(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
}
function telemtScheduleContainerStatusRefresh() {
if (containerStatusRefreshCallPending) {
return
}
containerStatusRefreshCallPending = true
Qt.callLater(function () {
containerStatusRefreshCallPending = false
root.telemtRequestContainerStatusRefresh()
})
}
function telemtOnPageShown() {
if (root.pageOpenHandled) {
return
}
root.pageOpenHandled = true
PageController.disableControls(navigationBlockedWhileBusy)
if (!NetworkReachabilityController.hasInternetAccess) {
isCheckingStatus = false
} else {
isCheckingStatus = true
}
syncPageBusyIndicator()
root.telemtScheduleContainerStatusRefresh()
}
// Defer SSH/updateContainer so QML control handlers return before nested event loops run.
function telemtScheduleUpdate(closePage) {
var cp = closePage === undefined ? false : closePage
Qt.callLater(function () {
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
})
}
@@ -189,7 +105,12 @@ PageType {
root.savedTlsDomain = TelemtConfigModel.getTlsDomain()
root.savedPublicHost = TelemtConfigModel.getPublicHost()
Qt.callLater(root.telemtOnPageShown)
if (!NetworkReachabilityController.hasInternetAccess) {
isCheckingStatus = false
return
}
isCheckingStatus = true
InstallController.refreshContainerStatus(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
}
onNavigationBlockedWhileBusyChanged: {
@@ -200,16 +121,10 @@ PageType {
onVisibleChanged: {
if (!visible) {
root.pageOpenHandled = false
containerStatusRefreshCallPending = false
isCheckingStatus = false
isFetchingSecret = false
busyIndicatorShown = false
PageController.disableControls(false)
PageController.showBusyIndicator(false)
diagLoading = false
} else {
root.telemtOnPageShown()
PageController.disableControls(navigationBlockedWhileBusy)
}
}
@@ -221,7 +136,8 @@ PageType {
return
}
if (NetworkReachabilityController.hasInternetAccess) {
root.telemtScheduleContainerStatusRefresh()
isCheckingStatus = true
InstallController.refreshContainerStatus(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
}
}
}
@@ -229,15 +145,10 @@ PageType {
Connections {
target: InstallController
function onServerIsBusy(busy) {
remoteOperationBusy = busy
}
function onUpdateContainerFinished(message, closePage) {
if (!root.visible) {
isUpdating = false
isCheckingStatus = false
isFetchingSecret = false
return
}
isUpdating = false
@@ -255,11 +166,9 @@ PageType {
if (!root.visible) {
isUpdating = false
isCheckingStatus = false
isFetchingSecret = false
return
}
isUpdating = false
isFetchingSecret = false
containerStatus = previousContainerStatus
TelemtConfigModel.setEnabled(previousEnabled)
TelemtConfigModel.setPort(previousPort)
@@ -272,9 +181,6 @@ PageType {
TelemtConfigModel.setNatEnabled(previousNatEnabled)
TelemtConfigModel.setNatInternalIp(previousNatInternalIp)
TelemtConfigModel.setNatExternalIp(previousNatExternalIp)
if (previousSecret !== "") {
TelemtConfigModel.setSecret(previousSecret)
}
}
function onSetContainerEnabledFinished(enabled) {
@@ -284,7 +190,6 @@ PageType {
}
if (enabled && pendingUpdateAfterEnable) {
pendingUpdateAfterEnable = false
isUpdating = true
root.telemtScheduleUpdate(false)
return
}
@@ -297,9 +202,9 @@ PageType {
function onContainerStatusRefreshed(status) {
if (!root.visible) {
isCheckingStatus = false
isFetchingSecret = false
return
}
isCheckingStatus = false
containerStatus = status
root.savedTransportMode = TelemtConfigModel.getTransportMode()
@@ -307,17 +212,10 @@ PageType {
root.savedPublicHost = TelemtConfigModel.getPublicHost()
if (status === 1) {
TelemtConfigModel.setEnabled(true)
isFetchingSecret = true
isCheckingStatus = false
InstallController.fetchContainerSecret(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
} else {
isFetchingSecret = false
isCheckingStatus = false
if (status === 2) {
TelemtConfigModel.setEnabled(false)
}
InstallController.fetchContainerSecret(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
} else if (status === 2) {
TelemtConfigModel.setEnabled(false)
}
syncPageBusyIndicator()
}
function onContainerDiagnosticsRefreshed(portReachable, upstreamReachable, clientsConnected, lastConfigRefresh, statsEndpoint) {
@@ -334,35 +232,20 @@ PageType {
function onContainerSecretFetched(secret) {
if (!root.visible) {
isFetchingSecret = false
return
}
isFetchingSecret = false
syncPageBusyIndicator()
TelemtConfigModel.validateAndSetSecret(secret)
}
}
Item {
id: contentLayer
anchors.fill: parent
enabled: !root.pageBusy
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20 + PageController.safeAreaTopMargin
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: {
if (this.activeFocus) {
if (mainTabBar.currentIndex === 0) {
connectionListView.positionViewAtBeginning()
} else {
settingsListView.positionViewAtBeginning()
}
}
if (this.activeFocus) connectionListView.positionViewAtBeginning()
}
}
@@ -371,62 +254,57 @@ PageType {
anchors.top: backButton.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 8
spacing: 0
BaseHeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 24
headerText: qsTr("Telemt settings")
descriptionLinkText: qsTr("Read more about this settings")
descriptionLinkUrl: "https://github.com/telemt/telemt"
}
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
visible: root.telemtNetworkBlocked
text: qsTr("No internet connection. Connect to the internet to change Telemt settings.")
color: AmneziaStyle.color.mutedGray
wrapMode: Text.WordWrap
font.pixelSize: 14
headerText: qsTr("Telemt settings")
}
}
TabBar {
id: mainTabBar
anchors.top: pageHeader.bottom
anchors.left: parent.left
anchors.right: parent.right
width: parent.width
background: Rectangle {
color: AmneziaStyle.color.transparent
Rectangle {
width: parent.width
height: 1
anchors.bottom: parent.bottom
color: AmneziaStyle.color.slateGray
LabelWithButtonType {
Layout.fillWidth: true
Layout.leftMargin: 0
Layout.rightMargin: 16
text: qsTr("Read more about this settings")
textColor: AmneziaStyle.color.goldenApricot
clickedFunction: function () {
Qt.openUrlExternally("https://github.com/telemt/telemt")
}
}
TabButtonType {
text: qsTr("Connection")
isSelected: mainTabBar.currentIndex === 0
}
TabButtonType {
text: qsTr("Settings")
isSelected: mainTabBar.currentIndex === 1
TabBar {
id: mainTabBar
Layout.fillWidth: true
Layout.topMargin: 4
background: Rectangle {
color: AmneziaStyle.color.transparent
Rectangle {
width: parent.width
height: 1
anchors.bottom: parent.bottom
color: AmneziaStyle.color.slateGray
}
}
TabButtonType {
text: qsTr("Connection")
isSelected: mainTabBar.currentIndex === 0
}
TabButtonType {
text: qsTr("Settings")
isSelected: mainTabBar.currentIndex === 1
}
}
}
StackLayout {
id: tabContent
anchors.top: mainTabBar.bottom
anchors.top: pageHeader.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
@@ -440,11 +318,36 @@ PageType {
width: connectionListView.width
spacing: 0
function domainToHex(domain) {
var hex = ""
for (var i = 0; i < domain.length; i++) {
var code = domain.charCodeAt(i).toString(16)
hex += (code.length < 2 ? "0" : "") + code
}
return hex
}
function secretForMode(mode) {
if (mode === "faketls") {
var domain = root.savedTlsDomain !== "" ? root.savedTlsDomain : TelemtConfigModel.defaultTlsDomain()
return "ee" + secret + domainToHex(domain)
} else if (mode === "padded") {
return "dd" + secret
}
// Telemt default (secure MTProto, not FakeTLS): Telegram proxy links require dd + hex secret
return "dd" + secret
}
property int secretTabIndex: root.syncedSecretTabIndex
function activeSecret() {
return root.telemtClientSecretForTabIndex(secret, root.syncedSecretTabIndex,
root.savedTlsDomain, TelemtConfigModel.defaultTlsDomain())
if (root.syncedSecretTabIndex === 0) {
return secretForMode("standard")
}
if (root.syncedSecretTabIndex === 1) {
return secretForMode("padded")
}
return secretForMode("faketls")
}
function effectiveSecret() {
@@ -452,7 +355,7 @@ PageType {
}
function effectiveHost() {
return root.savedPublicHost !== "" ? root.savedPublicHost : ServersUiController.serverHostName(ServersUiController.processedServerId)
return root.savedPublicHost !== "" ? root.savedPublicHost : ServersModel.getProcessedServerData("hostName")
}
function tmeLink() {
@@ -754,7 +657,7 @@ PageType {
Layout.bottomMargin: 24
Layout.leftMargin: 0
Layout.rightMargin: 16
visible: ServersUiController.isProcessedServerHasWriteAccess()
visible: ServersModel.isProcessedServerHasWriteAccess()
text: qsTr("Delete Telemt")
textColor: AmneziaStyle.color.vibrantRed
clickedFunction: function () {
@@ -764,7 +667,7 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function () {
PageController.goToPage(PageEnum.PageDeinstalling)
InstallController.removeContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
InstallController.removeContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, function () {
})
@@ -787,11 +690,6 @@ PageType {
width: settingsListView.width
spacing: 0
function telemtActiveSecretForBaseHex(baseHex) {
return root.telemtClientSecretForTabIndex(baseHex, root.syncedSecretTabIndex,
root.savedTlsDomain, TelemtConfigModel.defaultTlsDomain())
}
SwitcherType {
id: enableTelemtSwitch
Layout.fillWidth: true
@@ -801,20 +699,18 @@ PageType {
Layout.bottomMargin: 16
text: qsTr("Enable Telemt")
checked: isEnabled
enabled: containerStatus !== 0 && containerStatus !== 3 && !root.pageBusy
&& !root.telemtNetworkBlocked
enabled: !isCheckingStatus && containerStatus !== 0 && containerStatus !== 3 && !isUpdating
onToggled: function () {
if (checked !== isEnabled) {
previousEnabled = isEnabled
previousContainerStatus = containerStatus
root.previousSecret = secret
isEnabled = checked
isUpdating = true
if (checked) {
root.pendingUpdateAfterEnable = true
InstallController.setContainerEnabled(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, true)
InstallController.setContainerEnabled(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, true)
} else {
InstallController.setContainerEnabled(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, false)
InstallController.setContainerEnabled(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, false)
}
}
}
@@ -840,29 +736,26 @@ PageType {
CaptionTextType {
Layout.fillWidth: true
text: secret !== "" ? telemtActiveSecretForBaseHex(secret) : qsTr("Not generated")
text: secret !== "" ? secret : qsTr("Not generated")
color: secret !== "" ? AmneziaStyle.color.paleGray : AmneziaStyle.color.mutedGray
wrapMode: Text.WrapAnywhere
elide: Text.ElideMiddle
font.pixelSize: 14
}
ImageButtonType {
Layout.alignment: Qt.AlignTop
implicitWidth: 36
implicitHeight: 36
hoverEnabled: true
image: "qrc:/images/controls/refresh-cw.svg"
imageColor: AmneziaStyle.color.paleGray
visible: ServersUiController.isProcessedServerHasWriteAccess()
visible: ServersModel.isProcessedServerHasWriteAccess()
onClicked: {
var secretSnapshot = secret
showQuestionDrawer(
qsTr("Generate new secret?"),
qsTr("All existing connection links will stop working. Users will need new links."),
qsTr("Generate"),
qsTr("Cancel"),
function () {
root.previousSecret = secretSnapshot
if (containerStatus === 1) {
isUpdating = true
TelemtConfigModel.generateSecret()
@@ -887,7 +780,7 @@ PageType {
Layout.rightMargin: 16
Layout.bottomMargin: 4
headerText: qsTr("Public host / IP")
textField.placeholderText: ServersUiController.serverHostName(ServersUiController.processedServerId)
textField.placeholderText: ServersModel.getProcessedServerData("hostName")
textField.text: publicHost
textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
@@ -916,7 +809,7 @@ PageType {
Layout.rightMargin: 16
Layout.bottomMargin: 12
visible: publicHostTextField.textField.text !== "" &&
publicHostTextField.textField.text !== ServersUiController.serverHostName(ServersUiController.processedServerId)
publicHostTextField.textField.text !== ServersModel.getProcessedServerData("hostName")
text: qsTr("⚠ This overrides the server IP in connection links. Make sure this host/domain points to your server.")
color: AmneziaStyle.color.goldenApricot
font.pixelSize: 12
@@ -1033,7 +926,6 @@ PageType {
clickedFunction: function () {
transportMode = (index === 0) ? "standard" : "faketls"
TelemtConfigModel.setTransportMode(transportMode)
root.syncedSecretTabIndex = transportMode === "faketls" ? 1 : 0
transportModeDropDown.closeTriggered()
}
}
@@ -1348,7 +1240,7 @@ PageType {
enabled: !diagLoading
onClicked: {
diagLoading = true
InstallController.refreshContainerDiagnostics(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, parseInt(port))
InstallController.refreshContainerDiagnostics(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, parseInt(port))
}
}
}
@@ -1474,7 +1366,7 @@ PageType {
Layout.bottomMargin: 32
Layout.rightMargin: 16
Layout.leftMargin: 16
visible: ServersUiController.isProcessedServerHasWriteAccess()
visible: ServersModel.isProcessedServerHasWriteAccess()
text: qsTr("Save")
clickedFunc: function () {
var portValue = portTextField.textField.text === ""
@@ -1514,7 +1406,6 @@ PageType {
previousNatEnabled = natEnabled
previousNatInternalIp = natInternalIp
previousNatExternalIp = natExternalIp
root.previousSecret = secret
isUpdating = true
root.telemtScheduleUpdate(false)
}
@@ -1523,5 +1414,34 @@ PageType {
}
}
Rectangle {
anchors.fill: parent
visible: isCheckingStatus || isUpdating || root.telemtNetworkBlocked
color: AmneziaStyle.color.midnightBlack
opacity: 0.6
z: 1
MouseArea {
anchors.fill: parent
}
BusyIndicator {
anchors.centerIn: parent
visible: isCheckingStatus || isUpdating
running: isCheckingStatus || isUpdating
width: 48
height: 48
}
CaptionTextType {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 24
anchors.rightMargin: 24
visible: root.telemtNetworkBlocked && !isCheckingStatus && !isUpdating
horizontalAlignment: Text.AlignHCenter
text: qsTr("No internet connection. Connect to the internet to change Telemt settings.")
color: AmneziaStyle.color.paleGray
wrapMode: Text.WordWrap
font.pixelSize: 14
}
}
}

View File

@@ -24,8 +24,8 @@ PageType {
property bool isInAppPurchase: false
function updateSubscriptionState() {
root.subscriptionExpired = ServersUiController.isServerSubscriptionExpired(ServersUiController.processedServerId)
root.subscriptionExpiringSoon = ServersUiController.isServerSubscriptionExpiringSoon(ServersUiController.processedServerId)
root.subscriptionExpired = ServersModel.getProcessedServerData("isSubscriptionExpired")
root.subscriptionExpiringSoon = ServersModel.getProcessedServerData("isSubscriptionExpiringSoon")
root.isSubscriptionRenewalAvailable = ApiAccountInfoModel.data("isSubscriptionRenewalAvailable")
root.isInAppPurchase = ApiAccountInfoModel.data("isInAppPurchase")
}
@@ -34,20 +34,12 @@ PageType {
root.updateSubscriptionState()
}
Connections {
target: ServersUiController
function onProcessedServerIdChanged() {
root.processedServer = proxyServersModel.get(0)
root.updateSubscriptionState()
}
}
Connections {
target: ServersModel
function onModelReset() {
function onProcessedServerChanged() {
root.processedServer = proxyServersModel.get(0)
root.updateSubscriptionState()
}
}
@@ -66,8 +58,8 @@ PageType {
sourceModel: ServersModel
filters: [
ValueFilter {
roleName: "serverId"
value: ServersUiController.processedServerId
roleName: "isCurrentlyProcessed"
value: true
}
]
@@ -116,7 +108,7 @@ PageType {
actionButtonFunction: function() {
PageController.showBusyIndicator(true)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.processedServerId, false)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.getServerId(ServersUiController.processedServerIndex), false)
PageController.showBusyIndicator(false)
if (!result) {
return
@@ -156,7 +148,7 @@ PageType {
text: qsTr("Renew subscription")
clickedFunc: function() {
SubscriptionUiController.getRenewalLink(ServersUiController.processedServerId)
SubscriptionUiController.getRenewalLink(ServersUiController.getServerId(ServersUiController.processedServerIndex))
}
}
@@ -208,7 +200,7 @@ PageType {
PageController.showBusyIndicator(true)
var prevIndex = ApiCountryModel.currentIndex
ApiCountryModel.currentIndex = index
if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)) {
if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.getServerId(ServersUiController.processedServerIndex), countryCode, countryName)) {
ApiCountryModel.currentIndex = prevIndex
}
PageController.showBusyIndicator(false)

View File

@@ -82,7 +82,7 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
var serverId = ServersUiController.processedServerId
var serverId = ServersUiController.getServerId(ServersUiController.processedServerIndex)
Qt.callLater(deactivateExternalDevice, serverId, supportTag, countryCode)
}
var noButtonFunction = function() {

View File

@@ -191,7 +191,7 @@ PageType {
}
if (fileName !== "") {
PageController.showBusyIndicator(true)
let result = SubscriptionUiController.exportNativeConfig(ServersUiController.processedServerId, countryCode, fileName)
let result = SubscriptionUiController.exportNativeConfig(ServersUiController.getServerId(ServersUiController.processedServerIndex), countryCode, fileName)
PageController.showBusyIndicator(false)
if (result) {
@@ -202,9 +202,9 @@ PageType {
function revokeConfig(countryCode) {
PageController.showBusyIndicator(true)
let result = SubscriptionUiController.revokeNativeConfig(ServersUiController.processedServerId, countryCode)
let result = SubscriptionUiController.revokeNativeConfig(ServersUiController.getServerId(ServersUiController.processedServerIndex), countryCode)
if (result) {
SubscriptionUiController.getAccountInfo(ServersUiController.processedServerId, true)
SubscriptionUiController.getAccountInfo(ServersUiController.getServerId(ServersUiController.processedServerIndex), true)
}
PageController.showBusyIndicator(false)

View File

@@ -76,18 +76,10 @@ PageType {
}
}
Connections {
target: ServersUiController
function onProcessedServerIdChanged() {
root.processedServer = proxyServersModel.get(0)
}
}
Connections {
target: ServersModel
function onModelReset() {
function onProcessedServerChanged() {
root.processedServer = proxyServersModel.get(0)
}
}
@@ -99,8 +91,8 @@ PageType {
sourceModel: ServersModel
filters: [
ValueFilter {
roleName: "serverId"
value: ServersUiController.processedServerId
roleName: "isCurrentlyProcessed"
value: true
}
]
@@ -139,7 +131,7 @@ PageType {
actionButtonImage: "qrc:/images/controls/edit-3.svg"
headerText: root.processedServer != null ? root.processedServer.name : ""
headerText: root.processedServer.name
actionButtonFunction: function() {
serverNameEditDrawer.openTriggered()
@@ -194,7 +186,7 @@ PageType {
textColor: AmneziaStyle.color.midnightBlack
clickedFunc: function() {
SubscriptionUiController.getRenewalLink(ServersUiController.processedServerId)
SubscriptionUiController.getRenewalLink(ServersUiController.getServerId(ServersUiController.processedServerIndex))
}
}
}
@@ -254,7 +246,7 @@ PageType {
text: qsTr("Renew subscription")
clickedFunc: function() {
SubscriptionUiController.getRenewalLink(ServersUiController.processedServerId)
SubscriptionUiController.getRenewalLink(ServersUiController.getServerId(ServersUiController.processedServerIndex))
}
}
@@ -266,8 +258,8 @@ PageType {
SwitcherType {
id: switcher
readonly property bool isVlessProtocol: SubscriptionUiController.isVlessProtocol(ServersUiController.processedServerId)
readonly property bool isProtocolSwitchBlocked: ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected
readonly property bool isVlessProtocol: SubscriptionUiController.isVlessProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex))
readonly property bool isProtocolSwitchBlocked: ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected
Layout.fillWidth: true
Layout.topMargin: 24
@@ -285,8 +277,8 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
} else {
PageController.showBusyIndicator(true)
SubscriptionUiController.setCurrentProtocol(ServersUiController.processedServerId, switcher.isVlessProtocol ? "awg" : "vless")
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, "", "", true)
SubscriptionUiController.setCurrentProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex), switcher.isVlessProtocol ? "awg" : "vless")
SubscriptionUiController.updateServiceFromGateway(ServersUiController.getServerId(ServersUiController.processedServerIndex), "", "", true)
PageController.showBusyIndicator(false)
}
}
@@ -334,7 +326,7 @@ PageType {
PageController.goToPage(PageEnum.PageSettingsApiSubscriptionKey)
PageController.showBusyIndicator(true)
SubscriptionUiController.prepareVpnKeyExport(ServersUiController.processedServerId)
SubscriptionUiController.prepareVpnKeyExport(ServersUiController.getServerId(ServersUiController.processedServerIndex))
PageController.showBusyIndicator(false)
}
@@ -440,7 +432,7 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot reload API config during active connection"))
} else {
PageController.showBusyIndicator(true)
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, "", "", true)
SubscriptionUiController.updateServiceFromGateway(ServersUiController.getServerId(ServersUiController.processedServerIndex), "", "", true)
PageController.showBusyIndicator(false)
}
}
@@ -478,8 +470,8 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection"))
} else {
PageController.showBusyIndicator(true)
if (SubscriptionUiController.deactivateDevice(ServersUiController.processedServerId)) {
SubscriptionUiController.getAccountInfo(ServersUiController.processedServerId, true)
if (SubscriptionUiController.deactivateDevice(ServersUiController.getServerId(ServersUiController.processedServerIndex))) {
SubscriptionUiController.getAccountInfo(ServersUiController.getServerId(ServersUiController.processedServerIndex), true)
}
PageController.showBusyIndicator(false)
}
@@ -515,7 +507,7 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot remove server during active connection"))
} else {
PageController.showBusyIndicator(true)
SubscriptionUiController.removeServer(ServersUiController.processedServerId)
SubscriptionUiController.removeServer(ServersUiController.getServerId(ServersUiController.processedServerIndex))
PageController.showBusyIndicator(false)
}
}
@@ -534,6 +526,6 @@ PageType {
anchors.fill: parent
expandedHeight: parent.height * 0.35
serverNameText: root.processedServer != null ? root.processedServer.name : ""
serverNameText: root.processedServer.name
}
}

View File

@@ -21,18 +21,10 @@ PageType {
property var processedServer
Connections {
target: ServersUiController
function onProcessedServerIdChanged() {
root.processedServer = proxyServersModel.get(0)
}
}
Connections {
target: ServersModel
function onModelReset() {
function onProcessedServerChanged() {
root.processedServer = proxyServersModel.get(0)
}
}
@@ -44,8 +36,8 @@ PageType {
sourceModel: ServersModel
filters: [
ValueFilter {
roleName: "serverId"
value: ServersUiController.processedServerId
roleName: "isCurrentlyProcessed"
value: true
}
]
@@ -56,7 +48,7 @@ PageType {
Component.onCompleted: {
PageController.showBusyIndicator(true)
SubscriptionUiController.prepareVpnKeyExport(ServersUiController.processedServerId)
SubscriptionUiController.prepareVpnKeyExport(ServersUiController.getServerId(ServersUiController.processedServerIndex))
PageController.showBusyIndicator(false)
}
@@ -127,7 +119,7 @@ PageType {
if (fileName !== "") {
PageController.showBusyIndicator(true)
let ok = SubscriptionUiController.exportVpnKey(ServersUiController.processedServerId, fileName)
let ok = SubscriptionUiController.exportVpnKey(ServersUiController.getServerId(ServersUiController.processedServerIndex), fileName)
PageController.showBusyIndicator(false)
if (ok) {
PageController.showNotificationMessage(qsTr("Config file saved"))
@@ -152,7 +144,7 @@ PageType {
clickedFunc: function() {
PageController.showBusyIndicator(true)
SubscriptionUiController.prepareVpnKeyExport(ServersUiController.processedServerId)
SubscriptionUiController.prepareVpnKeyExport(ServersUiController.getServerId(ServersUiController.processedServerIndex))
PageController.showBusyIndicator(false)
vpnKeyDrawer.openTriggered()
}

View File

@@ -154,10 +154,10 @@ PageType {
text: qsTr("Start minimized")
descriptionText: qsTr("Launch application minimized (works with autostart option turned on)")
enabled: SettingsController.isAutoStartEnabled()
enabled: switcherAutoStart.checked
opacity: enabled ? 1.0 : 0.5
checked: SettingsController.isAutoStartEnabled() && SettingsController.startMinimized
checked: SettingsController.startMinimized
onToggled: function() {
if (checked !== SettingsController.startMinimized) {
SettingsController.toggleStartMinimized(checked)

View File

@@ -37,7 +37,7 @@ PageType {
anchors.right: parent.right
anchors.left: parent.left
property var isServerFromApi: ServersUiController.isDefaultServerFromApi
property var isServerFromApi: ServersModel.isServerFromApi(ServersUiController.defaultServerIndex)
enabled: !isServerFromApi

View File

@@ -59,7 +59,7 @@ PageType {
Connections {
target: ServersUiController
function onProcessedServerIdChanged() {
function onProcessedServerIndexChanged() {
root.isServerWithWriteAccess = ServersUiController.isProcessedServerHasWriteAccess()
}
}
@@ -111,7 +111,7 @@ PageType {
readonly property var tColor: AmneziaStyle.color.paleGray
readonly property var clickedHandler: function() {
PageController.showBusyIndicator(true)
InstallController.scanServerForInstalledContainers(ServersUiController.processedServerId)
InstallController.scanServerForInstalledContainers(ServersUiController.getServerId(ServersUiController.processedServerIndex))
PageController.showBusyIndicator(false)
}
}
@@ -134,7 +134,7 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot reboot server during active connection"))
} else {
PageController.showBusyIndicator(true)
InstallController.rebootServer(ServersUiController.processedServerId)
InstallController.rebootServer(ServersUiController.getServerId(ServersUiController.processedServerIndex))
PageController.showBusyIndicator(false)
}
}
@@ -164,7 +164,7 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot remove server during active connection"))
} else {
PageController.showBusyIndicator(true)
InstallController.removeServer(ServersUiController.processedServerId)
InstallController.removeServer(ServersUiController.getServerId(ServersUiController.processedServerIndex))
PageController.showBusyIndicator(false)
}
}
@@ -194,7 +194,7 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot clear server from Amnezia software during active connection"))
} else {
PageController.goToPage(PageEnum.PageDeinstalling)
InstallController.removeAllContainers(ServersUiController.processedServerId)
InstallController.removeAllContainers(ServersUiController.getServerId(ServersUiController.processedServerIndex))
}
}
var noButtonFunction = function() {
@@ -208,7 +208,7 @@ PageType {
QtObject {
id: reset
property bool isVisible: ServersUiController.isServerFromApi(ServersUiController.processedServerId)
property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
readonly property string title: qsTr("Reset API config")
readonly property string description: ""
readonly property var tColor: AmneziaStyle.color.vibrantRed
@@ -223,7 +223,7 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot reset API config during active connection"))
} else {
PageController.showBusyIndicator(true)
SubscriptionUiController.removeApiConfig(ServersUiController.processedServerId)
SubscriptionUiController.removeApiConfig(ServersUiController.getServerId(ServersUiController.processedServerIndex))
PageController.showBusyIndicator(false)
}
}

View File

@@ -31,18 +31,10 @@ PageType {
}
}
Connections {
target: ServersUiController
function onProcessedServerIdChanged() {
root.processedServer = proxyServersModel.get(0)
}
}
Connections {
target: ServersModel
function onModelReset() {
function onProcessedServerChanged() {
root.processedServer = proxyServersModel.get(0)
}
}
@@ -54,8 +46,8 @@ PageType {
sourceModel: ServersModel
filters: [
ValueFilter {
roleName: "serverId"
value: ServersUiController.processedServerId
roleName: "isCurrentlyProcessed"
value: true
}
]
@@ -88,14 +80,11 @@ PageType {
actionButtonImage: "qrc:/images/controls/edit-3.svg"
headerText: root.processedServer != null ? root.processedServer.name : ""
headerText: root.processedServer.name
descriptionText: {
if (root.processedServer == null) {
return ""
}
if (ServersUiController.isServerFromApi(ServersUiController.processedServerId)) {
if (root.processedServer.isServerFromTelegramApi) {
return root.processedServer.serverDescription
} else if (ServersUiController.isProcessedServerHasWriteAccess()) {
} else if (root.processedServer.hasWriteAccess) {
return root.processedServer.credentialsLogin + " · " + root.processedServer.hostName
} else {
return root.processedServer.hostName
@@ -115,7 +104,7 @@ PageType {
anchors.fill: parent
expandedHeight: root.height * 0.35
serverNameText: root.processedServer != null ? root.processedServer.name : ""
serverNameText: root.processedServer.name
}
TabBar {
@@ -123,8 +112,8 @@ PageType {
Layout.fillWidth: true
currentIndex: (ServersUiController.isServerFromApi(ServersUiController.processedServerId)
&& !ServersUiController.serverHasInstalledContainers(ServersUiController.processedServerId)) ?
currentIndex: (ServersModel.getProcessedServerData("isServerFromTelegramApi")
&& !ServersModel.getProcessedServerData("hasInstalledContainers")) ?
root.pageSettingsServerData : root.pageSettingsServerProtocols
background: Rectangle {

View File

@@ -76,7 +76,7 @@ PageType {
clickedFunction: function() {
if (isClientProtocolExists) {
InstallController.openClientSettings(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, protocolIndex)
InstallController.openClientSettings(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, protocolIndex)
PageController.goToPage(clientProtocolPage);
} else {
PageController.showNotificationMessage(qsTr("Click the \"connect\" button to create a connection configuration"))
@@ -104,7 +104,7 @@ PageType {
visible: delegateContent.isServerSettingsVisible
clickedFunction: function() {
InstallController.openServerSettings(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, protocolIndex)
InstallController.openServerSettings(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, protocolIndex)
PageController.goToPage(serverProtocolPage);
}
@@ -140,14 +140,14 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
var message = qsTr("Unable to clear %1 profile while there is an active connection").arg(ContainersModel.getProcessedContainerName())
PageController.showNotificationMessage(message)
return
}
PageController.showBusyIndicator(true)
InstallController.clearCachedProfile(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
InstallController.clearCachedProfile(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
PageController.showBusyIndicator(false)
}
@@ -186,12 +186,12 @@ PageType {
var yesButtonFunction = function() {
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected
&& ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
&& ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Cannot remove active container"))
} else
{
PageController.goToPage(PageEnum.PageDeinstalling)
InstallController.removeContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
InstallController.removeContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex)
}
}
var noButtonFunction = function() {

View File

@@ -32,7 +32,7 @@ PageType {
Connections {
target: ServersUiController
function onProcessedServerIdChanged() {
function onProcessedServerIndexChanged() {
settingsContainersListView.updateContainersModelFilters()
}
}

Some files were not shown because too many files have changed in this diff Show More