mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-28 13:56:16 +03:00
Compare commits
4 Commits
server_scr
...
feat/imple
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d194e01fc0 | ||
|
|
7931317dec | ||
|
|
66103dac5a | ||
|
|
15669b3f98 |
1
.github/workflows/deploy.yml
vendored
1
.github/workflows/deploy.yml
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/.*\///');\
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
145
client/tests/CMakeLists.txt
Normal 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)
|
||||
147
client/tests/testAdminSelfHostedExport.cpp
Normal file
147
client/tests/testAdminSelfHostedExport.cpp
Normal 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"
|
||||
111
client/tests/testComplexOperations.cpp
Normal file
111
client/tests/testComplexOperations.cpp
Normal 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"
|
||||
|
||||
128
client/tests/testDefaultServerChange.cpp
Normal file
128
client/tests/testDefaultServerChange.cpp
Normal 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"
|
||||
|
||||
195
client/tests/testMultipleImports.cpp
Normal file
195
client/tests/testMultipleImports.cpp
Normal 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"
|
||||
385
client/tests/testSelfHostedServerSetup.cpp
Normal file
385
client/tests/testSelfHostedServerSetup.cpp
Normal 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"
|
||||
|
||||
114
client/tests/testServerEdgeCases.cpp
Normal file
114
client/tests/testServerEdgeCases.cpp
Normal 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"
|
||||
|
||||
106
client/tests/testServerEdit.cpp
Normal file
106
client/tests/testServerEdit.cpp
Normal 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"
|
||||
|
||||
93
client/tests/testServerRepositoryHelpers.h
Normal file
93
client/tests/testServerRepositoryHelpers.h
Normal 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
|
||||
112
client/tests/testServersModelSync.cpp
Normal file
112
client/tests/testServersModelSync.cpp
Normal 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"
|
||||
|
||||
265
client/tests/testSettingsSignals.cpp
Normal file
265
client/tests/testSettingsSignals.cpp
Normal 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"
|
||||
|
||||
88
client/tests/testSignalOrder.cpp
Normal file
88
client/tests/testSignalOrder.cpp
Normal 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"
|
||||
|
||||
306
client/tests/testUiServersModelAndController.cpp
Normal file
306
client/tests/testUiServersModelAndController.cpp
Normal 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"
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -44,6 +44,7 @@ signals:
|
||||
void connectionStateChanged();
|
||||
|
||||
void connectionErrorOccurred(ErrorCode errorCode);
|
||||
void reconnectWithUpdatedContainer(const QString &message);
|
||||
|
||||
void connectButtonClicked();
|
||||
void preparingConfig();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ Button {
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
ServersUiController.setProcessedServerIndex(ServersUiController.defaultServerIndex)
|
||||
ConnectionController.connectButtonClicked()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ Item {
|
||||
|
||||
onButtonStartChanged: {
|
||||
if (buttonStart) {
|
||||
ServersUiController.setProcessedServerIndex(ServersUiController.defaultServerIndex)
|
||||
ConnectionController.connectButtonClicked()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user