virtui-manager 1.1.6__py3-none-any.whl → 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {virtui_manager-1.1.6.dist-info → virtui_manager-1.3.0.dist-info}/METADATA +1 -1
  2. virtui_manager-1.3.0.dist-info/RECORD +73 -0
  3. vmanager/constants.py +737 -108
  4. vmanager/dialog.css +24 -0
  5. vmanager/firmware_manager.py +4 -1
  6. vmanager/i18n.py +32 -0
  7. vmanager/libvirt_utils.py +132 -3
  8. vmanager/locales/de/LC_MESSAGES/virtui-manager.po +3012 -0
  9. vmanager/locales/fr/LC_MESSAGES/virtui-manager.mo +0 -0
  10. vmanager/locales/fr/LC_MESSAGES/virtui-manager.po +3124 -0
  11. vmanager/locales/it/LC_MESSAGES/virtui-manager.po +3012 -0
  12. vmanager/locales/virtui-manager.pot +3012 -0
  13. vmanager/modals/bulk_modals.py +13 -12
  14. vmanager/modals/cache_stats_modal.py +6 -5
  15. vmanager/modals/capabilities_modal.py +133 -0
  16. vmanager/modals/config_modal.py +25 -24
  17. vmanager/modals/cpu_mem_pc_modals.py +22 -21
  18. vmanager/modals/custom_migration_modal.py +10 -9
  19. vmanager/modals/disk_pool_modals.py +60 -59
  20. vmanager/modals/host_dashboard_modal.py +137 -0
  21. vmanager/modals/howto_disk_modal.py +2 -1
  22. vmanager/modals/howto_network_modal.py +2 -1
  23. vmanager/modals/howto_overlay_modal.py +2 -1
  24. vmanager/modals/howto_ssh_modal.py +2 -1
  25. vmanager/modals/howto_virtiofs_modal.py +2 -1
  26. vmanager/modals/input_modals.py +11 -10
  27. vmanager/modals/log_modal.py +2 -1
  28. vmanager/modals/migration_modals.py +20 -18
  29. vmanager/modals/network_modals.py +45 -36
  30. vmanager/modals/provisioning_modals.py +56 -56
  31. vmanager/modals/select_server_modals.py +8 -7
  32. vmanager/modals/selection_modals.py +7 -6
  33. vmanager/modals/server_modals.py +24 -23
  34. vmanager/modals/server_prefs_modals.py +78 -71
  35. vmanager/modals/utils_modals.py +10 -9
  36. vmanager/modals/virsh_modals.py +3 -2
  37. vmanager/modals/virtiofs_modals.py +6 -5
  38. vmanager/modals/vm_type_info_modal.py +2 -1
  39. vmanager/modals/vmanager_modals.py +19 -19
  40. vmanager/modals/vmcard_dialog.py +57 -57
  41. vmanager/modals/vmdetails_modals.py +115 -123
  42. vmanager/modals/xml_modals.py +3 -2
  43. vmanager/network_manager.py +4 -1
  44. vmanager/storage_manager.py +157 -39
  45. vmanager/utils.py +39 -6
  46. vmanager/vm_actions.py +28 -24
  47. vmanager/vm_queries.py +67 -25
  48. vmanager/vm_service.py +8 -5
  49. vmanager/vmanager.css +46 -0
  50. vmanager/vmanager.py +178 -112
  51. vmanager/vmcard.py +161 -159
  52. vmanager/webconsole_manager.py +21 -21
  53. virtui_manager-1.1.6.dist-info/RECORD +0 -65
  54. {virtui_manager-1.1.6.dist-info → virtui_manager-1.3.0.dist-info}/WHEEL +0 -0
  55. {virtui_manager-1.1.6.dist-info → virtui_manager-1.3.0.dist-info}/entry_points.txt +0 -0
  56. {virtui_manager-1.1.6.dist-info → virtui_manager-1.3.0.dist-info}/licenses/LICENSE +0 -0
  57. {virtui_manager-1.1.6.dist-info → virtui_manager-1.3.0.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,7 @@ from textual import on, work
12
12
 
13
13
  import libvirt
14
14
  from ..config import load_config
15
- from ..constants import AppInfo
15
+ from ..constants import AppInfo, ErrorMessages, SuccessMessages, ButtonLabels, StaticText
16
16
  from ..vm_provisioner import VMProvisioner, VMType, OpenSUSEDistro
17
17
  from ..storage_manager import list_storage_pools
18
18
  from ..vm_service import VMService
@@ -43,16 +43,16 @@ class InstallVMModal(BaseModal[str | None]):
43
43
  default_pool = 'default' if any(p[0] == 'default' for p in active_pools) else (active_pools[0][1] if active_pools else None)
44
44
 
45
45
  with ScrollableContainer(id="install-dialog"):
46
- yield Label(f"Install OpenSUSE VM on {self.uri}", classes="title")
47
- yield Label("VM Name:", classes="label")
46
+ yield Label(StaticText.INSTALL_OPENSUSE_VM.format(uri=self.uri), classes="title")
47
+ yield Label(StaticText.VM_NAME, classes="label")
48
48
  yield Input(placeholder="my-new-vm", id="vm-name")
49
49
 
50
- yield Label("VM Type:", classes="label")
50
+ yield Label(StaticText.VM_TYPE, classes="label")
51
51
  with Horizontal(classes="label-row"):
52
52
  yield Select([(t.value, t) for t in VMType], value=VMType.DESKTOP, id="vm-type", allow_blank=False)
53
- yield Button("Info", id="vm-type-info-btn", variant="primary")
53
+ yield Button(ButtonLabels.INFO, id="vm-type-info-btn", variant="primary")
54
54
 
55
- yield Label("Distribution:", classes="label")
55
+ yield Label(StaticText.DISTRIBUTION, classes="label")
56
56
  distro_options = [(d.value, d) for d in OpenSUSEDistro]
57
57
  distro_options.insert(0, ("Cached ISOs", "cached"))
58
58
  custom_repos = self.provisioner.get_custom_repos()
@@ -70,58 +70,58 @@ class InstallVMModal(BaseModal[str | None]):
70
70
 
71
71
  # Container for ISO selection (Repo)
72
72
  with Vertical(id="repo-iso-container"):
73
- yield Label("ISO Image (Repo):", classes="label")
73
+ yield Label(StaticText.ISO_IMAGE_REPO, classes="label")
74
74
  config = load_config()
75
75
  iso_path = Path(config.get('ISO_DOWNLOAD_PATH', str(Path.home() / ".cache" / AppInfo.name / "isos")))
76
- yield Label(f"ISOs will be downloaded to: {iso_path}", classes="info-text", id="iso-path-label")
76
+ yield Label(StaticText.ISOS_DOWNLOAD_PATH.format(iso_path=iso_path), classes="info-text", id="iso-path-label")
77
77
  yield Select([], prompt="Select ISO...", id="iso-select", disabled=True)
78
78
 
79
79
  # Container for Custom ISO
80
80
  with Vertical(id="custom-iso-container"):
81
- yield Label("Custom ISO (Local Path):", classes="label")
81
+ yield Label(StaticText.CUSTOM_ISO_LOCAL_PATH, classes="label")
82
82
  with Horizontal(classes="input-row"):
83
83
  yield Input(placeholder="/path/to/local.iso", id="custom-iso-path", classes="path-input")
84
- yield Button("Browse", id="browse-iso-btn")
84
+ yield Button(ButtonLabels.BROWSE, id="browse-iso-btn")
85
85
 
86
86
  with Vertical(id="checksum-container"):
87
- yield Checkbox("Validate Checksum", id="validate-checksum", value=False)
87
+ yield Checkbox(StaticText.VALIDATE_CHECKSUM, id="validate-checksum", value=False)
88
88
  yield Input(placeholder="SHA256 Checksum (Optional)", id="checksum-input", disabled=True)
89
- yield Label("", id="checksum-status", classes="status-text")
90
-
89
+ yield Label(StaticText.EMPTY_LABEL, id="checksum-status", classes="status-text")
90
+
91
91
  # Container for ISO selection from Storage Pools
92
92
  with Vertical(id="pool-iso-container"):
93
- yield Label("Select Storage Pool:", classes="label")
93
+ yield Label(StaticText.SELECT_STORAGE_POOL, classes="label")
94
94
  yield Select(active_pools, prompt="Select Pool...", id="storage-pool-select", allow_blank=False)
95
- yield Label("Select ISO Volume:", classes="label")
95
+ yield Label(StaticText.SELECT_ISO_VOLUME, classes="label")
96
96
  yield Select([], prompt="Select ISO Volume...", id="iso-volume-select", disabled=True)
97
97
 
98
- yield Label("Storage Pool:", id="vminstall-storage-label")
98
+ yield Label(StaticText.STORAGE_POOL, id="vminstall-storage-label")
99
99
  yield Select(active_pools, value=default_pool, id="pool", allow_blank=False)
100
100
  with Collapsible(title="Expert Mode", id="expert-mode-collapsible"):
101
101
  with Horizontal(id="expert-mode"):
102
102
  with Vertical(id="expert-mem"):
103
- yield Label(" Memory (GB)", classes="label")
103
+ yield Label(StaticText.MEMORY_GB_LABEL, classes="label")
104
104
  yield Input("4", id="memory-input", type="integer")
105
105
  with Vertical(id="expert-cpu"):
106
- yield Label(" CPUs", classes="label")
106
+ yield Label(StaticText.CPUS_LABEL, classes="label")
107
107
  yield Input("2", id="cpu-input", type="integer")
108
108
  with Vertical(id="expert-disk-size"):
109
- yield Label(" Disk Size(GB)", classes="label")
109
+ yield Label(StaticText.DISK_SIZE_GB_LABEL, classes="label")
110
110
  yield Input("8", id="disk-size-input", type="integer")
111
111
  with Vertical(id="expert-disk-format"):
112
- yield Label(" Disk Format", classes="label")
112
+ yield Label(StaticText.DISK_FORMAT_LABEL, classes="label")
113
113
  yield Select([("Qcow2", "qcow2"), ("Raw", "raw")], value="qcow2", id="disk-format")
114
114
  with Vertical(id="expert-firmware"):
115
- yield Label(" Firmware", classes="label")
116
- yield Checkbox("UEFI", id="boot-uefi-checkbox", value=True, tooltip="Unchecked means legacy boot")
115
+ yield Label(StaticText.FIRMWARE_LABEL, classes="label")
116
+ yield Checkbox(StaticText.UEFI, id="boot-uefi-checkbox", value=True, tooltip="Unchecked means legacy boot")
117
117
 
118
- yield Checkbox("Configure before install", id="configure-before-install-checkbox", value=False, tooltip="Show VM configuration before starting")
118
+ yield Checkbox(StaticText.CONFIGURE_BEFORE_INSTALL, id="configure-before-install-checkbox", value=False, tooltip="Show VM configuration before starting")
119
119
  yield ProgressBar(total=100, show_eta=False, id="progress-bar")
120
- yield Label("", id="status-label")
120
+ yield Label(StaticText.EMPTY_LABEL, id="status-label")
121
121
 
122
122
  with Horizontal(classes="buttons"):
123
- yield Button("Install", variant="primary", id="install-btn", disabled=True)
124
- yield Button("Cancel", variant="default", id="cancel-btn")
123
+ yield Button(ButtonLabels.INSTALL, variant="primary", id="install-btn", disabled=True)
124
+ yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn")
125
125
 
126
126
  def on_mount(self):
127
127
  """Called when modal is mounted."""
@@ -270,7 +270,7 @@ class InstallVMModal(BaseModal[str | None]):
270
270
  self.app.call_from_thread(update_iso_volume_select)
271
271
 
272
272
  except Exception as e:
273
- self.app.call_from_thread(self.app.show_error_message, f"Failed to fetch ISO volumes from {pool_name}: {e}")
273
+ self.app.call_from_thread(self.app.show_error_message, ErrorMessages.FAILED_TO_FETCH_ISO_VOLUMES_TEMPLATE.format(pool_name=pool_name, error=e))
274
274
  self.app.call_from_thread(self._update_iso_status, "Error fetching volumes", False)
275
275
  self.app.call_from_thread(iso_volume_select.clear)
276
276
  self.app.call_from_thread(lambda: setattr(iso_volume_select, 'disabled', True))
@@ -311,7 +311,7 @@ class InstallVMModal(BaseModal[str | None]):
311
311
  self.app.call_from_thread(update_select)
312
312
 
313
313
  except Exception as e:
314
- self.app.call_from_thread(self.app.show_error_message, f"Failed to fetch ISOs: {e}")
314
+ self.app.call_from_thread(self.app.show_error_message, ErrorMessages.FAILED_TO_FETCH_ISOS_TEMPLATE.format(error=e))
315
315
  self.app.call_from_thread(self._update_iso_status, "Error fetching ISOs", False)
316
316
 
317
317
  def _update_iso_status(self, message, loading):
@@ -385,24 +385,24 @@ class InstallVMModal(BaseModal[str | None]):
385
385
  return
386
386
 
387
387
  if was_modified:
388
- self.app.show_quick_message(f"VM name sanitized: '{vm_name_raw}' -> '{vm_name}'")
388
+ self.app.show_quick_message(SuccessMessages.VM_NAME_SANITIZED_TEMPLATE.format(original=vm_name_raw, sanitized=vm_name))
389
389
  self.query_one("#vm-name", Input).value = vm_name
390
390
 
391
391
  if not vm_name:
392
- self.app.show_error_message("VM name cannot be empty.")
392
+ self.app.show_error_message(ErrorMessages.VM_NAME_CANNOT_BE_EMPTY)
393
393
  return
394
394
 
395
395
  # 2. Check if VM exists
396
396
  try:
397
397
  self.conn.lookupByName(vm_name)
398
- self.app.show_error_message(f"A VM with the name '{vm_name}' already exists. Please choose a different name.")
398
+ self.app.show_error_message(ErrorMessages.VM_NAME_ALREADY_EXISTS_TEMPLATE.format(vm_name=vm_name))
399
399
  return
400
400
  except libvirt.libvirtError as e:
401
401
  if e.get_error_code() != libvirt.VIR_ERR_NO_DOMAIN:
402
- self.app.show_error_message(f"Error checking VM name: {e}")
402
+ self.app.show_error_message(ErrorMessages.ERROR_CHECKING_VM_NAME_TEMPLATE.format(error=e))
403
403
  return
404
404
  except Exception as e:
405
- self.app.show_error_message(f"An unexpected error occurred: {e}")
405
+ self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE.format(error=e))
406
406
  return
