feature/app-split-tunneling (#702)
App Split Tunneling for Windows and Android
This commit is contained in:
parent
e7bd24f065
commit
adab30fc81
48 changed files with 1225 additions and 98 deletions
50
client/ui/controllers/appSplitTunnelingController.cpp
Normal file
50
client/ui/controllers/appSplitTunnelingController.cpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#include "appSplitTunnelingController.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "core/defs.h"
|
||||
|
||||
AppSplitTunnelingController::AppSplitTunnelingController(const std::shared_ptr<Settings> &settings,
|
||||
const QSharedPointer<AppSplitTunnelingModel> &appSplitTunnelingModel, QObject *parent)
|
||||
: QObject(parent), m_settings(settings), m_appSplitTunnelingModel(appSplitTunnelingModel)
|
||||
{
|
||||
}
|
||||
|
||||
void AppSplitTunnelingController::addApp(const QString &appPath)
|
||||
{
|
||||
|
||||
InstalledAppInfo appInfo { "", "", appPath };
|
||||
if (!appPath.isEmpty()) {
|
||||
QFileInfo fileInfo(appPath);
|
||||
appInfo.appName = fileInfo.fileName();
|
||||
}
|
||||
|
||||
if (m_appSplitTunnelingModel->addApp(appInfo)) {
|
||||
emit finished(tr("Application added: %1").arg(appInfo.appName));
|
||||
|
||||
} else {
|
||||
emit errorOccurred(tr("The application has already been added"));
|
||||
}
|
||||
}
|
||||
|
||||
void AppSplitTunnelingController::addApps(QVector<QPair<QString, QString>> apps)
|
||||
{
|
||||
qDebug() << apps;
|
||||
for (const auto &app : apps) {
|
||||
InstalledAppInfo appInfo { app.first, app.second, "" };
|
||||
|
||||
m_appSplitTunnelingModel->addApp(appInfo);
|
||||
}
|
||||
emit finished(tr("The selected applications have been added"));
|
||||
}
|
||||
|
||||
void AppSplitTunnelingController::removeApp(const int index)
|
||||
{
|
||||
auto modelIndex = m_appSplitTunnelingModel->index(index);
|
||||
auto appPath = m_appSplitTunnelingModel->data(modelIndex, AppSplitTunnelingModel::Roles::AppPathRole).toString();
|
||||
m_appSplitTunnelingModel->removeApp(modelIndex);
|
||||
|
||||
QFileInfo fileInfo(appPath);
|
||||
|
||||
emit finished(tr("Application removed: %1").arg(fileInfo.fileName()));
|
||||
}
|
||||
31
client/ui/controllers/appSplitTunnelingController.h
Normal file
31
client/ui/controllers/appSplitTunnelingController.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef APPSPLITTUNNELINGCONTROLLER_H
|
||||
#define APPSPLITTUNNELINGCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "settings.h"
|
||||
#include "ui/models/appSplitTunnelingModel.h"
|
||||
|
||||
class AppSplitTunnelingController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AppSplitTunnelingController(const std::shared_ptr<Settings> &settings,
|
||||
const QSharedPointer<AppSplitTunnelingModel> &sitesModel, QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void addApp(const QString &appPath);
|
||||
void addApps(QVector<QPair<QString, QString>> apps);
|
||||
void removeApp(const int index);
|
||||
|
||||
signals:
|
||||
void errorOccurred(const QString &errorMessage);
|
||||
void finished(const QString &message);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||
};
|
||||
|
||||
#endif // APPSPLITTUNNELINGCONTROLLER_H
|
||||
|
|
@ -29,6 +29,7 @@ namespace PageLoader
|
|||
PageSettingsAbout,
|
||||
PageSettingsLogging,
|
||||
PageSettingsSplitTunneling,
|
||||
PageSettingsAppSplitTunneling,
|
||||
|
||||
PageServiceSftpSettings,
|
||||
PageServiceTorWebsiteSettings,
|
||||
|
|
|
|||
101
client/ui/models/appSplitTunnelingModel.cpp
Normal file
101
client/ui/models/appSplitTunnelingModel.cpp
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#include "appSplitTunnelingModel.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
AppSplitTunnelingModel::AppSplitTunnelingModel(std::shared_ptr<Settings> settings, QObject *parent)
|
||||
: QAbstractListModel(parent), m_settings(settings)
|
||||
{
|
||||
auto routeMode = m_settings->getAppsRouteMode();
|
||||
if (routeMode == Settings::AppsRouteMode::VpnAllApps) {
|
||||
m_isSplitTunnelingEnabled = false;
|
||||
m_currentRouteMode = Settings::AppsRouteMode::VpnAllExceptApps;
|
||||
} else {
|
||||
m_isSplitTunnelingEnabled = true;
|
||||
m_currentRouteMode = routeMode;
|
||||
}
|
||||
m_apps = m_settings->getVpnApps(m_currentRouteMode);
|
||||
}
|
||||
|
||||
int AppSplitTunnelingModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_apps.size();
|
||||
}
|
||||
|
||||
QVariant AppSplitTunnelingModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
|
||||
return QVariant();
|
||||
|
||||
switch (role) {
|
||||
case AppPathRole: {
|
||||
return m_apps.at(index.row()).appName;
|
||||
}
|
||||
default: {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool AppSplitTunnelingModel::addApp(const InstalledAppInfo &appInfo)
|
||||
{
|
||||
if (m_apps.contains(appInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
m_apps.append(appInfo);
|
||||
m_settings->setVpnApps(m_currentRouteMode, m_apps);
|
||||
endInsertRows();
|
||||
|
||||
qDebug() << "app added " << appInfo.appName;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppSplitTunnelingModel::removeApp(QModelIndex index)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), index.row(), index.row());
|
||||
m_apps.removeAt(index.row());
|
||||
m_settings->setVpnApps(m_currentRouteMode, m_apps);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
int AppSplitTunnelingModel::getRouteMode()
|
||||
{
|
||||
return m_currentRouteMode;
|
||||
}
|
||||
|
||||
void AppSplitTunnelingModel::setRouteMode(int routeMode)
|
||||
{
|
||||
beginResetModel();
|
||||
m_settings->setAppsRouteMode(static_cast<Settings::AppsRouteMode>(routeMode));
|
||||
m_currentRouteMode = m_settings->getAppsRouteMode();
|
||||
m_apps = m_settings->getVpnApps(m_currentRouteMode);
|
||||
endResetModel();
|
||||
emit routeModeChanged();
|
||||
}
|
||||
|
||||
bool AppSplitTunnelingModel::isSplitTunnelingEnabled()
|
||||
{
|
||||
return m_isSplitTunnelingEnabled;
|
||||
}
|
||||
|
||||
void AppSplitTunnelingModel::toggleSplitTunneling(bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
setRouteMode(m_currentRouteMode);
|
||||
} else {
|
||||
m_settings->setAppsRouteMode(Settings::AppsRouteMode::VpnAllApps);
|
||||
}
|
||||
m_isSplitTunnelingEnabled = enabled;
|
||||
emit splitTunnelingToggled();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> AppSplitTunnelingModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[AppPathRole] = "appPath";
|
||||
return roles;
|
||||
}
|
||||
55
client/ui/models/appSplitTunnelingModel.h
Normal file
55
client/ui/models/appSplitTunnelingModel.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef APPSPLITTUNNELINGMODEL_H
|
||||
#define APPSPLITTUNNELINGMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "settings.h"
|
||||
#include "core/defs.h"
|
||||
|
||||
class AppSplitTunnelingModel: public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
AppPathRole = Qt::UserRole + 1,
|
||||
PackageAppNameRole,
|
||||
PackageAppIconRole
|
||||
};
|
||||
|
||||
explicit AppSplitTunnelingModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
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 addApp(const InstalledAppInfo &appInfo);
|
||||
void removeApp(QModelIndex index);
|
||||
|
||||
int getRouteMode();
|
||||
void setRouteMode(int routeMode);
|
||||
|
||||
bool isSplitTunnelingEnabled();
|
||||
void toggleSplitTunneling(bool enabled);
|
||||
|
||||
signals:
|
||||
void routeModeChanged();
|
||||
void splitTunnelingToggled();
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
bool m_isSplitTunnelingEnabled;
|
||||
Settings::AppsRouteMode m_currentRouteMode;
|
||||
|
||||
QVector<InstalledAppInfo> m_apps;
|
||||
};
|
||||
|
||||
#endif // APPSPLITTUNNELINGMODEL_H
|
||||
96
client/ui/models/installedAppsModel.cpp
Normal file
96
client/ui/models/installedAppsModel.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include "installedAppsModel.h"
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
||||
InstalledAppsModel::InstalledAppsModel(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
int InstalledAppsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_installedApps.size();
|
||||
}
|
||||
|
||||
QVariant InstalledAppsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
|
||||
return QVariant();
|
||||
|
||||
switch (role) {
|
||||
case AppNameRole: {
|
||||
auto appName = m_installedApps.at(index.row()).toObject().value("name").toString();
|
||||
auto packageName = m_installedApps.at(index.row()).toObject().value("package").toString();
|
||||
if (appName.isEmpty()) {
|
||||
appName = packageName;
|
||||
}
|
||||
return appName;
|
||||
}
|
||||
case AppIconRole: {
|
||||
return m_installedApps.at(index.row()).toObject().value("package").toString();
|
||||
}
|
||||
case PackageNameRole: {
|
||||
return m_installedApps.at(index.row()).toObject().value("package");
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void InstalledAppsModel::selectedStateChanged(const int index, const bool selected)
|
||||
{
|
||||
if (selected) {
|
||||
m_selectedAppIndexes.insert(index);
|
||||
} else {
|
||||
m_selectedAppIndexes.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QPair<QString, QString>> InstalledAppsModel::getSelectedAppsInfo()
|
||||
{
|
||||
QVector<QPair<QString, QString>> appsInfo;
|
||||
for (const auto i : m_selectedAppIndexes) {
|
||||
QString packageName = data(index(i, 0), PackageNameRole).toString();
|
||||
QString appName = data(index(i, 0), AppNameRole).toString();
|
||||
if (appName.isEmpty()) {
|
||||
appName = packageName;
|
||||
}
|
||||
|
||||
appsInfo.push_back({ appName, packageName });
|
||||
}
|
||||
|
||||
return appsInfo;
|
||||
}
|
||||
|
||||
void InstalledAppsModel::updateModel()
|
||||
{
|
||||
QFuture<void> future = QtConcurrent::run([this]() {
|
||||
beginResetModel();
|
||||
#ifdef Q_OS_ANDROID
|
||||
m_installedApps = AndroidController::instance()->getAppList();
|
||||
#endif
|
||||
endResetModel();
|
||||
});
|
||||
|
||||
QFutureWatcher<void> watcher;
|
||||
QEventLoop wait;
|
||||
connect(&watcher, &QFutureWatcher<void>::finished, &wait, &QEventLoop::quit);
|
||||
watcher.setFuture(future);
|
||||
wait.exec();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> InstalledAppsModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[AppNameRole] = "appName";
|
||||
roles[AppIconRole] = "appIcon";
|
||||
roles[PackageNameRole] = "packageName";
|
||||
return roles;
|
||||
}
|
||||
38
client/ui/models/installedAppsModel.h
Normal file
38
client/ui/models/installedAppsModel.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef INSTALLEDAPPSMODEL_H
|
||||
#define INSTALLEDAPPSMODEL_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
class InstalledAppsModel: public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
AppNameRole= Qt::UserRole + 1,
|
||||
PackageNameRole,
|
||||
AppIconRole
|
||||
};
|
||||
|
||||
explicit InstalledAppsModel(QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
public slots:
|
||||
void selectedStateChanged(const int index, const bool selected);
|
||||
QVector<QPair<QString, QString>> getSelectedAppsInfo();
|
||||
|
||||
void updateModel();
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
QJsonArray m_installedApps;
|
||||
QSet<int> m_selectedAppIndexes;
|
||||
};
|
||||
|
||||
#endif // INSTALLEDAPPSMODEL_H
|
||||
|
|
@ -57,7 +57,7 @@ DrawerType2 {
|
|||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
enabled: ! ServersModel.isDefaultServerDefaultContainerHasSplitTunneling || !ServersModel.getDefaultServerData("isServerFromApi")
|
||||
enabled: !ServersModel.isDefaultServerDefaultContainerHasSplitTunneling || !ServersModel.getDefaultServerData("isServerFromApi")
|
||||
|
||||
text: qsTr("Site-based split tunneling")
|
||||
descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
|
||||
|
|
@ -80,7 +80,7 @@ DrawerType2 {
|
|||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
// PageController.goToPage(PageEnum.PageSetupWizardConfigSource)
|
||||
PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
136
client/ui/qml/Components/InstalledAppsDrawer.qml
Normal file
136
client/ui/qml/Components/InstalledAppsDrawer.qml
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
import InstalledAppsModel 1.0
|
||||
|
||||
DrawerType2 {
|
||||
id: root
|
||||
|
||||
anchors.fill: parent
|
||||
expandedHeight: parent.height * 0.9
|
||||
|
||||
onAboutToShow: {
|
||||
PageController.showBusyIndicator(true)
|
||||
installedAppsModel.updateModel()
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
|
||||
InstalledAppsModel {
|
||||
id: installedAppsModel
|
||||
}
|
||||
|
||||
expandedContent: Item {
|
||||
id: container
|
||||
|
||||
implicitHeight: expandedHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: addButton.top
|
||||
anchors.topMargin: 16
|
||||
|
||||
BackButtonType {
|
||||
backButtonImage: "qrc:/images/controls/arrow-left.svg"
|
||||
backButtonFunction: function() {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
Header2Type {
|
||||
id: header
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
headerText: qsTr("Choose application")
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
clip: true
|
||||
interactive: true
|
||||
|
||||
model: installedAppsModel
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: scrollBar
|
||||
policy: ScrollBar.AlwaysOn
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: buttonGroup
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
implicitWidth: root.width
|
||||
implicitHeight: delegateContent.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: delegateContent
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
RowLayout {
|
||||
CheckBoxType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: appName
|
||||
|
||||
onCheckedChanged: {
|
||||
listView.model.selectedStateChanged(index, checked)
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
source: "image://installedAppImage/" + appIcon
|
||||
|
||||
sourceSize.width: 24
|
||||
sourceSize.height: 24
|
||||
|
||||
Layout.rightMargin: 48
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: addButton
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
|
||||
text: qsTr("Add selected")
|
||||
|
||||
clickedFunc: function() {
|
||||
PageController.showBusyIndicator(true)
|
||||
AppSplitTunnelingController.addApps(listView.model.getSelectedAppsInfo())
|
||||
PageController.showBusyIndicator(false)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -90,12 +90,12 @@ CheckBox {
|
|||
}
|
||||
|
||||
contentItem: Item {
|
||||
implicitWidth: content.implicitWidth
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 8 + background.width
|
||||
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
|
||||
|
|
|
|||
|
|
@ -126,32 +126,6 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
visible: (root.buttonText !== "") || (root.buttonImageSource !== "")
|
||||
|
||||
// defaultColor: "transparent"
|
||||
// hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
// pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
// disabledColor: "#878B91"
|
||||
// textColor: "#D7D8DB"
|
||||
// borderWidth: 0
|
||||
|
||||
focusPolicy: Qt.NoFocus
|
||||
text: root.buttonText
|
||||
imageSource: root.buttonImageSource
|
||||
|
||||
// Layout.rightMargin: 24
|
||||
Layout.preferredHeight: content.implicitHeight
|
||||
Layout.preferredWidth: content.implicitHeight
|
||||
squareLeftSide: true
|
||||
|
||||
clickedFunc: function() {
|
||||
if (root.clickedFunc && typeof root.clickedFunc === "function") {
|
||||
root.clickedFunc()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -187,6 +161,28 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
visible: (root.buttonText !== "") || (root.buttonImageSource !== "")
|
||||
|
||||
focusPolicy: Qt.NoFocus
|
||||
text: root.buttonText
|
||||
imageSource: root.buttonImageSource
|
||||
|
||||
anchors.top: content.top
|
||||
anchors.bottom: content.bottom
|
||||
anchors.right: content.right
|
||||
|
||||
height: content.implicitHeight
|
||||
width: content.implicitHeight
|
||||
squareLeftSide: true
|
||||
|
||||
clickedFunc: function() {
|
||||
if (root.clickedFunc && typeof root.clickedFunc === "function") {
|
||||
root.clickedFunc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBackgroundBorderColor(noneFocusedColor) {
|
||||
return textField.focus ? root.borderFocusedColor : noneFocusedColor
|
||||
}
|
||||
|
|
|
|||
256
client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml
Normal file
256
client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
|
||||
import QtCore
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import PageEnum 1.0
|
||||
import ProtocolEnum 1.0
|
||||
import ContainerProps 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
QtObject {
|
||||
id: routeMode
|
||||
property int allApps: 0
|
||||
property int onlyForwardApps: 1
|
||||
property int allExceptApps: 2
|
||||
}
|
||||
|
||||
property list<QtObject> routeModesModel: [
|
||||
onlyForwardApps,
|
||||
allExceptApps
|
||||
]
|
||||
|
||||
QtObject {
|
||||
id: onlyForwardApps
|
||||
property string name: qsTr("Only the Apps listed here will be accessed through the VPN")
|
||||
property int type: routeMode.onlyForwardApps
|
||||
}
|
||||
QtObject {
|
||||
id: allExceptApps
|
||||
property string name: qsTr("Apps from the list should not be accessed via VPN")
|
||||
property int type: routeMode.allExceptApps
|
||||
}
|
||||
|
||||
function getRouteModesModelIndex() {
|
||||
var currentRouteMode = AppSplitTunnelingModel.routeMode
|
||||
if ((routeMode.onlyForwardApps === currentRouteMode) || (routeMode.allApps === currentRouteMode)) {
|
||||
return 0
|
||||
} else if (routeMode.allExceptApps === currentRouteMode) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: header
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.topMargin: 20
|
||||
|
||||
BackButtonType {
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
HeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
|
||||
headerText: qsTr("App split tunneling")
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
id: switcher
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
|
||||
checked: AppSplitTunnelingModel.isTunnelingEnabled
|
||||
onToggled: {
|
||||
AppSplitTunnelingModel.toggleSplitTunneling(checked)
|
||||
selector.text = root.routeModesModel[getRouteModesModelIndex()].name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: selector
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
drawerHeight: 0.4375
|
||||
drawerParent: root
|
||||
|
||||
headerText: qsTr("Mode")
|
||||
|
||||
enabled: Qt.platform.os === "android"
|
||||
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
|
||||
model: root.routeModesModel
|
||||
|
||||
currentIndex: getRouteModesModelIndex()
|
||||
|
||||
clickedFunction: function() {
|
||||
selector.text = selectedText
|
||||
selector.close()
|
||||
if (AppSplitTunnelingModel.routeMode !== root.routeModesModel[currentIndex].type) {
|
||||
AppSplitTunnelingModel.routeMode = root.routeModesModel[currentIndex].type
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.routeModesModel[currentIndex].type === AppSplitTunnelingModel.routeMode) {
|
||||
selector.text = selectedText
|
||||
} else {
|
||||
selector.text = root.routeModesModel[0].name
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AppSplitTunnelingModel
|
||||
function onRouteModeChanged() {
|
||||
currentIndex = getRouteModesModelIndex()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
anchors.top: header.bottom
|
||||
anchors.topMargin: 16
|
||||
contentHeight: col.implicitHeight + addAppButton.implicitHeight + addAppButton.anchors.bottomMargin + addAppButton.anchors.topMargin
|
||||
|
||||
Column {
|
||||
id: col
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
ListView {
|
||||
id: apps
|
||||
width: parent.width
|
||||
height: apps.contentItem.height
|
||||
|
||||
model: SortFilterProxyModel {
|
||||
id: proxyAppSplitTunnelingModel
|
||||
sourceModel: AppSplitTunnelingModel
|
||||
filters: RegExpFilter {
|
||||
roleName: "appPath"
|
||||
pattern: ".*" + searchField.textField.text + ".*"
|
||||
caseSensitivity: Qt.CaseInsensitive
|
||||
}
|
||||
}
|
||||
|
||||
clip: true
|
||||
interactive: false
|
||||
|
||||
delegate: Item {
|
||||
implicitWidth: apps.width
|
||||
implicitHeight: delegateContent.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: delegateContent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: appPath
|
||||
rightImageSource: "qrc:/images/controls/trash.svg"
|
||||
rightImageColor: "#D7D8DB"
|
||||
|
||||
clickedFunction: function() {
|
||||
var headerText = qsTr("Remove ") + appPath + "?"
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
var yesButtonFunction = function() {
|
||||
AppSplitTunnelingController.removeApp(proxyAppSplitTunnelingModel.mapToSource(index))
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
}
|
||||
|
||||
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: addAppButton
|
||||
anchors.bottomMargin: -24
|
||||
color: "#0E0E11"
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: addAppButton
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 24
|
||||
anchors.rightMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
anchors.bottomMargin: 24
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: searchField
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
textFieldPlaceholderText: qsTr("application name")
|
||||
buttonImageSource: "qrc:/images/controls/plus.svg"
|
||||
|
||||
clickedFunc: function() {
|
||||
searchField.focus = false
|
||||
PageController.showBusyIndicator(true)
|
||||
|
||||
if (Qt.platform.os === "windows") {
|
||||
var fileName = SystemController.getFileName(qsTr("Open executable file"),
|
||||
qsTr("Executable file (*.*)"))
|
||||
if (fileName !== "") {
|
||||
AppSplitTunnelingController.addApp(fileName)
|
||||
}
|
||||
} else if (Qt.platform.os === "android"){
|
||||
installedAppDrawer.open()
|
||||
}
|
||||
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InstalledAppsDrawer {
|
||||
id: installedAppDrawer
|
||||
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ import "../Config"
|
|||
PageType {
|
||||
id: root
|
||||
|
||||
property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android"
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
|
|
@ -73,8 +75,6 @@ PageType {
|
|||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
visible: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Site-based split tunneling")
|
||||
|
|
@ -87,11 +87,11 @@ PageType {
|
|||
}
|
||||
|
||||
DividerType {
|
||||
visible: GC.isDesktop()
|
||||
visible: root.isAppSplitTinnelingEnabled
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
visible: false
|
||||
visible: root.isAppSplitTinnelingEnabled
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
|
@ -100,11 +100,12 @@ PageType {
|
|||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
visible: false
|
||||
visible: root.isAppSplitTinnelingEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ PageType {
|
|||
|
||||
property var isServerFromApi: ServersModel.getDefaultServerData("isServerFromApi")
|
||||
|
||||
defaultActiveFocusItem: website_ip_field.textField
|
||||
defaultActiveFocusItem: searchField.textField
|
||||
|
||||
property bool pageEnabled: {
|
||||
return !ConnectionController.isConnected && !isServerFromApi
|
||||
|
|
@ -188,7 +188,24 @@ PageType {
|
|||
width: parent.width
|
||||
height: sites.contentItem.height
|
||||
|
||||
model: SitesModel
|
||||
model: SortFilterProxyModel {
|
||||
id: proxySitesModel
|
||||
sourceModel: SitesModel
|
||||
filters: [
|
||||
AnyOf {
|
||||
RegExpFilter {
|
||||
roleName: "url"
|
||||
pattern: ".*" + searchField.textField.text + ".*"
|
||||
caseSensitivity: Qt.CaseInsensitive
|
||||
}
|
||||
RegExpFilter {
|
||||
roleName: "ip"
|
||||
pattern: ".*" + searchField.textField.text + ".*"
|
||||
caseSensitivity: Qt.CaseInsensitive
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
clip: true
|
||||
interactive: false
|
||||
|
|
@ -218,7 +235,7 @@ PageType {
|
|||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
var yesButtonFunction = function() {
|
||||
SitesController.removeSite(index)
|
||||
SitesController.removeSite(proxySitesModel.mapToSource(index))
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
}
|
||||
|
|
@ -255,7 +272,7 @@ PageType {
|
|||
anchors.bottomMargin: 24
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: website_ip_field
|
||||
id: searchField
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
|
@ -430,8 +447,4 @@ PageType {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue