From 91e657a06d64ba38febcb43e2bbe2065a730d0dc Mon Sep 17 00:00:00 2001 From: JOhn Date: Thu, 2 Jul 2026 17:12:23 -0400 Subject: [PATCH] update: updated billing page, pagedataworker, forced_sync --- gui/__pycache__/main_ui.cpython-312.pyc | Bin 43497 -> 0 bytes gui/main_ui.py | 20 +++- gui/v2/infrastructure/navigator.py | 8 +- gui/v2/ui/pages/editor_page.py | 34 +++++- gui/v2/ui/pages/fast_registration_page.py | 29 ++++- gui/v2/ui/pages/id_page.py | 38 ++++++- gui/v2/ui/pages/payment_details_page.py | 10 ++ gui/v2/ui/pages/settings_page.py | 108 +------------------ gui/v2/ui/pages/ticket_crypto_picker_page.py | 13 ++- gui/v2/ui/popups/message_box.py | 18 ++++ gui/v2/workers/page_data_worker.py | 13 +-- 11 files changed, 168 insertions(+), 123 deletions(-) delete mode 100644 gui/__pycache__/main_ui.cpython-312.pyc create mode 100755 gui/v2/ui/popups/message_box.py diff --git a/gui/__pycache__/main_ui.cpython-312.pyc b/gui/__pycache__/main_ui.cpython-312.pyc deleted file mode 100644 index 8bb45ea08e91928d23f9f7257399a0731ab09710..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43497 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 diff --git a/gui/main_ui.py b/gui/main_ui.py index e4ba5b7..34bedc6 100755 --- a/gui/main_ui.py +++ b/gui/main_ui.py @@ -10,7 +10,7 @@ import traceback from PyQt6.QtWidgets import ( QApplication, QMainWindow, QStackedWidget, QLabel, QPushButton, - QDialog, QVBoxLayout, QHBoxLayout + QDialog, QVBoxLayout, QHBoxLayout, QMessageBox ) from PyQt6.QtGui import QPixmap, QFont, QFontDatabase from PyQt6 import QtGui @@ -61,6 +61,7 @@ from gui.v2.actions.ticket_failure import ( clear_ticket_verification_failure, ) from gui.v2.actions.should_be_synchronized import should_be_synchronized +from gui.v2.ui.popups.message_box import style_message_box, mark_confirm_button class CustomWindow(QMainWindow): @@ -192,6 +193,23 @@ class CustomWindow(QMainWindow): else: self.navigator.navigate("menu") self.navigator.start_preload() + if self.force_sync: + QTimer.singleShot(0, self._prompt_force_sync) + + def _prompt_force_sync(self): + connection = ConfigurationController.get_connection() + box = QMessageBox(self) + box.setWindowTitle("Data Update Needed") + box.setText( + "Please download the new data to avoid errors. You have profiles that need the data to function.\n\n" + f"Connection type: {connection}") + style_message_box(box) + sync_button = box.addButton("Sync", QMessageBox.ButtonRole.AcceptRole) + mark_confirm_button(sync_button) + box.addButton("Cancel", QMessageBox.ButtonRole.RejectRole) + box.exec() + if box.clickedButton() == sync_button: + self.sync() def perform_update_check(self): from core.controllers.ClientController import ClientController diff --git a/gui/v2/infrastructure/navigator.py b/gui/v2/infrastructure/navigator.py index 1499649..a836bf7 100755 --- a/gui/v2/infrastructure/navigator.py +++ b/gui/v2/infrastructure/navigator.py @@ -157,10 +157,16 @@ class Navigator(QObject): else: self._maybe_emit_preload_summary() + def _prepare_page_data(self, name): + module_path, class_name = PAGE_REGISTRY[name] + module = importlib.import_module(module_path) + cls = getattr(module, class_name) + return cls.prepare_data(self.custom_window) + def _dispatch_async_preload(self, name): module_path, class_name = PAGE_REGISTRY[name] print(f"[preload] {name:30s} ASYNC {module_path}.{class_name}", flush=True) - worker = PageDataWorker(name, module_path, class_name, self.custom_window) + worker = PageDataWorker(name, self._prepare_page_data, name) worker.data_ready.connect(self._on_preload_data_ready) worker.failed.connect(self._on_preload_failed) worker.finished.connect(lambda n=name: self._cleanup_preload_worker(n)) diff --git a/gui/v2/ui/pages/editor_page.py b/gui/v2/ui/pages/editor_page.py index ce00ca4..aafcb6b 100755 --- a/gui/v2/ui/pages/editor_page.py +++ b/gui/v2/ui/pages/editor_page.py @@ -16,6 +16,7 @@ from gui.v2.ui.pages.browser_page import BrowserPage from gui.v2.ui.pages.location_page import LocationPage from gui.v2.ui.pages.screen_page import ScreenPage from gui.v2.ui.popups.confirmation_popup import ConfirmationPopup +from gui.v2.workers.page_data_worker import PageDataWorker class EditorPage(Page): @@ -80,6 +81,13 @@ class EditorPage(Page): self.brow_disp.hide() self.brow_disp.lower() + self._prefetched_profiles = None + self._editor_workers = set() + + @staticmethod + def prepare_data(custom_window): + return ProfileController.get_all() + def _selected_profiles_str(self): menu_page = self.find_menu_page() if menu_page is not None and menu_page.selected_profiles: @@ -117,8 +125,28 @@ class EditorPage(Page): def showEvent(self, event): super().showEvent(event) self.res_hint_shown = False + worker = PageDataWorker("editor", EditorPage.prepare_data, self.custom_window) + worker.data_ready.connect( + lambda name, profiles: self._on_editor_data_ready(profiles)) + worker.failed.connect( + lambda name, error: self._on_editor_data_failed(error)) + worker.finished.connect( + lambda w=worker: self._cleanup_editor_worker(w)) + self._editor_workers.add(worker) + worker.start() + + def _on_editor_data_ready(self, profiles): + self._prefetched_profiles = profiles self.extraccion() + def _on_editor_data_failed(self, error): + self.update_status.update_status(f"Editor data load failed: {error}") + self.extraccion() + + def _cleanup_editor_worker(self, worker): + self._editor_workers.discard(worker) + worker.deleteLater() + def extraccion(self): self.data_profile = {} for label in self.labels: @@ -131,7 +159,11 @@ class EditorPage(Page): selected_profiles_str = self._selected_profiles_str() menu_page = self.find_menu_page() if menu_page: - new_profiles = ProfileController.get_all() + if self._prefetched_profiles is not None: + new_profiles = self._prefetched_profiles + self._prefetched_profiles = None + else: + new_profiles = ProfileController.get_all() self.profiles_data = menu_page.match_core_profiles( profiles_dict=new_profiles) self.data_profile = self.profiles_data[selected_profiles_str].copy( diff --git a/gui/v2/ui/pages/fast_registration_page.py b/gui/v2/ui/pages/fast_registration_page.py index c06234b..3199b47 100755 --- a/gui/v2/ui/pages/fast_registration_page.py +++ b/gui/v2/ui/pages/fast_registration_page.py @@ -15,6 +15,7 @@ from gui.v2.ui.pages.Page import Page from gui.v2.ui.pages.browser_page import BrowserPage from gui.v2.ui.pages.location_page import LocationPage from gui.v2.ui.pages.location_verification_page import LocationVerificationPage +from gui.v2.workers.page_data_worker import PageDataWorker from gui.v2.workers.worker_thread import WorkerThread @@ -73,9 +74,15 @@ class FastRegistrationPage(Page): 'resolution': '1024x760' } self.res_index = 1 + self._prefetched_profiles = None + self._fast_reg_workers = set() self.initialize_default_selections() + @staticmethod + def prepare_data(custom_window): + return ProfileController.get_all() + def initialize_default_selections(self): if not self.selected_values['location']: locations = self.connection_manager.get_location_list() @@ -107,6 +114,24 @@ class FastRegistrationPage(Page): super().showEvent(event) self.initialize_default_selections() self.create_interface_elements() + self._start_profiles_prefetch() + + def _start_profiles_prefetch(self): + worker = PageDataWorker( + "fast_registration", FastRegistrationPage.prepare_data, self.custom_window) + worker.data_ready.connect( + lambda name, profiles: self._on_profiles_prefetched(profiles)) + worker.finished.connect( + lambda w=worker: self._cleanup_fast_reg_worker(w)) + self._fast_reg_workers.add(worker) + worker.start() + + def _on_profiles_prefetched(self, profiles): + self._prefetched_profiles = profiles + + def _cleanup_fast_reg_worker(self, worker): + self._fast_reg_workers.discard(worker) + worker.deleteLater() def create_interface_elements(self): for label in self.labels: @@ -728,7 +753,7 @@ class FastRegistrationPage(Page): self.update_status.update_status('Invalid location selected') return - profiles = ProfileController.get_all() + profiles = self._prefetched_profiles if self._prefetched_profiles is not None else ProfileController.get_all() profile_id = self.get_next_available_profile_id(profiles) profile_data_for_resume = { 'id': profile_id, @@ -762,7 +787,7 @@ class FastRegistrationPage(Page): connection_type = 'tor' if profile_data['connection'] == 'tor' else 'system' - profiles = ProfileController.get_all() + profiles = self._prefetched_profiles if self._prefetched_profiles is not None else ProfileController.get_all() profile_id = self.get_next_available_profile_id(profiles) profile_data_for_resume = { 'id': profile_id, diff --git a/gui/v2/ui/pages/id_page.py b/gui/v2/ui/pages/id_page.py index bb44443..28bbea0 100755 --- a/gui/v2/ui/pages/id_page.py +++ b/gui/v2/ui/pages/id_page.py @@ -2,13 +2,16 @@ import os import re from PyQt6.QtWidgets import ( - QButtonGroup, QFrame, QLabel, QPushButton, QTextEdit, + QButtonGroup, QFrame, QLabel, QMessageBox, QPushButton, QTextEdit, ) from PyQt6.QtGui import QIcon, QPixmap from PyQt6.QtCore import Qt from PyQt6 import QtCore +from core.services.payment_phase.do_we_have_billing_id import do_we_have_billing_id + from gui.v2.ui.pages.Page import Page +from gui.v2.ui.popups.message_box import style_message_box, mark_confirm_button HOW_MANY_PROFILES_DEFAULT = 6 @@ -150,11 +153,44 @@ class IdPage(Page): self.custom_window.navigator.navigate("duration_selection") def go_multiple_profiles(self): + billing_id = do_we_have_billing_id() + if billing_id: + self._prompt_existing_tickets(billing_id) + else: + self._continue_multiple_flow() + + def _continue_multiple_flow(self): self.custom_window.navigator.navigate("plan_picker") plan_page = self.custom_window.navigator.get_cached("plan_picker") if plan_page is not None: plan_page.start_sync() + def _prompt_existing_tickets(self, billing_id): + box = QMessageBox(self) + box.setWindowTitle("Existing ticket purchase found") + box.setText( + "You already have a pending ticket purchase. Continue to check if it's paid, " + "or wipe it and start over?") + style_message_box(box) + check_button = box.addButton( + "Check payment", QMessageBox.ButtonRole.AcceptRole) + mark_confirm_button(check_button) + wipe_button = box.addButton( + "Wipe and start over", QMessageBox.ButtonRole.DestructiveRole) + box.exec() + clicked = box.clickedButton() + if clicked == check_button: + self._resume_existing_ticket(billing_id) + elif clicked == wipe_button: + self._continue_multiple_flow() + + def _resume_existing_ticket(self, billing_id): + self.update_status.update_status("Checking existing ticket payment...") + self.custom_window.navigator.navigate("payment_details") + payment_page = self.custom_window.navigator.get_cached("payment_details") + if payment_page is not None: + payment_page.resume_ticket_billing(billing_id) + def toggle_button_state(self): text = self.text_edit.toPlainText() if text.strip(): diff --git a/gui/v2/ui/pages/payment_details_page.py b/gui/v2/ui/pages/payment_details_page.py index ffe9dca..20c383f 100755 --- a/gui/v2/ui/pages/payment_details_page.py +++ b/gui/v2/ui/pages/payment_details_page.py @@ -351,6 +351,16 @@ class PaymentDetailsPage(Page): self.ticket_poll_timer.start(3000) self.update_status.update_status('Awaiting ticket payment...') + def resume_ticket_billing(self, temp_billing_code): + self.ticketing = True + self.temp_billing_code = temp_billing_code + self.selected_currency = None + self.text_fields[0].setText(str(temp_billing_code or '')) + if self.ticket_poll_timer.isActive(): + self.ticket_poll_timer.stop() + self.ticket_poll_timer.start(3000) + self.update_status.update_status('Checking existing ticket payment...') + def _populate_ticket_fields(self, invoice): self.text_fields[0].setText(str(getattr(invoice, 'temp_billing_code', '') or '')) self.text_fields[1].setText(self.selected_duration) diff --git a/gui/v2/ui/pages/settings_page.py b/gui/v2/ui/pages/settings_page.py index a4af5e8..82b5a45 100755 --- a/gui/v2/ui/pages/settings_page.py +++ b/gui/v2/ui/pages/settings_page.py @@ -35,7 +35,6 @@ from core.errors.logger import logger as core_logger from gui.v2.actions import settings_data from gui.v2.actions.key_interpretation import interpret_key_results from gui.v2.actions.profile_order import normalize_profile_order -from gui.v2.infrastructure.page_registry import PAGE_REGISTRY from gui.v2.infrastructure.setup_observers import ticket_observer from gui.v2.ui.pages.Page import Page from gui.v2.ui.popups.confirmation_popup import ConfirmationPopup @@ -103,7 +102,6 @@ class Settings(Page): ("Create/Edit", self.show_registrations_page), ("Verification", self.show_verification_page), ("Legacy-Version", self.show_systemwide_page), - ("Bwrap Permission", self.show_bwrap_page), ("Delete Profile", self.show_delete_page), ("Error Logs", self.show_logs_page), ("Debug Help", self.show_debug_page) @@ -1176,9 +1174,8 @@ class Settings(Page): super().showEvent(event) self._settings_refresh_seq += 1 seq = self._settings_refresh_seq - module_path, class_name = PAGE_REGISTRY["settings"] worker = PageDataWorker( - "settings", module_path, class_name, self.custom_window) + "settings", settings_data.prepare, self.custom_window.gui_config_file) worker.data_ready.connect( lambda name, payload, s=seq: self._on_settings_data_ready(payload, s)) worker.failed.connect( @@ -1226,10 +1223,6 @@ class Settings(Page): self.systemwide_page = self.create_systemwide_page() self.content_layout.addWidget(self.systemwide_page) - self.content_layout.removeWidget(self.bwrap_page) - self.bwrap_page = self.create_bwrap_page() - self.content_layout.addWidget(self.bwrap_page) - self.content_layout.removeWidget(self.delete_page) self.delete_page = self.create_delete_page() self.content_layout.addWidget(self.delete_page) @@ -1443,7 +1436,6 @@ class Settings(Page): self.registrations_page = self.create_registrations_page() self.verification_page = self.create_verification_page() self.systemwide_page = self.create_systemwide_page() - self.bwrap_page = self.create_bwrap_page() self.logs_page = self.create_logs_page() self.delete_page = self.create_delete_page() self.debug_page = self.create_debug_page() @@ -1454,7 +1446,6 @@ class Settings(Page): self.content_layout.addWidget(self.registrations_page) self.content_layout.addWidget(self.verification_page) self.content_layout.addWidget(self.systemwide_page) - self.content_layout.addWidget(self.bwrap_page) self.content_layout.addWidget(self.logs_page) self.content_layout.addWidget(self.delete_page) self.content_layout.addWidget(self.debug_page) @@ -1508,11 +1499,6 @@ class Settings(Page): self.content_layout.setCurrentWidget(self.systemwide_page) self._select_menu_button("Legacy-Version") - def show_bwrap_page(self): - core_logger.info("User navigated to Settings -> Bwrap Permission") - self.content_layout.setCurrentWidget(self.bwrap_page) - self._select_menu_button("Bwrap Permission") - def show_logs_page(self): core_logger.info("User navigated to Settings -> Error Logs") self.content_layout.setCurrentWidget(self.logs_page) @@ -1995,59 +1981,6 @@ class Settings(Page): self._prepared_value("systemwide_enabled", settings_data.read_systemwide_enabled)) return page - def create_bwrap_page(self): - page = QWidget() - layout = QVBoxLayout(page) - layout.setSpacing(20) - layout.setContentsMargins(20, 20, 20, 20) - - title = QLabel("BWRAP PERMISSION") - title.setStyleSheet( - f"color: #808080; font-size: 12px; font-weight: bold; {self.font_style}") - layout.addWidget(title) - - description = QLabel( - "Control whether HydraVeil configures a capability policy so bwrap can be used without requiring additional permissions.") - 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.bwrap_status_value = QLabel("") - self.bwrap_status_value.setStyleSheet( - f"color: #e67e22; font-size: 14px; {self.font_style}") - status_layout.addWidget(status_label) - status_layout.addWidget(self.bwrap_status_value) - status_layout.addStretch() - layout.addLayout(status_layout) - - toggle_layout = QHBoxLayout() - self.bwrap_toggle = QCheckBox("Enable capability policy") - self.bwrap_toggle.setStyleSheet(self.get_checkbox_style()) - toggle_layout.addWidget(self.bwrap_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_bwrap_settings) - - button_layout = QHBoxLayout() - button_layout.addWidget(save_button) - button_layout.addStretch() - layout.addLayout(button_layout) - - layout.addStretch() - self.load_bwrap_settings( - self._prepared_value("bwrap_enabled", settings_data.read_bwrap_enabled)) - return page - def load_systemwide_settings(self, enabled=None): if enabled is None: enabled = settings_data.read_systemwide_enabled() @@ -2087,45 +2020,6 @@ class Settings(Page): self.systemwide_status_value.setStyleSheet( f"color: red; font-size: 14px; {self.font_style}") - def load_bwrap_settings(self, enabled=None): - if enabled is None: - enabled = settings_data.read_bwrap_enabled() - self.bwrap_toggle.setChecked(enabled) - if enabled: - self.bwrap_status_value.setText("Enabled") - self.bwrap_status_value.setStyleSheet( - f"color: #2ecc71; font-size: 14px; {self.font_style}") - else: - self.bwrap_status_value.setText("Disabled") - self.bwrap_status_value.setStyleSheet( - f"color: #e67e22; font-size: 14px; {self.font_style}") - - def save_bwrap_settings(self): - enable = self.bwrap_toggle.isChecked() - try: - capability_policy = PolicyController.get('capability') - if capability_policy is not None: - if enable: - PolicyController.instate(capability_policy) - else: - if PolicyController.is_instated(capability_policy): - PolicyController.revoke(capability_policy) - self.load_bwrap_settings() - self.update_status.update_status( - "Capability policy settings updated") - except CommandNotFoundError as e: - self.bwrap_status_value.setText(str(e)) - self.bwrap_status_value.setStyleSheet( - f"color: red; font-size: 14px; {self.font_style}") - except (PolicyAssignmentError, PolicyInstatementError, PolicyRevocationError) as e: - self.bwrap_status_value.setText(str(e)) - self.bwrap_status_value.setStyleSheet( - f"color: red; font-size: 14px; {self.font_style}") - except Exception: - self.bwrap_status_value.setText("Failed to update policy") - self.bwrap_status_value.setStyleSheet( - f"color: red; font-size: 14px; {self.font_style}") - def load_registrations_settings(self) -> None: try: config = self._prepared_value( diff --git a/gui/v2/ui/pages/ticket_crypto_picker_page.py b/gui/v2/ui/pages/ticket_crypto_picker_page.py index 60e798d..be110d6 100755 --- a/gui/v2/ui/pages/ticket_crypto_picker_page.py +++ b/gui/v2/ui/pages/ticket_crypto_picker_page.py @@ -9,6 +9,7 @@ from core.controllers.tickets.TicketPayController import check_if_paid from gui.v2.infrastructure.setup_observers import connection_observer, ticket_observer from gui.v2.ui.pages.Page import Page +from gui.v2.ui.popups.message_box import style_message_box, mark_confirm_button from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread @@ -77,7 +78,13 @@ class TicketCryptoPickerPage(Page): clipboard = QApplication.clipboard() clipboard.setText(temp_billing_code) not_paid_msg = f"The billing code is not yet showing paid for {temp_billing_code}. Right now, your clipboard has the billing code, to paste it in any text editor. If you did pay, either wait longer for blockchain confirmation, or contact customer support with the code in your clipboard now." - QMessageBox.information(None, "Not Paid", not_paid_msg) + info = QMessageBox(self) + info.setWindowTitle("Not Paid") + info.setText(not_paid_msg) + info.setStandardButtons(QMessageBox.StandardButton.Ok) + style_message_box(info) + mark_confirm_button(info.button(QMessageBox.StandardButton.Ok)) + info.exec() def start_initiate_payment(self, currency): self.update_status.update_status("Initiating payment...") @@ -121,6 +128,8 @@ class TicketCryptoPickerPage(Page): msg.setWindowTitle("Existing tickets found") msg.setText("You already have ticket data. Wipe it and start over?") msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) + style_message_box(msg) + mark_confirm_button(msg.button(QMessageBox.StandardButton.Yes)) result = msg.exec() if result == QMessageBox.StandardButton.Yes: self.bypass_existing = True @@ -135,6 +144,8 @@ class TicketCryptoPickerPage(Page): msg.setWindowTitle("Existing billing code found") msg.setText("You already have a ticket billing code. Do you want to use it? Only hit YES if you ALREADY paid.") msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) + style_message_box(msg) + mark_confirm_button(msg.button(QMessageBox.StandardButton.Yes)) result = msg.exec() if result == QMessageBox.StandardButton.Yes: self.update_status.update_status("Reusing same code") diff --git a/gui/v2/ui/popups/message_box.py b/gui/v2/ui/popups/message_box.py new file mode 100755 index 0000000..6798785 --- /dev/null +++ b/gui/v2/ui/popups/message_box.py @@ -0,0 +1,18 @@ +MESSAGE_BOX_QSS = ( + "QMessageBox { background-color: white; }" + "QMessageBox QLabel { color: black; }" + "QMessageBox QPushButton { color: black; padding: 5px 14px; }" +) + +CONFIRM_BUTTON_QSS = "color: #2e7d32; font-weight: bold; padding: 5px 14px;" + + +def style_message_box(box): + box.setStyleSheet(MESSAGE_BOX_QSS) + + +def mark_confirm_button(button): + if button is None: + return + button.setText(f"✓ {button.text()}") + button.setStyleSheet(CONFIRM_BUTTON_QSS) diff --git a/gui/v2/workers/page_data_worker.py b/gui/v2/workers/page_data_worker.py index 630341e..378ab4f 100755 --- a/gui/v2/workers/page_data_worker.py +++ b/gui/v2/workers/page_data_worker.py @@ -1,5 +1,3 @@ -import importlib - from PyQt6.QtCore import QThread, pyqtSignal @@ -7,18 +5,15 @@ class PageDataWorker(QThread): data_ready = pyqtSignal(str, object) failed = pyqtSignal(str, str) - def __init__(self, name, module_path, class_name, custom_window): + def __init__(self, name, fn, *args): super().__init__() self.name = name - self.module_path = module_path - self.class_name = class_name - self.custom_window = custom_window + self.fn = fn + self.args = args def run(self): try: - module = importlib.import_module(self.module_path) - cls = getattr(module, self.class_name) - payload = cls.prepare_data(self.custom_window) + payload = self.fn(*self.args) self.data_ready.emit(self.name, payload) except Exception as error: self.failed.emit(self.name, f"{type(error).__name__}: {error}")