407
407
 
408
408
  vm_type = self.query_one("#vm-type", Select).value
@@ -412,7 +412,7 @@ class InstallVMModal(BaseModal[str | None]):
412
412
 
413
413
  # Validate storage pool
414
414
  if not pool_name or pool_name == Select.BLANK:
415
- self.app.show_error_message("Please select a valid storage pool.")
415
+ self.app.show_error_message(ErrorMessages.PLEASE_SELECT_VALID_STORAGE_POOL)
416
416
  return
417
417
 
418
418
  iso_url = None
@@ -428,11 +428,11 @@ class InstallVMModal(BaseModal[str | None]):
428
428
  elif distro == "pool_volumes":
429
429
  iso_url = self.query_one("#iso-volume-select", Select).value
430
430
  if not iso_url or iso_url == Select.BLANK:
431
- self.app.show_error_message("Please select a valid ISO volume from the storage pool.")
431
+ self.app.show_error_message(ErrorMessages.SELECT_VALID_ISO_VOLUME)
432
432
  return
433
433
  # Validate that the volume path exists and is accessible
434
434
  if not os.path.exists(iso_url):
435
- self.app.show_error_message(f"Selected ISO volume does not exist: {iso_url}")
435
+ self.app.show_error_message(ErrorMessages.ISO_VOLUME_NOT_FOUND_TEMPLATE.format(iso_url=iso_url))
436
436
  return
437
437
  else:
438
438
  iso_url = self.query_one("#iso-select", Select).value
@@ -446,27 +446,27 @@ class InstallVMModal(BaseModal[str | None]):
446
446
  disk_format = self.query_one("#disk-format", Select).value
447
447
  boot_uefi = self.query_one("#boot-uefi-checkbox", Checkbox).value
448
448
  except ValueError:
449
- self.app.show_error_message("Invalid input for expert settings. Using defaults.")
449
+ self.app.show_error_message(ErrorMessages.INVALID_EXPERT_SETTINGS)
450
450
  return
451
451
 
452
452
  # Validate expert mode inputs
453
453
  if memory_gb < 1 or memory_gb > 8192:
454
- self.app.show_error_message("Memory must be between 1 and 8192 GB.")
454
+ self.app.show_error_message(ErrorMessages.MEMORY_RANGE_ERROR)
455
455
  return
456
456
  if vcpu < 1 or vcpu > 768:
457
- self.app.show_error_message("CPU count must be between 1 and 768.")
457
+ self.app.show_error_message(ErrorMessages.CPU_RANGE_ERROR)
458
458
  return
459
459
  if disk_size < 1 or disk_size > 10000:
460
- self.app.show_error_message("Disk size must be between 1 and 10000 GB.")
460
+ self.app.show_error_message(ErrorMessages.DISK_SIZE_RANGE_ERROR)
461
461
  return
