diff --git a/client/core/controllers/selfhosted/importController.cpp b/client/core/controllers/selfhosted/importController.cpp index c1c7503eb..c92418490 100644 --- a/client/core/controllers/selfhosted/importController.cpp +++ b/client/core/controllers/selfhosted/importController.cpp @@ -11,6 +11,9 @@ #include #include #include +#include +#include +#include #include #include "core/utils/containerEnum.h" @@ -372,6 +375,224 @@ int ImportController::qrChunksTotal() const return m_totalQrCodeChunksCount; } +ImportController::ImportResult ImportController::importLink(const QUrl &url) +{ + ImportResult result; + + if (!url.isValid()) { + qWarning() << "Invalid URL:" << url; + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + + QNetworkAccessManager *manager = new QNetworkAccessManager(); + + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); + + QNetworkReply *reply = manager->get(request); + + QEventLoop loop; + QTimer timer; + timer.setSingleShot(true); + bool timedOut = false; + + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + connect(&timer, &QTimer::timeout, &loop, [&]() { + timedOut = true; + loop.quit(); + }); + + timer.start(10000); // 10 sec + loop.exec(); + + if (timedOut) { + qWarning() << "Request timed out"; + reply->abort(); + reply->deleteLater(); + + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + + if (reply->error() != QNetworkReply::NoError) { + qWarning() << "Network error:" << reply->errorString(); + reply->deleteLater(); + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + + QByteArray data = reply->readAll(); + reply->deleteLater(); + + if (data.isEmpty()) { + qWarning() << "Empty response"; + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + + QByteArray decoded; + QString text; + + if (isValidBase64(data)) { + decoded = QByteArray::fromBase64(data); + text = QString::fromUtf8(decoded).trimmed(); + } else { + data.replace('\r', ""); + text = QString::fromUtf8(data).trimmed(); + } + + if (text.isEmpty()) { + qWarning() << "Decoded text is empty"; + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + + QStringList configs = text.split('\n', Qt::SkipEmptyParts); + + QJsonArray configStrings; + QJsonArray configNames; + + for (const QString &cfg : configs) { + + if (!(cfg.startsWith("vless://") || cfg.startsWith("vmess://") || cfg.startsWith("trojan://") + || cfg.startsWith("ss://") || cfg.startsWith("ssd://"))) { + + qWarning() << "Unknown protocol:" << cfg.left(20); + continue; + } + + QUrl url(cfg); + QUrlQuery query(url); + + QString security = query.queryItemValue("security").isEmpty() ? "None" : query.queryItemValue("security"); + QString name = QUrl::fromPercentEncoding(url.fragment().toUtf8()); + + if (name.isEmpty()) + name = "Unnamed"; + + configStrings.append(cfg); + configNames.append(name + " (" + security + ")"); + } + + if (configStrings.isEmpty()) { + qWarning() << "No valid configs found"; + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + + QString firstConfig = configStrings.first().toString(); + result = extractConfigFromData(firstConfig); + + QJsonObject xraySubConfig; + QJsonObject serverConfig; + + xraySubConfig["config_string"] = configStrings; + xraySubConfig["config_name"] = configNames; + + for (auto it = result.config.begin(); it != result.config.end(); ++it) { + serverConfig.insert(it.key(), it.value()); + } + + serverConfig.insert(configKey::description, m_appSettingsRepository->nextAvailableServerName()); + serverConfig["xray_subscription_config"] = xraySubConfig; + serverConfig["xray_subscription_config_current"] = 0; + + result.config = serverConfig; + + return result; +} + +ImportController::ImportResult ImportController::editServerConfigWithData(QString data, int serverIndex) +{ + ImportResult result = extractConfigFromData(data); + + ServerConfig serverCurrentConfig = m_serversRepository->server(serverIndex); + ServerConfig serverConfig = ServerConfig::fromJson(result.config); + + serverConfig.visit([&](auto &cfg) { + using T = std::decay_t; + + if (auto current = serverCurrentConfig.as()) { + cfg.description = current->description; + + if constexpr (std::is_same_v) { + cfg.xraySubscriptionConfigs->configString = current->xraySubscriptionConfigs->configString; + cfg.currentConfig = m_serversRepository->getCurrentConfigIndex(); + } + } + }); + + m_serversRepository->editServer(serverIndex, serverConfig); + + return result; +} + +bool ImportController::isValidBase64(const QByteArray &input) +{ + QByteArray data = input; + data = data.trimmed(); + + if (data.isEmpty()) + return false; + + static QRegularExpression base64Regex("^[A-Za-z0-9+/=_\\r\\n-]+$"); + + if (!base64Regex.match(QString::fromLatin1(data)).hasMatch()) + return false; + + data.replace("\r", ""); + data.replace("\n", ""); + + if (data.size() % 4 != 0) + return false; + + QByteArray decoded = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding); + + if (decoded.isEmpty()) + decoded = QByteArray::fromBase64(data); + + return !decoded.isEmpty(); +} + +QByteArray ImportController::base64Decode(const QByteArray &input) +{ + std::string clean(input.constData(), input.length()); + + for (auto &c : clean) { + if (c == '-') + c = '+'; + if (c == '_') + c = '/'; + } + + while (clean.size() % 4 != 0) + clean += '='; + + static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + std::string output; + std::vector T(256, -1); + for (int i = 0; i < 64; i++) + T[base64_chars[i]] = i; + + int val = 0, valb = -8; + for (unsigned char c : clean) { + if (T[c] == -1) + break; + val = (val << 6) + T[c]; + valb += 6; + if (valb >= 0) { + output.push_back(char((val >> valb) & 0xFF)); + valb -= 8; + } + } + QByteArray output_qa(output.c_str(), output.length()); + return output_qa; +} + void ImportController::importConfig(const QJsonObject &config) { ServerCredentials credentials; @@ -380,8 +601,20 @@ void ImportController::importConfig(const QJsonObject &config) credentials.userName = config.value(configKey::userName).toString(); credentials.secretData = config.value(configKey::password).toString(); + qDebug() << "credentials"; + qDebug() << "hostName: " << credentials.hostName; + qDebug() << "port: " << credentials.port; + qDebug() << "userName: " << credentials.userName; + qDebug() << "secretData: " << credentials.secretData << '\n'; + + qDebug() << "creds valid? -> " << credentials.isValid(); + qDebug() << "config contains containers? -> " << config.contains(configKey::containers); + if (credentials.isValid() || config.contains(configKey::containers)) { ServerConfig serverConfig = ServerConfig::fromJson(config); + qDebug() << "server config is SH? -> " << serverConfig.isSelfHosted(); + qDebug() << "server config is XRay? -> " << serverConfig.isXRayConfig(); + qDebug() << "adding server"; m_serversRepository->addServer(serverConfig); emit importFinished(); } else if (config.contains(configKey::configVersion)) { diff --git a/client/core/controllers/selfhosted/importController.h b/client/core/controllers/selfhosted/importController.h index 3168f6f4a..43e9214b9 100644 --- a/client/core/controllers/selfhosted/importController.h +++ b/client/core/controllers/selfhosted/importController.h @@ -63,11 +63,17 @@ public: int qrChunksReceived() const; int qrChunksTotal() const; + ImportResult importLink(const QUrl &url); + ImportResult editServerConfigWithData(QString data, int serverIndex); + bool isValidBase64(const QByteArray &input); + QByteArray base64Decode(const QByteArray &input); + void importConfig(const QJsonObject &config); QJsonObject processNativeWireGuardConfig(const QJsonObject &config); signals: void importFinished(); + void linkImportFinished(const ImportResult &result); void importErrorOccurred(ErrorCode errorCode, bool goToPageHome); void restoreAppConfig(const QByteArray &data); diff --git a/client/core/controllers/serversController.cpp b/client/core/controllers/serversController.cpp index 1842775b8..5bdbcaad3 100644 --- a/client/core/controllers/serversController.cpp +++ b/client/core/controllers/serversController.cpp @@ -58,6 +58,31 @@ void ServersController::clearCachedProfile(int serverIndex, DockerContainer cont m_serversRepository->clearLastConnectionConfig(serverIndex, container); } +void ServersController::setCurrentConfigIndex(const int index) +{ + m_serversRepository->setCurrentConfigIndex(index); +} + +int ServersController::getCurrentConfigIndex() const +{ + return m_serversRepository->getCurrentConfigIndex(); +} + +QString ServersController::getConfigString(const int index) const +{ + return m_serversRepository->getConfigString(index); +} + +QString ServersController::getConfigName(const int index) const +{ + return m_serversRepository->getConfigName(index); +} + +QJsonArray ServersController::getConfigNames() const +{ + return m_serversRepository->getConfigNames(); +} + QJsonArray ServersController::getServersArray() const { QJsonArray result; diff --git a/client/core/controllers/serversController.h b/client/core/controllers/serversController.h index 3b91e5558..61c97de09 100644 --- a/client/core/controllers/serversController.h +++ b/client/core/controllers/serversController.h @@ -64,6 +64,13 @@ public: // Cache management void clearCachedProfile(int serverIndex, DockerContainer container); + // XRay subscription config getters/setters + void setCurrentConfigIndex(int index); + int getCurrentConfigIndex() const; + QString getConfigString(const int index) const; + QString getConfigName(const int index) const; + QJsonArray getConfigNames() const; + // Getters QJsonArray getServersArray() const; QVector getServers() const; diff --git a/client/core/models/selfhosted/selfHostedServerConfig.cpp b/client/core/models/selfhosted/selfHostedServerConfig.cpp index 535350936..ecd900d9c 100644 --- a/client/core/models/selfhosted/selfHostedServerConfig.cpp +++ b/client/core/models/selfhosted/selfHostedServerConfig.cpp @@ -15,6 +15,31 @@ namespace amnezia { +QJsonObject SelfHostedServerConfig::XRaySubscriptionConfigs::toJson() const + { + QJsonObject obj; + + if (!configString.isEmpty()) { + obj[QLatin1String("config_string")] = configString; + } + + if (!configName.isEmpty()) { + obj[QLatin1String("config_name")] = configName; + } + + return obj; +} + +SelfHostedServerConfig::XRaySubscriptionConfigs SelfHostedServerConfig::XRaySubscriptionConfigs::fromJson(const QJsonObject &json) +{ + QJsonObject xrayConfigObj = json.value(QLatin1String("xray_subscription_config")).toObject(); + + XRaySubscriptionConfigs xraySubscriptionConfigs; + xraySubscriptionConfigs.configString = xrayConfigObj.value(QLatin1String("config_string")).toArray(); + xraySubscriptionConfigs.configName = xrayConfigObj.value(QLatin1String("config_name")).toArray(); + return xraySubscriptionConfigs; +} + using namespace ContainerEnumNS; bool SelfHostedServerConfig::hasCredentials() const @@ -95,6 +120,15 @@ QJsonObject SelfHostedServerConfig::toJson() const if (port.has_value()) { obj[configKey::port] = port.value(); } + + QJsonObject xraySubscriptionConfigsObj = xraySubscriptionConfigs->toJson(); + if (!xraySubscriptionConfigsObj.isEmpty()) { + obj[QLatin1String("xray_subscription_configs")] = xraySubscriptionConfigsObj; + } + + if (currentConfig) { + obj[QLatin1String("xray_subscription_config_current")] = currentConfig.value(); + } return obj; } diff --git a/client/core/models/selfhosted/selfHostedServerConfig.h b/client/core/models/selfhosted/selfHostedServerConfig.h index 39dc88540..d3b6aac6c 100644 --- a/client/core/models/selfhosted/selfHostedServerConfig.h +++ b/client/core/models/selfhosted/selfHostedServerConfig.h @@ -2,6 +2,7 @@ #define SELFHOSTEDSERVERCONFIG_H #include +#include #include #include @@ -29,6 +30,17 @@ struct SelfHostedServerConfig { std::optional userName; std::optional password; std::optional port; + + struct XRaySubscriptionConfigs + { + QJsonArray configString; + QJsonArray configName; + + QJsonObject toJson() const; + static XRaySubscriptionConfigs fromJson(const QJsonObject &json); + }; + std::optional xraySubscriptionConfigs; + std::optional currentConfig; bool hasCredentials() const; bool isReadOnly() const; diff --git a/client/core/models/serverConfig.cpp b/client/core/models/serverConfig.cpp index 7006fb07b..08b01eac5 100644 --- a/client/core/models/serverConfig.cpp +++ b/client/core/models/serverConfig.cpp @@ -120,6 +120,10 @@ bool ServerConfig::isApiConfig() const return isApiV1() || isApiV2(); } +bool ServerConfig::isXRayConfig() const { + return isSelfHosted() && std::get(data).currentConfig.has_value(); +} + QJsonObject ServerConfig::toJson() const { return std::visit([](const auto& v) { return v.toJson(); }, data); diff --git a/client/core/models/serverConfig.h b/client/core/models/serverConfig.h index 93ef1842d..7e396eaf0 100644 --- a/client/core/models/serverConfig.h +++ b/client/core/models/serverConfig.h @@ -57,6 +57,7 @@ struct ServerConfig { bool isApiV1() const; bool isApiV2() const; bool isApiConfig() const; + bool isXRayConfig() const; template T* as() { diff --git a/client/core/repositories/secureServersRepository.cpp b/client/core/repositories/secureServersRepository.cpp index 7107b8aa5..d2de82aed 100644 --- a/client/core/repositories/secureServersRepository.cpp +++ b/client/core/repositories/secureServersRepository.cpp @@ -176,6 +176,55 @@ void SecureServersRepository::clearLastConnectionConfig(int serverIndex, DockerC setContainerConfig(serverIndex, container, containerCfg); } +void SecureServersRepository::setCurrentConfigIndex(const int index) +{ + ServerConfig config = server(m_defaultServerIndex); + + const SelfHostedServerConfig *xrayConfigs = config.as(); + if (auto *cfg = std::get_if(&config.data)) + cfg->currentConfig = index; +} + +int SecureServersRepository::getCurrentConfigIndex() const +{ + const ServerConfig config = server(m_defaultServerIndex); + if (!config.isXRayConfig()) + return int(); + + const SelfHostedServerConfig *xrayConfigs = config.as(); + return xrayConfigs->currentConfig.value(); +} + +QString SecureServersRepository::getConfigString(const int index) const +{ + const ServerConfig config = server(m_defaultServerIndex); + if (!config.isXRayConfig()) + return QString(); + + const SelfHostedServerConfig *xrayConfigs = config.as(); + return xrayConfigs->xraySubscriptionConfigs->configString.at(index).toString(); +} + +QString SecureServersRepository::getConfigName(const int index) const +{ + const ServerConfig config = server(m_defaultServerIndex); + if (!config.isXRayConfig()) + return QString(); + + const SelfHostedServerConfig *xrayConfigs = config.as(); + return xrayConfigs->xraySubscriptionConfigs->configName.at(index).toString(); +} + +QJsonArray SecureServersRepository::getConfigNames() const +{ + const ServerConfig config = server(m_defaultServerIndex); + if (!config.isXRayConfig()) + return QJsonArray(); + + const SelfHostedServerConfig *xrayConfigs = config.as(); + return xrayConfigs->xraySubscriptionConfigs->configName; +} + ServerCredentials SecureServersRepository::serverCredentials(int index) const { ServerConfig config = server(index); diff --git a/client/core/repositories/secureServersRepository.h b/client/core/repositories/secureServersRepository.h index 03c876a71..a52c9b776 100644 --- a/client/core/repositories/secureServersRepository.h +++ b/client/core/repositories/secureServersRepository.h @@ -35,6 +35,12 @@ public: void setContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config); void clearLastConnectionConfig(int serverIndex, DockerContainer container); + void setCurrentConfigIndex(int index); + int getCurrentConfigIndex() const; + QString getConfigString(const int index) const; + QString getConfigName(const int index) const; + QJsonArray getConfigNames() const; + ServerCredentials serverCredentials(int index) const; bool hasServerWithVpnKey(const QString &vpnKey) const; bool hasServerWithCrc(quint16 crc) const; diff --git a/client/images/images.qrc b/client/images/images.qrc index 6bbfb6308..504c8ee9c 100644 --- a/client/images/images.qrc +++ b/client/images/images.qrc @@ -9,8 +9,10 @@ controls/amnezia.svg controls/app.svg controls/archive-restore.svg + controls/arrow-down.svg controls/arrow-left.svg controls/arrow-right.svg + controls/arrow-up.svg controls/bug.svg controls/check.svg controls/chevron-down.svg diff --git a/client/ui/controllers/importUiController.cpp b/client/ui/controllers/importUiController.cpp index ce9b952c1..265a22b3b 100644 --- a/client/ui/controllers/importUiController.cpp +++ b/client/ui/controllers/importUiController.cpp @@ -31,6 +31,41 @@ ImportUiController::ImportUiController(ImportController* importController, QObje connect(m_importController, &ImportController::restoreAppConfig, this, &ImportUiController::restoreAppConfig); } +bool ImportUiController::importLink(const QUrl &url) +{ + auto result = m_importController->importLink(url); + + if (result.errorCode != ErrorCode::NoError) { + emit importErrorOccurred(result.errorCode, false); + return false; + } + + m_config = result.config; + m_configFileName = result.configFileName; + m_maliciousWarningText = result.maliciousWarningText; + m_isNativeWireGuardConfig = result.isNativeWireGuardConfig; + + return true; +} + +bool ImportUiController::editServerConfigWithData(QString data, int serverIndex) +{ + auto result = m_importController->editServerConfigWithData(data, serverIndex); + + if (result.errorCode != ErrorCode::NoError) { + emit importErrorOccurred(result.errorCode, false); + return false; + } + + m_config = result.config; + m_configFileName = result.configFileName; + m_maliciousWarningText = result.maliciousWarningText; + m_isNativeWireGuardConfig = result.isNativeWireGuardConfig; + + return true; +} + + bool ImportUiController::extractConfigFromFile(const QString &fileName) { QString data; diff --git a/client/ui/controllers/importUiController.h b/client/ui/controllers/importUiController.h index 853539d05..f465203d3 100644 --- a/client/ui/controllers/importUiController.h +++ b/client/ui/controllers/importUiController.h @@ -20,6 +20,8 @@ public: public slots: void importConfig(); void clearConfigFileName(); + bool importLink(const QUrl &url); + bool editServerConfigWithData(QString data, int serverIndex); bool extractConfigFromFile(const QString &fileName); bool extractConfigFromData(QString data); bool extractConfigFromQr(const QByteArray &data); diff --git a/client/ui/controllers/serversUiController.cpp b/client/ui/controllers/serversUiController.cpp index ccafe0f4b..c28aa4a53 100644 --- a/client/ui/controllers/serversUiController.cpp +++ b/client/ui/controllers/serversUiController.cpp @@ -272,6 +272,13 @@ bool ServersUiController::isDefaultServerFromApi() const || configVersion == apiDefs::ConfigSource::AmneziaGateway; } +bool ServersUiController::isDefaultServerContainXRayConfigs() const +{ + int defaultIndex = getDefaultServerIndex(); + const ServerConfig server = m_serversController->getServerConfig(defaultIndex); + return server.isXRayConfig(); +} + int ServersUiController::getProcessedServerIndex() const { return m_processedServerIndex; @@ -444,6 +451,26 @@ QString ServersUiController::adDescription() const return QString(); } +void ServersUiController::setCurrentConfigIndex(const int index) +{ + m_serversController->setCurrentConfigIndex(index); +} + +int ServersUiController::getCurrentConfigIndex() const +{ + return m_serversController->getCurrentConfigIndex(); +} + +QString ServersUiController::getConfigName(const int index) const +{ + return m_serversController->getConfigName(index); +} + +QJsonArray ServersUiController::getConfigNames() const +{ + return m_serversController->getConfigNames(); +} + void ServersUiController::updateContainersModel() { if (m_processedServerIndex < 0 || m_processedServerIndex >= m_serversController->getServersCount()) { diff --git a/client/ui/controllers/serversUiController.h b/client/ui/controllers/serversUiController.h index 7d16362af..5f841ea24 100644 --- a/client/ui/controllers/serversUiController.h +++ b/client/ui/controllers/serversUiController.h @@ -26,6 +26,8 @@ class ServersUiController : public QObject Q_PROPERTY(bool isDefaultServerDefaultContainerHasSplitTunneling READ isDefaultServerDefaultContainerHasSplitTunneling NOTIFY defaultServerIndexChanged) Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged) + Q_PROPERTY(bool isDefaultServerContainXRayConfigs READ isDefaultServerContainXRayConfigs NOTIFY defaultServerIndexChanged) + Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged) Q_PROPERTY(int processedContainerIndex READ getProcessedContainerIndex WRITE setProcessedContainerIndex NOTIFY processedContainerIndexChanged) Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerIndexChanged) @@ -61,6 +63,7 @@ public slots: QString getDefaultServerDescriptionExpanded() const; bool isDefaultServerDefaultContainerHasSplitTunneling() const; bool isDefaultServerFromApi() const; + bool isDefaultServerContainXRayConfigs() const; int getProcessedServerIndex() const; void setProcessedServerIndex(int index); @@ -77,6 +80,11 @@ public slots: bool isAdVisible() const; QString adHeader() const; QString adDescription() const; + + void setCurrentConfigIndex(int index); + int getCurrentConfigIndex() const; + QString getConfigName(const int index) const; + QJsonArray getConfigNames() const; QStringList getAllInstalledServicesName(int serverIndex) const; diff --git a/client/ui/models/serversModel.cpp b/client/ui/models/serversModel.cpp index 131d241f8..3ef926cee 100644 --- a/client/ui/models/serversModel.cpp +++ b/client/ui/models/serversModel.cpp @@ -34,6 +34,11 @@ namespace constexpr char publicKeyInfo[] = "public_key"; constexpr char expiresAt[] = "expires_at"; + + constexpr char xraySubscriptionConfig[] = "xray_subscription_configs"; + constexpr char xraySubscriptionConfigString[] = "config_string"; + constexpr char xraySubscriptionConfigName[] = "config_name"; + constexpr char xraySubscriptionConfigCurrent[] = "xray_subscription_config_current"; } QString normalizeVpnKey(const QString &vpnKey) @@ -207,6 +212,11 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const } return apiUtils::isSubscriptionExpiringSoon(apiConfig.subscription.endDate); } + case IsXRayConfigSelectionAvailableRole: { + if (server.isSelfHosted()) { + return server.as()->xraySubscriptionConfigs.has_value(); + } + } } return QVariant(); @@ -301,6 +311,11 @@ bool ServersModel::isDefaultServerFromApi() || data(m_defaultServerIndex, IsServerFromGatewayApiRole).toBool(); } +bool ServersModel::isDefaultServerContainXRayConfigs() +{ + return data(m_defaultServerIndex, IsXRayConfigSelectionAvailableRole).toBool(); +} + bool ServersModel::isProcessedServerHasWriteAccess() { return qvariant_cast(data(m_processedServerIndex, HasWriteAccessRole)); @@ -350,6 +365,8 @@ QHash ServersModel::roleNames() const roles[IsSubscriptionExpiredRole] = "isSubscriptionExpired"; roles[IsSubscriptionExpiringSoonRole] = "isSubscriptionExpiringSoon"; + roles[IsXRayConfigSelectionAvailableRole] = "isXRayConfigSelectionAvailable"; + return roles; } diff --git a/client/ui/models/serversModel.h b/client/ui/models/serversModel.h index 9bff2eadb..051be48e8 100644 --- a/client/ui/models/serversModel.h +++ b/client/ui/models/serversModel.h @@ -46,6 +46,8 @@ public: IsSubscriptionExpiredRole, IsSubscriptionExpiringSoonRole, + IsXRayConfigSelectionAvailableRole, + HasAmneziaDns }; @@ -60,6 +62,8 @@ public slots: bool isDefaultServerCurrentlyProcessed(); bool isDefaultServerFromApi(); + bool isDefaultServerContainXRayConfigs(); + bool isProcessedServerHasWriteAccess(); bool isDefaultServerHasWriteAccess(); bool hasServerWithWriteAccess(); diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 666b3e412..db34f9ade 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -311,11 +311,11 @@ PageType { objectName: "rowLayoutLabel" Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.topMargin: 8 - Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : ServersUiController.isDefaultServerFromApi ? 61 : 16 + Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : (ServersUiController.isDefaultServerFromApi || ServersUiController.isDefaultServerContainXRayConfigs) ? 61 : 16 spacing: 0 BasicButtonType { - enabled: (ServersUiController.defaultServerImagePathCollapsed !== "") && drawer.isCollapsedStateActive + enabled: (ServersUiController.defaultServerImagePathCollapsed !== "" || ServersUiController.isDefaultServerContainXRayConfigs) && drawer.isCollapsedStateActive hoverEnabled: enabled implicitHeight: 36 @@ -381,7 +381,7 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 8 - visible: !ServersUiController.isDefaultServerFromApi + visible: !ServersUiController.isDefaultServerFromApi && !ServersUiController.isDefaultServerContainXRayConfigs DropDownType { id: containersDropDown diff --git a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml index 51a805a66..d7b2cce52 100644 --- a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml @@ -107,7 +107,7 @@ PageType { imageSource: "qrc:/images/controls/download.svg" - checked: index === ServersModel.getCurrentConfigIndex() + checked: index === ServersUiController.getCurrentConfigIndex() checkable: !ConnectionController.isConnected onClicked: { @@ -120,10 +120,10 @@ PageType { return } - if (index !== ServersModel.getCurrentConfigIndex()) { + if (index !== ServersUiController.getCurrentConfigIndex()) { PageController.showBusyIndicator(true) - ServersModel.setCurrentConfigIndex(index) - ImportController.editServerConfigWithData(ServersModel.getConfigString(index), ServersModel.getProcessedServerIndex()) + ServersUiController.setCurrentConfigIndex(index) + ImportController.editServerConfigWithData(ServersUiController.getConfigString(index), ServersUiController.getProcessedServerIndex()) PageController.showBusyIndicator(false) } } @@ -154,7 +154,7 @@ PageType { } Component.onCompleted: { - const names = ServersModel.getConfigNames() + const names = ServersUiController.getConfigNames() xrayConfigs.clear() for (let i = 0; i < names.length; ++i) { diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 17b4abf96..39b3b2e8f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -197,8 +197,7 @@ PageType { } clickedFunc: function() { - if (isValidUrl(textKey.textField.text)) { - ImportController.importLink(textKey.textField.text) + if (isValidUrl(textKey.textField.text) && ImportController.importLink(textKey.textField.text)) { PageController.goToPage(PageEnum.PageSetupWizardViewConfig) }else if (ImportController.extractConfigFromData(textKey.textField.text)) { PageController.goToPage(PageEnum.PageSetupWizardViewConfig) diff --git a/client/ui/qml/qml.qrc b/client/ui/qml/qml.qrc index 9f2a8ccf6..1933bb7e1 100644 --- a/client/ui/qml/qml.qrc +++ b/client/ui/qml/qml.qrc @@ -99,6 +99,8 @@ Pages2/PageSettingsServerServices.qml Pages2/PageSettingsServersList.qml Pages2/PageSettingsSplitTunneling.qml + Pages2/PageSettingsXRayAvailableConfigs.qml + Pages2/PageSettingsXRayServerInfo.qml Pages2/PageSettingsNewsNotifications.qml Pages2/PageSettingsNewsDetail.qml Pages2/PageProtocolAwgClientSettings.qml