amnezia-client/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml
Mykola Baibuz f6d7552b58
feature: fillswitch strict mode (#1333)
* Add allowed DNS list for killswitch

* Windows killswitch strict mode backend part

* Killswitch strict mode for Linux and MacOS

* Windows fixes

* feature: Add Kill Switch settings page with strict mode option

* fix windows build after merge

* Refresh killswitch mode when it toggled

* Use HLM to store strictMode flag

* Some Linux updates

* feat: Enhance VerticalRadioButton with improved styling and disabled states

* Refresh killSwitch state update

* Fix build

* refactor: Modularize header components

* Change kill switch radio button styling

* Fix strict kill switch mode handling

* Refactor: Replace HeaderType with new Types for headers in QML pages

* Remove deprecated HeaderType QML component

* Refresh strict mode killswitch after global toggle change

* Implement model, controller and UI for killswitch dns exceptions

* Connect backend part and UI

* Change label text to DNS exceptions

* Remove HeaderType from PageSettingsApiDevices

* Some pretty fixes

* Fix problem with definition sequence of PageSettingsKillSwitchExceptions.pml elements

* Add exclusion method for Windows firewall

* Change ubuntu version in deploy script

* Update ubuntu version in GH actions

* Add confirmation popup for strict killswitch mode

* Add qt standard path for build script

* Add method to killswitch for expanding strickt mode exceptions list and fix allowTrafficTo() for Windows. Also Added cache in KillSwitch class for exceptions

* Add insertion of gateway address to strict killswitch exceptions

* Review fixes

* buildfix and naming

---------

Co-authored-by: aiamnezia <ai@amnezia.org>
2025-05-03 13:54:36 +07:00

277 lines
8.3 KiB
QML

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 Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
property bool pageEnabled
Component.onCompleted: {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change split tunneling settings during active connection"))
root.pageEnabled = false
} else {
root.pageEnabled = true
}
}
QtObject {
id: routeMode
property int allApps: 0
property int onlyForwardApps: 1
property int allExceptApps: 2
}
property list<QtObject> routeModesModel: [
onlyForwardApps,
allExceptApps
]
QtObject {
id: onlyForwardApps
readonly property string name: qsTr("Only the apps from the list should have access via VPN")
readonly property int type: routeMode.onlyForwardApps
}
QtObject {
id: allExceptApps
readonly property string name: qsTr("Apps from the list should not have access via VPN")
readonly 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 {
id: backButton
}
HeaderTypeWithSwitcher {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("App split tunneling")
enabled: root.pageEnabled
showSwitcher: true
switcher {
checked: AppSplitTunnelingModel.isTunnelingEnabled
enabled: root.pageEnabled
}
switcherFunction: function(checked) {
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" && root.pageEnabled
listView: ListViewWithRadioButtonType {
rootWidth: root.width
model: root.routeModesModel
selectedIndex: getRouteModesModelIndex()
clickedFunction: function() {
selector.text = selectedText
selector.closeTriggered()
if (AppSplitTunnelingModel.routeMode !== root.routeModesModel[selectedIndex].type) {
AppSplitTunnelingModel.routeMode = root.routeModesModel[selectedIndex].type
}
}
Component.onCompleted: {
if (root.routeModesModel[selectedIndex].type === AppSplitTunnelingModel.routeMode) {
selector.text = selectedText
} else {
selector.text = root.routeModesModel[0].name
}
}
Connections {
target: AppSplitTunnelingModel
function onRouteModeChanged() {
selectedIndex = getRouteModesModelIndex()
}
}
}
}
}
FlickableType {
anchors.top: header.bottom
anchors.topMargin: 16
contentHeight: col.implicitHeight + addAppButton.implicitHeight + addAppButton.anchors.bottomMargin + addAppButton.anchors.topMargin
enabled: root.pageEnabled
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
}
sorters: [
RoleSorter { roleName: "appPath"; sortOrder: Qt.AscendingOrder }
]
}
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: AmneziaStyle.color.paleGray
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: AmneziaStyle.color.midnightBlack
opacity: 0.8
}
RowLayout {
id: addAppButton
enabled: root.pageEnabled
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
textField.placeholderText: qsTr("application name")
buttonImageSource: "qrc:/images/controls/plus.svg"
rightButtonClickedOnEnter: true
clickedFunc: function() {
searchField.focus = false
PageController.showBusyIndicator(true)
if (Qt.platform.os === "windows") {
var fileName = SystemController.getFileName(qsTr("Open executable file"),
qsTr("Executable files (*.*)"))
if (fileName !== "") {
AppSplitTunnelingController.addApp(fileName)
}
} else if (Qt.platform.os === "android"){
installedAppDrawer.openTriggered()
}
PageController.showBusyIndicator(false)
}
}
}
InstalledAppsDrawer {
id: installedAppDrawer
anchors.fill: parent
}
}