forked from Support/sp-hydra-veil-gui
Compare commits
20 commits
john-botto
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e026c5287 | ||
|
|
3483b51200 | ||
| b444ff9218 | |||
| 1633562325 | |||
| b8d8689885 | |||
| 75bd51b8f8 | |||
|
|
ab23fc958e | ||
| d52ca4842c | |||
|
|
cfd9a92a24 | ||
| c3cf7e241e | |||
| 9cacab4d71 | |||
| 95e0f676df | |||
| baf7f7cec1 | |||
| 0a6280276f | |||
| 25489bf203 | |||
| e5c83e72b1 | |||
| 59cac90c58 | |||
| b536204048 | |||
| f8a09858cc | |||
| 50e9469178 |
24 changed files with 890 additions and 277 deletions
97
gui/__main__.py
Executable file → Normal file
97
gui/__main__.py
Executable file → Normal file
|
|
@ -1,14 +1,45 @@
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
from core.models.manage.session_management import init_session, get_session, close_session, create_ALL_tables, create_ONLY_db_version_table, get_path, does_it_exist, does_db_version_table_exist
|
from core.models.manage.session_management import init_session, get_session, close_session, create_ALL_tables, create_ONLY_db_version_table, get_path, does_it_exist, does_db_version_table_exist
|
||||||
from core.models.manage.version_check import check_database_compatibility, insert_new_version, get_custom_message
|
from core.models.manage.version_check import check_database_compatibility, insert_new_version, get_custom_message
|
||||||
|
from core.services.helpers.manage_assets import assets_folder_setup
|
||||||
from core.Constants import Constants
|
from core.Constants import Constants
|
||||||
|
from core.models.DatabaseOperation import DatabaseOperation, DBErrorType
|
||||||
|
from core.models.manage.migrations import migrate_sql
|
||||||
|
from core.models.manage.clear_sql_model import clear_sql_model
|
||||||
|
|
||||||
# generic
|
# generic
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# DISPLAY CHOICES AND RECOVERY UI
|
# UTIL FUNCTIONS AND RECOVERY UI
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
def clear_sync_cache():
|
||||||
|
"""
|
||||||
|
Purpose:
|
||||||
|
Clear CachedSync
|
||||||
|
If that fails, prompt for a database wipe.
|
||||||
|
|
||||||
|
Why:
|
||||||
|
If they migrated, they have new schemas, but old data.
|
||||||
|
The problem with that, is the newest CachedSync entry will fool the sync data functions into thinking they don't need to sync new data,
|
||||||
|
Even though they do need to sync with the new schema.
|
||||||
|
"""
|
||||||
|
from core.models.orm_models.CachedSync import CachedSync
|
||||||
|
result = clear_sql_model(CachedSync)
|
||||||
|
if result:
|
||||||
|
logger.info(f"[DB MANAGEMENT] After Migration cleared the CachedSync table: {result}.")
|
||||||
|
else:
|
||||||
|
custom_error = "There were issues with clearing the Sync Cache in your database. Please delete the old version and fetch the new public data (what locations, browsers, ect) This will NOT affect your profiles or browser sessions."
|
||||||
|
recovery_dialog(custom_error, "CachedSync Issue")
|
||||||
|
|
||||||
|
def failed_migration(db_version_you_have):
|
||||||
|
close_session() # shut down db connection
|
||||||
|
custom_error = "There were issues with migrating your database. We transitioned to a new Database format for new features! Please delete the old version and fetch the new public data (what locations, browsers, ect) This will NOT affect your profiles or browser sessions."
|
||||||
|
recovery_dialog(custom_error, db_version_you_have)
|
||||||
|
|
||||||
def recovery_dialog(custom_error, db_version_you_have):
|
def recovery_dialog(custom_error, db_version_you_have):
|
||||||
"""This ends the app by forcing them to delete the database, move it, or just quit."""
|
"""This ends the app by forcing them to delete the database, move it, or just quit."""
|
||||||
|
|
||||||
|
|
@ -19,13 +50,14 @@ def recovery_dialog(custom_error, db_version_you_have):
|
||||||
if choice == 1:
|
if choice == 1:
|
||||||
if os.path.exists(database_path):
|
if os.path.exists(database_path):
|
||||||
os.remove(database_path)
|
os.remove(database_path)
|
||||||
logger.info(f"[DB MANAGEMENT] Deleted the DB file at {database_path}")
|
logger.info(f"[DB MANAGEMENT] Deleted the DB file at {database_path}. Now putting lock on for forced sync..")
|
||||||
|
lock_file.touch()
|
||||||
logger.info(f"[DB MANAGEMENT] Closing the app gracefully, for them to reboot..")
|
logger.info(f"[DB MANAGEMENT] Closing the app gracefully, for them to reboot..")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
elif choice == 2:
|
elif choice == 2:
|
||||||
logger.info(f"[DB MANAGEMENT] User opted to move the DB file.")
|
logger.info(f"[DB MANAGEMENT] User opted to move the DB file.")
|
||||||
from v2.ui.popups.pick_folder_to_move import launch_file_picker
|
from gui.v2.ui.popups.pick_folder_to_move import launch_file_picker
|
||||||
launch_file_picker(database_path)
|
launch_file_picker(database_path)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
@ -33,9 +65,21 @@ def recovery_dialog(custom_error, db_version_you_have):
|
||||||
logger.error(f"[DB MANAGEMENT] User opted to close the app WITHOUT wiping the database, even though they need to.")
|
logger.error(f"[DB MANAGEMENT] User opted to close the app WITHOUT wiping the database, even though they need to.")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# ASSETS FOLDER
|
||||||
|
# ============================================================================
|
||||||
|
logger.info("Welcome, checking assets..")
|
||||||
|
if not assets_folder_setup():
|
||||||
|
from gui.v2.ui.popups.generic_error_popup import show_error
|
||||||
|
custom_error = "Unable to Setup your Assets Folder. Please create an assets directory at ~/.local/share/hydra-veil/assets"
|
||||||
|
show_error(custom_error, "Critical Error")
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# INITIALIZE DATABASE
|
# INITIALIZE DATABASE
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
lock_file = Path(f"{Constants.HV_DATA_HOME}/deleted_db.lock")
|
||||||
|
logger.info("[DB MANAGEMENT] Starting DB init..")
|
||||||
|
try:
|
||||||
# Does the database exist?
|
# Does the database exist?
|
||||||
system_path = get_path()
|
system_path = get_path()
|
||||||
database_path = system_path / "storage.db"
|
database_path = system_path / "storage.db"
|
||||||
|
|
@ -47,20 +91,30 @@ session = get_session()
|
||||||
|
|
||||||
# does the version checker table exist?
|
# does the version checker table exist?
|
||||||
version_table_exists = does_db_version_table_exist()
|
version_table_exists = does_db_version_table_exist()
|
||||||
|
except:
|
||||||
|
logger.error("Critical Error with database initialization!")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
# are they upgrading from a legacy version?
|
# ============================================================================
|
||||||
# that would mean the table existed, but not the version table,
|
# LEGACY DATABASE CHECKS
|
||||||
|
# ============================================================================
|
||||||
|
migration_happened = False
|
||||||
|
|
||||||
|
# are they upgrading from a legacy version? that would mean the table existed, but not the version table,
|
||||||
if not version_table_exists and main_db_exists:
|
if not version_table_exists and main_db_exists:
|
||||||
logger.info(f"[DB MANAGEMENT] We are dealing with a legacy database, we need to transition the user.")
|
logger.info(f"[DB MANAGEMENT] We are dealing with a legacy database, we need to transition the user.")
|
||||||
|
|
||||||
# shut down db connection
|
migration = migrate_sql()
|
||||||
close_session()
|
|
||||||
|
|
||||||
|
logger.info(f"[DB MANAGEMENT] Migration result is {migration.valid}.")
|
||||||
|
if migration.valid:
|
||||||
|
migration_happened = True
|
||||||
|
force_sync = True
|
||||||
|
clear_sync_cache()
|
||||||
|
else:
|
||||||
# THEN DISPLAY CHOICES AND RECOVERY UI
|
# THEN DISPLAY CHOICES AND RECOVERY UI
|
||||||
custom_error = "You're using a Legacy version of the Database scheme. Please delete it and fetch the new data."
|
|
||||||
db_version_you_have = "Old System"
|
db_version_you_have = "Old System"
|
||||||
recovery_dialog(custom_error, db_version_you_have)
|
failed_migration(db_version_you_have)
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
# If they're still here, then if it's NOT a legacy version,
|
# If they're still here, then if it's NOT a legacy version,
|
||||||
|
|
||||||
|
|
@ -80,18 +134,27 @@ logger.info(f"[DB MANAGEMENT] The result of the check is {is_compatable} and the
|
||||||
# IF THE VERSIONS MATCH
|
# IF THE VERSIONS MATCH
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
if is_compatable:
|
if is_compatable:
|
||||||
|
try:
|
||||||
made_tables = create_ALL_tables()
|
made_tables = create_ALL_tables()
|
||||||
|
|
||||||
# SCREEN FAILED TABLES
|
# SCREEN FAILED TABLES
|
||||||
if not made_tables:
|
if not made_tables:
|
||||||
custom_error = "Critical Failure with starting the models of the database."
|
custom_error = "Critical Failure with starting the models of the database."
|
||||||
logger.error(f"[DB MANAGEMENT] {custom_error}")
|
logger.error(f"[DB MANAGEMENT] {custom_error}")
|
||||||
close_session()
|
close_session()
|
||||||
from v2.ui.popups.Database_version import show_recovery_dialog
|
from gui.v2.ui.popups.Database_version import show_recovery_dialog
|
||||||
choice = show_recovery_dialog({"message": custom_error, "db_version": 0, "status": "cant_make"})
|
choice = show_recovery_dialog({"message": custom_error, "db_version": 0, "status": "cant_make"})
|
||||||
logger.info(f"[DB MANAGEMENT] From the database error options, the user picked {choice}")
|
logger.info(f"[DB MANAGEMENT] From the database error options, the user picked {choice}")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
except:
|
||||||
|
logger.info(f"[DB MANAGEMENT] create ALL tables failed. Running migrations...")
|
||||||
|
migration = migrate_sql()
|
||||||
|
logger.info(f"[DB MANAGEMENT] Results of migration is {migration.valid}")
|
||||||
|
|
||||||
|
if not migration.valid:
|
||||||
|
db_version_you_have = 0
|
||||||
|
failed_migration(db_version_you_have)
|
||||||
|
|
||||||
# ASSUME TABLES CREATED
|
# ASSUME TABLES CREATED
|
||||||
logger.info("[DB MANAGEMENT] Tables successfully made or initialized if pre-existing")
|
logger.info("[DB MANAGEMENT] Tables successfully made or initialized if pre-existing")
|
||||||
|
|
||||||
|
|
@ -109,9 +172,19 @@ if is_compatable:
|
||||||
force_sync = True
|
force_sync = True
|
||||||
else:
|
else:
|
||||||
logger.info(f"[DB MANAGEMENT] Launcher is now passing it off to launch the main GUI window WITHOUT force sync..")
|
logger.info(f"[DB MANAGEMENT] Launcher is now passing it off to launch the main GUI window WITHOUT force sync..")
|
||||||
|
if migration_happened:
|
||||||
|
force_sync = True
|
||||||
|
clear_sync_cache()
|
||||||
|
else:
|
||||||
force_sync = False
|
force_sync = False
|
||||||
|
|
||||||
|
# if we deleted their DB, we want to force sync,
|
||||||
|
if lock_file.exists():
|
||||||
|
force_sync = True
|
||||||
|
lock_file.unlink() # unlock for next time
|
||||||
|
|
||||||
# Start GUI either way:
|
# Start GUI either way:
|
||||||
|
logger.info(f"[DB MANAGEMENT] Starting GUI..")
|
||||||
start_ui(force_sync)
|
start_ui(force_sync)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
|
||||||
Binary file not shown.
54
gui/assets/yaml_mappings/locations.yaml
Normal file
54
gui/assets/yaml_mappings/locations.yaml
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
fields:
|
||||||
|
- name: country_code
|
||||||
|
path: ['country', 'code']
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- name: code # this is city code
|
||||||
|
path: ['code']
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- name: id
|
||||||
|
path: ['id']
|
||||||
|
|
||||||
|
- name: country_name
|
||||||
|
path: ['country', 'name']
|
||||||
|
|
||||||
|
- name: name # this is CITY name
|
||||||
|
path: ['name']
|
||||||
|
|
||||||
|
- name: time_zone
|
||||||
|
path: ['time_zone', 'code']
|
||||||
|
|
||||||
|
- name: operator_id
|
||||||
|
path: ['operator_id']
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- name: provider_name
|
||||||
|
path: ['provider', 'name']
|
||||||
|
|
||||||
|
- name: is_proxy_capable
|
||||||
|
path: ['is_proxy_capable']
|
||||||
|
|
||||||
|
- name: is_wireguard_capable
|
||||||
|
path: ['is_wireguard_capable']
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- name: is_hysteria2_capable
|
||||||
|
path: ['is_hysteria2_capable']
|
||||||
|
|
||||||
|
- name: is_vless_capable
|
||||||
|
path: ['is_vless_capable']
|
||||||
|
|
||||||
|
|
||||||
|
# original version:
|
||||||
|
# locations.append((
|
||||||
|
# location['country']['code'],
|
||||||
|
# location['code'],
|
||||||
|
# location['id'],
|
||||||
|
# location['country']['name'],
|
||||||
|
# location['name'],
|
||||||
|
# location['time_zone']['code'],
|
||||||
|
# location['operator_id'],
|
||||||
|
# location['provider']['name'],
|
||||||
|
# location['is_proxy_capable'],
|
||||||
|
# location['is_wireguard_capable']))
|
||||||
8
gui/assets/yaml_mappings/mapping_one.yaml
Normal file
8
gui/assets/yaml_mappings/mapping_one.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
fields:
|
||||||
|
- name: group_a_code
|
||||||
|
path: ['group_a', 'code']
|
||||||
|
- name: code
|
||||||
|
path: ['code']
|
||||||
|
- name: id
|
||||||
|
path: ['id']
|
||||||
|
required: true
|
||||||
19
gui/assets/yaml_mappings/operators.yaml
Normal file
19
gui/assets/yaml_mappings/operators.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
fields:
|
||||||
|
- name: id
|
||||||
|
path: ['id']
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- name: name
|
||||||
|
path: ['name']
|
||||||
|
|
||||||
|
- name: public_key # this is ed25519
|
||||||
|
path: ['public_key']
|
||||||
|
|
||||||
|
- name: nostr_public_key
|
||||||
|
path: ['nostr_public_key']
|
||||||
|
|
||||||
|
- name: nostr_profile_reference
|
||||||
|
path: ['nostr_profile_reference']
|
||||||
|
|
||||||
|
- name: nostr_attestation_event_reference
|
||||||
|
path: ['nostr_attestation', 'event_reference']
|
||||||
118
gui/main_ui.py
118
gui/main_ui.py
|
|
@ -12,17 +12,21 @@ from PyQt6.QtWidgets import (
|
||||||
QApplication, QMainWindow, QStackedWidget, QLabel, QPushButton,
|
QApplication, QMainWindow, QStackedWidget, QLabel, QPushButton,
|
||||||
QDialog, QVBoxLayout, QHBoxLayout, QMessageBox
|
QDialog, QVBoxLayout, QHBoxLayout, QMessageBox
|
||||||
)
|
)
|
||||||
from PyQt6.QtGui import QPixmap, QFont, QFontDatabase, QIcon
|
from PyQt6.QtGui import QPixmap, QFont, QFontDatabase
|
||||||
from PyQt6 import QtGui
|
from PyQt6 import QtGui
|
||||||
from PyQt6.QtCore import Qt, QTimer, QEvent
|
from PyQt6.QtCore import Qt, QTimer, QEvent
|
||||||
|
|
||||||
from core.Constants import Constants
|
from core.Constants import Constants
|
||||||
from core.controllers.ConfigurationController import ConfigurationController
|
from core.controllers.ConfigurationController import ConfigurationController
|
||||||
|
from core.controllers.ProfileController import ProfileController
|
||||||
from core.Errors import UnknownConnectionTypeError
|
from core.Errors import UnknownConnectionTypeError
|
||||||
from core.errors.logger import logger as core_logger
|
from core.errors.logger import logger as core_logger
|
||||||
|
|
||||||
core_logger.propagate = False
|
core_logger.propagate = False
|
||||||
|
|
||||||
|
from gui.v2.infrastructure.ThreadSafetyTool import ThreadSafetyTool
|
||||||
|
mainthread = ThreadSafetyTool()
|
||||||
|
|
||||||
from gui.v2.infrastructure import orm
|
from gui.v2.infrastructure import orm
|
||||||
from gui.v2.infrastructure.navigator import Navigator
|
from gui.v2.infrastructure.navigator import Navigator
|
||||||
from gui.v2.infrastructure.setup_observers import setup_observers
|
from gui.v2.infrastructure.setup_observers import setup_observers
|
||||||
|
|
@ -61,7 +65,6 @@ from gui.v2.actions.ticket_failure import (
|
||||||
clear_ticket_verification_failure,
|
clear_ticket_verification_failure,
|
||||||
)
|
)
|
||||||
from gui.v2.actions.should_be_synchronized import should_be_synchronized
|
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):
|
class CustomWindow(QMainWindow):
|
||||||
|
|
@ -73,9 +76,6 @@ class CustomWindow(QMainWindow):
|
||||||
gui_dir = os.path.dirname(os.path.abspath(__file__))
|
gui_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
font_path = os.path.join(gui_dir, 'resources', 'fonts')
|
font_path = os.path.join(gui_dir, 'resources', 'fonts')
|
||||||
|
|
||||||
# # Get's SQLAlchemy going,
|
|
||||||
# orm.start()
|
|
||||||
|
|
||||||
retro_gaming_path = os.path.join(font_path, 'retro-gaming.ttf')
|
retro_gaming_path = os.path.join(font_path, 'retro-gaming.ttf')
|
||||||
font_id = QFontDatabase.addApplicationFont(retro_gaming_path)
|
font_id = QFontDatabase.addApplicationFont(retro_gaming_path)
|
||||||
font_family = QFontDatabase.applicationFontFamilies(font_id)[0]
|
font_family = QFontDatabase.applicationFontFamilies(font_id)[0]
|
||||||
|
|
@ -126,6 +126,9 @@ class CustomWindow(QMainWindow):
|
||||||
self.animation_step = 0
|
self.animation_step = 0
|
||||||
self.page_history = []
|
self.page_history = []
|
||||||
self.log_path = None
|
self.log_path = None
|
||||||
|
self._close_confirmation_pending = False
|
||||||
|
self._close_disconnect_in_progress = False
|
||||||
|
self._closing_after_disconnect = False
|
||||||
|
|
||||||
self.setFixedSize(800, 570)
|
self.setFixedSize(800, 570)
|
||||||
|
|
||||||
|
|
@ -625,18 +628,95 @@ class CustomWindow(QMainWindow):
|
||||||
def closeEvent(self, event=None):
|
def closeEvent(self, event=None):
|
||||||
core_logger.info("HydraVeil application closing")
|
core_logger.info("HydraVeil application closing")
|
||||||
|
|
||||||
# Close SQLAlchemy,
|
if self._closing_after_disconnect:
|
||||||
orm.stop()
|
orm.stop()
|
||||||
|
if event is not None:
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
|
|
||||||
connected_profiles = self.connection_manager.get_connected_profiles()
|
if self._close_confirmation_pending or self._close_disconnect_in_progress:
|
||||||
|
if event is not None:
|
||||||
|
event.ignore()
|
||||||
|
return
|
||||||
|
|
||||||
|
connected_profiles = self._get_enabled_profile_ids()
|
||||||
if connected_profiles:
|
if connected_profiles:
|
||||||
self.update_status('Profiles are still connected (disconnect flow lands in Chunk 9).')
|
if event is not None:
|
||||||
if event is not None:
|
event.ignore()
|
||||||
event.accept()
|
self._show_close_disconnect_confirmation(connected_profiles)
|
||||||
else:
|
return
|
||||||
|
|
||||||
|
orm.stop()
|
||||||
if event is not None:
|
if event is not None:
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
def _get_enabled_profile_ids(self):
|
||||||
|
try:
|
||||||
|
profiles = ProfileController.get_all()
|
||||||
|
except Exception:
|
||||||
|
return self.connection_manager.get_connected_profiles()
|
||||||
|
|
||||||
|
connected_profiles = []
|
||||||
|
self.connection_manager._connected_profiles.clear()
|
||||||
|
for profile_id, profile in profiles.items():
|
||||||
|
try:
|
||||||
|
if ProfileController.is_enabled(profile):
|
||||||
|
connected_profiles.append(profile_id)
|
||||||
|
self.connection_manager.add_connected_profile(profile_id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return connected_profiles
|
||||||
|
|
||||||
|
def _show_close_disconnect_confirmation(self, connected_profiles):
|
||||||
|
from gui.v2.ui.popups.confirmation_popup import ConfirmationPopup
|
||||||
|
|
||||||
|
self._close_confirmation_pending = True
|
||||||
|
message = self._create_close_disconnect_message(connected_profiles)
|
||||||
|
self.popup = ConfirmationPopup(
|
||||||
|
self, message=message, action_button_text="Exit", cancel_button_text="Cancel")
|
||||||
|
self.popup.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||||
|
self.popup.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
||||||
|
self.popup.finished.connect(
|
||||||
|
lambda result: self._handle_close_disconnect_result(result, connected_profiles))
|
||||||
|
self.popup.destroyed.connect(
|
||||||
|
lambda *_: self._handle_close_disconnect_popup_closed())
|
||||||
|
self.popup.show()
|
||||||
|
|
||||||
|
def _create_close_disconnect_message(self, connected_profiles):
|
||||||
|
profile_numbers = ', '.join(str(profile_id)
|
||||||
|
for profile_id in connected_profiles)
|
||||||
|
is_are = "is" if len(connected_profiles) == 1 else "are"
|
||||||
|
return f'Profile{"" if len(connected_profiles) == 1 else "s"} {profile_numbers} {is_are} still connected.\nAll connected profiles will be disconnected on exit.\nDo you want to proceed?'
|
||||||
|
|
||||||
|
def _handle_close_disconnect_result(self, result, connected_profiles):
|
||||||
|
self._close_confirmation_pending = False
|
||||||
|
if not result:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._close_disconnect_in_progress = True
|
||||||
|
self.update_status('Disabling profiles...')
|
||||||
|
self.worker_thread = WorkerThread(
|
||||||
|
'DISABLE_ALL_PROFILES', profile_data=connected_profiles)
|
||||||
|
self.worker_thread.text_output.connect(self.update_status)
|
||||||
|
self.worker_thread.finished.connect(self._finish_close_disconnect)
|
||||||
|
self.worker_thread.start()
|
||||||
|
|
||||||
|
def _handle_close_disconnect_popup_closed(self):
|
||||||
|
if self._close_confirmation_pending:
|
||||||
|
self._close_confirmation_pending = False
|
||||||
|
self.popup = None
|
||||||
|
|
||||||
|
def _finish_close_disconnect(self, _ok=True):
|
||||||
|
self._close_disconnect_in_progress = False
|
||||||
|
connected_profiles = self._get_enabled_profile_ids()
|
||||||
|
if connected_profiles:
|
||||||
|
self.update_status('Could not disconnect all profiles. Exit canceled.')
|
||||||
|
return
|
||||||
|
|
||||||
|
self.update_status('All profiles disabled. Bye!')
|
||||||
|
self._closing_after_disconnect = True
|
||||||
|
QTimer.singleShot(0, self.close)
|
||||||
|
|
||||||
def update_image(self, appearance_value="original"):
|
def update_image(self, appearance_value="original"):
|
||||||
image_path = os.path.join(self.btn_path, f"{appearance_value}.png")
|
image_path = os.path.join(self.btn_path, f"{appearance_value}.png")
|
||||||
pixmap = QPixmap(image_path)
|
pixmap = QPixmap(image_path)
|
||||||
|
|
@ -656,7 +736,21 @@ class CustomWindow(QMainWindow):
|
||||||
self.status_label.setStyleSheet(
|
self.status_label.setStyleSheet(
|
||||||
f"color: rgb(0, 255, 255); font-size: {font_size}px;")
|
f"color: rgb(0, 255, 255); font-size: {font_size}px;")
|
||||||
|
|
||||||
|
@mainthread # wrapper of ThreadSafetyTool
|
||||||
def update_status(self, text, clear=False):
|
def update_status(self, text, clear=False):
|
||||||
|
"""
|
||||||
|
Purpose:
|
||||||
|
Update the bottom left status bar
|
||||||
|
|
||||||
|
Features:
|
||||||
|
Forced on the main Thread by ThreadSafetyTool
|
||||||
|
|
||||||
|
Depends on:
|
||||||
|
v2.infrastructure.ThreadSafetyTool
|
||||||
|
|
||||||
|
Called by:
|
||||||
|
v2.infrastructure.setup_observers (outside this class)
|
||||||
|
"""
|
||||||
if text is None:
|
if text is None:
|
||||||
self._set_status_font_size(16)
|
self._set_status_font_size(16)
|
||||||
self.status_label.setText('Status:')
|
self.status_label.setText('Status:')
|
||||||
|
|
@ -799,5 +893,5 @@ class CustomWindow(QMainWindow):
|
||||||
|
|
||||||
def start_ui(force_sync):
|
def start_ui(force_sync):
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
window = CustomWindow(force_sync)
|
window = CustomWindow(force_sync=force_sync)
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
|
|
|
||||||
BIN
gui/resources/images/networking_shield.png
Executable file
BIN
gui/resources/images/networking_shield.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -5,12 +5,23 @@ from core.Constants import Constants
|
||||||
from core.controllers.ConfigurationController import ConfigurationController
|
from core.controllers.ConfigurationController import ConfigurationController
|
||||||
from core.controllers.PolicyController import PolicyController
|
from core.controllers.PolicyController import PolicyController
|
||||||
from core.controllers.ProfileController import ProfileController
|
from core.controllers.ProfileController import ProfileController
|
||||||
|
from core.services.networking.systemwide.systemwide_utils import get_firewall_setting
|
||||||
|
|
||||||
from gui.v2.actions.config import load_gui_config
|
from gui.v2.actions.config import load_gui_config
|
||||||
from gui.v2.actions.profile_order import normalize_profile_order
|
from gui.v2.actions.profile_order import normalize_profile_order
|
||||||
from gui.v2.actions.ticket_failure import get_ticket_verification_failure
|
from gui.v2.actions.ticket_failure import get_ticket_verification_failure
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def read_firewall_setting():
|
||||||
|
try:
|
||||||
|
firewall_setting = get_firewall_setting()
|
||||||
|
return firewall_setting
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def read_systemwide_enabled():
|
def read_systemwide_enabled():
|
||||||
try:
|
try:
|
||||||
privilege_policy = PolicyController.get('privilege')
|
privilege_policy = PolicyController.get('privilege')
|
||||||
|
|
@ -45,6 +56,7 @@ def prepare(gui_config_file):
|
||||||
"endpoint_verification_enabled": endpoint_verification_enabled,
|
"endpoint_verification_enabled": endpoint_verification_enabled,
|
||||||
"systemwide_enabled": read_systemwide_enabled(),
|
"systemwide_enabled": read_systemwide_enabled(),
|
||||||
"bwrap_enabled": read_bwrap_enabled(),
|
"bwrap_enabled": read_bwrap_enabled(),
|
||||||
|
"firewall_setting": read_firewall_setting(),
|
||||||
"gui_config": load_gui_config(gui_config_file),
|
"gui_config": load_gui_config(gui_config_file),
|
||||||
"ticket_failure": get_ticket_verification_failure(gui_config_file),
|
"ticket_failure": get_ticket_verification_failure(gui_config_file),
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +70,7 @@ def empty_payload():
|
||||||
"endpoint_verification_enabled": False,
|
"endpoint_verification_enabled": False,
|
||||||
"systemwide_enabled": False,
|
"systemwide_enabled": False,
|
||||||
"bwrap_enabled": False,
|
"bwrap_enabled": False,
|
||||||
|
"firewall_setting": False,
|
||||||
"gui_config": None,
|
"gui_config": None,
|
||||||
"ticket_failure": None,
|
"ticket_failure": None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
def is_valid_sync_payload(available_locations, available_browsers, status, locations, all_browsers):
|
|
||||||
if status is not True:
|
|
||||||
return False
|
|
||||||
if isinstance(all_browsers, bool):
|
|
||||||
return False
|
|
||||||
if not available_locations or not available_browsers:
|
|
||||||
return False
|
|
||||||
if not locations or not all_browsers:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
21
gui/v2/infrastructure/ThreadSafetyTool.py
Executable file
21
gui/v2/infrastructure/ThreadSafetyTool.py
Executable file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QLabel
|
||||||
|
from PyQt6.QtCore import QObject, pyqtSignal, Qt
|
||||||
|
|
||||||
|
class ThreadSafetyTool(QObject):
|
||||||
|
"""Marshal function calls to the main thread."""
|
||||||
|
_signal = pyqtSignal(object, tuple, dict)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._signal.connect(self._execute, Qt.ConnectionType.QueuedConnection)
|
||||||
|
|
||||||
|
def _execute(self, func, args, kwargs):
|
||||||
|
func(*args, **kwargs)
|
||||||
|
|
||||||
|
def __call__(self, func):
|
||||||
|
"""Decorator that forces a function to run on the main thread."""
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
self._signal.emit(func, args, kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
@ -13,6 +13,7 @@ from gui.v2.infrastructure.page_registry import (
|
||||||
)
|
)
|
||||||
from gui.v2.workers.page_data_worker import PageDataWorker
|
from gui.v2.workers.page_data_worker import PageDataWorker
|
||||||
from gui.v2.actions.flags import is_fast_registration_enabled
|
from gui.v2.actions.flags import is_fast_registration_enabled
|
||||||
|
from core.errors.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Navigator(QObject):
|
class Navigator(QObject):
|
||||||
|
|
@ -79,26 +80,25 @@ class Navigator(QObject):
|
||||||
|
|
||||||
def _preload_one(self, name):
|
def _preload_one(self, name):
|
||||||
if name in self._instances:
|
if name in self._instances:
|
||||||
print(f"[preload] {name:30s} SKIP (already cached)", flush=True)
|
logger.debug(f"[preload] {name:30s} SKIP (already cached)")
|
||||||
return False
|
return False
|
||||||
if name not in PAGE_REGISTRY:
|
if name not in PAGE_REGISTRY:
|
||||||
print(f"[preload] {name:30s} SKIP (not in registry)", flush=True)
|
logger.debug(f"[preload] {name:30s} SKIP (not in registry)")
|
||||||
return False
|
return False
|
||||||
module_path, class_name = PAGE_REGISTRY[name]
|
module_path, class_name = PAGE_REGISTRY[name]
|
||||||
print(f"[preload] {name:30s} LOAD {module_path}.{class_name}", flush=True)
|
logger.debug(f"[preload] {name:30s} LOAD {module_path}.{class_name}")
|
||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
try:
|
try:
|
||||||
page = self._instantiate(name)
|
page = self._instantiate(name)
|
||||||
self.register_instance(name, page)
|
self.register_instance(name, page)
|
||||||
dt_ms = (time.perf_counter() - t0) * 1000.0
|
dt_ms = (time.perf_counter() - t0) * 1000.0
|
||||||
print(f"[preload] {name:30s} OK ({dt_ms:6.1f} ms, "
|
logger.debug(f"[preload] {name:30s} OK ({dt_ms:6.1f} ms, "
|
||||||
f"cached={len(self._instances)}, stack_size={self.page_stack.count()})",
|
f"cached={len(self._instances)}, stack_size={self.page_stack.count()})")
|
||||||
flush=True)
|
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
dt_ms = (time.perf_counter() - t0) * 1000.0
|
dt_ms = (time.perf_counter() - t0) * 1000.0
|
||||||
print(f"[preload] {name:30s} FAIL ({dt_ms:6.1f} ms) "
|
logger.debug(f"[preload] {name:30s} FAIL ({dt_ms:6.1f} ms) "
|
||||||
f"{type(e).__name__}: {e}", flush=True)
|
f"{type(e).__name__}: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
from core.errors.logger import logger as core_logger
|
from core.errors.logger import logger as core_logger
|
||||||
core_logger.warning(f"Background preload failed for '{name}': "
|
core_logger.warning(f"Background preload failed for '{name}': "
|
||||||
|
|
@ -121,29 +121,29 @@ class Navigator(QObject):
|
||||||
self._preload_inflight = 0
|
self._preload_inflight = 0
|
||||||
self._preload_summary_emitted = False
|
self._preload_summary_emitted = False
|
||||||
self._preload_t_start = time.perf_counter()
|
self._preload_t_start = time.perf_counter()
|
||||||
print(f"[preload] ============================================================", flush=True)
|
logger.debug(f"[preload] ============================================================")
|
||||||
print(f"[preload] starting background preload: {self._preload_total} pages queued", flush=True)
|
logger.debug(f"[preload] starting background preload: {self._preload_total} pages queued")
|
||||||
print(f"[preload] order : {', '.join(self._preload_queue)}", flush=True)
|
logger.debug(f"[preload] order : {', '.join(self._preload_queue)}")
|
||||||
print(f"[preload] skipped : {', '.join(sorted(PRELOAD_SKIP))} (unsafe init)", flush=True)
|
logger.debug(f"[preload] skipped : {', '.join(sorted(PRELOAD_SKIP))} (unsafe init)")
|
||||||
if fast_mode:
|
if fast_mode:
|
||||||
print(f"[preload] fastmode: ON -> skipping regular-flow pages: "
|
logger.debug(f"[preload] fastmode: ON -> skipping regular-flow pages: "
|
||||||
f"{', '.join(skipped_regular) if skipped_regular else '(none)'}", flush=True)
|
f"{', '.join(skipped_regular) if skipped_regular else '(none)'}")
|
||||||
print(f"[preload] gap : 250 ms between loads, 700 ms initial delay", flush=True)
|
logger.debug(f"[preload] gap : 250 ms between loads, 700 ms initial delay")
|
||||||
print(f"[preload] ============================================================", flush=True)
|
logger.debug(f"[preload] ============================================================")
|
||||||
self._preload_timer.start(700)
|
self._preload_timer.start(700)
|
||||||
|
|
||||||
def _preload_next(self):
|
def _preload_next(self):
|
||||||
while self._preload_queue and self._preload_queue[0] in self._instances:
|
while self._preload_queue and self._preload_queue[0] in self._instances:
|
||||||
skipped = self._preload_queue.pop(0)
|
skipped = self._preload_queue.pop(0)
|
||||||
self._preload_skipped_runtime += 1
|
self._preload_skipped_runtime += 1
|
||||||
print(f"[preload] {skipped:30s} SKIP (user navigated here first)", flush=True)
|
logger.debug(f"[preload] {skipped:30s} SKIP (user navigated here first)")
|
||||||
if not self._preload_queue:
|
if not self._preload_queue:
|
||||||
self._maybe_emit_preload_summary()
|
self._maybe_emit_preload_summary()
|
||||||
return
|
return
|
||||||
self._preload_dispatched += 1
|
self._preload_dispatched += 1
|
||||||
idx = self._preload_dispatched
|
idx = self._preload_dispatched
|
||||||
name = self._preload_queue.pop(0)
|
name = self._preload_queue.pop(0)
|
||||||
print(f"[preload] ---- {idx}/{self._preload_total} ----", flush=True)
|
logger.debug(f"[preload] ---- {idx}/{self._preload_total} ----")
|
||||||
if name in ASYNC_PREPARE:
|
if name in ASYNC_PREPARE:
|
||||||
self._dispatch_async_preload(name)
|
self._dispatch_async_preload(name)
|
||||||
else:
|
else:
|
||||||
|
|
@ -165,7 +165,7 @@ class Navigator(QObject):
|
||||||
|
|
||||||
def _dispatch_async_preload(self, name):
|
def _dispatch_async_preload(self, name):
|
||||||
module_path, class_name = PAGE_REGISTRY[name]
|
module_path, class_name = PAGE_REGISTRY[name]
|
||||||
print(f"[preload] {name:30s} ASYNC {module_path}.{class_name}", flush=True)
|
logger.debug(f"[preload] {name:30s} ASYNC {module_path}.{class_name}")
|
||||||
worker = PageDataWorker(name, self._prepare_page_data, name)
|
worker = PageDataWorker(name, self._prepare_page_data, name)
|
||||||
worker.data_ready.connect(self._on_preload_data_ready)
|
worker.data_ready.connect(self._on_preload_data_ready)
|
||||||
worker.failed.connect(self._on_preload_failed)
|
worker.failed.connect(self._on_preload_failed)
|
||||||
|
|
@ -177,22 +177,22 @@ class Navigator(QObject):
|
||||||
def _on_preload_data_ready(self, name, payload):
|
def _on_preload_data_ready(self, name, payload):
|
||||||
self._preload_inflight -= 1
|
self._preload_inflight -= 1
|
||||||
if name in self._instances:
|
if name in self._instances:
|
||||||
print(f"[preload] {name:30s} SKIP (already cached, async result dropped)", flush=True)
|
logger.debug(f"[preload] {name:30s} SKIP (already cached, async result dropped)")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
page = self._instantiate(name, prepared=payload)
|
page = self._instantiate(name, prepared=payload)
|
||||||
self.register_instance(name, page)
|
self.register_instance(name, page)
|
||||||
self._preload_done += 1
|
self._preload_done += 1
|
||||||
print(f"[preload] {name:30s} OK (async, cached={len(self._instances)}, "
|
logger.debug(f"[preload] {name:30s} OK (async, cached={len(self._instances)}, "
|
||||||
f"stack_size={self.page_stack.count()})", flush=True)
|
f"stack_size={self.page_stack.count()})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._preload_failed += 1
|
self._preload_failed += 1
|
||||||
print(f"[preload] {name:30s} FAIL (async build) {type(e).__name__}: {e}", flush=True)
|
logger.debug(f"[preload] {name:30s} FAIL (async build) {type(e).__name__}: {e}")
|
||||||
self._maybe_emit_preload_summary()
|
self._maybe_emit_preload_summary()
|
||||||
|
|
||||||
def _on_preload_failed(self, name, error):
|
def _on_preload_failed(self, name, error):
|
||||||
self._preload_inflight -= 1
|
self._preload_inflight -= 1
|
||||||
print(f"[preload] {name:30s} FAIL (async prepare) {error}", flush=True)
|
logger.debug(f"[preload] {name:30s} FAIL (async prepare) {error}")
|
||||||
if name not in self._instances:
|
if name not in self._instances:
|
||||||
ok = self._preload_one(name)
|
ok = self._preload_one(name)
|
||||||
if ok:
|
if ok:
|
||||||
|
|
@ -215,10 +215,10 @@ class Navigator(QObject):
|
||||||
|
|
||||||
def _emit_preload_summary(self):
|
def _emit_preload_summary(self):
|
||||||
total_s = time.perf_counter() - self._preload_t_start
|
total_s = time.perf_counter() - self._preload_t_start
|
||||||
print(f"[preload] ============================================================", flush=True)
|
logger.debug(f"[preload] ============================================================")
|
||||||
print(f"[preload] complete: {self._preload_done} loaded, "
|
logger.debug(f"[preload] complete: {self._preload_done} loaded, "
|
||||||
f"{self._preload_failed} failed, "
|
f"{self._preload_failed} failed, "
|
||||||
f"{self._preload_skipped_runtime} skipped (user beat us to it) "
|
f"{self._preload_skipped_runtime} skipped (user beat us to it) "
|
||||||
f"in {total_s:.2f}s", flush=True)
|
f"in {total_s:.2f}s")
|
||||||
print(f"[preload] cached pages now: {sorted(self._instances.keys())}", flush=True)
|
logger.debug(f"[preload] cached pages now: {sorted(self._instances.keys())}")
|
||||||
print(f"[preload] ============================================================", flush=True)
|
logger.debug(f"[preload] ============================================================")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
PAGE_REGISTRY = {
|
PAGE_REGISTRY = {
|
||||||
"blank": ("gui.v2.ui.pages._blank_page", "BlankPage"),
|
"blank": ("gui.v2.ui.pages._blank_page", "BlankPage"),
|
||||||
"welcome": ("gui.v2.ui.pages.welcome_page", "WelcomePage"),
|
"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"),
|
"menu": ("gui.v2.ui.pages.menu_page", "MenuPage"),
|
||||||
"protocol": ("gui.v2.ui.pages.protocol_page", "ProtocolPage"),
|
"protocol": ("gui.v2.ui.pages.protocol_page", "ProtocolPage"),
|
||||||
"hidetor": ("gui.v2.ui.pages.hidetor_page", "HidetorPage"),
|
"hidetor": ("gui.v2.ui.pages.hidetor_page", "HidetorPage"),
|
||||||
|
|
|
||||||
|
|
@ -5,22 +5,6 @@ from core.observers.InvoiceObserver import InvoiceObserver
|
||||||
from core.observers.ProfileObserver import ProfileObserver
|
from core.observers.ProfileObserver import ProfileObserver
|
||||||
from core.observers.TicketObserver import TicketObserver
|
from core.observers.TicketObserver import TicketObserver
|
||||||
from core.controllers.ApplicationController import ApplicationController
|
from core.controllers.ApplicationController import ApplicationController
|
||||||
from PyQt6.QtCore import QObject, pyqtSignal
|
|
||||||
|
|
||||||
|
|
||||||
class StatusRelay(QObject):
|
|
||||||
status_requested = pyqtSignal(object, bool)
|
|
||||||
|
|
||||||
def __init__(self, update_status):
|
|
||||||
super().__init__()
|
|
||||||
self.update_status = update_status
|
|
||||||
self.status_requested.connect(self.apply_status)
|
|
||||||
|
|
||||||
def post(self, text, clear=False):
|
|
||||||
self.status_requested.emit(text, clear)
|
|
||||||
|
|
||||||
def apply_status(self, text, clear=False):
|
|
||||||
self.update_status(text, clear=clear)
|
|
||||||
|
|
||||||
application_version_observer = ApplicationVersionObserver()
|
application_version_observer = ApplicationVersionObserver()
|
||||||
client_observer = ClientObserver()
|
client_observer = ClientObserver()
|
||||||
|
|
@ -30,64 +14,71 @@ profile_observer = ProfileObserver()
|
||||||
ticket_observer = TicketObserver()
|
ticket_observer = TicketObserver()
|
||||||
|
|
||||||
|
|
||||||
def observer_message(topic, event):
|
|
||||||
subject = getattr(event, 'subject', None)
|
|
||||||
meta = getattr(event, 'meta', None)
|
|
||||||
if subject is not None:
|
|
||||||
return str(subject)
|
|
||||||
if meta:
|
|
||||||
return str(meta)
|
|
||||||
return str(topic)
|
|
||||||
|
|
||||||
|
|
||||||
def subscribe_observer_messages(observer, post_status):
|
|
||||||
for attr in dir(observer):
|
|
||||||
if not attr.startswith('on_'):
|
|
||||||
continue
|
|
||||||
callbacks = getattr(observer, attr, None)
|
|
||||||
if not isinstance(callbacks, list):
|
|
||||||
continue
|
|
||||||
topic = attr[3:]
|
|
||||||
observer.subscribe(
|
|
||||||
topic,
|
|
||||||
lambda event, topic=topic: post_status(observer_message(topic, event)))
|
|
||||||
|
|
||||||
|
|
||||||
def setup_observers(update_status):
|
def setup_observers(update_status):
|
||||||
relay = StatusRelay(update_status)
|
|
||||||
post_status = relay.post
|
|
||||||
|
|
||||||
profile_observer.subscribe(
|
profile_observer.subscribe(
|
||||||
'created', lambda event: post_status('Profile Created'))
|
'created', lambda event: update_status('Profile Created'))
|
||||||
profile_observer.subscribe(
|
profile_observer.subscribe(
|
||||||
'destroyed', lambda event: post_status('Profile destroyed'))
|
'destroyed', lambda event: update_status('Profile destroyed'))
|
||||||
|
|
||||||
subscribe_observer_messages(client_observer, post_status)
|
# client_observer.subscribe(
|
||||||
|
# 'synchronizing', lambda event: update_status('Sync in progress...'))
|
||||||
|
|
||||||
application_version_observer.subscribe('downloading', lambda event: post_status(
|
client_observer.subscribe(
|
||||||
|
'synchronizing', lambda event: update_status(f'{event.subject if event.subject else "Sync in progress..."}'))
|
||||||
|
|
||||||
|
client_observer.subscribe(
|
||||||
|
'synchronized', lambda event: update_status('Sync complete'))
|
||||||
|
|
||||||
|
client_observer.subscribe(
|
||||||
|
'updating', lambda event: update_status('Updating client...'))
|
||||||
|
client_observer.subscribe('update_progressing', lambda event: update_status(
|
||||||
|
f'Current progress: {event.meta.get('progress'):.2f}%'))
|
||||||
|
client_observer.subscribe('updated', lambda event: update_status(
|
||||||
|
'Restart client to apply update.'))
|
||||||
|
|
||||||
|
client_observer.subscribe(
|
||||||
|
'custom_message', lambda event: update_status(f'{event.subject if event.subject else "Error, check logs"}'))
|
||||||
|
|
||||||
|
application_version_observer.subscribe('downloading', lambda event: update_status(
|
||||||
f'Downloading {ApplicationController.get(event.subject.application_code).name}'))
|
f'Downloading {ApplicationController.get(event.subject.application_code).name}'))
|
||||||
application_version_observer.subscribe('download_progressing', lambda event: post_status(
|
application_version_observer.subscribe('download_progressing', lambda event: update_status(
|
||||||
f'Downloading {ApplicationController.get(event.subject.application_code).name} {event.meta.get('progress'):.2f}%'))
|
f'Downloading {ApplicationController.get(event.subject.application_code).name} {event.meta.get('progress'):.2f}%'))
|
||||||
|
|
||||||
application_version_observer.subscribe('downloaded', lambda event: post_status(
|
application_version_observer.subscribe('downloaded', lambda event: update_status(
|
||||||
f'Downloaded {ApplicationController.get(event.subject.application_code).name}'))
|
f'Downloaded {ApplicationController.get(event.subject.application_code).name}'))
|
||||||
|
|
||||||
subscribe_observer_messages(connection_observer, post_status)
|
connection_observer.subscribe('connecting', lambda event: update_status(
|
||||||
|
f'[{event.subject.get("attempt_count")}/{event.subject.get("maximum_number_of_attempts")}] Performing connection attempt...'))
|
||||||
|
|
||||||
ticket_observer.subscribe('connecting', lambda event: post_status('Connecting to ticket server...'))
|
connection_observer.subscribe('tor_bootstrapping', lambda event: update_status(
|
||||||
ticket_observer.subscribe('sync_done', lambda event: post_status('Ticket prices synced.'))
|
'Establishing Tor connection...'))
|
||||||
ticket_observer.subscribe('waiting', lambda event: post_status('Waiting for payment...'))
|
|
||||||
ticket_observer.subscribe('paid', lambda event: post_status('Payment received.'))
|
connection_observer.subscribe(
|
||||||
ticket_observer.subscribe('ticket_ready', lambda event: post_status('Ticket ready.'))
|
'tor_bootstrap_progressing', lambda event: update_status(f'{event.subject if event.subject else "Tor Bootstrapping.."}'))
|
||||||
ticket_observer.subscribe('used', lambda event: post_status('Ticket used.'))
|
|
||||||
ticket_observer.subscribe('connection_error', lambda event: post_status('Ticket server connection error.'))
|
# original replaced version:
|
||||||
ticket_observer.subscribe('failed_output', lambda event: post_status(f'{event.subject if event.subject else ""}'))
|
# connection_observer.subscribe('tor_bootstrap_progressing', lambda event: update_status(
|
||||||
ticket_observer.subscribe('failed_input', lambda event: post_status(f'{event.subject if event.subject else ""}'))
|
# f'Bootstrapping Tor {event.meta.get('progress'):.2f}%'))
|
||||||
ticket_observer.subscribe('unknown_error', lambda event: post_status('Unknown ticket error.'))
|
|
||||||
ticket_observer.subscribe('error', lambda event: post_status(f'Ticket error: {event.subject if event.subject else ""}'))
|
connection_observer.subscribe(
|
||||||
|
'tor_bootstrapped', lambda event: update_status('Tor connection established.'))
|
||||||
|
|
||||||
|
client_observer.subscribe(
|
||||||
|
'custom_message', lambda event: update_status(f'{event.subject if event.subject else ""}'))
|
||||||
|
|
||||||
|
ticket_observer.subscribe('connecting', lambda event: update_status('Connecting to ticket server...'))
|
||||||
|
ticket_observer.subscribe('sync_done', lambda event: update_status('Ticket prices synced.'))
|
||||||
|
ticket_observer.subscribe('waiting', lambda event: update_status('Waiting for payment...'))
|
||||||
|
ticket_observer.subscribe('paid', lambda event: update_status('Payment received.'))
|
||||||
|
ticket_observer.subscribe('ticket_ready', lambda event: update_status('Ticket ready.'))
|
||||||
|
ticket_observer.subscribe('used', lambda event: update_status('Ticket used.'))
|
||||||
|
ticket_observer.subscribe('connection_error', lambda event: update_status('Ticket server connection error.'))
|
||||||
|
ticket_observer.subscribe('failed_output', lambda event: update_status(f'{event.subject if event.subject else ""}'))
|
||||||
|
ticket_observer.subscribe('failed_input', lambda event: update_status(f'{event.subject if event.subject else ""}'))
|
||||||
|
ticket_observer.subscribe('unknown_error', lambda event: update_status('Unknown ticket error.'))
|
||||||
|
ticket_observer.subscribe('error', lambda event: update_status(f'Ticket error: {event.subject if event.subject else ""}'))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'status_relay': relay,
|
|
||||||
'application_version_observer': application_version_observer,
|
'application_version_observer': application_version_observer,
|
||||||
'client_observer': client_observer,
|
'client_observer': client_observer,
|
||||||
'connection_observer': connection_observer,
|
'connection_observer': connection_observer,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ from PyQt6 import QtCore, QtGui
|
||||||
|
|
||||||
from core.controllers.ProfileController import ProfileController
|
from core.controllers.ProfileController import ProfileController
|
||||||
from core.controllers.LocationController import LocationController
|
from core.controllers.LocationController import LocationController
|
||||||
|
from core.models.Result import Result, ResultError
|
||||||
|
from core.controllers.profile_state.update_profile import update_profile
|
||||||
|
|
||||||
from gui.v2.ui.pages.Page import Page
|
from gui.v2.ui.pages.Page import Page
|
||||||
from gui.v2.ui.pages.browser_page import BrowserPage
|
from gui.v2.ui.pages.browser_page import BrowserPage
|
||||||
|
|
@ -576,9 +578,11 @@ class EditorPage(Page):
|
||||||
|
|
||||||
def on_sync_complete_for_edit_profile(self, available_locations, available_browsers, status, is_tor, locations, all_browsers):
|
def on_sync_complete_for_edit_profile(self, available_locations, available_browsers, status, is_tor, locations, all_browsers):
|
||||||
if status:
|
if status:
|
||||||
|
self.update_status.update_status('Sync complete.')
|
||||||
self.extraccion()
|
self.extraccion()
|
||||||
else:
|
else:
|
||||||
self.connection_manager.set_synced(False)
|
self.update_status.update_status(
|
||||||
|
'Sync failed. Please try again later.')
|
||||||
|
|
||||||
def show_previous_value(self, key: str, index: int, parameters: dict) -> None:
|
def show_previous_value(self, key: str, index: int, parameters: dict) -> None:
|
||||||
if key == 'browser' or key == 'location':
|
if key == 'browser' or key == 'location':
|
||||||
|
|
@ -793,49 +797,24 @@ class EditorPage(Page):
|
||||||
self._prefetched_profiles = None
|
self._prefetched_profiles = None
|
||||||
|
|
||||||
def update_core_profiles(self, key, new_value):
|
def update_core_profiles(self, key, new_value):
|
||||||
profile = ProfileController.get(
|
profile_id = int(self.update_status.current_profile_id)
|
||||||
int(self.update_status.current_profile_id))
|
|
||||||
self.update_res = False
|
# Sends to core directly,
|
||||||
if key == 'dimentions':
|
result = update_profile(profile_id, key, new_value)
|
||||||
profile.resolution = new_value
|
|
||||||
|
if result.valid:
|
||||||
|
self.update_status.update_status(
|
||||||
|
f'Updated profile {profile_id}')
|
||||||
|
|
||||||
|
if key == "resolution":
|
||||||
self.update_res = True
|
self.update_res = True
|
||||||
|
|
||||||
elif key == 'name':
|
if result.data == "edit_to_session":
|
||||||
profile.name = new_value
|
|
||||||
|
|
||||||
elif key == 'connection':
|
|
||||||
if new_value == 'tor':
|
|
||||||
profile.connection.code = new_value
|
|
||||||
profile.connection.masked = True
|
|
||||||
elif new_value == 'just proxy':
|
|
||||||
profile.connection.code = 'system'
|
|
||||||
profile.connection.masked = True
|
|
||||||
else:
|
|
||||||
self.update_status.update_status(
|
|
||||||
'System wide profiles not supported atm')
|
|
||||||
|
|
||||||
elif key == 'browser':
|
|
||||||
browser_type, browser_version = new_value.split(':', 1)
|
|
||||||
profile.application_version.application_code = browser_type
|
|
||||||
profile.application_version.version_number = browser_version
|
|
||||||
|
|
||||||
elif key == 'protocol':
|
|
||||||
if self.data_profile.get('connection') == 'system-wide':
|
|
||||||
self.edit_to_session()
|
self.edit_to_session()
|
||||||
else:
|
|
||||||
profile.connection.code = 'wireguard' if new_value == 'wireguard' else 'tor'
|
|
||||||
profile.connection.masked = False if new_value == 'wireguard' else True
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
location = self.connection_manager.get_location_info(new_value)
|
self.update_status.update_status(result.message)
|
||||||
|
|
||||||
if location:
|
|
||||||
profile.location.code = location.code
|
|
||||||
profile.location.country_code = location.country_code
|
|
||||||
profile.location.time_zone = location.time_zone
|
|
||||||
profile.subscription = None
|
|
||||||
|
|
||||||
ProfileController.update(profile)
|
|
||||||
|
|
||||||
def edit_to_session(self):
|
def edit_to_session(self):
|
||||||
id = int(self.update_status.current_profile_id)
|
id = int(self.update_status.current_profile_id)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ from core.models.system.SystemProfile import SystemProfile
|
||||||
|
|
||||||
from gui.v2.infrastructure.setup_observers import ticket_observer
|
from gui.v2.infrastructure.setup_observers import ticket_observer
|
||||||
from gui.v2.actions.profile_order import normalize_profile_order
|
from gui.v2.actions.profile_order import normalize_profile_order
|
||||||
from gui.v2.actions.sync_result import is_valid_sync_payload
|
|
||||||
from gui.v2.ui.pages.Page import Page
|
from gui.v2.ui.pages.Page import Page
|
||||||
from gui.v2.ui.pages.location_verification_page import LocationVerificationPage
|
from gui.v2.ui.pages.location_verification_page import LocationVerificationPage
|
||||||
from gui.v2.ui.styles.styles import SCROLLBAR_CYAN_QSS
|
from gui.v2.ui.styles.styles import SCROLLBAR_CYAN_QSS
|
||||||
|
|
@ -1009,14 +1008,9 @@ class MenuPage(Page):
|
||||||
if fast_mode:
|
if fast_mode:
|
||||||
if not self.connection_manager.is_synced():
|
if not self.connection_manager.is_synced():
|
||||||
self.update_status.update_status('Syncing in progress..')
|
self.update_status.update_status('Syncing in progress..')
|
||||||
self.fast_registration_sync_worker = self._build_sync_worker()
|
self.update_status.sync()
|
||||||
self.fast_registration_sync_worker.text_output.connect(
|
self.update_status.worker_thread.sync_output.connect(
|
||||||
self.update_status.update_status)
|
|
||||||
self.fast_registration_sync_worker.sync_output.connect(
|
|
||||||
self.update_status.update_values)
|
|
||||||
self.fast_registration_sync_worker.sync_output.connect(
|
|
||||||
self.on_sync_complete_for_fast_registration)
|
self.on_sync_complete_for_fast_registration)
|
||||||
self.fast_registration_sync_worker.start()
|
|
||||||
else:
|
else:
|
||||||
self.custom_window.navigator.navigate("fast_registration")
|
self.custom_window.navigator.navigate("fast_registration")
|
||||||
return
|
return
|
||||||
|
|
@ -1027,15 +1021,11 @@ class MenuPage(Page):
|
||||||
self.custom_window.navigator.navigate("protocol")
|
self.custom_window.navigator.navigate("protocol")
|
||||||
|
|
||||||
def on_sync_complete_for_fast_registration(self, available_locations, available_browsers, status, is_tor, locations, all_browsers):
|
def on_sync_complete_for_fast_registration(self, available_locations, available_browsers, status, is_tor, locations, all_browsers):
|
||||||
if is_valid_sync_payload(available_locations, available_browsers, status, locations, all_browsers):
|
if status:
|
||||||
self.custom_window.navigator.navigate("fast_registration")
|
self.custom_window.navigator.navigate("fast_registration")
|
||||||
else:
|
else:
|
||||||
self.connection_manager.set_synced(False)
|
self.update_status.update_status(
|
||||||
|
'Sync failed. Please try again later.')
|
||||||
def _build_sync_worker(self):
|
|
||||||
if self.update_status.get_current_connection() == 'tor':
|
|
||||||
return WorkerThread('SYNC_TOR')
|
|
||||||
return WorkerThread('SYNC')
|
|
||||||
|
|
||||||
def change_connect_button(self):
|
def change_connect_button(self):
|
||||||
profile = ProfileController.get(int(self.reverse_id))
|
profile = ProfileController.get(int(self.reverse_id))
|
||||||
|
|
@ -1077,7 +1067,8 @@ class MenuPage(Page):
|
||||||
if status:
|
if status:
|
||||||
self.custom_window.navigator.navigate("editor")
|
self.custom_window.navigator.navigate("editor")
|
||||||
else:
|
else:
|
||||||
self.connection_manager.set_synced(False)
|
self.update_status.update_status(
|
||||||
|
'Sync failed. Please try again later.')
|
||||||
|
|
||||||
def settings_gui(self):
|
def settings_gui(self):
|
||||||
self.custom_window.navigator.navigate("settings")
|
self.custom_window.navigator.navigate("settings")
|
||||||
|
|
@ -1172,11 +1163,9 @@ class MenuPage(Page):
|
||||||
self.update_status.update_status("Syncing...")
|
self.update_status.update_status("Syncing...")
|
||||||
self.worker_thread = WorkerThread('SYNC')
|
self.worker_thread = WorkerThread('SYNC')
|
||||||
self.worker_thread.finished.connect(
|
self.worker_thread.finished.connect(
|
||||||
lambda ok: self.handle_sync_after_verification(profile_id) if ok else None)
|
lambda: self.handle_sync_after_verification(profile_id))
|
||||||
self.worker_thread.sync_output.connect(
|
self.worker_thread.sync_output.connect(
|
||||||
self.update_status.update_values)
|
self.update_status.update_values)
|
||||||
self.worker_thread.text_output.connect(
|
|
||||||
self.update_status.update_status)
|
|
||||||
self.worker_thread.start()
|
self.worker_thread.start()
|
||||||
else:
|
else:
|
||||||
self.update_status.update_status("Profile enable aborted")
|
self.update_status.update_status("Profile enable aborted")
|
||||||
|
|
|
||||||
191
gui/v2/ui/pages/networking_setup_page.py
Executable file
191
gui/v2/ui/pages/networking_setup_page.py
Executable file
|
|
@ -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")
|
||||||
|
|
@ -31,6 +31,7 @@ from core.Errors import (
|
||||||
PolicyRevocationError,
|
PolicyRevocationError,
|
||||||
)
|
)
|
||||||
from core.errors.logger import logger as core_logger
|
from core.errors.logger import logger as core_logger
|
||||||
|
from core.services.networking.systemwide.systemwide_utils import get_firewall_setting
|
||||||
|
|
||||||
from gui.v2.actions import settings_data
|
from gui.v2.actions import settings_data
|
||||||
from gui.v2.actions.key_interpretation import interpret_key_results
|
from gui.v2.actions.key_interpretation import interpret_key_results
|
||||||
|
|
@ -102,6 +103,8 @@ class Settings(Page):
|
||||||
("Create/Edit", self.show_registrations_page),
|
("Create/Edit", self.show_registrations_page),
|
||||||
("Verification", self.show_verification_page),
|
("Verification", self.show_verification_page),
|
||||||
("Legacy-Version", self.show_systemwide_page),
|
("Legacy-Version", self.show_systemwide_page),
|
||||||
|
("Bwrap Permission", self.show_bwrap_page),
|
||||||
|
("Connections", self.show_connection_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)
|
||||||
|
|
@ -1223,6 +1226,14 @@ class Settings(Page):
|
||||||
self.systemwide_page = self.create_systemwide_page()
|
self.systemwide_page = self.create_systemwide_page()
|
||||||
self.content_layout.addWidget(self.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.connection_page)
|
||||||
|
self.connection_page = self.create_connection_page()
|
||||||
|
self.content_layout.addWidget(self.connection_page)
|
||||||
|
|
||||||
self.content_layout.removeWidget(self.delete_page)
|
self.content_layout.removeWidget(self.delete_page)
|
||||||
self.delete_page = self.create_delete_page()
|
self.delete_page = self.create_delete_page()
|
||||||
self.content_layout.addWidget(self.delete_page)
|
self.content_layout.addWidget(self.delete_page)
|
||||||
|
|
@ -1436,6 +1447,8 @@ class Settings(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.systemwide_page = self.create_systemwide_page()
|
||||||
|
self.bwrap_page = self.create_bwrap_page()
|
||||||
|
self.connection_page = self.create_connection_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()
|
||||||
|
|
@ -1446,6 +1459,8 @@ class Settings(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.systemwide_page)
|
||||||
|
self.content_layout.addWidget(self.bwrap_page)
|
||||||
|
self.content_layout.addWidget(self.connection_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)
|
||||||
|
|
@ -1499,6 +1514,16 @@ class Settings(Page):
|
||||||
self.content_layout.setCurrentWidget(self.systemwide_page)
|
self.content_layout.setCurrentWidget(self.systemwide_page)
|
||||||
self._select_menu_button("Legacy-Version")
|
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_connection_page(self):
|
||||||
|
core_logger.info("User navigated to Settings -> Connection")
|
||||||
|
self.content_layout.setCurrentWidget(self.connection_page)
|
||||||
|
self._select_menu_button("Connection Page")
|
||||||
|
|
||||||
def show_logs_page(self):
|
def show_logs_page(self):
|
||||||
core_logger.info("User navigated to Settings -> Error Logs")
|
core_logger.info("User navigated to Settings -> Error Logs")
|
||||||
self.content_layout.setCurrentWidget(self.logs_page)
|
self.content_layout.setCurrentWidget(self.logs_page)
|
||||||
|
|
@ -1981,6 +2006,59 @@ class Settings(Page):
|
||||||
self._prepared_value("systemwide_enabled", settings_data.read_systemwide_enabled))
|
self._prepared_value("systemwide_enabled", settings_data.read_systemwide_enabled))
|
||||||
return page
|
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):
|
def load_systemwide_settings(self, enabled=None):
|
||||||
if enabled is None:
|
if enabled is None:
|
||||||
enabled = settings_data.read_systemwide_enabled()
|
enabled = settings_data.read_systemwide_enabled()
|
||||||
|
|
@ -2020,6 +2098,83 @@ class Settings(Page):
|
||||||
self.systemwide_status_value.setStyleSheet(
|
self.systemwide_status_value.setStyleSheet(
|
||||||
f"color: red; font-size: 14px; {self.font_style}")
|
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 load_firewall_settings(self, enabled=None):
|
||||||
|
if enabled is None:
|
||||||
|
enabled = settings_data.load(read_firewall_setting)
|
||||||
|
self.firewall_toggle.setChecked(enabled)
|
||||||
|
if enabled:
|
||||||
|
self.firewall_status_value.setText("Enabled")
|
||||||
|
self.firewall_status_value.setStyleSheet(
|
||||||
|
f"color: #2ecc71; font-size: 14px; {self.font_style}")
|
||||||
|
else:
|
||||||
|
self.firewall_status_value.setText("Disabled")
|
||||||
|
self.firewall_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 save_firewall_settings(self):
|
||||||
|
enable = self.firewall_toggle.isChecked()
|
||||||
|
try:
|
||||||
|
if enable:
|
||||||
|
ConfigurationController.change_firewall(True)
|
||||||
|
self.firewall_status_value.setText("Enabled")
|
||||||
|
else:
|
||||||
|
ConfigurationController.change_firewall(False)
|
||||||
|
self.firewall_status_value.setText("Disabled")
|
||||||
|
self.update_status.update_status(
|
||||||
|
"Firewall settings updated")
|
||||||
|
except CommandNotFoundError as e:
|
||||||
|
self.firewall_status_value.setText(str(e))
|
||||||
|
self.firewall_status_value.setStyleSheet(
|
||||||
|
f"color: red; font-size: 14px; {self.font_style}")
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
self.firewall_status_value.setText(f"Failed to update firewall setting {e}")
|
||||||
|
self.firewall_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._prepared_value(
|
config = self._prepared_value(
|
||||||
|
|
@ -2081,3 +2236,58 @@ class Settings(Page):
|
||||||
logging.error(f"Error saving registration settings: {str(e)}")
|
logging.error(f"Error saving registration settings: {str(e)}")
|
||||||
self.update_status.update_status(
|
self.update_status.update_status(
|
||||||
"Error saving registration settings")
|
"Error saving registration settings")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def create_connection_page(self):
|
||||||
|
page = QWidget()
|
||||||
|
layout = QVBoxLayout(page)
|
||||||
|
layout.setSpacing(20)
|
||||||
|
layout.setContentsMargins(20, 20, 20, 20)
|
||||||
|
|
||||||
|
title = QLabel("CONNECTION PAGE")
|
||||||
|
title.setStyleSheet(
|
||||||
|
f"color: #808080; font-size: 12px; font-weight: bold; {self.font_style}")
|
||||||
|
layout.addWidget(title)
|
||||||
|
|
||||||
|
description = QLabel(
|
||||||
|
"Control connection settings such as the firewall.")
|
||||||
|
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.firewall_status_value = QLabel("")
|
||||||
|
self.firewall_status_value.setStyleSheet(
|
||||||
|
f"color: #e67e22; font-size: 14px; {self.font_style}")
|
||||||
|
status_layout.addWidget(status_label)
|
||||||
|
status_layout.addWidget(self.firewall_status_value)
|
||||||
|
status_layout.addStretch()
|
||||||
|
layout.addLayout(status_layout)
|
||||||
|
|
||||||
|
toggle_layout = QHBoxLayout()
|
||||||
|
self.firewall_toggle = QCheckBox("Enable Firewall Automatically when Systemwide Profiles are Activated")
|
||||||
|
self.firewall_toggle.setStyleSheet(self.get_checkbox_style())
|
||||||
|
toggle_layout.addWidget(self.firewall_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_firewall_settings)
|
||||||
|
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
button_layout.addWidget(save_button)
|
||||||
|
button_layout.addStretch()
|
||||||
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
layout.addStretch()
|
||||||
|
self.load_firewall_settings(
|
||||||
|
self._prepared_value("firewall_setting", settings_data.read_firewall_setting))
|
||||||
|
return page
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ from PyQt6.QtGui import QPixmap
|
||||||
|
|
||||||
from core.controllers.ClientController import ClientController
|
from core.controllers.ClientController import ClientController
|
||||||
|
|
||||||
from gui.v2.actions.sync_result import is_valid_sync_payload
|
|
||||||
from gui.v2.ui.pages.Page import Page
|
from gui.v2.ui.pages.Page import Page
|
||||||
from gui.v2.workers.worker_thread import WorkerThread
|
from gui.v2.workers.worker_thread import WorkerThread
|
||||||
|
|
||||||
|
|
@ -83,11 +82,10 @@ class SyncScreen(Page):
|
||||||
self.worker_thread = WorkerThread('SYNC')
|
self.worker_thread = WorkerThread('SYNC')
|
||||||
|
|
||||||
self.worker_thread.sync_output.connect(self.update_output)
|
self.worker_thread.sync_output.connect(self.update_output)
|
||||||
self.worker_thread.text_output.connect(self.update_status.update_status)
|
|
||||||
self.worker_thread.start()
|
self.worker_thread.start()
|
||||||
|
|
||||||
def update_output(self, available_locations, available_browsers, status, is_tor, locations, all_browsers):
|
def update_output(self, available_locations, available_browsers, status, is_tor, locations, all_browsers):
|
||||||
if status is True and isinstance(all_browsers, bool) and not all_browsers:
|
if isinstance(all_browsers, bool) and not all_browsers:
|
||||||
self.custom_window.navigator.navigate("install_system_package")
|
self.custom_window.navigator.navigate("install_system_package")
|
||||||
install_page = self.custom_window.navigator.get_cached("install_system_package")
|
install_page = self.custom_window.navigator.get_cached("install_system_package")
|
||||||
if install_page is not None:
|
if install_page is not None:
|
||||||
|
|
@ -97,12 +95,14 @@ class SyncScreen(Page):
|
||||||
self.button_back.setEnabled(True)
|
self.button_back.setEnabled(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not is_valid_sync_payload(available_locations, available_browsers, status, locations, all_browsers):
|
if status is False:
|
||||||
self.connection_manager.set_synced(False)
|
|
||||||
self.button_go.setEnabled(True)
|
self.button_go.setEnabled(True)
|
||||||
self.button_back.setEnabled(True)
|
self.button_back.setEnabled(True)
|
||||||
|
self.update_status.update_status('An error occurred during sync')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.update_status.update_status('Sync complete')
|
||||||
|
|
||||||
update_available = ClientController.can_be_updated()
|
update_available = ClientController.can_be_updated()
|
||||||
|
|
||||||
if update_available:
|
if update_available:
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class WelcomePage(Page):
|
||||||
self.btn_path = main_window.btn_path
|
self.btn_path = main_window.btn_path
|
||||||
self.update_status = main_window
|
self.update_status = main_window
|
||||||
self.ui_elements = []
|
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.button_next.setVisible(True)
|
||||||
self._setup_welcome_ui()
|
self._setup_welcome_ui()
|
||||||
self._setup_stats_display()
|
self._setup_stats_display()
|
||||||
|
|
@ -30,7 +30,7 @@ class WelcomePage(Page):
|
||||||
|
|
||||||
welcome_msg = QLabel(
|
welcome_msg = QLabel(
|
||||||
"Before we begin your journey, we need to set up a few essential components. "
|
"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.setGeometry(40, 100, 720, 80)
|
||||||
welcome_msg.setWordWrap(True)
|
welcome_msg.setWordWrap(True)
|
||||||
welcome_msg.setStyleSheet("""
|
welcome_msg.setStyleSheet("""
|
||||||
|
|
@ -95,7 +95,7 @@ class WelcomePage(Page):
|
||||||
"font-size: 16px; font-weight: bold; color: #2c3e50;")
|
"font-size: 16px; font-weight: bold; color: #2c3e50;")
|
||||||
title_layout.addWidget(title_label)
|
title_layout.addWidget(title_label)
|
||||||
|
|
||||||
status_indicator = QLabel("●")
|
status_indicator = QLabel(chr(9679))
|
||||||
status_indicator.setStyleSheet("color: #2ecc71; font-size: 16px;")
|
status_indicator.setStyleSheet("color: #2ecc71; font-size: 16px;")
|
||||||
title_layout.addWidget(status_indicator)
|
title_layout.addWidget(status_indicator)
|
||||||
|
|
||||||
|
|
@ -117,8 +117,5 @@ class WelcomePage(Page):
|
||||||
|
|
||||||
grid_layout.addWidget(stat_widget, row, col)
|
grid_layout.addWidget(stat_widget, row, col)
|
||||||
|
|
||||||
def go_to_install(self):
|
def go_to_networking_setup(self):
|
||||||
self.custom_window.navigator.navigate("install_system_package")
|
self.custom_window.navigator.navigate("networking_setup")
|
||||||
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')
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ class DatabaseConflictDialog(QDialog):
|
||||||
|
|
||||||
# First which messages to display,
|
# First which messages to display,
|
||||||
if check["status"] == "version_mismatch":
|
if check["status"] == "version_mismatch":
|
||||||
error_title = "Database Compatibility"
|
error_title = "Upgrade Time!"
|
||||||
error_subtitle = "Your App version and Database don't match"
|
error_subtitle = "Your App version and Database don't match"
|
||||||
else:
|
else:
|
||||||
error_title = "Database Error"
|
error_title = "Database Error"
|
||||||
|
|
|
||||||
10
gui/v2/ui/popups/generic_error_popup.py
Executable file
10
gui/v2/ui/popups/generic_error_popup.py
Executable file
|
|
@ -0,0 +1,10 @@
|
||||||
|
from PyQt6.QtWidgets import QApplication, QMessageBox
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def show_error(error_text, title="Error"):
|
||||||
|
app = QApplication.instance()
|
||||||
|
if app is None:
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
QMessageBox.critical(None, title, error_text)
|
||||||
|
app.quit()
|
||||||
|
|
@ -10,6 +10,8 @@ from core.controllers.tickets.UseTicketController import (
|
||||||
)
|
)
|
||||||
from core.models.session.SessionProfile import SessionProfile
|
from core.models.session.SessionProfile import SessionProfile
|
||||||
from core.models.system.SystemProfile import SystemProfile
|
from core.models.system.SystemProfile import SystemProfile
|
||||||
|
from core.models.Result import Result, ResultError
|
||||||
|
from core.errors.exceptions import SudoScript, MissingPreReqs
|
||||||
from core.Errors import (
|
from core.Errors import (
|
||||||
CommandNotFoundError,
|
CommandNotFoundError,
|
||||||
EndpointVerificationError,
|
EndpointVerificationError,
|
||||||
|
|
@ -114,6 +116,10 @@ class Worker(QObject):
|
||||||
"PROFILE_STATE_CONFLICT_ERROR", False, self.profile_data['id'], None, None)
|
"PROFILE_STATE_CONFLICT_ERROR", False, self.profile_data['id'], None, None)
|
||||||
except CommandNotFoundError as e:
|
except CommandNotFoundError as e:
|
||||||
self.update_signal.emit(str(e.subject), False, -1, None, None)
|
self.update_signal.emit(str(e.subject), False, -1, None, None)
|
||||||
|
except SudoScript as e:
|
||||||
|
self.update_signal.emit(str(e), False, None, None, None)
|
||||||
|
except MissingPreReqs as e:
|
||||||
|
self.update_signal.emit(str(e), False, None, None, None)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
self.update_signal.emit(
|
self.update_signal.emit(
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@ from core.controllers.SubscriptionPlanController import SubscriptionPlanControll
|
||||||
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
|
||||||
|
from core.models.BaseProfile import ProfileType
|
||||||
from core.models.system.SystemProfile import SystemProfile
|
from core.models.system.SystemProfile import SystemProfile
|
||||||
from core.observers.ClientObserver import ClientObserver
|
from core.errors.exceptions import SudoScript, MissingPreReqs
|
||||||
from core.observers.ConnectionObserver import ConnectionObserver
|
from core.models.Result import Result, ResultError
|
||||||
|
|
||||||
from gui.v2.infrastructure.setup_observers import (
|
from gui.v2.infrastructure.setup_observers import (
|
||||||
client_observer,
|
client_observer,
|
||||||
|
|
@ -134,6 +135,10 @@ class WorkerThread(QThread):
|
||||||
ProfileController.disable(
|
ProfileController.disable(
|
||||||
profile, ignore=True, profile_observer=profile_observer)
|
profile, ignore=True, profile_observer=profile_observer)
|
||||||
self.text_output.emit("All profiles were successfully disabled")
|
self.text_output.emit("All profiles were successfully disabled")
|
||||||
|
# except SudoScript as e:
|
||||||
|
# self.text_output.emit(e)
|
||||||
|
# except MissingPreReqs as e:
|
||||||
|
# self.text_output.emit(e)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.text_output.emit("An error occurred when disabling profile")
|
self.text_output.emit("An error occurred when disabling profile")
|
||||||
finally:
|
finally:
|
||||||
|
|
@ -191,12 +196,23 @@ class WorkerThread(QThread):
|
||||||
resolution = self.profile_data['resolution']
|
resolution = self.profile_data['resolution']
|
||||||
connection = SessionConnection(connection_type, mask_connection)
|
connection = SessionConnection(connection_type, mask_connection)
|
||||||
profile = SessionProfile(
|
profile = SessionProfile(
|
||||||
profile_id, name, None, location, resolution, application_version, connection)
|
id=profile_id,
|
||||||
|
name=name,
|
||||||
|
subscription=None,
|
||||||
|
type=ProfileType.SESSION,
|
||||||
|
location=location,
|
||||||
|
resolution=resolution,
|
||||||
|
application_version=application_version,
|
||||||
|
connection=connection)
|
||||||
elif profile_type == "system":
|
elif profile_type == "system":
|
||||||
connection = SystemConnection(connection_type)
|
connection = SystemConnection(connection_type)
|
||||||
profile = SystemProfile(
|
profile = SystemProfile(
|
||||||
profile_id, name, None, location, connection)
|
id=profile_id,
|
||||||
|
name=name,
|
||||||
|
type=ProfileType.SYSTEM,
|
||||||
|
subscription=None,
|
||||||
|
location=location,
|
||||||
|
connection=connection)
|
||||||
else:
|
else:
|
||||||
self.text_output.emit(f"Invalid profile type: {profile_type}")
|
self.text_output.emit(f"Invalid profile type: {profile_type}")
|
||||||
return
|
return
|
||||||
|
|
@ -219,86 +235,37 @@ class WorkerThread(QThread):
|
||||||
else:
|
else:
|
||||||
self.text_output.emit(
|
self.text_output.emit(
|
||||||
f"No profile found with ID: {self.profile_data['id']}")
|
f"No profile found with ID: {self.profile_data['id']}")
|
||||||
|
# except SudoScript as e:
|
||||||
|
# self.text_output.emit(str(e))
|
||||||
|
# except MissingPreReqs as e:
|
||||||
|
# self.text_output.emit(str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.text_output.emit("An error occurred when disabling profile")
|
self.text_output.emit("An error occurred when disabling profile")
|
||||||
finally:
|
finally:
|
||||||
self.finished.emit(True)
|
self.finished.emit(True)
|
||||||
|
|
||||||
def _emit_observer_message(self, topic, event):
|
|
||||||
subject = getattr(event, 'subject', None)
|
|
||||||
meta = getattr(event, 'meta', None)
|
|
||||||
if subject is not None:
|
|
||||||
self.text_output.emit(str(subject))
|
|
||||||
elif meta:
|
|
||||||
self.text_output.emit(str(meta))
|
|
||||||
else:
|
|
||||||
self.text_output.emit(str(topic))
|
|
||||||
|
|
||||||
def _subscribe_observer_messages(self, observer, skip_topics=None):
|
|
||||||
skip_topics = skip_topics or set()
|
|
||||||
for attr in dir(observer):
|
|
||||||
if not attr.startswith('on_'):
|
|
||||||
continue
|
|
||||||
callbacks = getattr(observer, attr, None)
|
|
||||||
if not isinstance(callbacks, list):
|
|
||||||
continue
|
|
||||||
topic = attr[3:]
|
|
||||||
if topic in skip_topics:
|
|
||||||
continue
|
|
||||||
observer.subscribe(
|
|
||||||
topic,
|
|
||||||
lambda event, topic=topic: self._emit_observer_message(topic, event))
|
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
try:
|
try:
|
||||||
if self.action == 'SYNC_TOR':
|
if self.action == 'SYNC_TOR':
|
||||||
ConfigurationController.set_connection('tor')
|
ConfigurationController.set_connection('tor')
|
||||||
else:
|
else:
|
||||||
ConfigurationController.set_connection('system')
|
ConfigurationController.set_connection('system')
|
||||||
sync_succeeded = False
|
self.check_for_update()
|
||||||
|
|
||||||
def sync_complete(event):
|
|
||||||
nonlocal sync_succeeded
|
|
||||||
sync_succeeded = True
|
|
||||||
self._emit_observer_message('synchronized', event)
|
|
||||||
|
|
||||||
sync_client_observer = ClientObserver()
|
|
||||||
sync_connection_observer = ConnectionObserver()
|
|
||||||
self._subscribe_observer_messages(
|
|
||||||
sync_client_observer, {'synchronized'})
|
|
||||||
self._subscribe_observer_messages(sync_connection_observer)
|
|
||||||
sync_client_observer.subscribe('synchronized', sync_complete)
|
|
||||||
|
|
||||||
ClientController.sync(
|
|
||||||
client_observer=sync_client_observer,
|
|
||||||
connection_observer=sync_connection_observer)
|
|
||||||
|
|
||||||
if not sync_succeeded:
|
|
||||||
self.sync_output.emit([], [], False, False, [], [])
|
|
||||||
self.finished.emit(False)
|
|
||||||
return
|
|
||||||
|
|
||||||
locations = LocationController.get_all()
|
locations = LocationController.get_all()
|
||||||
|
|
||||||
|
|
||||||
browser = ApplicationVersionController.get_all()
|
browser = ApplicationVersionController.get_all()
|
||||||
|
# print('the browser is: ', browser)
|
||||||
all_browser_versions = [
|
all_browser_versions = [
|
||||||
f"{browser.application_code}:{browser.version_number}" for browser in browser if browser.supported]
|
f"{browser.application_code}:{browser.version_number}" for browser in browser if browser.supported]
|
||||||
all_location_codes = [
|
all_location_codes = [
|
||||||
f"{location.country_code}_{location.code}" for location in locations]
|
f"{location.country_code}_{location.code}" for location in locations]
|
||||||
|
|
||||||
if not all_location_codes or not all_browser_versions or not locations or not browser:
|
|
||||||
self.sync_output.emit([], [], False, False, [], [])
|
|
||||||
self.finished.emit(False)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.sync_output.emit(
|
self.sync_output.emit(
|
||||||
all_location_codes, all_browser_versions, True, False, locations, browser)
|
all_location_codes, all_browser_versions, True, False, locations, browser)
|
||||||
self.finished.emit(True)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'the error is: {e}')
|
print(f'the error is: {e}')
|
||||||
self.sync_output.emit([], [], False, False, [], [])
|
self.sync_output.emit([], [], False, False, [], [])
|
||||||
self.finished.emit(False)
|
|
||||||
|
|
||||||
def get_connection(self):
|
def get_connection(self):
|
||||||
connection = ConfigurationController.get_connection()
|
connection = ConfigurationController.get_connection()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "sp-hydra-veil-gui"
|
name = "sp-hydra-veil-gui"
|
||||||
version = "2.4.6"
|
version = "2.4.7"
|
||||||
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 == 2.3.8",
|
"sp-hydra-veil-core == 2.4.3",
|
||||||
"pyperclip ~= 1.9.0",
|
"pyperclip ~= 1.9.0",
|
||||||
"pyqt6 ~= 6.7.1",
|
"pyqt6 ~= 6.7.1",
|
||||||
"qrcode[pil] ~= 8.2"
|
"qrcode[pil] ~= 8.2"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue