diff --git a/gui/resources/images/networking_auto.png b/gui/resources/images/networking_auto.png new file mode 100755 index 0000000..ab02711 Binary files /dev/null and b/gui/resources/images/networking_auto.png differ diff --git a/gui/resources/images/networking_manual.png b/gui/resources/images/networking_manual.png new file mode 100755 index 0000000..e4dda64 Binary files /dev/null and b/gui/resources/images/networking_manual.png differ diff --git a/gui/resources/images/networking_shield.png b/gui/resources/images/networking_shield.png new file mode 100755 index 0000000..622aa4b Binary files /dev/null and b/gui/resources/images/networking_shield.png differ diff --git a/gui/v2/infrastructure/page_registry.py b/gui/v2/infrastructure/page_registry.py index 9d761b2..3fa915f 100755 --- a/gui/v2/infrastructure/page_registry.py +++ b/gui/v2/infrastructure/page_registry.py @@ -1,6 +1,7 @@ PAGE_REGISTRY = { "blank": ("gui.v2.ui.pages._blank_page", "BlankPage"), "welcome": ("gui.v2.ui.pages.welcome_page", "WelcomePage"), + "networking_setup": ("gui.v2.ui.pages.networking_setup_page", "NetworkingSetupPage"), "menu": ("gui.v2.ui.pages.menu_page", "MenuPage"), "protocol": ("gui.v2.ui.pages.protocol_page", "ProtocolPage"), "hidetor": ("gui.v2.ui.pages.hidetor_page", "HidetorPage"), diff --git a/gui/v2/ui/pages/networking_setup_page.py b/gui/v2/ui/pages/networking_setup_page.py new file mode 100755 index 0000000..3a7acf6 --- /dev/null +++ b/gui/v2/ui/pages/networking_setup_page.py @@ -0,0 +1,191 @@ +import os + +from PyQt6.QtWidgets import QLabel, QPushButton, QWidget +from PyQt6.QtGui import QPixmap +from PyQt6.QtCore import Qt, QTimer + +from core.services.helpers.setup_sudo_scripts import auto_install_sudo_script, test_if_in_sudo_folder +from core.services.helpers.manage_assets import sudo_assets_folder_setup +from core.models.Result import Result, ResultError + +import sys + +from gui.v2.ui.pages.Page import Page + + +class NetworkingSetupPage(Page): + def __init__(self, page_stack, main_window=None, parent=None): + super().__init__("NetworkingSetup", page_stack, main_window, parent) + self.btn_path = main_window.btn_path + self.update_status = main_window + self.manual_scripts_ready = False + self.button_next.setVisible(True) + self.button_next.clicked.connect(self.go_to_install) + self._setup_ui() + + def _setup_ui(self): + header_icon = self._icon_label("networking_shield.png", 48, self) + header_icon.setGeometry(74, 66, 48, 48) + + title = QLabel("Firewall & Managed-DNS Setup", self) + title.setGeometry(138, 68, 590, 44) + title.setStyleSheet("font-size: 26px; font-weight: bold; color: cyan;") + + subtitle = QLabel( + "Optional privileged networking helpers", + self) + subtitle.setGeometry(140, 108, 560, 24) + subtitle.setStyleSheet("font-size: 14px; color: #d8ffff;") + + description = QLabel( + "These firewall and managed-DNS helpers are separate bash scripts. " + "They stay outside the AppImage Python runtime, and if installed they are placed " + "in sudo-protected folders owned by root.", + self) + description.setGeometry(80, 152, 640, 72) + description.setWordWrap(True) + description.setStyleSheet("font-size: 15px; color: cyan;") + + manual_panel = self._option_panel(74, 248, "Option 1", "Manual review") + manual_text = QLabel( + "Copy the scripts to Downloads, review them, then run sudo bash setup.sh yourself.", + manual_panel) + manual_text.setGeometry(24, 76, 252, 48) + manual_text.setWordWrap(True) + manual_text.setStyleSheet("font-size: 13px; color: #d8ffff;") + + self.manual_button = QPushButton("Get scripts", manual_panel) + self.manual_button.setGeometry(24, 132, 252, 38) + self.manual_button.setStyleSheet(self._button_style("#007AFF", "#0056CC")) + self.manual_button.clicked.connect(self.handle_manual_setup) + + auto_panel = self._option_panel(426, 248, "Option 2", "Automated installer") + auto_text = QLabel( + "Let HydraVeil run the installer, then test that the scripts reached the sudo folder.", + auto_panel) + auto_text.setGeometry(24, 76, 252, 48) + auto_text.setWordWrap(True) + auto_text.setStyleSheet("font-size: 13px; color: #d8ffff;") + + self.auto_button = QPushButton("Automated installer", auto_panel) + self.auto_button.setGeometry(24, 132, 252, 38) + self.auto_button.setStyleSheet(self._button_style("#16a085", "#117a65")) + self.auto_button.clicked.connect(self.install_sudo_scripts_automatically) + + self.status_label = QLabel("Choose manual or automated setup, or press Next to skip and continue.", self) + self.status_label.setGeometry(82, 462, 636, 38) + self.status_label.setWordWrap(True) + self.status_label.setStyleSheet("font-size: 13px; color: #d8ffff;") + + + def _option_panel(self, x, y, label, title): + panel = QWidget(self) + panel.setObjectName("networkingOptionPanel") + panel.setGeometry(x, y, 300, 186) + panel.setStyleSheet(""" + QWidget#networkingOptionPanel { + background-color: #17323d; + border: 1px solid #2c7a8d; + border-radius: 6px; + } + """) + + option_label = QLabel(label, panel) + option_label.setGeometry(24, 20, 120, 20) + option_label.setStyleSheet("font-size: 11px; font-weight: bold; color: #8de9ff; border: none;") + + title_label = QLabel(title, panel) + title_label.setGeometry(24, 42, 240, 28) + title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: cyan; border: none;") + + return panel + + def _icon_label(self, icon_name, size, parent): + label = QLabel(parent) + icon_path = os.path.join(self.btn_path, icon_name) + label.setPixmap(QPixmap(icon_path).scaled( + size, size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) + label.setFixedSize(size, size) + label.setStyleSheet("border: none;") + return label + + def _button_style(self, background, hover): + return f""" + QPushButton {{ + background: {background}; + color: #f4ffff; + border: none; + border-radius: 4px; + font-size: 12px; + font-weight: bold; + padding: 4px 8px; + }} + QPushButton:hover {{ + background: {hover}; + }} + QPushButton:disabled {{ + background: #3f4954; + color: #94a3ad; + }} + """ + + def _set_status(self, message, color="#d8ffff"): + self.status_label.setText(message) + self.status_label.setStyleSheet(f"font-size: 13px; color: {color};") + self.update_status.update_status(message) + + def _result_is_valid(self, result: Result): + if hasattr(result, "valid"): + return result.valid + if result is None: + return True + return bool(result) + + def _result_message(self, result: Result, fallback): + return getattr(result, "message", fallback) + + def handle_manual_setup(self): + if self.manual_scripts_ready: + self.confirm_sudo_scripts() + return + results = sudo_assets_folder_setup() + if not self._result_is_valid(results): + self._set_status( + f"Could not copy scripts: {self._result_message(results, 'Unknown error')}", "#ff6b6b") + return + self.manual_scripts_ready = True + self.manual_button.setText("Confirm manual install") + self._set_status( + "Copied to Downloads/hydraveil_sudo_scripts. Review them, run 'sudo bash setup.sh', then click Confirm manual install.", "#d8ffff") + + def confirm_sudo_scripts(self): + confirmed = test_if_in_sudo_folder() + if confirmed.valid: + self._set_status("Confirmed it worked. Continuing to prerequisite installation.", "#2ecc71") + QTimer.singleShot(600, self.go_to_install) + else: + self._set_status( + f"I'm sorry it did not work, {confirmed.message}", "#ff6b6b") + + def install_sudo_scripts_automatically(self): + results = auto_install_sudo_script() + if results.valid: + confirmed = test_if_in_sudo_folder() + if confirmed.valid: + self._set_status("The setup script ran successfully. Continuing to prerequisite installation.", "#2ecc71") + QTimer.singleShot(600, self.go_to_install) + else: + self._set_status( + f"I'm sorry it did not work, {confirmed.message}", "#ff6b6b") + else: + self._set_status( + f"Setup script did NOT work because {results.message}", "#ff6b6b") + + def go_to_install(self): + self.custom_window.navigator.navigate("install_system_package") + install_page = self.custom_window.navigator.get_cached("install_system_package") + if install_page is not None: + install_page.configure(package_name='all', distro='debian') + + def back_to_welcome(self): + self.custom_window.navigator.navigate("welcome") diff --git a/gui/v2/ui/pages/welcome_page.py b/gui/v2/ui/pages/welcome_page.py index c7d0c83..2045c3c 100755 --- a/gui/v2/ui/pages/welcome_page.py +++ b/gui/v2/ui/pages/welcome_page.py @@ -13,7 +13,7 @@ class WelcomePage(Page): self.btn_path = main_window.btn_path self.update_status = main_window self.ui_elements = [] - self.button_next.clicked.connect(self.go_to_install) + self.button_next.clicked.connect(self.go_to_networking_setup) self.button_next.setVisible(True) self._setup_welcome_ui() self._setup_stats_display() @@ -30,7 +30,7 @@ class WelcomePage(Page): welcome_msg = QLabel( "Before we begin your journey, we need to set up a few essential components. " - "Click 'Next' to take you to the installation page.", self) + "Click 'Next' to review the optional networking setup.", self) welcome_msg.setGeometry(40, 100, 720, 80) welcome_msg.setWordWrap(True) welcome_msg.setStyleSheet(""" @@ -95,7 +95,7 @@ class WelcomePage(Page): "font-size: 16px; font-weight: bold; color: #2c3e50;") title_layout.addWidget(title_label) - status_indicator = QLabel("●") + status_indicator = QLabel(chr(9679)) status_indicator.setStyleSheet("color: #2ecc71; font-size: 16px;") title_layout.addWidget(status_indicator) @@ -117,8 +117,5 @@ class WelcomePage(Page): grid_layout.addWidget(stat_widget, row, col) - def go_to_install(self): - self.custom_window.navigator.navigate("install_system_package") - install_page = self.custom_window.navigator.get_cached("install_system_package") - if install_page is not None: - install_page.configure(package_name='all', distro='debian') + def go_to_networking_setup(self): + self.custom_window.navigator.navigate("networking_setup")