462
462
 
463
463
  try:
464
464
  pool = self.conn.storagePoolLookupByName(pool_name)
465
465
  if not pool.isActive():
466
- self.app.show_error_message(f"Storage pool '{pool_name}' is not active. Please activate it first.")
466
+ self.app.show_error_message(ErrorMessages.STORAGE_POOL_NOT_ACTIVE_TEMPLATE.format(pool_name=pool_name))
467
467
  return
468
468
  except Exception as e:
469
- self.app.show_error_message(f"Error accessing storage pool '{pool_name}': {e}")
469
+ self.app.show_error_message(ErrorMessages.ERROR_ACCESSING_STORAGE_POOL_TEMPLATE.format(pool_name=pool_name, error=e))
470
470
  return
471
471
 
472
472
  # Disable inputs
@@ -494,17 +494,17 @@ class InstallVMModal(BaseModal[str | None]):
494
494
  if custom_path:
495
495
  # Validate custom path exists
496
496
  if not os.path.exists(custom_path):
497
- raise Exception(f"Custom ISO path does not exist: {custom_path}")
497
+ raise Exception(ErrorMessages.CUSTOM_ISO_PATH_NOT_EXIST_TEMPLATE.format(path=custom_path))
498
498
  if not os.path.isfile(custom_path):
499
- raise Exception(f"Custom ISO path is not a file: {custom_path}")
499
+ raise Exception(ErrorMessages.CUSTOM_ISO_NOT_FILE_TEMPLATE.format(path=custom_path))
500
500
 
501
501
  # 1. Validate Checksum
502
502
  if validate:
503
503
  if not checksum:
