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
@@ -25,7 +25,11 @@ from ..network_manager import (
25
25
  from .. import storage_manager
26
26
  from ..storage_manager import list_storage_volumes
27
27
 
28
- from ..constants import AppCacheTimeout
28
+ from ..constants import (
29
+ AppCacheTimeout, ErrorMessages, SuccessMessages,
30
+ ButtonLabels, WarningMessages,
31
+ StaticText
32
+ )
29
33
  from .base_modals import BaseModal
30
34
  from .network_modals import AddEditNetworkModal, NetworkXMLModal
31
35
  from .disk_pool_modals import (
@@ -51,7 +55,7 @@ class ServerPrefModal(BaseModal[None]):
51
55
 
52
56
  def compose(self) -> ComposeResult:
53
57
  with Vertical(id="server-pref-dialog",):
54
- yield Label("Server Preferences", id="server-pref-title")
58
+ yield Label(StaticText.SERVER_PREFERENCES, id="server-pref-title")
55
59
  yield Static(classes="button-separator")
56
60
  with TabbedContent(id="server-pref-tabs"):
57
61
  with TabPane("Network", id="tab-network"):
@@ -65,19 +69,19 @@ class ServerPrefModal(BaseModal[None]):
65
69
  uri_to_connect = self.uri
66
70
  if uri_to_connect is None:
67
71
  if len(self.app.active_uris) == 0:
68
- self.app.show_error_message("Not connected to any server.")
72
+ self.app.show_error_message(ErrorMessages.NOT_CONNECTED_TO_ANY_SERVER_PREFS)
69
73
  self.dismiss()
70
74
  return
71
75
  if len(self.app.active_uris) > 1:
72
76
  # This should not happen if the app logic uses the server selection modal
73
- self.app.show_error_message("Multiple servers active but none selected for preferences.")
77
+ self.app.show_error_message(ErrorMessages.MULTIPLE_SERVERS_ACTIVE_NONE_SELECTED)
74
78
  self.dismiss()
75
79
  return
76
80
  uri_to_connect = self.app.active_uris[0]
77
81
 
78
82
  self.conn = self.app.vm_service.connect(uri_to_connect)
79
83
  if not self.conn:
80
- self.app.show_error_message(f"Failed to get connection for server preferences on {uri_to_connect}.")
84
+ self.app.show_error_message(ErrorMessages.FAILED_TO_GET_CONNECTION_PREFS_TEMPLATE.format(uri=uri_to_connect))
81
85
  self.dismiss()
82
86
  return
83
87
 
@@ -103,21 +107,21 @@ class ServerPrefModal(BaseModal[None]):
103
107
 
104
108
  button_container = Vertical(
105
109
  Horizontal(
106
- Button("De/Active", id="toggle-net-active-btn", classes="toggle-detail-button",
110
+ Button(ButtonLabels.DE_ACTIVE, id="toggle-net-active-btn", classes="toggle-detail-button",
107
111
  variant="primary", disabled=True, tooltip="Activate or Deactivate the selected network."),
108
- Button("Autostart", id="toggle-net-autostart-btn", classes="toggle-detail-button",
112
+ Button(ButtonLabels.AUTOSTART, id="toggle-net-autostart-btn", classes="toggle-detail-button",
109
113
  variant="primary", disabled=True, tooltip="Enable or Disable autostart for the selected network."),
110
114
  ),
111
115
  Horizontal(
112
- Button("Add", id="add-net-btn", variant="success", classes="toggle-detail-button",
116
+ Button(ButtonLabels.ADD, id="add-net-btn", variant="success", classes="toggle-detail-button",
113
117
  tooltip="Add a new network."),
114
- Button("Edit", id="edit-net-btn", variant="success", classes="toggle-detail-button",
118
+ Button(ButtonLabels.EDIT, id="edit-net-btn", variant="success", classes="toggle-detail-button",
115
119
  disabled=True, tooltip="Edit the selected network."),
116
- Button("View", id="view-net-btn", variant="success", classes="toggle-detail-button",
120
+ Button(ButtonLabels.VIEW, id="view-net-btn", variant="success", classes="toggle-detail-button",
117
121
  disabled=True, tooltip="View XML details of the selected network."),
118
- Button("Delete", id="delete-net-btn", variant="error", classes="toggle-detail-button",
122
+ Button(ButtonLabels.DELETE, id="delete-net-btn", variant="error", classes="toggle-detail-button",
119
123
  disabled=True, tooltip="Delete the selected network."),
120
- Button("Help", id="help-net-btn", variant="success", classes="toggle-detail-button",
124
+ Button(ButtonLabels.HELP, id="help-net-btn", variant="success", classes="toggle-detail-button",
121
125
  tooltip="Show network configuration help."),
122
126
  ),
123
127
  classes="server-pref-button"
@@ -145,9 +149,9 @@ class ServerPrefModal(BaseModal[None]):
145
149
 
146
150
 
147
151
  if num_vms > AppCacheTimeout.DONT_DISPLAY_DISK_USAGE:
148
- self.app.show_warning_message(f"More than {AppCacheTimeout.DONT_DISPLAY_DISK_USAGE} VMs detected on this server.\nSkipping disk usage scan to prevent UI freeze.")
152
+ self.app.show_warning_message(WarningMessages.TOO_MANY_VMS_DISK_USAGE_WARNING_TEMPLATE.format(count=AppCacheTimeout.DONT_DISPLAY_DISK_USAGE))
149
153
  else:
150
- self.app.show_warning_message(f"Runing disk usage scan, this can freeze the UI for larger numbers of VMs and disks.")
154
+ #self.app.show_warning_message(WarningMessages.RUNNING_DISK_USAGE_SCAN_WARNING)
151
155
  disk_map = get_all_vm_disk_usage(self.conn)
152
156
  nvram_map = get_all_vm_nvram_usage(self.conn)
153
157
  #overlay_map = get_all_vm_overlay_usage(self.conn)
@@ -177,25 +181,25 @@ class ServerPrefModal(BaseModal[None]):
177
181
  Button(id="toggle-autostart-pool-btn", variant="primary", classes="toggle-detail-button",
178
182
  tooltip="Enable or Disable autostart for the selected storage pool."),
179
183
  Vertical(
180
- Button("Add Pool", id="add-pool-btn", variant="success", classes="toggle-detail-button",
184
+ Button(ButtonLabels.ADD_POOL, id="add-pool-btn", variant="success", classes="toggle-detail-button",
181
185
  tooltip="Add a new storage pool."),
182
- Button("Delete Pool", id="del-pool-btn", variant="error", classes="toggle-detail-button",
186
+ Button(ButtonLabels.DELETE_POOL, id="del-pool-btn", variant="error", classes="toggle-detail-button",
183
187
  tooltip="Delete the selected storage pool."),
184
188
  ),
185
189
  Vertical(
186
- Button("New Volume", id="add-vol-btn", variant="success", classes="toggle-detail-button",
190
+ Button(ButtonLabels.NEW_VOLUME, id="add-vol-btn", variant="success", classes="toggle-detail-button",
187
191
  tooltip="Create a new volume in the selected pool."),
188
- Button("Attach Vol", id="attach-vol-btn", variant="success", classes="toggle-detail-button",
192
+ Button(ButtonLabels.ATTACH_VOL, id="attach-vol-btn", variant="success", classes="toggle-detail-button",
189
193
  tooltip="Attach an existing disk file as a volume."),
190
- Button("Move Volume", id="move-vol-btn", variant="success", classes="toggle-detail-button",
194
+ Button(ButtonLabels.MOVE_VOLUME, id="move-vol-btn", variant="success", classes="toggle-detail-button",
191
195
  tooltip="Move the selected volume to another pool."),
192
- Button("Delete Volume", id="del-vol-btn", variant="error", classes="toggle-detail-button",
196
+ Button(ButtonLabels.DELETE_VOLUME, id="del-vol-btn", variant="error", classes="toggle-detail-button",
193
197
  tooltip="Delete the selected volume."),
194
198
  ),
195
199
  Vertical(
196
- Button("View XML", id="view-storage-xml-btn", variant="primary", classes="toggle-detail-button",
200
+ Button(ButtonLabels.VIEW_XML, id="view-storage-xml-btn", variant="primary", classes="toggle-detail-button",
197
201
  tooltip="View XML details of the selected pool or volume."),
198
- Button("Edit XML", id="edit-pool-xml-btn", variant="primary", classes="toggle-detail-button",
202
+ Button(ButtonLabels.EDIT_XML, id="edit-pool-xml-btn", variant="primary", classes="toggle-detail-button",
199
203
  tooltip="Edit XML of the selected pool."),
200
204
  ),
201
205
  ),
@@ -286,7 +290,7 @@ class ServerPrefModal(BaseModal[None]):
286
290
 
287
291
  table.clear()
288
292
 
289
- self.app.show_warning_message(f"Runing network usage scan, this can freeze the UI for larger numbers of VMs.")
293
+ #self.app.show_warning_message(WarningMessages.RUNNING_NETWORK_USAGE_SCAN_WARNING)
290
294
  network_usage = get_all_network_usage(self.conn)
291
295
  self.networks_list = list_networks(self.conn)
292
296
 
@@ -388,14 +392,14 @@ class ServerPrefModal(BaseModal[None]):
388
392
  return
389
393
 
390
394
  if not target_obj:
391
- self.app.show_error_message("Could not find object to display XML for.")
395
+ self.app.show_error_message(ErrorMessages.COULD_NOT_FIND_OBJECT_XML)
392
396
  return
393
397
 
394
398
  try:
395
399
  xml_content = target_obj.XMLDesc(0)
396
400
  self.app.push_screen(XMLDisplayModal(xml_content, read_only=True))
397
401
  except libvirt.libvirtError as e:
398
- self.app.show_error_message(f"Error getting XML for {node_type}: {e}")
402
+ self.app.show_error_message(ErrorMessages.ERROR_GETTING_XML_FOR_TYPE_TEMPLATE.format(obj_type=node_type, error=e))
399
403
 
400
404
  @on(Button.Pressed, "#edit-pool-xml-btn")
401
405
  def on_edit_pool_xml_button_pressed(self, event: Button.Pressed) -> None:
@@ -410,11 +414,11 @@ class ServerPrefModal(BaseModal[None]):
410
414
 
411
415
  pool = node_data.get('pool')
412
416
  if not pool:
413
- self.app.show_error_message("Could not find pool object to edit.")
417
+ self.app.show_error_message(ErrorMessages.COULD_NOT_FIND_POOL_EDIT)
414
418
  return
415
419
 
416
420
  if node_data.get('status') == 'active':
417
- self.app.show_error_message("Pool must be inactive to edit its XML definition.")
421
+ self.app.show_error_message(ErrorMessages.POOL_MUST_BE_INACTIVE_FOR_XML_EDIT)
418
422
  return
419
423
 
420
424
  def on_edit_confirm(confirmed: bool) -> None:
@@ -424,7 +428,7 @@ class ServerPrefModal(BaseModal[None]):
424
428
  try:
425
429
  xml_content = pool.XMLDesc(0)
426
430
  except libvirt.libvirtError as e:
427
- self.app.show_error_message(f"Error getting XML for pool: {e}")
431
+ self.app.show_error_message(ErrorMessages.ERROR_GETTING_XML_FOR_POOL_TEMPLATE.format(error=e))
428
432
  return
429
433
 
430
434
  def on_xml_save(new_xml: str | None) -> None:
@@ -434,21 +438,17 @@ class ServerPrefModal(BaseModal[None]):
434
438
  try:
435
439
  # Redefine the pool with the new XML
436
440
  self.conn.storagePoolDefineXML(new_xml, 0)
437
- self.app.show_success_message(f"Storage pool '{pool.name()}' updated successfully.")
441
+ self.app.show_success_message(SuccessMessages.STORAGE_POOL_UPDATED_SUCCESSFULLY_TEMPLATE.format(pool_name=pool.name()))
438
442
  storage_manager.list_storage_pools.cache_clear()
439
443
  self._load_storage_pools() # Refresh the tree
440
444
  except libvirt.libvirtError as e:
441
- self.app.show_error_message(f"Error updating pool XML: {e}")
445
+ self.app.show_error_message(ErrorMessages.ERROR_UPDATING_POOL_XML_TEMPLATE.format(error=e))
442
446
  except Exception as e:
443
- self.app.show_error_message(f"An unexpected error occurred: {e}")
447
+ self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
444
448
 
445
449
  self.app.push_screen(XMLDisplayModal(xml_content, read_only=False), on_xml_save)
446
450
 
447
- warning_message = (
448
- "Editing a pool's XML definition is an advanced operation.\n"
449
- "An invalid configuration may make its volumes inaccessible to VMs.\n\n"
450
- "Are you sure you want to proceed?"
451
- )
451
+ warning_message = ErrorMessages.EDIT_POOL_XML_WARNING
452
452
  self.app.push_screen(ConfirmationDialog(warning_message), on_edit_confirm)
453
453
 
454
454
  @on(Button.Pressed, "#toggle-active-pool-btn")
@@ -467,12 +467,13 @@ class ServerPrefModal(BaseModal[None]):
467
467
  is_active = node_data.get('status') == 'active'
468
468
  try:
469
469
  storage_manager.set_pool_active(pool, not is_active)
470
- self.app.show_success_message(f"Pool '{pool.name()}' is now {'inactive' if is_active else 'active'}.")
470
+ status = 'inactive' if is_active else 'active'
471
+ self.app.show_success_message(SuccessMessages.POOL_ACTIVATION_CHANGE_TEMPLATE.format(pool_name=pool.name(), status=status))
471
472
  node_data['status'] = 'inactive' if is_active else 'active'
472
473
  storage_manager.list_storage_pools.cache_clear()
473
474
  self._load_storage_pools(select_pool=pool_name) # Refresh the tree
474
475
  except Exception as e:
475
- self.app.show_error_message(str(e))
476
+ self.app.show_error_message(ErrorMessages.ERROR_TOGGLE_POOL_ACTIVE_TEMPLATE.format(error=e))
476
477
 
477
478
  @on(Button.Pressed, "#toggle-autostart-pool-btn")
478
479
  def on_toggle_autostart_pool_button_pressed(self, event: Button.Pressed) -> None:
@@ -490,12 +491,13 @@ class ServerPrefModal(BaseModal[None]):
490
491
  has_autostart = node_data.get('autostart', False)
491
492
  try:
492
493
  storage_manager.set_pool_autostart(pool, not has_autostart)
493
- self.app.show_success_message(f"Autostart for pool '{pool.name()}' is now {'off' if has_autostart else 'on'}.")
494
+ status = 'off' if has_autostart else 'on'
495
+ self.app.show_success_message(SuccessMessages.POOL_AUTOSTART_CHANGE_TEMPLATE.format(pool_name=pool.name(), status=status))
494
496
  node_data['autostart'] = not has_autostart
495
497
  storage_manager.list_storage_pools.cache_clear()
496
498
  self._load_storage_pools(select_pool=pool_name) # Refresh the tree
497
499
  except Exception as e:
498
- self.app.show_error_message(str(e))
500
+ self.app.show_error_message(ErrorMessages.ERROR_TOGGLE_POOL_AUTOSTART_TEMPLATE.format(error=e))
499
501
 
500
502
  @on(Button.Pressed, "#add-vol-btn")
501
503
  def on_add_volume_button_pressed(self, event: Button.Pressed) -> None:
@@ -519,7 +521,7 @@ class ServerPrefModal(BaseModal[None]):
519
521
  result['size_gb'],
520
522
  result['format']
521
523
  )
522
- self.app.show_success_message(f"Volume [b]{result['name']} {result['size_gb']}Gb {result['format']}[/b] created successfully.")
524
+ self.app.show_success_message(SuccessMessages.VOLUME_CREATED_SUCCESSFULLY_TEMPLATE.format(name=result['name'], size_gb=result['size_gb'], format=result['format']))
523
525
  storage_manager.list_storage_volumes.cache_clear()
524
526
  pool_node.add_leaf(result['name'])
525
527
 
@@ -536,7 +538,7 @@ class ServerPrefModal(BaseModal[None]):
536
538
 
537
539
  volume_path = result.get('path')
538
540
  if not volume_path or not os.path.exists(volume_path):
539
- self.app.show_error_message(f"Invalid or non-existent path: {volume_path}")
541
+ self.app.show_error_message(ErrorMessages.INVALID_OR_NON_EXISTENT_PATH_TEMPLATE.format(path=volume_path))
540
542
  return
541
543
 
542
544
  volume_dir = os.path.dirname(volume_path)
@@ -547,11 +549,10 @@ class ServerPrefModal(BaseModal[None]):
547
549
  existing_pool.refresh(0)
548
550
  storage_manager.list_storage_volumes.cache_clear()
549
551
  self.app.show_success_message(
550
- f"A pool named '{existing_pool.name()}' already manages this directory.\n"
551
- "Refreshed pool to include the new volume."
552
+ SuccessMessages.POOL_MANAGED_BY_EXISTING_POOL_TEMPLATE.format(pool_name=existing_pool.name())
552
553
  )
553
554
  except libvirt.libvirtError as e:
554
- self.app.show_error_message(f"Error refreshing pool: {e}")
555
+ self.app.show_error_message(ErrorMessages.ERROR_REFRESHING_POOL_TEMPLATE.format(error=e))
555
556
 
556
557
  self._load_storage_pools(expand_pools=[existing_pool.name()])
557
558
  return
@@ -563,17 +564,16 @@ class ServerPrefModal(BaseModal[None]):
563
564
  try:
564
565
  storage_manager.create_storage_pool(self.conn, new_pool_name, 'dir', volume_dir)
565
566
  self.app.show_success_message(
566
- f"Storage pool '{new_pool_name}' created for directory:\n{volume_dir}"
567
+ SuccessMessages.STORAGE_POOL_CREATED_TEMPLATE.format(name=new_pool_name)
567
568
  )
568
569
  storage_manager.list_storage_pools.cache_clear()
569
570
  self._load_storage_pools(expand_pools=[new_pool_name])
570
571
  except Exception as e:
571
- self.app.show_error_message(f"Error creating storage pool: {e}")
572
+ self.app.show_error_message(ErrorMessages.ERROR_CREATING_STORAGE_POOL_FOR_DIR_TEMPLATE.format(error=e))
572
573
 
573
574
  self.app.push_screen(
574
575
  ConfirmationDialog(
575
- f"No storage pool exists for the directory:\n'{volume_dir}'.\n\n"
576
- f"Create a new pool named '{new_pool_name}'?"
576
+ ErrorMessages.NO_POOL_FOR_DIRECTORY_CONFIRM_TEMPLATE.format(directory=volume_dir, pool_name=new_pool_name)
577
577
  ),
578
578
  on_confirm_create
579
579
  )
@@ -606,14 +606,14 @@ class ServerPrefModal(BaseModal[None]):
606
606
  if confirmed:
607
607
  try:
608
608
  storage_manager.delete_storage_pool(pool)
609
- self.app.show_success_message(f"Storage pool '{pool_name}' deleted successfully.")
609
+ self.app.show_success_message(SuccessMessages.STORAGE_POOL_DELETED_SUCCESSFULLY_TEMPLATE.format(pool_name=pool_name))
610
610
  storage_manager.list_storage_pools.cache_clear()
611
611
  self._load_storage_pools() # Refresh the tree
612
612
  except Exception as e:
613
- self.app.show_error_message(str(e))
613
+ self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
614
614
 
615
615
  self.app.push_screen(
616
- ConfirmationDialog(f"Are you sure you want to delete storage pool:\n' {pool_name}'\nThis will delete the pool definition but not the data on it."), on_confirm)
616
+ ConfirmationDialog(ErrorMessages.DELETE_STORAGE_POOL_CONFIRMATION_TEMPLATE.format(pool_name=pool_name)), on_confirm)
617
617
 
618
618
  @on(Button.Pressed, "#del-vol-btn")
619
619
  def on_delete_volume_button_pressed(self, event: Button.Pressed) -> None:
@@ -632,7 +632,7 @@ class ServerPrefModal(BaseModal[None]):
632
632
  if confirmed:
633
633
  try:
634
634
  storage_manager.delete_volume(vol)
635
- self.app.show_success_message(f"Volume '{vol_name}' deleted successfully.")
635
+ self.app.show_success_message(SuccessMessages.VOLUME_DELETED_SUCCESSFULLY_TEMPLATE.format(volume_name=vol_name))
636
636
  # Clear the cache to force a refresh
637
637
  list_storage_volumes.cache_clear()
638
638
  # Refresh the parent node
@@ -642,10 +642,10 @@ class ServerPrefModal(BaseModal[None]):
642
642
  parent_node.add_leaf("No volumes")
643
643
 
644
644
  except Exception as e:
645
- self.app.show_error_message(str(e))
645
+ self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
646
646
 
647
647
  self.app.push_screen(
648
- ConfirmationDialog(f"Are you sure you want to delete volume:\n'{vol_name}'"),
648
+ ConfirmationDialog(ErrorMessages.DELETE_VOLUME_CONFIRMATION_TEMPLATE.format(vol_name=vol_name)),
649
649
  on_confirm
650
650
  )
651
651
 
@@ -661,7 +661,7 @@ class ServerPrefModal(BaseModal[None]):
661
661
 
662
662
  volume_name = node_data.get('name')
663
663
  if not tree.cursor_node.parent or not tree.cursor_node.parent.data:
664
- self.app.show_error_message("Could not determine the source pool.")
664
+ self.app.show_error_message(ErrorMessages.COULD_NOT_DETERMINE_SOURCE_POOL)
665
665
  return
666
666
  source_pool_name = tree.cursor_node.parent.data.get('name')
667
667
 
@@ -717,7 +717,7 @@ class ServerPrefModal(BaseModal[None]):
717
717
  self._load_storage_pools()
718
718
 
719
719
  return {
720
- "message": f"Volume '{volume_name}' moved to pool '{dest_pool_name}'.",
720
+ "message": SuccessMessages.VOLUME_MOVED_SUCCESSFULLY_TEMPLATE.format(volume_name=volume_name, dest_pool_name=dest_pool_name),
721
721
  "source_pool": source_pool_name,
722
722
  "dest_pool": dest_pool_name,
723
723
  "updated_vms": updated_vms
@@ -759,10 +759,10 @@ class ServerPrefModal(BaseModal[None]):
759
759
  updated_vms = result.get("updated_vms", [])
760
760
  if updated_vms:
761
761
  vm_list = ", ".join(updated_vms)
762
- self.app.show_success_message(f"Updated VM configurations for: {vm_list}")
762
+ self.app.show_success_message(SuccessMessages.UPDATED_VM_CONFIGURATIONS_TEMPLATE.format(vm_list=vm_list))
763
763
  self._load_storage_pools(expand_pools=[result["source_pool"], result["dest_pool"]])
764
764
  elif event.worker.state == "ERROR":
765
- self.app.show_error_message(f"Move operation failed: {event.worker.error}")
765
+ self.app.show_error_message(ErrorMessages.MOVE_OPERATION_FAILED_TEMPLATE.format(error=event.worker.error))
766
766
  self._load_storage_pools()
767
767
 
768
768
  @on(DataTable.RowSelected, "#networks-table")
@@ -795,10 +795,12 @@ class ServerPrefModal(BaseModal[None]):
795
795
  if net_info:
796
796
  try:
797
797
  set_network_active(self.conn, net_name, not net_info['active'])
798
- self.app.show_success_message(f"Network '{net_name}' is now {'inactive' if net_info['active'] else 'active'}.")
798
+ status = 'inactive' if net_info['active'] else 'active'
799
+ self.app.show_success_message(SuccessMessages.NETWORK_ACTIVATION_CHANGE_TEMPLATE.format(net_name=net_name, status=status))
800
+ list_networks.cache_clear()
799
801
  self._load_networks()
800
802
  except Exception as e:
801
- self.app.show_error_message(str(e))
803
+ self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
802
804
 
803
805
  @on(Button.Pressed, "#toggle-net-autostart-btn")
804
806
  def on_toggle_net_autostart_pressed(self, event: Button.Pressed) -> None:
@@ -813,10 +815,12 @@ class ServerPrefModal(BaseModal[None]):
813
815
  if net_info:
814
816
  try:
815
817
  set_network_autostart(self.conn, net_name, not net_info['autostart'])
816
- self.app.show_success_message(f"Autostart for network '{net_name}' is now {'off' if net_info['autostart'] else 'on'}.")
818
+ status = 'off' if net_info['autostart'] else 'on'
819
+ self.app.show_success_message(SuccessMessages.NETWORK_AUTOSTART_CHANGE_TEMPLATE.format(net_name=net_name, status=status))
820
+ list_networks.cache_clear()
817
821
  self._load_networks()
818
822
  except Exception as e:
819
- self.app.show_error_message(str(e))
823
+ self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
820
824
 
821
825
  def on_button_pressed(self, event: Button.Pressed) -> None:
822
826
  if event.button.id == "close-btn":
@@ -831,15 +835,15 @@ class ServerPrefModal(BaseModal[None]):
831
835
  try:
832
836
  conn = self.conn
833
837
  if conn is None:
834
- self.app.show_error_message("Not connected to libvirt.")
838
+ self.app.show_error_message(ErrorMessages.NOT_CONNECTED_TO_LIBVIRT)
835
839
  return
836
840
  net = conn.networkLookupByName(network_name)
837
841
  network_xml = net.XMLDesc(0)
838
842
  self.app.push_screen(NetworkXMLModal(network_name, network_xml))
839
843
  except libvirt.libvirtError as e:
840
- self.app.show_error_message(f"Error getting network XML: {e}")
844
+ self.app.show_error_message(ErrorMessages.ERROR_GETTING_NETWORK_XML_TEMPLATE.format(error=e))
841
845
  except Exception as e:
842
- self.app.show_error_message(f"An unexpected error occurred: {e}")
846
+ self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
843
847
 
844
848
  elif event.button.id == "edit-net-btn":
845
849
  table = self.query_one("#networks-table", DataTable)
@@ -851,17 +855,19 @@ class ServerPrefModal(BaseModal[None]):
851
855
 
852
856
  network_info = get_network_info(self.conn, network_name)
853
857
  if not network_info:
854
- self.app.show_error_message(f"Could not retrieve info for network '{network_name}'.")
858
+ self.app.show_error_message(ErrorMessages.COULD_NOT_RETRIEVE_NETWORK_INFO_TEMPLATE.format(network_name=network_name))
855
859
  return
856
860
 
857
861
  def on_create(success: bool):
858
862
  if success:
863
+ list_networks.cache_clear()
859
864
  self._load_networks()
860
865
  self.app.push_screen(AddEditNetworkModal(self.conn, network_info=network_info), on_create)
861
866
 
862
867
  elif event.button.id == "add-net-btn":
863
868
  def on_create(success: bool):
864
869
  if success:
870
+ list_networks.cache_clear()
865
871
  self._load_networks()
866
872
  self.app.push_screen(AddEditNetworkModal(self.conn), on_create)
867
873
 
@@ -874,19 +880,20 @@ class ServerPrefModal(BaseModal[None]):
874
880
  network_name = row_key.value
875
881
  vms_using_network = get_vms_using_network(self.conn, network_name)
876
882
 
877
- confirm_message = f"Are you sure you want to delete network:\n'{network_name}'"
883
+ confirm_message = ErrorMessages.DELETE_NETWORK_CONFIRM_TEMPLATE.format(network_name=network_name)
878
884
  if vms_using_network:
879
885
  vm_list = ", ".join(vms_using_network)
880
- confirm_message += f"\nThis network is currently in use by the following VMs:\n{vm_list}."
886
+ confirm_message += ErrorMessages.NETWORK_IN_USE_WARNING_TEMPLATE.format(vm_list=vm_list)
881
887
 
882
888
  def on_confirm(confirmed: bool) -> None:
883
889
  if confirmed:
884
890
  try:
885
891
  delete_network(self.conn, network_name)
886
- self.app.show_success_message(f"Network '{network_name}' deleted successfully.")
892
+ self.app.show_success_message(SuccessMessages.NETWORK_DELETED_SUCCESSFULLY_TEMPLATE.format(network_name=network_name))
893
+ list_networks.cache_clear()
887
894
  self._load_networks()
888
895
  except Exception as e:
889
- self.app.show_error_message(f"Error deleting network '{network_name}': {e}")
896
+ self.app.show_error_message(ErrorMessages.ERROR_DELETING_NETWORK_TEMPLATE.format(network_name=network_name, error=e))
890
897
 
891
898
  self.app.push_screen(
892
899
  ConfirmationDialog(confirm_message), on_confirm
@@ -20,6 +20,7 @@ from textual.widgets import (
20
20
  )
21
21
 
22
22
  from .base_modals import BaseDialog, BaseModal
23
+ from ..constants import ButtonLabels
23
24
 
24
25
 
25
26
  def _sanitize_message(message: str) -> str:
@@ -93,11 +94,11 @@ class DirectorySelectionModal(BaseModal[str | None]):
93
94
 
94
95
  def compose(self) -> ComposeResult:
95
96
  with Vertical(id="directory-selection-dialog"):
96
- yield Label("Select a Directory")
97
+ yield Label(StaticText.SELECT_A_DIRECTORY)
97
98
  yield SafeDirectoryTree(self.start_path, id="dir-tree")
98
99
  with Horizontal():
99
- yield Button("Select", variant="primary", id="select-btn", disabled=True)
100
- yield Button("Cancel", variant="default", id="cancel-btn")
100
+ yield Button(ButtonLabels.SELECT, variant="primary", id="select-btn", disabled=True)
101
+ yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn")
101
102
 
102
103
  def on_mount(self) -> None:
103
104
  self.query_one(SafeDirectoryTree).focus()
@@ -126,11 +127,11 @@ class FileSelectionModal(BaseModal[str | None]):
126
127
 
127
128
  def compose(self) -> ComposeResult:
128
129
  with Vertical(id="file-selection-dialog", classes="file-selection-dialog"):
129
- yield Label("Select a File")
130
+ yield Label(StaticText.SELECT_A_FILE)
130
131
  yield SafeDirectoryTree(self.start_path, id="file-tree")
131
132
  with Horizontal():
132
- yield Button("Select", variant="primary", id="select-btn", disabled=True)
133
- yield Button("Cancel", variant="default", id="cancel-btn")
133
+ yield Button(ButtonLabels.SELECT, variant="primary", id="select-btn", disabled=True)
134
+ yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn")
134
135
 
135
136
  def on_mount(self) -> None:
136
137
  self.query_one(SafeDirectoryTree).focus()
@@ -207,8 +208,8 @@ class ConfirmationDialog(BaseDialog[bool]):
207
208
  yield Vertical(
208
209
  Markdown(self.prompt, id="question"),
209
210
  Horizontal(
210
- Button("Yes", variant="error", id="yes", classes="dialog-buttons"),
211
- Button("No", variant="primary", id="no", classes="dialog-buttons"),
211
+ Button(ButtonLabels.YES, variant="error", id="yes", classes="dialog-buttons"),
212
+ Button(ButtonLabels.NO, variant="primary", id="no", classes="dialog-buttons"),
212
213
  id="dialog-buttons",
213
214
  ),
214
215
  id="dialog",
@@ -237,7 +238,7 @@ class InfoModal(BaseModal[None]):
237
238
  yield Label(self._title, classes="dialog-title")
238
239
  yield Markdown(self._message, classes="dialog-message")
239
240
  with Horizontal(classes="dialog-buttons"):
240
- yield Button("OK", variant="primary", id="ok-btn")
241
+ yield Button(ButtonLabels.OK, variant="primary", id="ok-btn")
241
242
 
242
243
  def on_button_pressed(self, event: Button.Pressed) -> None:
243
244
  self.dismiss()
@@ -11,6 +11,7 @@ from textual.widgets import (
11
11
  from textual.containers import Horizontal, Vertical
12
12
  from textual.screen import ModalScreen
13
13
  from textual import on
14
+ from ..constants import StaticText
14
15
 
15
16
  class VirshShellScreen(ModalScreen):
16
17
  """Screen for an interactive virsh shell."""
@@ -26,8 +27,8 @@ class VirshShellScreen(ModalScreen):
26
27
  def compose(self) -> ComposeResult:
27
28
  with Vertical(id="virsh-shell-container"):
28
29
  yield Header()
29
- yield Label("Virsh Interactive Shell (esc to quit)", id="virsh-shell-title")
30
- yield Label("Note: This shell starts a new `virsh` process, which may require re-authentication for remote connections. Using SSH keys is recommended.", classes="virsh-shell-note")
30
+ yield Label(StaticText.VIRSH_SHELL_TITLE, id="virsh-shell-title")
31
+ yield Label(StaticText.VIRSH_SHELL_NOTE, classes="virsh-shell-note")
31
32
  yield TextArea(
32
33
  id="virsh-output",
33
34
  read_only=True,
@@ -6,6 +6,7 @@ from textual.containers import Horizontal, Vertical
6
6
  from textual.widgets import (
7
7
  Button, Input, Label, Checkbox,
8
8
  )
9
+ from ..constants import ErrorMessages, ButtonLabels, StaticText
9
10
  from .base_modals import BaseModal
10
11
 
11
12
  class AddEditVirtIOFSModal(BaseModal[dict | None]):
@@ -20,13 +21,13 @@ class AddEditVirtIOFSModal(BaseModal[dict | None]):
20
21
 
21
22
  def compose(self) -> ComposeResult:
22
23
  with Vertical(id="add-edit-virtiofs-dialog"):
23
- yield Label("Edit VirtIO-FS Mount" if self.is_edit else "Add VirtIO-FS Mount")
24
+ yield Label(StaticText.EDIT_VIRTIOFS_MOUNT if self.is_edit else StaticText.ADD_VIRTIOFS_MOUNT)
24
25
  yield Input(placeholder="Source Path (e.g., /mnt/share)", id="virtiofs-source-input", value=self.source_path)
25
26
  yield Input(placeholder="Target Path (e.g., /share)", id="virtiofs-target-input", value=self.target_path)
26
- yield Checkbox("Export filesystem as readonly mount", id="virtiofs-readonly-checkbox", value=self.readonly)
27
+ yield Checkbox(StaticText.EXPORT_READONLY_MOUNT, id="virtiofs-readonly-checkbox", value=self.readonly)
27
28
  with Horizontal():
28
- yield Button("Save" if self.is_edit else "Add", variant="primary", id="save-add-btn", classes="Buttonpage")
29
- yield Button("Cancel", variant="default", id="cancel-btn", classes="Buttonpage")
29
+ yield Button(ButtonLabels.SAVE if self.is_edit else ButtonLabels.ADD, variant="primary", id="save-add-btn", classes="Buttonpage")
30
+ yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn", classes="Buttonpage")
30
31
 
31
32
  def on_button_pressed(self, event: Button.Pressed) -> None:
32
33
  if event.button.id == "save-add-btn":
@@ -35,7 +36,7 @@ class AddEditVirtIOFSModal(BaseModal[dict | None]):
35
36
  readonly = self.query_one("#virtiofs-readonly-checkbox", Checkbox).value
36
37
 
37
38
  if not source_path or not target_path:
38
- self.app.show_error_message("Source Path and Target Path cannot be empty.")
39
+ self.app.show_error_message(ErrorMessages.VIRTIOFS_PATH_EMPTY)
39
40
  return
40
41
 
41
42
  self.dismiss({'source_path': source_path, 'target_path': target_path, 'readonly': readonly})
@@ -6,6 +6,7 @@ from textual.containers import Vertical, Horizontal, ScrollableContainer
6
6
  from textual.widgets import Button, Markdown
7
7
  from textual import on
8
8
  from .base_modals import BaseModal
9
+ from ..constants import ButtonLabels
9
10
 
10
11
  VM_TYPE_INFO_TEXT = """
11
12
  | [Storage Settings](https://www.qemu.org/docs/master/system/qemu-block-drivers.html) | Secure VM | Computation | Desktop (Linux) | Windows | Win Legacy | Server |
@@ -55,7 +56,7 @@ class VMTypeInfoModal(BaseModal[None]):
55
56
  def compose(self) -> ComposeResult:
56
57
  with ScrollableContainer(id="howto-vmtype-dialog", classes="howto-dialog"):
57
58
  yield Markdown(VM_TYPE_INFO_TEXT, id="howto-vmtype-markdown")
58
- yield Button("Close", id="close-btn", variant="primary")
59
+ yield Button(ButtonLabels.CLOSE, id="close-btn", variant="primary")
59
60
 
60
61
  @on(Button.Pressed)
61
62
  def on_button_pressed(self, event: Button.Pressed) -> None: