From 61abf74b2d123e3a691ff2b0a6f7695db3b83da9 Mon Sep 17 00:00:00 2001 From: Nethius Date: Wed, 21 Feb 2024 18:27:27 +0700 Subject: [PATCH] feature/page-home-split-tunneling (#540) Added split tunneling button on home page --- client/images/controls/split-tunneling.svg | 6 ++ client/resources.qrc | 2 + client/ui/models/servers_model.cpp | 15 +++ client/ui/models/servers_model.h | 4 + client/ui/models/sites_model.cpp | 1 + client/ui/models/sites_model.h | 2 + .../Components/HomeSplitTunnelingDrawer.qml | 92 +++++++++++++++++++ client/ui/qml/Controls2/BasicButtonType.qml | 19 +++- client/ui/qml/Pages2/PageHome.qml | 37 ++++++++ .../qml/Pages2/PageSettingsSplitTunneling.qml | 10 +- client/ui/qml/Pages2/PageStart.qml | 3 +- 11 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 client/images/controls/split-tunneling.svg create mode 100644 client/ui/qml/Components/HomeSplitTunnelingDrawer.qml diff --git a/client/images/controls/split-tunneling.svg b/client/images/controls/split-tunneling.svg new file mode 100644 index 00000000..3062054d --- /dev/null +++ b/client/images/controls/split-tunneling.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index 59a540cc..b9a69023 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -224,6 +224,8 @@ ui/qml/Pages2/PageShareFullAccess.qml images/controls/close.svg images/controls/search.svg + ui/qml/Components/HomeSplitTunnelingDrawer.qml + images/controls/split-tunneling.svg ui/qml/Controls2/DrawerType2.qml diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 2449de77..8294cc01 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -577,3 +577,18 @@ void ServersModel::setProcessedServerData(const QString roleString, const QVaria } +bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling() +{ + auto server = m_servers.at(m_defaultServerIndex).toObject(); + auto defaultContainer = ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString()); + auto containerConfig = server.value(config_key::containers).toArray().at(defaultContainer).toObject(); + auto protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(defaultContainer)).toObject(); + + if (defaultContainer == DockerContainer::Awg || defaultContainer == DockerContainer::WireGuard) { + return !(protocolConfig.value(config_key::last_config).toString().contains("AllowedIPs = 0.0.0.0/0, ::/0")); + } else if (defaultContainer == DockerContainer::Cloak || defaultContainer == DockerContainer::OpenVpn || defaultContainer == DockerContainer::ShadowSocks) { + return !(protocolConfig.value(config_key::last_config).toString().contains("redirect-gateway")); + } + + return false; +} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 1adaebae..b694b6f6 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -48,6 +48,8 @@ public: Q_PROPERTY(QString defaultServerDefaultContainerName READ getDefaultServerDefaultContainerName NOTIFY defaultServerDefaultContainerChanged) Q_PROPERTY(QString defaultServerDescriptionCollapsed READ getDefaultServerDescriptionCollapsed NOTIFY defaultServerDefaultContainerChanged) Q_PROPERTY(QString defaultServerDescriptionExpanded READ getDefaultServerDescriptionExpanded NOTIFY defaultServerDefaultContainerChanged) + Q_PROPERTY(bool isDefaultServerDefaultContainerHasSplitTunneling READ isDefaultServerDefaultContainerHasSplitTunneling NOTIFY defaultServerDefaultContainerChanged) + Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged) @@ -103,6 +105,8 @@ public slots: QVariant getProcessedServerData(const QString roleString); void setProcessedServerData(const QString roleString, const QVariant &value); + bool isDefaultServerDefaultContainerHasSplitTunneling(); + protected: QHash roleNames() const override; diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp index f6cb9b13..96b6ca60 100644 --- a/client/ui/models/sites_model.cpp +++ b/client/ui/models/sites_model.cpp @@ -113,6 +113,7 @@ void SitesModel::toggleSplitTunneling(bool enabled) m_settings->setRouteMode(Settings::RouteMode::VpnAllSites); } m_isSplitTunnelingEnabled = enabled; + emit splitTunnelingToggled(); } QVector > SitesModel::getCurrentSites() diff --git a/client/ui/models/sites_model.h b/client/ui/models/sites_model.h index ad16b7a3..803b7fd1 100644 --- a/client/ui/models/sites_model.h +++ b/client/ui/models/sites_model.h @@ -22,6 +22,7 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Q_PROPERTY(int routeMode READ getRouteMode WRITE setRouteMode NOTIFY routeModeChanged) + Q_PROPERTY(bool isTunnelingEnabled READ isSplitTunnelingEnabled NOTIFY splitTunnelingToggled) public slots: bool addSite(const QString &hostname, const QString &ip); @@ -38,6 +39,7 @@ public slots: signals: void routeModeChanged(); + void splitTunnelingToggled(); protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml new file mode 100644 index 00000000..bc1f1008 --- /dev/null +++ b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +DrawerType2 { + id: root + + anchors.fill: parent + expandedHeight: parent.height * 0.7 + + expandedContent: ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + spacing: 0 + + Header2Type { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + + headerText: qsTr("Split tunneling") + descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi") + + text: qsTr("Split tunneling on the server") + descriptionText: qsTr("Enabled \nCan't be disabled for current server") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { +// PageController.goToPage(PageEnum.PageSettingsSplitTunneling) +// root.close() + } + } + + DividerType { + visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + enabled: ! ServersModel.isDefaultServerDefaultContainerHasSplitTunneling || !ServersModel.getDefaultServerData("isServerFromApi") + + text: qsTr("Site-based split tunneling") + descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsSplitTunneling) + root.close() + } + } + + DividerType { + } + + LabelWithButtonType { + Layout.fillWidth: true + visible: false + + text: qsTr("App-based split tunneling") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { +// PageController.goToPage(PageEnum.PageSetupWizardConfigSource) + root.close() + } + } + + DividerType { + visible: false + } + } +} diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 77d4b5fb..257486d6 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -21,6 +21,8 @@ Button { property int borderFocusedWidth: 1 property string imageSource + property string rightImageSource + property string leftImageColor: textColor property bool squareLeftSide: false @@ -118,7 +120,7 @@ Button { layer { enabled: true effect: ColorOverlay { - color: textColor + color: leftImageColor } } } @@ -131,6 +133,21 @@ Button { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter } + + Image { + Layout.preferredHeight: 20 + Layout.preferredWidth: 20 + + source: root.rightImageSource + visible: root.rightImageSource === "" ? false : true + + layer { + enabled: true + effect: ColorOverlay { + color: textColor + } + } + } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 4b5ba53d..04f59627 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -34,8 +34,45 @@ PageType { anchors.bottomMargin: drawer.collapsedHeight ConnectButton { + id: connectButton anchors.centerIn: parent } + + BasicButtonType { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 34 + leftPadding: 16 + rightPadding: 16 + + implicitHeight: 36 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#878B91" + leftImageColor: "transparent" + borderWidth: 0 + + property bool isSplitTunnelingEnabled: SitesModel.isTunnelingEnabled || + (ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi")) + + text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled") + + imageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : "" + rightImageSource: "qrc:/images/controls/chevron-down.svg" + + onClicked: { + homeSplitTunnelingDrawer.open() + } + + HomeSplitTunnelingDrawer { + id: homeSplitTunnelingDrawer + + parent: root + } + } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 729a6e9d..1ce3cd64 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -29,8 +29,14 @@ PageType { } Component.onCompleted: { - if (isServerFromApi) { + if (ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot change split tunneling settings during active connection")) + root.pageEnabled = false + } else if (ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && isServerFromApi) { PageController.showNotificationMessage(qsTr("Default server does not support split tunneling function")) + root.pageEnabled = false + } else { + root.pageEnabled = true } } @@ -108,7 +114,7 @@ PageType { Layout.fillWidth: true Layout.rightMargin: 16 - checked: SitesModel.isSplitTunnelingEnabled() + checked: SitesModel.isTunnelingEnabled onToggled: { SitesModel.toggleSplitTunneling(checked) selector.text = root.routeModesModel[getRouteModesModelIndex()].name diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 38a8e0b8..dac9db93 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -44,6 +44,7 @@ PageType { function onClosePage() { tabBar.isServerInfoShow = tabBarStackView.currentItem.objectName !== PageController.getPagePath(PageEnum.PageSettingsServerInfo) + && tabBarStackView.currentItem.objectName !== PageController.getPagePath(PageEnum.PageSettingsSplitTunneling) if (tabBarStackView.depth <= 1) { return @@ -60,7 +61,7 @@ PageType { tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) } - tabBar.isServerInfoShow = page === PageEnum.PageSettingsServerInfo || tabBar.isServerInfoShow + tabBar.isServerInfoShow = page === PageEnum.PageSettingsServerInfo || PageEnum.PageSettingsSplitTunneling || tabBar.isServerInfoShow } function onGoToStartPage() {