504
- raise Exception("Checksum validation enabled but no checksum provided")
504
+ raise Exception(ErrorMessages.CHECKSUM_MISSING)
505
505
  progress_cb("Validating Checksum...", 0)
506
506
  if not self.provisioner.validate_iso(custom_path, checksum):
507
- raise Exception("Checksum validation failed!")
507
+ raise Exception(ErrorMessages.CHECKSUM_VALIDATION_FAILED)
508
508
  progress_cb("Checksum Validated", 10)
509
509
 
510
510
  # 2. Upload
@@ -514,7 +514,7 @@ class InstallVMModal(BaseModal[str | None]):
514
514
 
515
515
  final_iso_url = self.provisioner.upload_iso(custom_path, pool_name, upload_progress)
516
516
  if not final_iso_url:
517
- raise Exception("No ISO URL specified for provisioning")
517
+ raise Exception(ErrorMessages.NO_ISO_URL_SPECIFIED)
518
518
 
519
519
  # 3. Provision
520
520
  # Suspend global updates to prevent UI freeze during heavy provisioning ops
@@ -544,7 +544,7 @@ class InstallVMModal(BaseModal[str | None]):
544
544
  try:
545
545
  if not domain_obj.isActive():
546
546
  domain_obj.create()
547
- app.call_from_thread(app.show_success_message, f"VM '{vm_name}' started.")
547
+ app.call_from_thread(app.show_success_message, SuccessMessages.VM_STARTED_TEMPLATE.format(vm_name=vm_name))
548
548
  # Launch viewer
549
549
  domain_name = domain_obj.name()
550
550
  cmd = remote_viewer_cmd(self.uri, domain_name, app.r_viewer)
@@ -555,10 +555,10 @@ class InstallVMModal(BaseModal[str | None]):
555
555
  preexec_fn=os.setsid,
556
556
  )
557
557
  logging.info(f"{app.r_viewer} started with PID {proc.pid} for {domain_name}")
558
- app.call_from_thread(app.show_quick_message, f"Remote viewer {app.r_viewer} started for {domain_name}")
558
+ app.call_from_thread(app.show_quick_message, SuccessMessages.REMOTE_VIEWER_STARTED_TEMPLATE.format(viewer=app.r_viewer, vm_name=domain_name))
559
559
  except Exception as e:
560
560
  logging.error(f"Failed to start VM or viewer: {e}")
561
- app.call_from_thread(app.show_error_message, f"Failed to start VM or viewer: {e}")
561
+ app.call_from_thread(app.show_error_message, ErrorMessages.FAILED_TO_START_VM_OR_VIEWER_TEMPLATE.format(error=e))
562
562
 
563
563
  app.worker_manager.run(start_and_view, name=f"start_view_{vm_name}")
564
564
 
@@ -567,7 +567,7 @@ class InstallVMModal(BaseModal[str | None]):
567
567
  on_details_closed
568
568
  )
569
569
  else:
570
- app.show_error_message(f"Could not get details for {vm_name}")
570
+ app.show_error_message(ErrorMessages.COULD_NOT_GET_VM_DETAILS_TEMPLATE.format(vm_name=vm_name))
571
571
  self.app.call_from_thread(push_details)
572
572
 
573
573
  dom = self.provisioner.provision_vm(
@@ -591,10 +591,10 @@ class InstallVMModal(BaseModal[str | None]):
591
591
  self.app.call_from_thread(self.app.on_vm_data_update)
592
592
 
593
593
  if configure_before_install:
594
- self.app.call_from_thread(self.app.show_success_message, f"VM '{name}' defined. Please configure and start it.")
594
+ self.app.call_from_thread(self.app.show_success_message, SuccessMessages.VM_DEFINED_CONFIGURE_TEMPLATE.format(vm_name=name))
595
595
  return
596
596
 
597
- self.app.call_from_thread(self.app.show_success_message, f"VM '{name}' created successfully!")
597
+ self.app.call_from_thread(self.app.show_success_message, SuccessMessages.VM_CREATED_SUCCESSFULLY_TEMPLATE.format(vm_name=name))
598
598
 
599
599
  # 4. Auto-connect Remote Viewer
600
600
  def launch_viewer():
@@ -608,12 +608,12 @@ class InstallVMModal(BaseModal[str | None]):
608
608
  preexec_fn=os.setsid,
609
609
  )
610
610
  logging.info(f"{self.app.r_viewer} started with PID {proc.pid} for {domain_name}")
611
- self.app.show_quick_message(f"Remote viewer {self.app.r_viewer} started for {domain_name}")
611
+ self.app.show_quick_message(SuccessMessages.REMOTE_VIEWER_STARTED_TEMPLATE.format(viewer=self.app.r_viewer, vm_name=domain_name))
612
612
  except Exception as e:
613
613
  logging.error(f"Failed to spawn {self.app.r_viewer} for {domain_name}: {e}")
