update: added systemwide tab in settings

This commit is contained in:
Your Name 2025-11-26 16:47:37 +01:00
parent fc944b0cb5
commit 043f1fd2b0
2 changed files with 248 additions and 37 deletions

View file

@ -14,7 +14,7 @@ import subprocess
import qrcode import qrcode
from io import BytesIO from io import BytesIO
from typing import Union from typing import Union
from core.Errors import UnknownConnectionTypeError, CommandNotFoundError, MissingSubscriptionError, InvalidSubscriptionError, ProfileActivationError, UnsupportedApplicationVersionError, FileIntegrityError, ProfileModificationError, ProfileStateConflictError, EndpointVerificationError from core.Errors import UnknownConnectionTypeError, CommandNotFoundError, MissingSubscriptionError, InvalidSubscriptionError, ProfileActivationError, UnsupportedApplicationVersionError, FileIntegrityError, ProfileModificationError, ProfileStateConflictError, EndpointVerificationError, PolicyAssignmentError, PolicyInstatementError, PolicyRevocationError
from core.controllers.ApplicationVersionController import ApplicationVersionController from core.controllers.ApplicationVersionController import ApplicationVersionController
from core.controllers.ApplicationController import ApplicationController from core.controllers.ApplicationController import ApplicationController
from core.controllers.ClientController import ClientController from core.controllers.ClientController import ClientController
@ -24,6 +24,7 @@ from core.controllers.LocationController import LocationController
from core.controllers.ProfileController import ProfileController from core.controllers.ProfileController import ProfileController
from core.controllers.SubscriptionController import SubscriptionController from core.controllers.SubscriptionController import SubscriptionController
from core.controllers.SubscriptionPlanController import SubscriptionPlanController from core.controllers.SubscriptionPlanController import SubscriptionPlanController
from core.controllers.PrivilegePolicyController import PrivilegePolicyController
from core.models.session.SessionConnection import SessionConnection from core.models.session.SessionConnection import SessionConnection
from core.models.session.SessionProfile import SessionProfile from core.models.session.SessionProfile import SessionProfile
from core.models.system.SystemConnection import SystemConnection from core.models.system.SystemConnection import SystemConnection
@ -608,6 +609,21 @@ class CustomWindow(QMainWindow):
if config["logging"]["gui_logging_enabled"]: if config["logging"]["gui_logging_enabled"]:
self._setup_gui_logging() self._setup_gui_logging()
def has_shown_systemwide_prompt(self):
config = self._load_gui_config()
if not config:
return False
return config.get("systemwide", {}).get("prompt_shown", False)
def mark_systemwide_prompt_shown(self):
config = self._load_gui_config()
if config is None:
config = {"logging": {"gui_logging_enabled": False, "log_level": "INFO"}}
if "systemwide" not in config:
config["systemwide"] = {}
config["systemwide"]["prompt_shown"] = True
self._save_gui_config(config)
def stop_gui_logging(self): def stop_gui_logging(self):
@ -895,6 +911,7 @@ class CustomWindow(QMainWindow):
ResidentialPage(self.page_stack, self), ResidentialPage(self.page_stack, self),
InstallSystemPackage(self.page_stack, self), InstallSystemPackage(self.page_stack, self),
WelcomePage(self.page_stack, self), WelcomePage(self.page_stack, self),
SystemwidePromptPage(self.page_stack, self),
PaymentDetailsPage(self.page_stack, self), PaymentDetailsPage(self.page_stack, self),
DurationSelectionPage(self.page_stack, self), DurationSelectionPage(self.page_stack, self),
CurrencySelectionPage(self.page_stack, self), CurrencySelectionPage(self.page_stack, self),
@ -2841,7 +2858,14 @@ class InstallSystemPackage(Page):
self.status_label.setStyleSheet("color: red;") self.status_label.setStyleSheet("color: red;")
def go_next(self): def go_next(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) if not self.update_status.has_shown_systemwide_prompt():
system_page = self.page_stack.findChild(SystemwidePromptPage)
if system_page is None:
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
return
self.page_stack.setCurrentIndex(self.page_stack.indexOf(system_page))
else:
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
class ProtocolPage(Page): class ProtocolPage(Page):
def __init__(self, page_stack, main_window=None, parent=None): def __init__(self, page_stack, main_window=None, parent=None):
@ -5308,6 +5332,7 @@ class Settings(Page):
("Subscriptions", self.show_subscription_page), ("Subscriptions", self.show_subscription_page),
("Create/Edit", self.show_registrations_page), ("Create/Edit", self.show_registrations_page),
("Verification", self.show_verification_page), ("Verification", self.show_verification_page),
("Systemwide", self.show_systemwide_page),
("Delete Profile", self.show_delete_page), ("Delete Profile", self.show_delete_page),
("Error Logs", self.show_logs_page), ("Error Logs", self.show_logs_page),
("Debug Help", self.show_debug_page) ("Debug Help", self.show_debug_page)
@ -6101,39 +6126,6 @@ class Settings(Page):
def show_account_page(self):
self.content_layout.setCurrentWidget(self.account_page)
self.update_button_states(0)
def show_subscription_page(self):
self.content_layout.setCurrentWidget(self.subscription_page)
self.update_button_states(1)
def show_registrations_page(self):
self.content_layout.setCurrentWidget(self.registrations_page)
self.update_button_states(2)
def show_verification_page(self):
self.content_layout.setCurrentWidget(self.verification_page)
self.update_button_states(3)
def show_delete_page(self):
self.content_layout.setCurrentWidget(self.delete_page)
self.update_button_states(4)
def show_logs_page(self):
self.content_layout.setCurrentWidget(self.logs_page)
self.update_button_states(5)
def show_debug_page(self):
self.content_layout.setCurrentWidget(self.debug_page)
self.update_button_states(6)
def update_button_states(self, active_index):
for i, btn in enumerate(self.menu_buttons):
btn.setChecked(i == active_index)
def reverse(self): def reverse(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
@ -6547,6 +6539,10 @@ class Settings(Page):
self.verification_page = self.create_verification_page() self.verification_page = self.create_verification_page()
self.content_layout.addWidget(self.verification_page) self.content_layout.addWidget(self.verification_page)
self.content_layout.removeWidget(self.systemwide_page)
self.systemwide_page = self.create_systemwide_page()
self.content_layout.addWidget(self.systemwide_page)
self.content_layout.removeWidget(self.logs_page) self.content_layout.removeWidget(self.logs_page)
self.logs_page = self.create_logs_page() self.logs_page = self.create_logs_page()
self.content_layout.addWidget(self.logs_page) self.content_layout.addWidget(self.logs_page)
@ -6751,6 +6747,7 @@ class Settings(Page):
self.subscription_page = self.create_subscription_page() self.subscription_page = self.create_subscription_page()
self.registrations_page = self.create_registrations_page() self.registrations_page = self.create_registrations_page()
self.verification_page = self.create_verification_page() self.verification_page = self.create_verification_page()
self.systemwide_page = self.create_systemwide_page()
self.logs_page = self.create_logs_page() self.logs_page = self.create_logs_page()
self.delete_page = self.create_delete_page() self.delete_page = self.create_delete_page()
self.debug_page = self.create_debug_page() self.debug_page = self.create_debug_page()
@ -6759,6 +6756,7 @@ class Settings(Page):
self.content_layout.addWidget(self.subscription_page) self.content_layout.addWidget(self.subscription_page)
self.content_layout.addWidget(self.registrations_page) self.content_layout.addWidget(self.registrations_page)
self.content_layout.addWidget(self.verification_page) self.content_layout.addWidget(self.verification_page)
self.content_layout.addWidget(self.systemwide_page)
self.content_layout.addWidget(self.logs_page) self.content_layout.addWidget(self.logs_page)
self.content_layout.addWidget(self.delete_page) self.content_layout.addWidget(self.delete_page)
self.content_layout.addWidget(self.debug_page) self.content_layout.addWidget(self.debug_page)
@ -6766,6 +6764,45 @@ class Settings(Page):
self.content_layout.setCurrentIndex(0) self.content_layout.setCurrentIndex(0)
self.show_account_page() self.show_account_page()
def show_account_page(self):
self.content_layout.setCurrentWidget(self.account_page)
self._select_menu_button("Overview")
def show_subscription_page(self):
self.content_layout.setCurrentWidget(self.subscription_page)
self._select_menu_button("Subscriptions")
def show_registrations_page(self):
self.content_layout.setCurrentWidget(self.registrations_page)
self._select_menu_button("Create/Edit")
def show_verification_page(self):
self.content_layout.setCurrentWidget(self.verification_page)
self._select_menu_button("Verification")
def show_systemwide_page(self):
self.content_layout.setCurrentWidget(self.systemwide_page)
self._select_menu_button("Systemwide")
def show_logs_page(self):
self.content_layout.setCurrentWidget(self.logs_page)
self._select_menu_button("Error Logs")
def show_delete_page(self):
self.content_layout.setCurrentWidget(self.delete_page)
self._select_menu_button("Delete Profile")
def show_debug_page(self):
self.content_layout.setCurrentWidget(self.debug_page)
self._select_menu_button("Debug Help")
def _select_menu_button(self, label):
for btn in self.menu_buttons:
if btn.text() == label:
btn.setChecked(True)
else:
btn.setChecked(False)
def create_logs_page(self): def create_logs_page(self):
page = QWidget() page = QWidget()
layout = QVBoxLayout(page) layout = QVBoxLayout(page)
@ -6879,6 +6916,87 @@ class Settings(Page):
self.load_registrations_settings() self.load_registrations_settings()
return page return page
def create_systemwide_page(self):
page = QWidget()
layout = QVBoxLayout(page)
layout.setSpacing(20)
layout.setContentsMargins(20, 20, 20, 20)
title = QLabel("SYSTEM-WIDE PROFILES")
title.setStyleSheet(f"color: #808080; font-size: 12px; font-weight: bold; {self.font_style}")
layout.addWidget(title)
description = QLabel("Control whether HydraVeil configures a sudo policy so system-wide WireGuard profiles can be started without entering your sudo password.")
description.setWordWrap(True)
description.setStyleSheet(f"color: white; font-size: 14px; {self.font_style}")
layout.addWidget(description)
status_layout = QHBoxLayout()
status_label = QLabel("Current status:")
status_label.setStyleSheet(f"color: white; font-size: 14px; {self.font_style}")
self.systemwide_status_value = QLabel("")
self.systemwide_status_value.setStyleSheet(f"color: #e67e22; font-size: 14px; {self.font_style}")
status_layout.addWidget(status_label)
status_layout.addWidget(self.systemwide_status_value)
status_layout.addStretch()
layout.addLayout(status_layout)
toggle_layout = QHBoxLayout()
self.systemwide_toggle = QCheckBox("Enable system-wide policy")
self.systemwide_toggle.setStyleSheet(self.get_checkbox_style())
toggle_layout.addWidget(self.systemwide_toggle)
toggle_layout.addStretch()
layout.addLayout(toggle_layout)
save_button = QPushButton()
save_button.setFixedSize(75, 46)
save_button.setIcon(QIcon(os.path.join(self.btn_path, "save.png")))
save_button.setIconSize(QSize(75, 46))
save_button.clicked.connect(self.save_systemwide_settings)
button_layout = QHBoxLayout()
button_layout.addWidget(save_button)
button_layout.addStretch()
layout.addLayout(button_layout)
layout.addStretch()
self.load_systemwide_settings()
return page
def load_systemwide_settings(self):
enabled = False
try:
enabled = PrivilegePolicyController.is_instated()
except Exception:
enabled = False
self.systemwide_toggle.setChecked(enabled)
if enabled:
self.systemwide_status_value.setText("Enabled")
self.systemwide_status_value.setStyleSheet(f"color: #2ecc71; font-size: 14px; {self.font_style}")
else:
self.systemwide_status_value.setText("Disabled")
self.systemwide_status_value.setStyleSheet(f"color: #e67e22; font-size: 14px; {self.font_style}")
def save_systemwide_settings(self):
enable = self.systemwide_toggle.isChecked()
try:
if enable:
PrivilegePolicyController.instate()
else:
if PrivilegePolicyController.is_instated():
PrivilegePolicyController.revoke()
self.load_systemwide_settings()
self.update_status.update_status("System-wide policy settings updated")
except CommandNotFoundError as e:
self.systemwide_status_value.setText(str(e))
self.systemwide_status_value.setStyleSheet(f"color: red; font-size: 14px; {self.font_style}")
except (PolicyAssignmentError, PolicyInstatementError, PolicyRevocationError) as e:
self.systemwide_status_value.setText(str(e))
self.systemwide_status_value.setStyleSheet(f"color: red; font-size: 14px; {self.font_style}")
except Exception:
self.systemwide_status_value.setText("Failed to update policy")
self.systemwide_status_value.setStyleSheet(f"color: red; font-size: 14px; {self.font_style}")
def load_registrations_settings(self) -> None: def load_registrations_settings(self) -> None:
try: try:
config = self.update_status._load_gui_config() config = self.update_status._load_gui_config()
@ -7855,6 +7973,99 @@ class WelcomePage(Page):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(install_page)) self.page_stack.setCurrentIndex(self.page_stack.indexOf(install_page))
class SystemwidePromptPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Systemwide", page_stack, main_window, parent)
self.btn_path = main_window.btn_path
self.update_status = main_window
self.button_back.setVisible(True)
self.button_back.clicked.connect(self.back_to_install)
self.button_next.setVisible(False)
self.button_go.setVisible(False)
self.status_label = QLabel(self)
self.status_label.setGeometry(80, 430, 640, 40)
self.status_label.setStyleSheet("font-size: 14px; color: cyan;")
self.setup_ui()
def setup_ui(self):
self.title.setGeometry(20, 50, 760, 40)
self.title.setText("Enable system-wide WireGuard profiles")
description = QLabel(self)
description.setGeometry(80, 100, 640, 80)
description.setWordWrap(True)
description.setStyleSheet("font-size: 14px; color: cyan;")
description.setText("You can enable system-wide WireGuard profiles so they can be started without entering your sudo password each time. This uses a secure sudo policy tailored to your user.")
icon_label = QLabel(self)
icon_label.setGeometry(80, 200, 64, 64)
icon_pix = QPixmap(os.path.join(self.btn_path, "wireguard_system_wide.png"))
icon_label.setPixmap(icon_pix.scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
yes_button = QPushButton("Yes, enable system-wide profiles", self)
yes_button.setGeometry(170, 200, 300, 50)
yes_button.setStyleSheet("""
QPushButton {
background: #007AFF;
color: white;
border: none;
border-radius: 4px;
font-size: 13px;
font-weight: bold;
}
QPushButton:hover {
background: #0056CC;
}
""")
yes_button.clicked.connect(self.enable_systemwide)
no_button = QPushButton("Not now", self)
no_button.setGeometry(170, 270, 120, 40)
no_button.setStyleSheet("""
QPushButton {
background: #007AFF;
color: white;
border: none;
border-radius: 4px;
font-size: 13px;
font-weight: bold;
}
QPushButton:hover {
background: #0056CC;
}
""")
no_button.clicked.connect(self.skip_systemwide)
self.refresh_status()
def refresh_status(self):
if PrivilegePolicyController.is_instated():
self.status_label.setText("Current status: system-wide policy is enabled.")
self.status_label.setStyleSheet("font-size: 14px; color: #2ecc71;")
else:
self.status_label.setText("Current status: system-wide policy is disabled.")
self.status_label.setStyleSheet("font-size: 14px; color: #e67e22;")
def enable_systemwide(self):
try:
PrivilegePolicyController.instate()
self.update_status.mark_systemwide_prompt_shown()
self.refresh_status()
self.update_status.update_status("System-wide policy enabled")
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
except CommandNotFoundError as e:
self.status_label.setText(str(e))
self.status_label.setStyleSheet("font-size: 14px; color: red;")
except (PolicyAssignmentError, PolicyInstatementError) as e:
self.status_label.setText(str(e))
self.status_label.setStyleSheet("font-size: 14px; color: red;")
except Exception:
self.status_label.setText("Failed to enable system-wide policy")
self.status_label.setStyleSheet("font-size: 14px; color: red;")
def skip_systemwide(self):
self.update_status.mark_systemwide_prompt_shown()
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
def back_to_install(self):
install_page = self.page_stack.findChild(InstallSystemPackage)
if install_page:
self.page_stack.setCurrentIndex(self.page_stack.indexOf(install_page))
class DurationSelectionPage(Page): class DurationSelectionPage(Page):
def __init__(self, page_stack, main_window=None, parent=None): def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Select Duration", page_stack, main_window, parent) super().__init__("Select Duration", page_stack, main_window, parent)

View file

@ -1,6 +1,6 @@
[project] [project]
name = "sp-hydra-veil-gui" name = "sp-hydra-veil-gui"
version = "1.1.7" version = "1.1.8"
authors = [ authors = [
{ name = "Simplified Privacy" }, { name = "Simplified Privacy" },
] ]
@ -12,7 +12,7 @@ classifiers = [
"Operating System :: POSIX :: Linux", "Operating System :: POSIX :: Linux",
] ]
dependencies = [ dependencies = [
"sp-hydra-veil-core == 1.1.7", "sp-hydra-veil-core == 1.1.8-a2",
"pyperclip ~= 1.9.0", "pyperclip ~= 1.9.0",
"pyqt6 ~= 6.7.1", "pyqt6 ~= 6.7.1",
"qrcode[pil] ~= 8.2" "qrcode[pil] ~= 8.2"