mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
feat: implement local proxy settings UI and functionality
- Added new QML pages for managing local proxy settings and connection types. - Updated SettingsController to handle local proxy enablement and port configuration. - Enhanced server model to include processed server UUID for local proxy management.
This commit is contained in:
@@ -253,6 +253,8 @@
|
||||
<file>ui/qml/Components/AwgTextField.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
|
||||
<file>ui/qml/Components/SmartScroll.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsConnectionType.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsLocalProxy.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/countriesFlags">
|
||||
<file>images/flagKit/ZW.svg</file>
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace PageLoader
|
||||
PageSettingsApiDevices,
|
||||
PageSettingsApiSubscriptionKey,
|
||||
PageSettingsKillSwitchExceptions,
|
||||
PageSettingsConnectionType,
|
||||
PageSettingsLocalProxy,
|
||||
|
||||
PageServiceSftpSettings,
|
||||
PageServiceTorWebsiteSettings,
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr int kLocalProxyPortMin = 1024;
|
||||
constexpr int kLocalProxyPortMax = 65535;
|
||||
}
|
||||
|
||||
SettingsController::SettingsController(const QSharedPointer<ServersModel> &serversModel,
|
||||
const QSharedPointer<ContainersModel> &containersModel,
|
||||
const QSharedPointer<LanguageModel> &languageModel,
|
||||
@@ -49,6 +54,8 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
|
||||
|
||||
m_isDevModeEnabled = m_settings->isDevGatewayEnv();
|
||||
toggleDevGatewayEnv(m_isDevModeEnabled);
|
||||
|
||||
connect(m_settings.get(), &Settings::localProxySettingsChanged, this, &SettingsController::localProxySettingsUpdated);
|
||||
}
|
||||
|
||||
QString getPlatformName()
|
||||
@@ -523,3 +530,57 @@ void SettingsController::disableHomeAdLabel()
|
||||
m_settings->disableHomeAdLabel();
|
||||
emit isHomeAdLabelVisibleChanged(false);
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxyHttpEnabled() const
|
||||
{
|
||||
return m_settings->isLocalProxyHttpEnabled();
|
||||
}
|
||||
|
||||
int SettingsController::localProxyPort() const
|
||||
{
|
||||
return static_cast<int>(m_settings->localProxyPort());
|
||||
}
|
||||
|
||||
QString SettingsController::localProxyOwnerUuid() const
|
||||
{
|
||||
return m_settings->localProxyOwnerUuid();
|
||||
}
|
||||
|
||||
bool SettingsController::setLocalProxyPort(int port)
|
||||
{
|
||||
if (port < kLocalProxyPortMin || port > kLocalProxyPortMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_settings->localProxyPort() == static_cast<quint16>(port)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
m_settings->setLocalProxyPort(static_cast<quint16>(port));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SettingsController::enableLocalProxy(const QString &ownerUuid, int port)
|
||||
{
|
||||
if (port < kLocalProxyPortMin || port > kLocalProxyPortMax || ownerUuid.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_settings->isLocalProxyHttpEnabled() && m_settings->localProxyOwnerUuid() != ownerUuid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_settings->setLocalProxyOwnerUuid(ownerUuid);
|
||||
setLocalProxyPort(port);
|
||||
m_settings->setLocalProxyHttpEnabled(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SettingsController::disableLocalProxy()
|
||||
{
|
||||
if (m_settings->isLocalProxyHttpEnabled()) {
|
||||
m_settings->setLocalProxyHttpEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@ public:
|
||||
Q_PROPERTY(int safeAreaTopMargin READ getSafeAreaTopMargin NOTIFY safeAreaTopMarginChanged)
|
||||
Q_PROPERTY(int safeAreaBottomMargin READ getSafeAreaBottomMargin NOTIFY safeAreaBottomMarginChanged)
|
||||
Q_PROPERTY(int imeHeight READ getImeHeight NOTIFY imeHeightChanged)
|
||||
Q_PROPERTY(bool isLocalProxyHttpEnabled READ isLocalProxyHttpEnabled NOTIFY localProxySettingsUpdated)
|
||||
Q_PROPERTY(int localProxyPort READ localProxyPort WRITE setLocalProxyPort NOTIFY localProxySettingsUpdated)
|
||||
Q_PROPERTY(QString localProxyOwnerUuid READ localProxyOwnerUuid NOTIFY localProxySettingsUpdated)
|
||||
|
||||
public slots:
|
||||
void toggleAmneziaDns(bool enable);
|
||||
@@ -109,6 +112,13 @@ public slots:
|
||||
bool isHomeAdLabelVisible();
|
||||
void disableHomeAdLabel();
|
||||
|
||||
bool isLocalProxyHttpEnabled() const;
|
||||
int localProxyPort() const;
|
||||
QString localProxyOwnerUuid() const;
|
||||
bool setLocalProxyPort(int port);
|
||||
bool enableLocalProxy(const QString &ownerUuid, int port);
|
||||
void disableLocalProxy();
|
||||
|
||||
signals:
|
||||
void primaryDnsChanged();
|
||||
void secondaryDnsChanged();
|
||||
@@ -140,6 +150,7 @@ signals:
|
||||
|
||||
void isHomeAdLabelVisibleChanged(bool visible);
|
||||
void startMinimizedChanged();
|
||||
void localProxySettingsUpdated();
|
||||
|
||||
private:
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
|
||||
@@ -984,3 +984,9 @@ QString ServersModel::getServerUuid(int index) const
|
||||
return QString();
|
||||
return m_servers.at(index).toObject().value(config_key::server_uuid).toString();
|
||||
}
|
||||
|
||||
QString ServersModel::getProcessedServerUuid() const
|
||||
{
|
||||
return getServerUuid(m_processedServerIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
|
||||
Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged)
|
||||
Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerChanged)
|
||||
Q_PROPERTY(QString processedServerUuid READ getProcessedServerUuid NOTIFY processedServerChanged)
|
||||
|
||||
Q_PROPERTY(bool isAdVisible READ isAdVisible NOTIFY defaultServerIndexChanged)
|
||||
Q_PROPERTY(QString adHeader READ adHeader NOTIFY defaultServerIndexChanged)
|
||||
@@ -160,6 +161,8 @@ public slots:
|
||||
QString adDescription();
|
||||
|
||||
QString getServerUuid(int index) const;
|
||||
|
||||
QString getProcessedServerUuid() const;
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
@@ -151,32 +151,6 @@ PageType {
|
||||
|
||||
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
|
||||
|
||||
SwitcherType {
|
||||
id: switcher
|
||||
|
||||
readonly property bool isVlessProtocol: ApiConfigsController.isVlessProtocol()
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
visible: ApiAccountInfoModel.data("isProtocolSelectionSupported")
|
||||
|
||||
text: qsTr("Use VLESS protocol")
|
||||
checked: switcher.isVlessProtocol
|
||||
onToggled: function() {
|
||||
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
|
||||
} else {
|
||||
PageController.showBusyIndicator(true)
|
||||
ApiConfigsController.setCurrentProtocol(switcher.isVlessProtocol ? "awg" : "vless")
|
||||
ApiConfigsController.updateServiceFromGateway(ServersModel.processedIndex, "", "", true)
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WarningType {
|
||||
id: warning
|
||||
|
||||
@@ -201,11 +175,25 @@ PageType {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: vpnKey
|
||||
id: connectionSwitcher
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: warning.visible ? 16 : 32
|
||||
text: qsTr("Connection")
|
||||
descriptionText: qsTr("Protocol selection and local proxy setup")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsConnectionType)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: vpnKey
|
||||
|
||||
Layout.fillWidth: true
|
||||
visible: footer.isVisibleForAmneziaFree
|
||||
|
||||
text: qsTr("Subscription Key")
|
||||
|
||||
93
client/ui/qml/Pages2/PageSettingsConnectionType.qml
Normal file
93
client/ui/qml/Pages2/PageSettingsConnectionType.qml
Normal file
@@ -0,0 +1,93 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("Connection")
|
||||
}
|
||||
}
|
||||
|
||||
model: 1
|
||||
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
LabelWithButtonType {
|
||||
id: vpnProtocolButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("VPN protocol")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsConnectionProtocols)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: localProxyButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("Local proxy")
|
||||
descriptionText: SettingsController.isLocalProxyHttpEnabled ? qsTr("Running: 127.0.0.1:%1").arg(SettingsController.localProxyPort || 0)
|
||||
: qsTr("Disabled")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsLocalProxy)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
}
|
||||
}
|
||||
}
|
||||
207
client/ui/qml/Pages2/PageSettingsLocalProxy.qml
Normal file
207
client/ui/qml/Pages2/PageSettingsLocalProxy.qml
Normal file
@@ -0,0 +1,207 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
readonly property int localProxyPortMin: 1024
|
||||
readonly property int localProxyPortMax: 65535
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
headerText: qsTr("Local proxy")
|
||||
}
|
||||
}
|
||||
|
||||
model: 1 // fake model to force the ListView to be created without a model
|
||||
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
spacing: 16
|
||||
|
||||
SwitcherType {
|
||||
id: localProxySwitch
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 16
|
||||
|
||||
property string statusText: ""
|
||||
|
||||
text: qsTr("Enable local proxy")
|
||||
descriptionText: statusText
|
||||
|
||||
checked: SettingsController.isLocalProxyHttpEnabled
|
||||
|
||||
function computeStatusText() {
|
||||
statusText = SettingsController.isLocalProxyHttpEnabled
|
||||
? qsTr("Running: 127.0.0.1:%1").arg(SettingsController.localProxyPort || 0)
|
||||
: qsTr("Disabled")
|
||||
}
|
||||
|
||||
function syncState() {
|
||||
computeStatusText()
|
||||
if (checked !== SettingsController.isLocalProxyHttpEnabled) {
|
||||
checked = SettingsController.isLocalProxyHttpEnabled
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: syncState()
|
||||
|
||||
onToggled: function() {
|
||||
if (checked) {
|
||||
const serverUuid = ServersModel.processedServerUuid
|
||||
if (!serverUuid) {
|
||||
checked = false
|
||||
PageController.showNotificationMessage(qsTr("Unable to determine the current server"))
|
||||
return
|
||||
}
|
||||
|
||||
if (SettingsController.isLocalProxyHttpEnabled
|
||||
&& SettingsController.localProxyOwnerUuid
|
||||
&& SettingsController.localProxyOwnerUuid !== serverUuid) {
|
||||
checked = false
|
||||
PageController.showNotificationMessage(qsTr("Local proxy is already enabled for another server"))
|
||||
return
|
||||
}
|
||||
|
||||
const requestedPort = portField.portValue()
|
||||
if (requestedPort < root.localProxyPortMin || requestedPort > root.localProxyPortMax) {
|
||||
checked = false
|
||||
PageController.showNotificationMessage(qsTr("Port must be between %1 and %2")
|
||||
.arg(root.localProxyPortMin)
|
||||
.arg(root.localProxyPortMax))
|
||||
return
|
||||
}
|
||||
|
||||
if (!SettingsController.enableLocalProxy(serverUuid, requestedPort)) {
|
||||
checked = false
|
||||
PageController.showNotificationMessage(qsTr("Failed to enable local proxy. Check the port (%1-%2).")
|
||||
.arg(root.localProxyPortMin)
|
||||
.arg(root.localProxyPortMax))
|
||||
}
|
||||
} else {
|
||||
SettingsController.disableLocalProxy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: portField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("HTTP API port")
|
||||
|
||||
enabled: true
|
||||
|
||||
textField.validator: IntValidator {
|
||||
bottom: root.localProxyPortMin
|
||||
top: root.localProxyPortMax
|
||||
}
|
||||
|
||||
function syncPortValue() {
|
||||
const port = SettingsController.localProxyPort
|
||||
textField.text = (port >= root.localProxyPortMin && port <= root.localProxyPortMax) ? port.toString() : ""
|
||||
}
|
||||
|
||||
function portValue() {
|
||||
const value = parseInt(textField.text)
|
||||
return isNaN(value) ? -1 : value
|
||||
}
|
||||
|
||||
Component.onCompleted: syncPortValue()
|
||||
|
||||
textField.onEditingFinished: {
|
||||
const value = portField.portValue()
|
||||
if (value < root.localProxyPortMin || value > root.localProxyPortMax) {
|
||||
PageController.showNotificationMessage(qsTr("Port must be between %1 and %2")
|
||||
.arg(root.localProxyPortMin)
|
||||
.arg(root.localProxyPortMax))
|
||||
portField.syncPortValue()
|
||||
return
|
||||
}
|
||||
|
||||
if (!SettingsController.setLocalProxyPort(value)) {
|
||||
PageController.showNotificationMessage(qsTr("Failed to save port. Valid range: %1-%2")
|
||||
.arg(root.localProxyPortMin)
|
||||
.arg(root.localProxyPortMax))
|
||||
|
||||
}
|
||||
|
||||
portField.syncPortValue()
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("HTTP API controls Xray via /api/v1/up and /api/v1/down. SOCKS inbound stays on port 10808.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsController
|
||||
|
||||
function onLocalProxySettingsUpdated() {
|
||||
localProxySwitch.syncState()
|
||||
if (!portField.textField.activeFocus) {
|
||||
portField.syncPortValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ServersModel
|
||||
|
||||
function onProcessedServerChanged() {
|
||||
localProxySwitch.syncState()
|
||||
if (!portField.textField.activeFocus) {
|
||||
portField.syncPortValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user