From 87393f124f4db85683c122d1fcdf92e05ce50e0b Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Fri, 13 Feb 2026 18:08:59 +0200 Subject: [PATCH 01/14] demo-parser --- client/ui/controllers/importController.cpp | 122 +++++++++++++++++++++ client/ui/controllers/importController.h | 3 + 2 files changed, 125 insertions(+) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 91d7ec3b5..58629a097 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "core/api/apiDefs.h" #include "core/api/apiUtils.h" @@ -77,6 +78,127 @@ ImportController::ImportController(const QSharedPointer &serversMo #endif } +bool ImportController::httpGet(const QUrl &url) +{ + QNetworkAccessManager manager; + + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); + + QNetworkReply *reply = manager.get(request); + + QEventLoop loop; + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + + if (reply->error() != QNetworkReply::NoError) { + QString error = reply->errorString(); + reply->deleteLater(); + qWarning() << error.toStdString(); + return false; + } + + QByteArray data = reply->readAll(); + reply->deleteLater(); + + QByteArray decoded; + QString text; + if (isValidBase64(data)) { + qDebug() << "Data is a base64 string\n"; + // decoded = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding); + decoded = base64Decode(data); + text = QString::fromUtf8(decoded).trimmed(); + } else { + qDebug() << "Data isn't a base64 string\n"; + data.replace('\r', ""); + text = QString::fromUtf8(data).trimmed(); + } + QStringList configs = text.split('\n', Qt::SkipEmptyParts); + + qDebug() << decoded << "\n"; + qDebug() << text << "\n"; + + for (const QString &cfg : configs) { + if (cfg.startsWith("vmess://")) + qDebug() << cfg; + else if (cfg.startsWith("vless://")) + qDebug() << cfg; + else if (cfg.startsWith("ss://")) + qDebug() << cfg; + else if (cfg.startsWith("trojan://")) + qDebug() << cfg; + else + qDebug() << "Unknown protocol:\n" << cfg.left(10); + } + + return true; +} + +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(); +} + +static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +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 += '='; + + 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; +} + bool ImportController::extractConfigFromFile(const QString &fileName) { QString data; diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index c2da0b8bd..3944aead9 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -31,6 +31,9 @@ public: public slots: void importConfig(); void clearConfigFileName(); + bool httpGet(const QUrl &url); + bool isValidBase64(const QByteArray &input); + QByteArray base64Decode(const QByteArray &input); bool extractConfigFromFile(const QString &fileName); bool extractConfigFromData(QString data); bool extractConfigFromQr(const QByteArray &data); From 8fee9864aa1f742e1538b7d43f5b9b657aa02bad Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Mon, 16 Feb 2026 13:35:58 +0200 Subject: [PATCH 02/14] demo-ui --- client/resources.qrc | 4 +- client/ui/controllers/pageController.h | 2 + client/ui/qml/Pages2/PageHome.qml | 2 + .../PageSettingsXRayAvailableConfigs.qml | 165 ++++++++++++ .../qml/Pages2/PageSettingsXRayServerInfo.qml | 235 ++++++++++++++++++ .../Pages2/PageSetupWizardConfigSource.qml | 34 +++ 6 files changed, 441 insertions(+), 1 deletion(-) create mode 100644 client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml create mode 100644 client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml diff --git a/client/resources.qrc b/client/resources.qrc index c050650eb..e92661613 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -225,7 +225,9 @@ ui/qml/Pages2/PageSettingsNewsDetail.qml ui/qml/Pages2/PageProtocolAwgClientSettings.qml ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml - ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml + ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml + ui/qml/Pages2/PageSettingsXRayServerInfo.qml + ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml ui/qml/Pages2/PageSetupWizardApiServicesList.qml ui/qml/Pages2/PageSetupWizardConfigSource.qml ui/qml/Pages2/PageSetupWizardCredentials.qml diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 529106343..ffbb7bc92 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -41,6 +41,8 @@ namespace PageLoader PageSettingsApiNativeConfigs, PageSettingsApiDevices, PageSettingsApiSubscriptionKey, + PageSettingsXRayAvailableConfigs, + PageSettingsXRayServerInfo, PageSettingsKillSwitchExceptions, PageServiceSftpSettings, diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 764f3d13f..9d28e44aa 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -358,6 +358,8 @@ PageType { PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } + /*} else if (ServersModel.getProcessedServerData("isConfigSelectionAvailable")) { + PageController.goToPage(PageEnum.PageSettingsXRayAvailableConfigs)*/ } else { PageController.goToPage(PageEnum.PageSettingsServerInfo) } diff --git a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml new file mode 100644 index 000000000..99f7867e9 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml @@ -0,0 +1,165 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + property var processedServer + + Connections { + target: ServersModel + + function onProcessedServerChanged() { + root.processedServer = proxyServersModel.get(0) + } + } + + SortFilterProxyModel { + id: proxyServersModel + objectName: "proxyServersModel" + + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + + Component.onCompleted: { + root.processedServer = proxyServersModel.get(0) + } + } + + ListViewType { + id: menuContent + + anchors.fill: parent + + model: XRayConfigsModel + + currentIndex: 0 + + ButtonGroup { + id: containersRadioButtonGroup + } + + header: ColumnLayout { + width: menuContent.width + + spacing: 4 + + BackButtonType { + id: backButton + objectName: "backButton" + + Layout.topMargin: 20 + SettingsController.safeAreaTopMargin + } + + HeaderTypeWithButton { + id: headerContent + objectName: "headerContent" + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 10 + + actionButtonImage: "qrc:/images/controls/settings.svg" + + headerText: root.processedServer.name + + actionButtonFunction: function() { + /* TODO: chnge server info to processed + PageController.showBusyIndicator(true) + let result = ApiSettingsController.getAccountInfo(false) + PageController.showBusyIndicator(false) + if (!result) { + return + }*/ + + PageController.goToPage(PageEnum.PageSettingsXRayServerInfo) + } + } + } + + delegate: ColumnLayout { + id: content + + width: menuContent.width + height: content.implicitHeight + + RowLayout { + VerticalRadioButton { + id: containerRadioButton + + Layout.fillWidth: true + Layout.leftMargin: 16 + // TODO: proper name and description + // e.g.: [DE] VMESS - WS + // VMES/WS/None + text: configName + + ButtonGroup.group: containersRadioButtonGroup + + imageSource: "qrc:/images/controls/download.svg" + + checked: index === XRayConfigsModel.currentIndex + checkable: !ConnectionController.isConnected + + onClicked: { + if (ConnectionController.isConnectionInProgress) { + PageController.showNotificationMessage(qsTr("Unable change config while trying to make an active connection")) + return + } + if (ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Unable change config while there is an active connection")) + return + } + + if (index !== XRayConfigsModel.currentIndex) { + PageController.showBusyIndicator(true) + var prevIndex = XRayConfigsModel.currentIndex + XRayConfigsModel.currentIndex = index + // TODO: properly realize switching between configs + if (!XRayConfigsController.updateServer(ServersModel.defaultIndex, configCode, configName)) { + XRayConfigsModel.currentIndex = prevIndex + } + PageController.showBusyIndicator(false) + } + } + + Keys.onEnterPressed: { + if (checkable) { + checked = true + } + containerRadioButton.clicked() + } + Keys.onReturnPressed: { + if (checkable) { + checked = true + } + containerRadioButton.clicked() + } + } + } + + DividerType { + Layout.fillWidth: true + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml b/client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml new file mode 100644 index 000000000..e296f61af --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml @@ -0,0 +1,235 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + property list labelsModel: [ + lastUpdateObject, + sendObject, + downloadedObject + ] + + // TODO: contentKey functionality + QtObject { + id: lastUpdateObject + + readonly property string title: qsTr("Configuration Last Update") + readonly property string contentKey: "20 dec 2024 22:10" + readonly property string objectImageSource: "qrc:/images/controls/info.svg" // reload img + readonly property bool isRichText: true + } + + QtObject { + id: sendObject + + readonly property string title: qsTr("Send") + readonly property string contentKey: "48,9 MB" + readonly property string objectImageSource: "qrc:/images/controls/history.svg" // arrow-up img + readonly property bool isRichText: false + } + + QtObject { + id: downloadedObject + + readonly property string title: qsTr("Downloaded") + readonly property string contentKey: "2,78 GB" + readonly property string objectImageSource: "qrc:/images/controls/monitor.svg" // arrow-down img + readonly property bool isRichText: false + } + + property var processedServer + + Connections { + target: ServersModel + + function onProcessedServerChanged() { + root.processedServer = proxyServersModel.get(0) + } + } + + SortFilterProxyModel { + id: proxyServersModel + objectName: "proxyServersModel" + + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + + Component.onCompleted: { + root.processedServer = proxyServersModel.get(0) + } + } + + ListViewType { + id: listView + + anchors.fill: parent + + model: labelsModel + + header: ColumnLayout { + width: listView.width + + spacing: 4 + + BackButtonType { + id: backButton + objectName: "backButton" + + Layout.topMargin: 20 + SettingsController.safeAreaTopMargin + } + + HeaderTypeWithButton { + id: headerContent + objectName: "headerContent" + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 10 + + actionButtonImage: "qrc:/images/controls/edit-3.svg" + + headerText: root.processedServer.name + + actionButtonFunction: function() { + serverNameEditDrawer.openTriggered() + } + } + } + + delegate: ColumnLayout { + width: listView.width + spacing: 0 +/* + Connections { + target: ApiAccountInfoModel + + function onModelReset() { + delegateItem.rightText = ApiAccountInfoModel.data(contentKey) + } + } +*/ + LabelWithImageType { + id: delegateItem + + Layout.fillWidth: true + Layout.margins: 16 + + imageSource: objectImageSource + leftText: title + rightText: contentKey // ApiAccountInfoModel.data(contentKey) + rightTextFormat: isRichText ? Text.RichText : Text.PlainText + + visible: rightText !== "" + } + } + + footer: ColumnLayout { + id: footer + + width: listView.width + spacing: 0 + + BasicButtonType { + id: resetButton + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 24 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + textColor: AmneziaStyle.color.vibrantRed + + text: qsTr("Reload config") + + clickedFunc: function() { + var headerText = qsTr("Reload config?") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot reload config during active connection")) + } else { + PageController.showBusyIndicator(true) + // TODO: server config reload function + PageController.showNotificationMessage(qsTr("Config reloaded")) + PageController.showBusyIndicator(false) + } + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + BasicButtonType { + id: removeButton + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + textColor: AmneziaStyle.color.vibrantRed + + text: qsTr("Remove from application") + + clickedFunc: function() { + var headerText = qsTr("Remove from application?") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot remove server during active connection")) + } else { + PageController.showBusyIndicator(true) + // TODO: server remove function + PageController.showNotificationMessage(qsTr("Server removed")) + PageController.showBusyIndicator(false) + } + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + } + } + + RenameServerDrawer { + id: serverNameEditDrawer + + anchors.fill: parent + expandedHeight: parent.height * 0.35 + + serverNameText: root.processedServer.name + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 160177b6c..52112a40a 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -194,6 +194,40 @@ PageType { } } + TextFieldWithHeaderType { + id: texturl + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Insert url") + buttonText: qsTr("Insert") + + clickedFunc: function() { + textField.text = "" + textField.paste() + } + } + + BasicButtonType { + id: urlcontinueButton + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + visible: texturl.textField.text !== "" + + text: qsTr("Continue") + + clickedFunc: function() { + ImportController.httpGet(texturl.textField.text) + } + } + ParagraphTextType { Layout.fillWidth: true Layout.topMargin: 32 From 3ef52d14f8866bc8141078cfdc0fb39c18007bfd Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Mon, 16 Feb 2026 14:23:14 +0200 Subject: [PATCH 03/14] update: check if entered text is url --- client/ui/controllers/importController.cpp | 10 ++-- .../Pages2/PageSetupWizardConfigSource.qml | 47 +++++-------------- 2 files changed, 18 insertions(+), 39 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 58629a097..7439348c9 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "core/api/apiDefs.h" #include "core/api/apiUtils.h" @@ -105,7 +104,6 @@ bool ImportController::httpGet(const QUrl &url) QString text; if (isValidBase64(data)) { qDebug() << "Data is a base64 string\n"; - // decoded = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding); decoded = base64Decode(data); text = QString::fromUtf8(decoded).trimmed(); } else { @@ -119,13 +117,15 @@ bool ImportController::httpGet(const QUrl &url) qDebug() << text << "\n"; for (const QString &cfg : configs) { - if (cfg.startsWith("vmess://")) + if (cfg.startsWith("vless://")) qDebug() << cfg; - else if (cfg.startsWith("vless://")) + else if (cfg.startsWith("vmess://")) + qDebug() << cfg; + else if (cfg.startsWith("trojan://")) qDebug() << cfg; else if (cfg.startsWith("ss://")) qDebug() << cfg; - else if (cfg.startsWith("trojan://")) + else if (cfg.startsWith("ssd://")) qDebug() << cfg; else qDebug() << "Unknown protocol:\n" << cfg.left(10); diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 52112a40a..08bf427dc 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -187,47 +187,26 @@ PageType { text: qsTr("Continue") + function isValidUrl(text) { + try { + var u = new URL(text) + return u.protocol === "http:" || u.protocol === "https:" + } catch(e) { + return false + } + } + clickedFunc: function() { + if (isValidUrl(textKey.textField.text)) { + ImportController.httpGet(textKey.textField.text) + return + } if (ImportController.extractConfigFromData(textKey.textField.text)) { PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } } } - TextFieldWithHeaderType { - id: texturl - - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.rightMargin: 16 - Layout.leftMargin: 16 - - headerText: qsTr("Insert url") - buttonText: qsTr("Insert") - - clickedFunc: function() { - textField.text = "" - textField.paste() - } - } - - BasicButtonType { - id: urlcontinueButton - - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.rightMargin: 16 - Layout.leftMargin: 16 - - visible: texturl.textField.text !== "" - - text: qsTr("Continue") - - clickedFunc: function() { - ImportController.httpGet(texturl.textField.text) - } - } - ParagraphTextType { Layout.fillWidth: true Layout.topMargin: 32 From 5aa12dc5572050322b8a1a517ea84176ada96c25 Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Mon, 23 Feb 2026 10:23:12 +0200 Subject: [PATCH 04/14] update: server with xray configs --- client/ui/controllers/importController.cpp | 46 +++++++++++-------- client/ui/controllers/importController.h | 2 +- client/ui/models/servers_model.cpp | 7 +++ client/ui/models/servers_model.h | 4 +- client/ui/qml/Pages2/PageHome.qml | 4 +- .../ui/qml/Pages2/PageSettingsServersList.qml | 2 + .../PageSettingsXRayAvailableConfigs.qml | 10 +--- .../Pages2/PageSetupWizardConfigSource.qml | 4 +- 8 files changed, 45 insertions(+), 34 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 7439348c9..c9b3b31af 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -77,7 +77,7 @@ ImportController::ImportController(const QSharedPointer &serversMo #endif } -bool ImportController::httpGet(const QUrl &url) +bool ImportController::importLink(const QUrl &url) { QNetworkAccessManager manager; @@ -103,34 +103,42 @@ bool ImportController::httpGet(const QUrl &url) QByteArray decoded; QString text; if (isValidBase64(data)) { - qDebug() << "Data is a base64 string\n"; decoded = base64Decode(data); text = QString::fromUtf8(decoded).trimmed(); } else { - qDebug() << "Data isn't a base64 string\n"; data.replace('\r', ""); text = QString::fromUtf8(data).trimmed(); } QStringList configs = text.split('\n', Qt::SkipEmptyParts); - qDebug() << decoded << "\n"; - qDebug() << text << "\n"; + QJsonArray configsArray; for (const QString &cfg : configs) { - if (cfg.startsWith("vless://")) - qDebug() << cfg; - else if (cfg.startsWith("vmess://")) - qDebug() << cfg; - else if (cfg.startsWith("trojan://")) - qDebug() << cfg; - else if (cfg.startsWith("ss://")) - qDebug() << cfg; - else if (cfg.startsWith("ssd://")) - qDebug() << cfg; + if (cfg.startsWith("vless://") || cfg.startsWith("vmess://") || cfg.startsWith("trojan://") + || cfg.startsWith("ss://") || cfg.startsWith("ssd://")) + configsArray.append(cfg); else qDebug() << "Unknown protocol:\n" << cfg.left(10); } + extractConfigFromData(configsArray.at(0).toString()); + + QJsonObject serverConfig; + + for (auto it = m_config.begin(); it != m_config.end(); ++it) { + serverConfig.insert(it.key(), it.value()); + } + + serverConfig.insert("xray_subscription_config", configsArray); + serverConfig.insert("xray_subscription_config_current", 0); + + m_serversModel->addServer(serverConfig); + emit importFinished(); + + m_config = {}; + m_configFileName.clear(); + m_maliciousWarningText.clear(); + return true; } @@ -161,10 +169,6 @@ bool ImportController::isValidBase64(const QByteArray &input) return !decoded.isEmpty(); } -static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - QByteArray ImportController::base64Decode(const QByteArray &input) { std::string clean(input.constData(), input.length()); @@ -179,6 +183,10 @@ QByteArray ImportController::base64Decode(const QByteArray &input) 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++) diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 3944aead9..6e5bca32e 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -31,7 +31,7 @@ public: public slots: void importConfig(); void clearConfigFileName(); - bool httpGet(const QUrl &url); + bool importLink(const QUrl &url); bool isValidBase64(const QByteArray &input); QByteArray base64Decode(const QByteArray &input); bool extractConfigFromFile(const QString &fileName); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 3af2a09a5..18d904aef 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -25,6 +25,8 @@ namespace constexpr char publicKeyInfo[] = "public_key"; constexpr char expiresAt[] = "expires_at"; + + constexpr char xraySubscriptionConfig[] = "xray_subscription_config"; } QString normalizeVpnKey(const QString &vpnKey) @@ -179,6 +181,9 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const case AdEndpointRole: { return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adEndpoint).toString(); } + case IsXRayConfigSelectionAvailableRole: { + return !server.value(configKey::xraySubscriptionConfig).toArray().isEmpty(); + } } return QVariant(); @@ -443,6 +448,8 @@ QHash ServersModel::roleNames() const roles[AdDescriptionRole] = "adDescription"; roles[AdEndpointRole] = "adEndpoint"; + roles[IsXRayConfigSelectionAvailableRole] = "isXRayConfigSelectionAvailable"; + return roles; } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 66779bc2e..86f9aeaa1 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -52,7 +52,9 @@ public: AdDescriptionRole, AdEndpointRole, - HasAmneziaDns + HasAmneziaDns, + + IsXRayConfigSelectionAvailableRole }; ServersModel(std::shared_ptr settings, QObject *parent = nullptr); diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 9d28e44aa..3a505d5f2 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -358,8 +358,8 @@ PageType { PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } - /*} else if (ServersModel.getProcessedServerData("isConfigSelectionAvailable")) { - PageController.goToPage(PageEnum.PageSettingsXRayAvailableConfigs)*/ + } else if (ServersModel.getProcessedServerData("isXRayConfigSelectionAvailable")) { + PageController.goToPage(PageEnum.PageSettingsXRayAvailableConfigs) } else { PageController.goToPage(PageEnum.PageSettingsServerInfo) } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 0a62fa736..e6ef8cabc 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -98,6 +98,8 @@ PageType { } PageController.goToPage(PageEnum.PageSettingsApiServerInfo) + } else if (ServersModel.getProcessedServerData("isXRayConfigSelectionAvailable")) { + PageController.goToPage(PageEnum.PageSettingsXRayServerInfo) } else { PageController.goToPage(PageEnum.PageSettingsServerInfo) } diff --git a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml index 99f7867e9..1f7139891 100644 --- a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml @@ -49,7 +49,7 @@ PageType { anchors.fill: parent - model: XRayConfigsModel + model: ServersModel currentIndex: 0 @@ -83,14 +83,6 @@ PageType { headerText: root.processedServer.name actionButtonFunction: function() { - /* TODO: chnge server info to processed - PageController.showBusyIndicator(true) - let result = ApiSettingsController.getAccountInfo(false) - PageController.showBusyIndicator(false) - if (!result) { - return - }*/ - PageController.goToPage(PageEnum.PageSettingsXRayServerInfo) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 08bf427dc..41a3b3c19 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -198,8 +198,8 @@ PageType { clickedFunc: function() { if (isValidUrl(textKey.textField.text)) { - ImportController.httpGet(textKey.textField.text) - return + ImportController.importLink(textKey.textField.text) + PageController.goToPageHome() } if (ImportController.extractConfigFromData(textKey.textField.text)) { PageController.goToPage(PageEnum.PageSetupWizardViewConfig) From 95d7b98478450062120672b1ec36c7dc98cb5689 Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Tue, 24 Feb 2026 13:19:44 +0200 Subject: [PATCH 05/14] update: show configs names in server list --- client/ui/controllers/importController.cpp | 16 ++++--- client/ui/models/servers_model.cpp | 44 +++++++++++++++++++ client/ui/models/servers_model.h | 5 +++ client/ui/qml/Pages2/PageHome.qml | 6 +-- .../PageSettingsXRayAvailableConfigs.qml | 34 ++++++++------ 5 files changed, 84 insertions(+), 21 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index c9b3b31af..b8394426f 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -111,24 +111,30 @@ bool ImportController::importLink(const QUrl &url) } QStringList configs = text.split('\n', Qt::SkipEmptyParts); + QJsonObject obj; QJsonArray configsArray; for (const QString &cfg : configs) { if (cfg.startsWith("vless://") || cfg.startsWith("vmess://") || cfg.startsWith("trojan://") - || cfg.startsWith("ss://") || cfg.startsWith("ssd://")) - configsArray.append(cfg); - else + || cfg.startsWith("ss://") || cfg.startsWith("ssd://")) { + extractConfigFromData(cfg); + obj["config_name"] = m_config.value(config_key::description); + qDebug() << m_config.value(config_key::description); + obj["config"] = cfg; + configsArray.append(obj); + } else qDebug() << "Unknown protocol:\n" << cfg.left(10); } - extractConfigFromData(configsArray.at(0).toString()); + extractConfigFromData(configsArray.first().toObject().value("config").toString()); QJsonObject serverConfig; for (auto it = m_config.begin(); it != m_config.end(); ++it) { serverConfig.insert(it.key(), it.value()); } - + // TODO: proper name instead of XRaySubLink Test + serverConfig.insert("description", "XRaySubLink Test"); serverConfig.insert("xray_subscription_config", configsArray); serverConfig.insert("xray_subscription_config_current", 0); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 18d904aef..b102982f7 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -27,6 +27,9 @@ namespace constexpr char expiresAt[] = "expires_at"; constexpr char xraySubscriptionConfig[] = "xray_subscription_config"; + constexpr char xraySubscriptionConfigName[] = "config_name"; + constexpr char xraySubscriptionConfigString[] = "config"; + constexpr char xraySubscriptionConfigCurrent[] = "xray_subscription_config_current"; } QString normalizeVpnKey(const QString &vpnKey) @@ -251,10 +254,16 @@ const QString ServersModel::getDefaultServerDescriptionCollapsed() const QJsonObject serverConfig = m_servers.at(m_defaultServerIndex).toObject(); const auto configVersion = serverConfig.value(config_key::configVersion).toInt(); auto description = getServerDescription(serverConfig, m_defaultServerIndex); + auto configName = getConfigName(getCurrentConfigIndex()); + if (configVersion) { return description; } + if (!configName.isEmpty()) { + return configName; + } + auto container = ContainerProps::containerFromString(serverConfig.value(config_key::defaultContainer).toString()); QString protocolVersion; QString containerName = ContainerProps::containerHumanNames().value(container); @@ -278,10 +287,16 @@ const QString ServersModel::getDefaultServerDescriptionExpanded() const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); const auto configVersion = server.value(config_key::configVersion).toInt(); auto description = getServerDescription(server, m_defaultServerIndex); + auto configName = getConfigName(getCurrentConfigIndex()); + if (configVersion) { return description; } + if (!configName.isEmpty()) { + return configName; + } + return description += server.value(config_key::hostName).toString(); } @@ -328,6 +343,35 @@ const ServerCredentials ServersModel::getServerCredentials(const int index) return serverCredentials(index); } +int ServersModel::getCurrentConfigIndex() +{ + const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); + return server.value(configKey::xraySubscriptionConfigCurrent).toInt(); +} + +void ServersModel::setCurrentConfigIndex(const int &index) +{ + QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); + server.insert(configKey::xraySubscriptionConfigCurrent, index); +} + +const QString ServersModel::getConfigName(const int &index) +{ + const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); + QJsonArray configsArray = server.value(configKey::xraySubscriptionConfig).toArray(); + return configsArray.at(index).toObject().value(configKey::xraySubscriptionConfigName).toString(); +} + +const QJsonArray ServersModel::getConfigNames() +{ + const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); + QJsonArray configsArray = server.value(configKey::xraySubscriptionConfig).toArray(); + QJsonArray configsNamesArray; + for (int i = 0; i < configsArray.count(); ++i) + configsNamesArray.append(configsArray.at(i).toObject().value(configKey::xraySubscriptionConfigName).toString()); + return configsNamesArray; +} + bool ServersModel::isDefaultServerCurrentlyProcessed() { return m_defaultServerIndex == m_processedServerIndex; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 86f9aeaa1..173085dde 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -116,6 +116,11 @@ public slots: const ServerCredentials getProcessedServerCredentials(); const ServerCredentials getServerCredentials(const int index); + int getCurrentConfigIndex(); + void setCurrentConfigIndex(const int &index); + const QString getConfigName(const int &index); + const QJsonArray getConfigNames(); + void addServer(const QJsonObject &server); void editServer(const QJsonObject &server, const int serverIndex); void removeServer(); diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 3a505d5f2..19b32f2f4 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -310,11 +310,11 @@ PageType { objectName: "rowLayoutLabel" Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.topMargin: 8 - Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16 + Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : (ServersModel.isDefaultServerFromApi || ServersModel.getProcessedServerData("isXRayConfigSelectionAvailable")) ? 61 : 16 spacing: 0 BasicButtonType { - enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsedStateActive + enabled: (ServersModel.defaultServerImagePathCollapsed !== "" || ServersModel.getProcessedServerData("isXRayConfigSelectionAvailable")) && drawer.isCollapsedStateActive hoverEnabled: enabled implicitHeight: 36 @@ -380,7 +380,7 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 8 - visible: !ServersModel.isDefaultServerFromApi + visible: !ServersModel.isDefaultServerFromApi || !ServersModel.getProcessedServerData("isXRayConfigSelectionAvailable") DropDownType { id: containersDropDown diff --git a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml index 1f7139891..13aa24390 100644 --- a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml @@ -49,7 +49,7 @@ PageType { anchors.fill: parent - model: ServersModel + model: xrayConfigs currentIndex: 0 @@ -100,16 +100,15 @@ PageType { Layout.fillWidth: true Layout.leftMargin: 16 - // TODO: proper name and description - // e.g.: [DE] VMESS - WS - // VMES/WS/None - text: configName + // TODO: add description + // e.g. VMES/WS/None + text: model.title ButtonGroup.group: containersRadioButtonGroup imageSource: "qrc:/images/controls/download.svg" - checked: index === XRayConfigsModel.currentIndex + checked: index === ServersModel.getCurrentConfigIndex() checkable: !ConnectionController.isConnected onClicked: { @@ -122,14 +121,10 @@ PageType { return } - if (index !== XRayConfigsModel.currentIndex) { + if (index !== ServersModel.getCurrentConfigIndex()) { PageController.showBusyIndicator(true) - var prevIndex = XRayConfigsModel.currentIndex - XRayConfigsModel.currentIndex = index - // TODO: properly realize switching between configs - if (!XRayConfigsController.updateServer(ServersModel.defaultIndex, configCode, configName)) { - XRayConfigsModel.currentIndex = prevIndex - } + var prevIndex = ServersModel.getCurrentConfigIndex() + ServersModel.setCurrentConfigIndex(index) PageController.showBusyIndicator(false) } } @@ -154,4 +149,17 @@ PageType { } } } + + ListModel { + id: xrayConfigs + } + + Component.onCompleted: { + const names = ServersModel.getConfigNames() + xrayConfigs.clear() + + for (let i = 0; i < names.length; ++i) { + xrayConfigs.append({ title: names[i] }) + } + } } From 194e6673d40bb474d4319c42c98784c047f78df9 Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Tue, 24 Feb 2026 17:37:01 +0200 Subject: [PATCH 06/14] update: configs from list can be choosen --- client/ui/controllers/importController.cpp | 29 ++++++++++++++++++- client/ui/controllers/importController.h | 1 + client/ui/models/servers_model.cpp | 7 +++++ client/ui/models/servers_model.h | 1 + .../PageSettingsXRayAvailableConfigs.qml | 2 +- 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index b8394426f..d0f35f933 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -117,6 +117,7 @@ bool ImportController::importLink(const QUrl &url) for (const QString &cfg : configs) { if (cfg.startsWith("vless://") || cfg.startsWith("vmess://") || cfg.startsWith("trojan://") || cfg.startsWith("ss://") || cfg.startsWith("ssd://")) { + // TODO: fix config_key::description for some configs extractConfigFromData(cfg); obj["config_name"] = m_config.value(config_key::description); qDebug() << m_config.value(config_key::description); @@ -134,7 +135,7 @@ bool ImportController::importLink(const QUrl &url) serverConfig.insert(it.key(), it.value()); } // TODO: proper name instead of XRaySubLink Test - serverConfig.insert("description", "XRaySubLink Test"); + serverConfig.insert(config_key::description, "XRaySubLink Test"); serverConfig.insert("xray_subscription_config", configsArray); serverConfig.insert("xray_subscription_config_current", 0); @@ -148,6 +149,32 @@ bool ImportController::importLink(const QUrl &url) return true; } +// TODO: remove configIndex and fix bug with "xray_subscription_config_current" value saving +bool ImportController::editServerConfigWithData(QString data, int serverIndex, int configIndex) +{ + m_maliciousWarningText.clear(); + + extractConfigFromData(data); + + QJsonObject serverCurrentConfig = m_serversModel->getServerConfig(serverIndex); + QJsonObject serverConfig; + + for (auto it = m_config.begin(); it != m_config.end(); ++it) { + serverConfig.insert(it.key(), it.value()); + } + serverConfig.insert(config_key::description, serverCurrentConfig.value(config_key::description)); + serverConfig.insert("xray_subscription_config", serverCurrentConfig.value("xray_subscription_config")); + serverConfig.insert("xray_subscription_config_current", configIndex); + + m_serversModel->editServer(serverConfig, serverIndex); + + m_config = {}; + m_configFileName.clear(); + m_maliciousWarningText.clear(); + + return true; +} + bool ImportController::isValidBase64(const QByteArray &input) { QByteArray data = input; diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 6e5bca32e..80e76cac6 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -34,6 +34,7 @@ public slots: bool importLink(const QUrl &url); bool isValidBase64(const QByteArray &input); QByteArray base64Decode(const QByteArray &input); + bool editServerConfigWithData(QString data, int serverIndex, int configIndex); bool extractConfigFromFile(const QString &fileName); bool extractConfigFromData(QString data); bool extractConfigFromQr(const QByteArray &data); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index b102982f7..6a6a8c982 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -372,6 +372,13 @@ const QJsonArray ServersModel::getConfigNames() return configsNamesArray; } +const QString ServersModel::getConfigString(const int &index) +{ + const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); + QJsonArray configsArray = server.value(configKey::xraySubscriptionConfig).toArray(); + return configsArray.at(index).toObject().value(configKey::xraySubscriptionConfigString).toString(); +} + bool ServersModel::isDefaultServerCurrentlyProcessed() { return m_defaultServerIndex == m_processedServerIndex; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 173085dde..6c0612dcf 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -120,6 +120,7 @@ public slots: void setCurrentConfigIndex(const int &index); const QString getConfigName(const int &index); const QJsonArray getConfigNames(); + const QString getConfigString(const int &index); void addServer(const QJsonObject &server); void editServer(const QJsonObject &server, const int serverIndex); diff --git a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml index 13aa24390..cf39611b2 100644 --- a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml @@ -123,8 +123,8 @@ PageType { if (index !== ServersModel.getCurrentConfigIndex()) { PageController.showBusyIndicator(true) - var prevIndex = ServersModel.getCurrentConfigIndex() ServersModel.setCurrentConfigIndex(index) + ImportController.editServerConfigWithData(ServersModel.getConfigString(index), ServersModel.getProcessedServerIndex(), index) PageController.showBusyIndicator(false) } } From b6d4b43fb730a830eb3e51ad86da4e77209334ef Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Wed, 25 Feb 2026 11:53:23 +0200 Subject: [PATCH 07/14] update: xray server can be removed and rebooted --- client/images/controls/arrow-down.svg | 4 ++++ client/images/controls/arrow-up.svg | 4 ++++ client/resources.qrc | 2 ++ .../qml/Pages2/PageSettingsXRayServerInfo.qml | 18 ++++++++---------- 4 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 client/images/controls/arrow-down.svg create mode 100644 client/images/controls/arrow-up.svg diff --git a/client/images/controls/arrow-down.svg b/client/images/controls/arrow-down.svg new file mode 100644 index 000000000..e5410c468 --- /dev/null +++ b/client/images/controls/arrow-down.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/arrow-up.svg b/client/images/controls/arrow-up.svg new file mode 100644 index 000000000..f7b8f28d1 --- /dev/null +++ b/client/images/controls/arrow-up.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/resources.qrc b/client/resources.qrc index e92661613..ef1bea88e 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -7,8 +7,10 @@ images/controls/amnezia.svg images/controls/app.svg images/controls/archive-restore.svg + images/controls/arrow-down.svg images/controls/arrow-left.svg images/controls/arrow-right.svg + images/controls/arrow-up.svg images/controls/bug.svg images/controls/check.svg images/controls/chevron-down.svg diff --git a/client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml b/client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml index e296f61af..fc1312f70 100644 --- a/client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml @@ -29,7 +29,7 @@ PageType { readonly property string title: qsTr("Configuration Last Update") readonly property string contentKey: "20 dec 2024 22:10" - readonly property string objectImageSource: "qrc:/images/controls/info.svg" // reload img + readonly property string objectImageSource: "qrc:/images/controls/refresh-cw.svg" readonly property bool isRichText: true } @@ -38,7 +38,7 @@ PageType { readonly property string title: qsTr("Send") readonly property string contentKey: "48,9 MB" - readonly property string objectImageSource: "qrc:/images/controls/history.svg" // arrow-up img + readonly property string objectImageSource: "qrc:/images/controls/arrow-up.svg" readonly property bool isRichText: false } @@ -47,7 +47,7 @@ PageType { readonly property string title: qsTr("Downloaded") readonly property string contentKey: "2,78 GB" - readonly property string objectImageSource: "qrc:/images/controls/monitor.svg" // arrow-down img + readonly property string objectImageSource: "qrc:/images/controls/arrow-down.svg" readonly property bool isRichText: false } @@ -121,10 +121,10 @@ PageType { spacing: 0 /* Connections { - target: ApiAccountInfoModel + target: ServersModel function onModelReset() { - delegateItem.rightText = ApiAccountInfoModel.data(contentKey) + delegateItem.rightText = ServersModel.data(contentKey) } } */ @@ -136,7 +136,7 @@ PageType { imageSource: objectImageSource leftText: title - rightText: contentKey // ApiAccountInfoModel.data(contentKey) + rightText: contentKey // ServersModel.data(contentKey) rightTextFormat: isRichText ? Text.RichText : Text.PlainText visible: rightText !== "" @@ -174,8 +174,7 @@ PageType { PageController.showNotificationMessage(qsTr("Cannot reload config during active connection")) } else { PageController.showBusyIndicator(true) - // TODO: server config reload function - PageController.showNotificationMessage(qsTr("Config reloaded")) + InstallController.rebootProcessedServer() PageController.showBusyIndicator(false) } } @@ -210,8 +209,7 @@ PageType { PageController.showNotificationMessage(qsTr("Cannot remove server during active connection")) } else { PageController.showBusyIndicator(true) - // TODO: server remove function - PageController.showNotificationMessage(qsTr("Server removed")) + InstallController.removeProcessedServer() PageController.showBusyIndicator(false) } } From d39d04258877a1d0d3c272aec3d73ef7b3a88ac0 Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Wed, 25 Feb 2026 12:59:06 +0200 Subject: [PATCH 08/14] update: properly adding xray server --- client/ui/controllers/importController.cpp | 8 +------- client/ui/qml/Pages2/PageSetupWizardConfigSource.qml | 5 ++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index d0f35f933..ab3dde9dd 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -120,7 +120,6 @@ bool ImportController::importLink(const QUrl &url) // TODO: fix config_key::description for some configs extractConfigFromData(cfg); obj["config_name"] = m_config.value(config_key::description); - qDebug() << m_config.value(config_key::description); obj["config"] = cfg; configsArray.append(obj); } else @@ -139,12 +138,7 @@ bool ImportController::importLink(const QUrl &url) serverConfig.insert("xray_subscription_config", configsArray); serverConfig.insert("xray_subscription_config_current", 0); - m_serversModel->addServer(serverConfig); - emit importFinished(); - - m_config = {}; - m_configFileName.clear(); - m_maliciousWarningText.clear(); + m_config = serverConfig; return true; } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 41a3b3c19..56a152007 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -199,9 +199,8 @@ PageType { clickedFunc: function() { if (isValidUrl(textKey.textField.text)) { ImportController.importLink(textKey.textField.text) - PageController.goToPageHome() - } - if (ImportController.extractConfigFromData(textKey.textField.text)) { + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + }else if (ImportController.extractConfigFromData(textKey.textField.text)) { PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } } From f5c0d9315fe4d829376afd18fc91eb67d89d3cbc Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Wed, 25 Feb 2026 13:24:14 +0200 Subject: [PATCH 09/14] update: proper configs list drawer functionality on HomePage --- client/ui/models/servers_model.cpp | 5 +++++ client/ui/models/servers_model.h | 4 ++++ client/ui/qml/Pages2/PageHome.qml | 6 +++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 6a6a8c982..3496a837e 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -390,6 +390,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)); diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 6c0612dcf..b9643a8d2 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -80,6 +80,8 @@ public: defaultServerDefaultContainerChanged) Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged) + Q_PROPERTY(bool isDefaultServerContainXRayConfigs READ isDefaultServerContainXRayConfigs NOTIFY defaultServerIndexChanged) + Q_PROPERTY(bool hasServersFromGatewayApi READ hasServersFromGatewayApi NOTIFY hasServersFromGatewayApiChanged) Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged) @@ -102,6 +104,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 19b32f2f4..028ce6da6 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -310,11 +310,11 @@ PageType { objectName: "rowLayoutLabel" Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.topMargin: 8 - Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : (ServersModel.isDefaultServerFromApi || ServersModel.getProcessedServerData("isXRayConfigSelectionAvailable")) ? 61 : 16 + Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : (ServersModel.isDefaultServerFromApi || ServersModel.isDefaultServerContainXRayConfigs) ? 61 : 16 spacing: 0 BasicButtonType { - enabled: (ServersModel.defaultServerImagePathCollapsed !== "" || ServersModel.getProcessedServerData("isXRayConfigSelectionAvailable")) && drawer.isCollapsedStateActive + enabled: (ServersModel.defaultServerImagePathCollapsed !== "" || ServersModel.isDefaultServerContainXRayConfigs) && drawer.isCollapsedStateActive hoverEnabled: enabled implicitHeight: 36 @@ -380,7 +380,7 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 8 - visible: !ServersModel.isDefaultServerFromApi || !ServersModel.getProcessedServerData("isXRayConfigSelectionAvailable") + visible: !ServersModel.isDefaultServerFromApi || !ServersModel.isDefaultServerContainXRayConfigs DropDownType { id: containersDropDown From efd90a9bf1c2f5c1b832abcbb2351063cf60ab30 Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Wed, 25 Feb 2026 13:43:00 +0200 Subject: [PATCH 10/14] update: changed visibility XRay container for subLink server and changed server navigation in ServersListView --- client/ui/qml/Components/ServersListView.qml | 2 ++ client/ui/qml/Pages2/PageHome.qml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 4417e0b29..9b7d216cb 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -117,6 +117,8 @@ ListViewType { PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } + } else if (ServersModel.getProcessedServerData("isXRayConfigSelectionAvailable")) { + PageController.goToPage(PageEnum.PageSettingsXRayAvailableConfigs) } else { PageController.goToPage(PageEnum.PageSettingsServerInfo) } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 028ce6da6..623e4d48c 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -380,7 +380,7 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 8 - visible: !ServersModel.isDefaultServerFromApi || !ServersModel.isDefaultServerContainXRayConfigs + visible: !ServersModel.isDefaultServerFromApi && !ServersModel.isDefaultServerContainXRayConfigs DropDownType { id: containersDropDown From 64d6291660ba00cbedd02ac1e48f0179df338239 Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Wed, 25 Feb 2026 16:17:14 +0200 Subject: [PATCH 11/14] update: default server name --- client/ui/controllers/importController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index ab3dde9dd..f2c7d7850 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -134,7 +134,7 @@ bool ImportController::importLink(const QUrl &url) serverConfig.insert(it.key(), it.value()); } // TODO: proper name instead of XRaySubLink Test - serverConfig.insert(config_key::description, "XRaySubLink Test"); + serverConfig.insert(config_key::description, m_settings->nextAvailableServerName()); serverConfig.insert("xray_subscription_config", configsArray); serverConfig.insert("xray_subscription_config_current", 0); From 32477eb98987a6a44f781cc389f293ad7d88a3ee Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Thu, 26 Feb 2026 16:19:18 +0200 Subject: [PATCH 12/14] update: editServerWithData --- client/ui/controllers/importController.cpp | 7 +++---- client/ui/controllers/importController.h | 2 +- client/ui/models/servers_model.cpp | 5 +++-- client/ui/models/servers_model.h | 4 ++-- client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index f2c7d7850..3e6414030 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -133,7 +133,7 @@ bool ImportController::importLink(const QUrl &url) for (auto it = m_config.begin(); it != m_config.end(); ++it) { serverConfig.insert(it.key(), it.value()); } - // TODO: proper name instead of XRaySubLink Test + serverConfig.insert(config_key::description, m_settings->nextAvailableServerName()); serverConfig.insert("xray_subscription_config", configsArray); serverConfig.insert("xray_subscription_config_current", 0); @@ -143,8 +143,7 @@ bool ImportController::importLink(const QUrl &url) return true; } -// TODO: remove configIndex and fix bug with "xray_subscription_config_current" value saving -bool ImportController::editServerConfigWithData(QString data, int serverIndex, int configIndex) +bool ImportController::editServerConfigWithData(QString data, int serverIndex) { m_maliciousWarningText.clear(); @@ -158,7 +157,7 @@ bool ImportController::editServerConfigWithData(QString data, int serverIndex, i } serverConfig.insert(config_key::description, serverCurrentConfig.value(config_key::description)); serverConfig.insert("xray_subscription_config", serverCurrentConfig.value("xray_subscription_config")); - serverConfig.insert("xray_subscription_config_current", configIndex); + serverConfig.insert("xray_subscription_config_current", m_serversModel->getCurrentConfigIndex()); m_serversModel->editServer(serverConfig, serverIndex); diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 80e76cac6..1b6e698c7 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -34,7 +34,7 @@ public slots: bool importLink(const QUrl &url); bool isValidBase64(const QByteArray &input); QByteArray base64Decode(const QByteArray &input); - bool editServerConfigWithData(QString data, int serverIndex, int configIndex); + 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/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 3496a837e..f58e97d82 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -343,16 +343,17 @@ const ServerCredentials ServersModel::getServerCredentials(const int index) return serverCredentials(index); } -int ServersModel::getCurrentConfigIndex() +const int ServersModel::getCurrentConfigIndex() { const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); return server.value(configKey::xraySubscriptionConfigCurrent).toInt(); } -void ServersModel::setCurrentConfigIndex(const int &index) +void ServersModel::setCurrentConfigIndex(const int index) { QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); server.insert(configKey::xraySubscriptionConfigCurrent, index); + m_servers[m_defaultServerIndex] = server; } const QString ServersModel::getConfigName(const int &index) diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index b9643a8d2..801ac4231 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -120,8 +120,8 @@ public slots: const ServerCredentials getProcessedServerCredentials(); const ServerCredentials getServerCredentials(const int index); - int getCurrentConfigIndex(); - void setCurrentConfigIndex(const int &index); + const int getCurrentConfigIndex(); + void setCurrentConfigIndex(const int index); const QString getConfigName(const int &index); const QJsonArray getConfigNames(); const QString getConfigString(const int &index); diff --git a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml index cf39611b2..5fd087fd9 100644 --- a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml @@ -124,7 +124,7 @@ PageType { if (index !== ServersModel.getCurrentConfigIndex()) { PageController.showBusyIndicator(true) ServersModel.setCurrentConfigIndex(index) - ImportController.editServerConfigWithData(ServersModel.getConfigString(index), ServersModel.getProcessedServerIndex(), index) + ImportController.editServerConfigWithData(ServersModel.getConfigString(index), ServersModel.getProcessedServerIndex()) PageController.showBusyIndicator(false) } } From 2215cb17a14de5abc2afe850f09d3938a1e497eb Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Mon, 2 Mar 2026 14:38:24 +0200 Subject: [PATCH 13/14] update: security in config name --- client/ui/controllers/importController.cpp | 8 ++++++-- client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 3e6414030..7ac87c701 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -117,10 +117,14 @@ bool ImportController::importLink(const QUrl &url) for (const QString &cfg : configs) { if (cfg.startsWith("vless://") || cfg.startsWith("vmess://") || cfg.startsWith("trojan://") || cfg.startsWith("ss://") || cfg.startsWith("ssd://")) { - // TODO: fix config_key::description for some configs extractConfigFromData(cfg); - obj["config_name"] = m_config.value(config_key::description); + + QUrl url(cfg); + QUrlQuery query(url); + QString security = query.queryItemValue("security").isEmpty() ? "None" : "Reality"; + obj["config_name"] = QUrl::fromPercentEncoding(m_config.value(config_key::description).toString().toUtf8()) + " (" + security + ")"; obj["config"] = cfg; + configsArray.append(obj); } else qDebug() << "Unknown protocol:\n" << cfg.left(10); diff --git a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml index 5fd087fd9..51a805a66 100644 --- a/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsXRayAvailableConfigs.qml @@ -100,8 +100,7 @@ PageType { Layout.fillWidth: true Layout.leftMargin: 16 - // TODO: add description - // e.g. VMES/WS/None + text: model.title ButtonGroup.group: containersRadioButtonGroup From 34f7a15e1f2afe36333cdaee1c62a15159b7f13c Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Mon, 2 Mar 2026 14:39:37 +0200 Subject: [PATCH 14/14] removed xray traffic-related functionality --- .../qml/Pages2/PageSettingsXRayServerInfo.qml | 63 +------------------ 1 file changed, 1 insertion(+), 62 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml b/client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml index fc1312f70..4f795a7a9 100644 --- a/client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsXRayServerInfo.qml @@ -17,40 +17,6 @@ import "../Components" PageType { id: root - property list labelsModel: [ - lastUpdateObject, - sendObject, - downloadedObject - ] - - // TODO: contentKey functionality - QtObject { - id: lastUpdateObject - - readonly property string title: qsTr("Configuration Last Update") - readonly property string contentKey: "20 dec 2024 22:10" - readonly property string objectImageSource: "qrc:/images/controls/refresh-cw.svg" - readonly property bool isRichText: true - } - - QtObject { - id: sendObject - - readonly property string title: qsTr("Send") - readonly property string contentKey: "48,9 MB" - readonly property string objectImageSource: "qrc:/images/controls/arrow-up.svg" - readonly property bool isRichText: false - } - - QtObject { - id: downloadedObject - - readonly property string title: qsTr("Downloaded") - readonly property string contentKey: "2,78 GB" - readonly property string objectImageSource: "qrc:/images/controls/arrow-down.svg" - readonly property bool isRichText: false - } - property var processedServer Connections { @@ -83,7 +49,7 @@ PageType { anchors.fill: parent - model: labelsModel + model: 1 header: ColumnLayout { width: listView.width @@ -116,33 +82,6 @@ PageType { } } - delegate: ColumnLayout { - width: listView.width - spacing: 0 -/* - Connections { - target: ServersModel - - function onModelReset() { - delegateItem.rightText = ServersModel.data(contentKey) - } - } -*/ - LabelWithImageType { - id: delegateItem - - Layout.fillWidth: true - Layout.margins: 16 - - imageSource: objectImageSource - leftText: title - rightText: contentKey // ServersModel.data(contentKey) - rightTextFormat: isRichText ? Text.RichText : Text.PlainText - - visible: rightText !== "" - } - } - footer: ColumnLayout { id: footer