From 2ddc367e4e12c13fc9499550fab62772408a6b47 Mon Sep 17 00:00:00 2001 From: Robin Seger Date: Tue, 20 Jan 2026 14:12:08 +0100 Subject: controlcenter: added VPN settings & management (#1095) * feat: add VPN settings and management UI - Add VPN configuration UI - Update VPN toggle visibility to check enabled providers * controlcenter: VPN modal transitions & cleanup * controlcenter: VPN modal styling * controlcenter: VPN modal scrim * controlcenter: VPN modal padding * controlcenter: VPN modal enter & exit behaviour --- modules/controlcenter/network/VpnSettings.qml | 232 ++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 modules/controlcenter/network/VpnSettings.qml (limited to 'modules/controlcenter/network/VpnSettings.qml') diff --git a/modules/controlcenter/network/VpnSettings.qml b/modules/controlcenter/network/VpnSettings.qml new file mode 100644 index 0000000..7387ddc --- /dev/null +++ b/modules/controlcenter/network/VpnSettings.qml @@ -0,0 +1,232 @@ +pragma ComponentBehavior: Bound + +import ".." +import "../components" +import qs.components +import qs.components.controls +import qs.components.containers +import qs.components.effects +import qs.services +import qs.config +import Quickshell +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property Session session + + spacing: Appearance.spacing.normal + + SettingsHeader { + icon: "vpn_key" + title: qsTr("VPN Settings") + } + + SectionHeader { + Layout.topMargin: Appearance.spacing.large + title: qsTr("General") + description: qsTr("VPN configuration") + } + + SectionContainer { + ToggleRow { + label: qsTr("VPN enabled") + checked: Config.utilities.vpn.enabled + toggle.onToggled: { + Config.utilities.vpn.enabled = checked; + Config.save(); + } + } + } + + SectionHeader { + Layout.topMargin: Appearance.spacing.large + title: qsTr("Providers") + description: qsTr("Manage VPN providers") + } + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + ListView { + Layout.fillWidth: true + Layout.preferredHeight: contentHeight + + interactive: false + spacing: Appearance.spacing.smaller + + model: ScriptModel { + values: Config.utilities.vpn.provider.map((provider, index) => { + const isObject = typeof provider === "object"; + const name = isObject ? (provider.name || "custom") : String(provider); + const displayName = isObject ? (provider.displayName || name) : name; + const iface = isObject ? (provider.interface || "") : ""; + + return { + index: index, + name: name, + displayName: displayName, + interface: iface, + provider: provider, + isActive: index === 0 + }; + }) + } + + delegate: Component { + StyledRect { + required property var modelData + required property int index + + width: ListView.view ? ListView.view.width : undefined + color: Colours.tPalette.m3surfaceContainerHigh + radius: Appearance.rounding.normal + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal + spacing: Appearance.spacing.normal + + MaterialIcon { + text: modelData.isActive ? "vpn_key" : "vpn_key_off" + font.pointSize: Appearance.font.size.large + color: modelData.isActive ? Colours.palette.m3primary : Colours.palette.m3outline + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + StyledText { + text: modelData.displayName + font.weight: modelData.isActive ? 500 : 400 + } + + StyledText { + text: qsTr("%1 • %2").arg(modelData.name).arg(modelData.interface || qsTr("No interface")) + font.pointSize: Appearance.font.size.small + color: Colours.palette.m3outline + } + } + + IconButton { + icon: modelData.isActive ? "arrow_downward" : "arrow_upward" + visible: !modelData.isActive || Config.utilities.vpn.provider.length > 1 + onClicked: { + if (modelData.isActive && index < Config.utilities.vpn.provider.length - 1) { + // Move down + const providers = [...Config.utilities.vpn.provider]; + const temp = providers[index]; + providers[index] = providers[index + 1]; + providers[index + 1] = temp; + Config.utilities.vpn.provider = providers; + Config.save(); + } else if (!modelData.isActive) { + // Make active (move to top) + const providers = [...Config.utilities.vpn.provider]; + const provider = providers.splice(index, 1)[0]; + providers.unshift(provider); + Config.utilities.vpn.provider = providers; + Config.save(); + } + } + } + + IconButton { + icon: "delete" + onClicked: { + const providers = [...Config.utilities.vpn.provider]; + providers.splice(index, 1); + Config.utilities.vpn.provider = providers; + Config.save(); + } + } + } + + implicitHeight: 60 + } + } + } + + TextButton { + Layout.fillWidth: true + Layout.topMargin: Appearance.spacing.normal + text: qsTr("+ Add Provider") + inactiveColour: Colours.palette.m3primaryContainer + inactiveOnColour: Colours.palette.m3onPrimaryContainer + + onClicked: { + addProviderDialog.open(); + } + } + } + + SectionHeader { + Layout.topMargin: Appearance.spacing.large + title: qsTr("Quick Add") + description: qsTr("Add common VPN providers") + } + + SectionContainer { + contentSpacing: Appearance.spacing.smaller + + TextButton { + Layout.fillWidth: true + text: qsTr("+ Add NetBird") + inactiveColour: Colours.tPalette.m3surfaceContainerHigh + inactiveOnColour: Colours.palette.m3onSurface + + onClicked: { + const providers = [...Config.utilities.vpn.provider]; + providers.push({ + name: "netbird", + displayName: "NetBird", + interface: "wt0" + }); + Config.utilities.vpn.provider = providers; + Config.save(); + } + } + + TextButton { + Layout.fillWidth: true + text: qsTr("+ Add Tailscale") + inactiveColour: Colours.tPalette.m3surfaceContainerHigh + inactiveOnColour: Colours.palette.m3onSurface + + onClicked: { + const providers = [...Config.utilities.vpn.provider]; + providers.push({ + name: "tailscale", + displayName: "Tailscale", + interface: "tailscale0" + }); + Config.utilities.vpn.provider = providers; + Config.save(); + } + } + + TextButton { + Layout.fillWidth: true + text: qsTr("+ Add Cloudflare WARP") + inactiveColour: Colours.tPalette.m3surfaceContainerHigh + inactiveOnColour: Colours.palette.m3onSurface + + onClicked: { + const providers = [...Config.utilities.vpn.provider]; + providers.push({ + name: "warp", + displayName: "Cloudflare WARP", + interface: "CloudflareWARP" + }); + Config.utilities.vpn.provider = providers; + Config.save(); + } + } + } +} -- cgit v1.2.3-freya