614
614
  self.app.call_from_thread(
615
615
  self.app.show_error_message,
616
- f"{self.app.r_viewer} failed to start for {domain_name}: {e}"
616
+ ErrorMessages.REMOTE_VIEWER_FAILED_TO_START_TEMPLATE.format(viewer=self.app.r_viewer, domain_name=domain_name, error=e)
617
617
  )
618
618
  return
619
619
 
@@ -621,5 +621,5 @@ class InstallVMModal(BaseModal[str | None]):
621
621
  self.app.call_from_thread(self.dismiss, True)
622
622
 
623
623
  except Exception as e:
624
- self.app.call_from_thread(self.app.show_error_message, f"Provisioning failed: {e}")
624
+ self.app.call_from_thread(self.app.show_error_message, ErrorMessages.PROVISIONING_FAILED_TEMPLATE.format(error=e))
625
625
  self.app.call_from_thread(self.dismiss)
@@ -12,6 +12,7 @@ from textual.screen import ModalScreen
12
12
  from .base_modals import BaseModal
13
13
  from .utils_modals import LoadingModal
14
14
  from ..connection_manager import ConnectionManager
15
+ from ..constants import ErrorMessages, StaticText, ButtonLabels
15
16
 
16
17
 
17
18
  class SelectServerModal(BaseModal[None]):
@@ -26,7 +27,7 @@ class SelectServerModal(BaseModal[None]):
26
27
 
27
28
  def compose(self) -> ComposeResult:
28
29
  with Vertical(id="select-server-container", classes="info-details"):
29
- yield Label("Select Servers to Display")
30
+ yield Label(StaticText.SELECT_SERVERS_TO_DISPLAY)
30
31
 
31
32
  checkboxes = []
32
33
  for i, server in enumerate(self.servers):
@@ -44,8 +45,8 @@ class SelectServerModal(BaseModal[None]):
44
45
  yield grid
45
46
 
46
47
  with Horizontal(classes="button-details"):
47
- yield Button("Done", id="done-servers", variant="primary", classes="done-button")
48
- yield Button("Cancel", id="cancel-servers", classes="cancel-button")
48
+ yield Button(ButtonLabels.DONE, id="done-servers", variant="primary", classes="done-button")
49
+ yield Button(ButtonLabels.CANCEL, id="cancel-servers", classes="cancel-button")
49
50
 
50
51
  @on(Checkbox.Changed)
51
52
  def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
@@ -57,7 +58,7 @@ class SelectServerModal(BaseModal[None]):
57
58
  return
58
59
 
59
60
  if event.value: # If checkbox is checked
60
- loading_modal = LoadingModal(f"Connecting to {uri}...")
61
+ loading_modal = LoadingModal(ErrorMessages.CONNECTING_TO_SERVER_TEMPLATE.format(uri=uri))
61
62
  self.app.push_screen(loading_modal)
62
63
 
63
64
  def connect_and_update():
@@ -66,7 +67,7 @@ class SelectServerModal(BaseModal[None]):
66
67
  if conn is None:
67
68
  self.app.call_from_thread(
68
69
  self.app.show_error_message,
69
- f"Failed to connect to {uri}"
70
+ ErrorMessages.FAILED_TO_CONNECT_TO_SERVER_TEMPLATE.format(uri=uri)
70
71
  )
71
72
  # Revert checkbox state on failure
72
73
  checkbox = self.query(f"#{checkbox_id}").first()
@@ -104,10 +105,10 @@ class SelectOneServerModal(BaseModal[str]):
104
105
  with Vertical(id="select-one-server-container"):
105
106
  yield Label(self.title_text)
106
107
  yield Select(self.server_options, prompt="Select server...", id="server-select")
107
- yield Label("")
108
+ yield Label(StaticText.EMPTY_LABEL)
108
109
  with Horizontal():
109
110
  yield Button(self.button_label, id="launch-btn", variant="primary", disabled=True)
110
- yield Button("Cancel", id="cancel-btn")
111
+ yield Button(ButtonLabels.CANCEL, id="cancel-btn")
111
112
 
112
113
  @on(Select.Changed, "#server-select")
113
114
  def on_server_select_changed(self, event: Select.Changed) -> None:
@@ -8,6 +8,7 @@ from textual.containers import Horizontal, Vertical, VerticalScroll, Grid
8
8
  from textual.widgets import Button, Input, Label, DataTable, Checkbox
9
9
  from textual import on
10
10
  from .base_modals import BaseModal
11
+ from ..constants import StaticText, ButtonLabels
11
12
 
12
13
  class PatternSelectModal(BaseModal[set[str] | None]):
13
14
  """Modal for selecting VMs by pattern across servers."""
@@ -23,7 +24,7 @@ class PatternSelectModal(BaseModal[set[str] | None]):
23
24
 
24
25
  def compose(self) -> ComposeResult:
