summaryrefslogtreecommitdiff
path: root/modules/controlcenter/network/VpnDetails.qml
diff options
context:
space:
mode:
Diffstat (limited to 'modules/controlcenter/network/VpnDetails.qml')
-rw-r--r--modules/controlcenter/network/VpnDetails.qml367
1 files changed, 367 insertions, 0 deletions
diff --git a/modules/controlcenter/network/VpnDetails.qml b/modules/controlcenter/network/VpnDetails.qml
new file mode 100644
index 0000000..76a9b17
--- /dev/null
+++ b/modules/controlcenter/network/VpnDetails.qml
@@ -0,0 +1,367 @@
+pragma ComponentBehavior: Bound
+
+import ".."
+import "../components"
+import qs.components
+import qs.components.controls
+import qs.components.effects
+import qs.components.containers
+import qs.services
+import qs.config
+import qs.utils
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
+
+DeviceDetails {
+ id: root
+
+ required property Session session
+ readonly property var vpnProvider: root.session.vpn.active
+ readonly property bool providerEnabled: {
+ if (!vpnProvider || vpnProvider.index === undefined) return false;
+ const provider = Config.utilities.vpn.provider[vpnProvider.index];
+ return provider && typeof provider === "object" && provider.enabled === true;
+ }
+
+ device: vpnProvider
+
+ headerComponent: Component {
+ ConnectionHeader {
+ icon: "vpn_key"
+ title: root.vpnProvider?.displayName ?? qsTr("Unknown")
+ }
+ }
+
+ sections: [
+ Component {
+ ColumnLayout {
+ spacing: Appearance.spacing.normal
+
+ SectionHeader {
+ title: qsTr("Connection status")
+ description: qsTr("VPN connection settings")
+ }
+
+ SectionContainer {
+ ToggleRow {
+ label: qsTr("Enable this provider")
+ checked: root.providerEnabled
+ toggle.onToggled: {
+ if (!root.vpnProvider) return;
+ const providers = [];
+ const index = root.vpnProvider.index;
+
+ // Copy providers and update enabled state
+ for (let i = 0; i < Config.utilities.vpn.provider.length; i++) {
+ const p = Config.utilities.vpn.provider[i];
+ if (typeof p === "object") {
+ const newProvider = {
+ name: p.name,
+ displayName: p.displayName,
+ interface: p.interface
+ };
+
+ if (checked) {
+ // Enable this one, disable others
+ newProvider.enabled = (i === index);
+ } else {
+ // Just disable this one
+ newProvider.enabled = (i === index) ? false : (p.enabled !== false);
+ }
+
+ providers.push(newProvider);
+ } else {
+ providers.push(p);
+ }
+ }
+
+ Config.utilities.vpn.provider = providers;
+ Config.save();
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.topMargin: Appearance.spacing.normal
+ spacing: Appearance.spacing.normal
+
+ TextButton {
+ Layout.fillWidth: true
+ Layout.minimumHeight: Appearance.font.size.normal + Appearance.padding.normal * 2
+ visible: root.providerEnabled
+ enabled: !VPN.connecting
+ inactiveColour: Colours.palette.m3primaryContainer
+ inactiveOnColour: Colours.palette.m3onPrimaryContainer
+ text: VPN.connected ? qsTr("Disconnect") : qsTr("Connect")
+
+ onClicked: {
+ VPN.toggle();
+ }
+ }
+
+ TextButton {
+ Layout.fillWidth: true
+ text: qsTr("Edit Provider")
+ inactiveColour: Colours.palette.m3secondaryContainer
+ inactiveOnColour: Colours.palette.m3onSecondaryContainer
+
+ onClicked: {
+ editVpnDialog.editIndex = root.vpnProvider.index;
+ editVpnDialog.providerName = root.vpnProvider.name;
+ editVpnDialog.displayName = root.vpnProvider.displayName;
+ editVpnDialog.interfaceName = root.vpnProvider.interface;
+ editVpnDialog.open();
+ }
+ }
+
+ TextButton {
+ Layout.fillWidth: true
+ text: qsTr("Delete Provider")
+ inactiveColour: Colours.palette.m3errorContainer
+ inactiveOnColour: Colours.palette.m3onErrorContainer
+
+ onClicked: {
+ const providers = [];
+ for (let i = 0; i < Config.utilities.vpn.provider.length; i++) {
+ if (i !== root.vpnProvider.index) {
+ providers.push(Config.utilities.vpn.provider[i]);
+ }
+ }
+ Config.utilities.vpn.provider = providers;
+ Config.save();
+ root.session.vpn.active = null;
+ }
+ }
+ }
+ }
+ }
+ },
+ Component {
+ ColumnLayout {
+ spacing: Appearance.spacing.normal
+
+ SectionHeader {
+ title: qsTr("Provider details")
+ description: qsTr("VPN provider information")
+ }
+
+ SectionContainer {
+ contentSpacing: Appearance.spacing.small / 2
+
+ PropertyRow {
+ label: qsTr("Provider")
+ value: root.vpnProvider?.name ?? qsTr("Unknown")
+ }
+
+ PropertyRow {
+ showTopMargin: true
+ label: qsTr("Display name")
+ value: root.vpnProvider?.displayName ?? qsTr("Unknown")
+ }
+
+ PropertyRow {
+ showTopMargin: true
+ label: qsTr("Interface")
+ value: root.vpnProvider?.interface || qsTr("N/A")
+ }
+
+ PropertyRow {
+ showTopMargin: true
+ label: qsTr("Status")
+ value: {
+ if (!root.providerEnabled) return qsTr("Disabled");
+ if (VPN.connecting) return qsTr("Connecting...");
+ if (VPN.connected) return qsTr("Connected");
+ return qsTr("Enabled (Not connected)");
+ }
+ }
+
+ PropertyRow {
+ showTopMargin: true
+ label: qsTr("Enabled")
+ value: root.providerEnabled ? qsTr("Yes") : qsTr("No")
+ }
+ }
+ }
+ }
+ ]
+
+ // Edit VPN Dialog
+ Popup {
+ id: editVpnDialog
+
+ property int editIndex: -1
+ property string providerName: ""
+ property string displayName: ""
+ property string interfaceName: ""
+
+ parent: Overlay.overlay
+ anchors.centerIn: parent
+ width: Math.min(400, parent.width - Appearance.padding.large * 2)
+ padding: Appearance.padding.large * 1.5
+
+ modal: true
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
+
+ opacity: 0
+ scale: 0.7
+
+ enter: Transition {
+ ParallelAnimation {
+ NumberAnimation { property: "opacity"; from: 0; to: 1; duration: Appearance.anim.durations.expressiveFastSpatial; easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial }
+ NumberAnimation { property: "scale"; from: 0.7; to: 1; duration: Appearance.anim.durations.expressiveFastSpatial; easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial }
+ }
+ }
+
+ exit: Transition {
+ ParallelAnimation {
+ NumberAnimation { property: "opacity"; from: 1; to: 0; duration: Appearance.anim.durations.expressiveFastSpatial; easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial }
+ NumberAnimation { property: "scale"; from: 1; to: 0.7; duration: Appearance.anim.durations.expressiveFastSpatial; easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial }
+ }
+ }
+
+ function closeWithAnimation(): void {
+ close();
+ }
+
+ Overlay.modal: Rectangle {
+ color: Qt.rgba(0, 0, 0, 0.4 * editVpnDialog.opacity)
+ }
+
+
+ background: StyledRect {
+ color: Colours.palette.m3surfaceContainerHigh
+ radius: Appearance.rounding.large
+
+ layer.enabled: true
+ layer.effect: DropShadow {
+ color: Qt.rgba(0, 0, 0, 0.3)
+ radius: 16
+ samples: 33
+ verticalOffset: 4
+ }
+ }
+
+ contentItem: ColumnLayout {
+ spacing: Appearance.spacing.normal
+
+ StyledText {
+ text: qsTr("Edit VPN Provider")
+ font.pointSize: Appearance.font.size.large
+ font.weight: 500
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: Appearance.spacing.smaller / 2
+
+ StyledText {
+ text: qsTr("Display Name")
+ font.pointSize: Appearance.font.size.small
+ color: Colours.palette.m3onSurfaceVariant
+ }
+
+ StyledRect {
+ Layout.fillWidth: true
+ implicitHeight: 40
+ color: displayNameField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
+ radius: Appearance.rounding.small
+ border.width: 1
+ border.color: displayNameField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
+
+ Behavior on color { CAnim {} }
+ Behavior on border.color { CAnim {} }
+
+ StyledTextField {
+ id: displayNameField
+ anchors.centerIn: parent
+ width: parent.width - Appearance.padding.normal
+ horizontalAlignment: TextInput.AlignLeft
+ text: editVpnDialog.displayName
+ onTextChanged: editVpnDialog.displayName = text
+ }
+ }
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: Appearance.spacing.smaller / 2
+
+ StyledText {
+ text: qsTr("Interface (e.g., wg0, torguard)")
+ font.pointSize: Appearance.font.size.small
+ color: Colours.palette.m3onSurfaceVariant
+ }
+
+ StyledRect {
+ Layout.fillWidth: true
+ implicitHeight: 40
+ color: interfaceNameField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
+ radius: Appearance.rounding.small
+ border.width: 1
+ border.color: interfaceNameField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
+
+ Behavior on color { CAnim {} }
+ Behavior on border.color { CAnim {} }
+
+ StyledTextField {
+ id: interfaceNameField
+ anchors.centerIn: parent
+ width: parent.width - Appearance.padding.normal
+ horizontalAlignment: TextInput.AlignLeft
+ text: editVpnDialog.interfaceName
+ onTextChanged: editVpnDialog.interfaceName = text
+ }
+ }
+ }
+
+ Item { Layout.preferredHeight: Appearance.spacing.normal }
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: Appearance.spacing.normal
+
+ TextButton {
+ Layout.fillWidth: true
+ text: qsTr("Cancel")
+ inactiveColour: Colours.tPalette.m3surfaceContainerHigh
+ inactiveOnColour: Colours.palette.m3onSurface
+ onClicked: editVpnDialog.closeWithAnimation()
+ }
+
+ TextButton {
+ Layout.fillWidth: true
+ text: qsTr("Save")
+ enabled: editVpnDialog.interfaceName.length > 0
+ inactiveColour: Colours.palette.m3primaryContainer
+ inactiveOnColour: Colours.palette.m3onPrimaryContainer
+
+ onClicked: {
+ const providers = [];
+ const oldProvider = Config.utilities.vpn.provider[editVpnDialog.editIndex];
+ const wasEnabled = typeof oldProvider === "object" ? (oldProvider.enabled !== false) : true;
+
+ for (let i = 0; i < Config.utilities.vpn.provider.length; i++) {
+ if (i === editVpnDialog.editIndex) {
+ providers.push({
+ name: editVpnDialog.providerName,
+ displayName: editVpnDialog.displayName || editVpnDialog.interfaceName,
+ interface: editVpnDialog.interfaceName,
+ enabled: wasEnabled
+ });
+ } else {
+ providers.push(Config.utilities.vpn.provider[i]);
+ }
+ }
+
+ Config.utilities.vpn.provider = providers;
+ Config.save();
+ editVpnDialog.closeWithAnimation();
+ }
+ }
+ }
+ }
+ }
+}