25
26
  with Vertical(id="pattern-select-container", classes="modal-container"):
26
- yield Label("Select VMs by Pattern (ctrl+u to unselect All VMS)", id="pattern-select-title")
27
+ yield Label(StaticText.SELECT_VMS_BY_PATTERN, id="pattern-select-title")
27
28
 
28
29
  with Horizontal(classes="pattern-input-row"):
29
30
  yield Input(
@@ -31,7 +32,7 @@ class PatternSelectModal(BaseModal[set[str] | None]):
31
32
  id="pattern-input",
32
33
  restrict=r"[a-zA-Z0-9_\-\*\?\.\^\|\$\( \[ \] \+\{\}\\]*"
33
34
  )
34
- yield Checkbox("Regex", id="regex-checkbox")
35
+ yield Checkbox(StaticText.REGEX, id="regex-checkbox")
35
36
 
36
37
  if self.available_servers:
37
38
  #yield Label("Search in Servers:")
@@ -48,15 +49,15 @@ class PatternSelectModal(BaseModal[set[str] | None]):
48
49
  grid.styles.grid_gutter_horizontal = 0
49
50
  yield grid
50
51
 
51
- yield Button("Search VMs", variant="primary", id="search-vms-btn")
52
+ yield Button(ButtonLabels.SEARCH_VMS, variant="primary", id="search-vms-btn")
52
53
 
53
- yield Label("Matching VMs:", id="results-label")
54
+ yield Label(StaticText.MATCHING_VMS, id="results-label")
54
55
  with VerticalScroll(id="results-container"):
55
56
  yield DataTable(id="results-table", cursor_type="row")
56
57
 
57
58
  with Horizontal(id="pattern-action-buttons"):
58
- yield Button("Select Matching", variant="success", id="select-btn", disabled=True)
59
- yield Button("Cancel", variant="error", id="cancel-btn")
59
+ yield Button(ButtonLabels.SELECT_MATCHING, variant="success", id="select-btn", disabled=True)
60
+ yield Button(ButtonLabels.CANCEL, variant="error", id="cancel-btn")
60
61
 
61
62
  def on_mount(self) -> None:
62
63
  table = self.query_one("#results-table", DataTable)
@@ -11,19 +11,20 @@ from .howto_ssh_modal import HowToSSHModal
11
11
  from .base_modals import BaseModal
12
12
 
13
13
  from ..config import save_config
14
+ from ..constants import ErrorMessages, SuccessMessages, ButtonLabels, StaticText
14
15
 
15
16
  class ConnectionModal(BaseModal[str | None]):
16
17
 
17
18
  def compose(self) -> ComposeResult:
18
19
  with Vertical(id="connection-dialog"):
19
- yield Label("Enter QEMU Connection URI:")
20
+ yield Label(StaticText.ENTER_QEMU_CONNECTION_URI)
20
21
  yield Input(
21
22
  placeholder="qemu+ssh://user@host/system or qemu:///system",
22
23
  id="uri-input",
23
24
  )
24
25
  with Horizontal():
25
- yield Button("Connect", variant="primary", id="connect-btn", classes="Buttonpage")
26
- yield Button("Cancel", variant="default", id="cancel-btn", classes="Buttonpage")
26
+ yield Button(ButtonLabels.CONNECT, variant="primary", id="connect-btn", classes="Buttonpage")
27
+ yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn", classes="Buttonpage")
27
28
 
28
29
  def on_button_pressed(self, event: Button.Pressed) -> None:
29
30
  if event.button.id == "connect-btn":
@@ -37,14 +38,14 @@ class AddServerModal(BaseModal[Tuple[str, str] | None]):
37
38
  """Modal for adding a new server with autoconnect option."""
38
39
  def compose(self) -> ComposeResult:
39
40
  with Vertical(id="add-server-dialog"):
40
- yield Label("Add New Server")
41
+ yield Label(StaticText.ADD_NEW_SERVER)
41
42
  yield Input(placeholder="Server Name", id="server-name-input")
42
43
  yield Input(placeholder="qemu+ssh://user@host/system", id="server-uri-input")
43
- yield Label("")
44
- yield Checkbox("Autoconnect at startup", id="autoconnect-checkbox", value=False)
44
+ yield Label(StaticText.EMPTY_LABEL)
45
+ yield Checkbox(StaticText.AUTOCONNECT_AT_STARTUP, id="autoconnect-checkbox", value=False)
45
46
  with Horizontal():
46
- yield Button("Save", variant="primary", id="save-btn", classes="Buttonpage")
47
- yield Button("Cancel", variant="default", id="cancel-btn", classes="Buttonpage")
47
+ yield Button(ButtonLabels.SAVE, variant="primary", id="save-btn", classes="Buttonpage")
48
+ yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn", classes="Buttonpage")
48
49
 
49
50
  def on_button_pressed(self, event: Button.Pressed) -> None:
50
51
  if event.button.id == "save-btn":
@@ -69,14 +70,14 @@ class EditServerModal(BaseModal[Tuple[str, str, bool] | None]):
69
70
 
70
71
  def compose(self) -> ComposeResult:
71
72
  with Vertical(id="edit-server-dialog"):
72
- yield Label("Edit Server")
73
+ yield Label(StaticText.EDIT_SERVER)
73
74
  yield Input(value=self.server_name, id="server-name-input")
74
75
  yield Input(value=self.server_uri, id="server-uri-input")
75
- yield Label("")
76
- yield Checkbox("Autoconnect at startup", id="autoconnect-checkbox", value=self.autoconnect)
76
+ yield Label(StaticText.EMPTY_LABEL)
77
+ yield Checkbox(StaticText.AUTOCONNECT_AT_STARTUP, id="autoconnect-checkbox", value=self.autoconnect)
77
78
  with Horizontal():
78
- yield Button("Save", variant="primary", id="save-btn", classes="Buttonpage")
79
- yield Button("Cancel", variant="default", id="cancel-btn", classes="Buttonpage")
79
+ yield Button(ButtonLabels.SAVE, variant="primary", id="save-btn", classes="Buttonpage")
80
+ yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn", classes="Buttonpage")
80
81
 
81
82
  def on_button_pressed(self, event: Button.Pressed) -> None:
82
83
  if event.button.id == "save-btn":
@@ -102,18 +103,18 @@ class ServerManagementModal(BaseModal [str | None]):
102
103
 
103
104
  def compose(self) -> ComposeResult:
104
105
  with Vertical(id="server-management-dialog"): #, classes="info-details"):
105
- yield Label("Server List Management") #, id="server-list-title")
106
+ yield Label(StaticText.SERVER_LIST_MANAGEMENT) #, id="server-list-title")
106
107
  with ScrollableContainer(classes="info-details"):
107
108
  yield DataTable(id="server-table", classes="server-list")
108
109
  with Vertical(classes="server-list"):
109
110
  with Horizontal():
110
- yield Button("Add", id="add-server-btn", classes="add-button", variant="success")
111
- yield Button("Edit", id="edit-server-btn", disabled=True, classes="edit-button")
112
- yield Button("Delete", id="delete-server-btn", disabled=True,)
111
+ yield Button(ButtonLabels.ADD, id="add-server-btn", classes="add-button", variant="success")
112
+ yield Button(ButtonLabels.EDIT, id="edit-server-btn", disabled=True, classes="edit-button")
113
+ yield Button(ButtonLabels.DELETE, id="delete-server-btn", disabled=True,)
113
114
  with Horizontal():
114
- yield Button("Connect", id="select-btn", variant="primary", disabled=True, classes="Buttonpage")
115
- yield Button("Custom URL", id="custom-conn-btn", classes="Buttonpage")
116
- yield Button("SSH Help", id="ssh-help-btn", classes="Buttonpage")
115
+ yield Button(ButtonLabels.CONNECT, id="select-btn", variant="primary", disabled=True, classes="Buttonpage")
116
+ yield Button(ButtonLabels.CUSTOM_URL, id="custom-conn-btn", classes="Buttonpage")
117
+ yield Button(ButtonLabels.SSH_HELP, id="ssh-help-btn", classes="Buttonpage")
117
118
  #yield Button("Close", id="close-btn", classes="close-button")
118
119
 
119
120
  def on_mount(self) -> None:
@@ -193,13 +194,13 @@ class ServerManagementModal(BaseModal [str | None]):
193
194
  self.query_one("#edit-server-btn").disabled = True
194
195
  self.query_one("#delete-server-btn").disabled = True
195
196
  self.query_one("#select-btn").disabled = True
196
- self.app.show_success_message(f"Server '{server_name_to_delete}' deleted successfully.")
197
+ self.app.show_success_message(SuccessMessages.SERVER_DELETED_TEMPLATE.format(server_name=server_name_to_delete))
197
198
  logging.info(f"Successfully deleted Server '{server_name_to_delete}'")
198
199
  except Exception as e:
199
- self.app.show_error_message(f"Error deleting server '{server_name_to_delete}': {e}")
200
+ self.app.show_error_message(ErrorMessages.ERROR_DELETING_SERVER_TEMPLATE.format(server_name=server_name_to_delete, error=e))
200
201
 
201
202
  self.app.push_screen(
202
- ConfirmationDialog(f"Are you sure you want to delete Server;\n'{server_name_to_delete}'\nfrom list?"), on_confirm)
203
+ ConfirmationDialog(ErrorMessages.DELETE_SERVER_CONFIRMATION_TEMPLATE.format(server_name=server_name_to_delete)), on_confirm)
203
204
 
204
205
  elif event.button.id == "custom-conn-btn":
205
206
  def connection_callback(uri: str | None):