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
vmanager/vmcard.py CHANGED
@@ -53,8 +53,9 @@ from .utils import (
53
53
  remote_viewer_cmd,
54
54
  )
55
55
  from .constants import (
56
- ButtonLabels, ButtonIds, TabTitles, StatusText,
57
- SparklineLabels, ErrorMessages, DialogMessages, VmAction
56
+ ButtonLabels, TabTitles, StatusText,
57
+ SparklineLabels, ErrorMessages, DialogMessages, VmAction,
58
+ WarningMessages, SuccessMessages
58
59
  )
59
60
 
60
61
  class VMCardActions(Static):
@@ -63,29 +64,29 @@ class VMCardActions(Static):
63
64
  super().__init__()
64
65
 
65
66
  def compose(self):
66
- self.card.ui[ButtonIds.START] = Button(ButtonLabels.START, id=ButtonIds.START, variant="success")
67
- self.card.ui[ButtonIds.SHUTDOWN] = Button(ButtonLabels.SHUTDOWN, id=ButtonIds.SHUTDOWN, variant="primary")
68
- self.card.ui[ButtonIds.STOP] = Button(ButtonLabels.FORCE_OFF, id=ButtonIds.STOP, variant="error")
69
- self.card.ui[ButtonIds.PAUSE] = Button(ButtonLabels.PAUSE, id=ButtonIds.PAUSE, variant="primary")
70
- self.card.ui[ButtonIds.RESUME] = Button(ButtonLabels.RESUME, id=ButtonIds.RESUME, variant="success")
71
- self.card.ui[ButtonIds.CONFIGURE_BUTTON] = Button(ButtonLabels.CONFIGURE, id=ButtonIds.CONFIGURE_BUTTON, variant="primary")
72
- self.card.ui[ButtonIds.WEB_CONSOLE] = Button(ButtonLabels.WEB_CONSOLE, id=ButtonIds.WEB_CONSOLE, variant="default")
73
- self.card.ui[ButtonIds.CONNECT] = Button(ButtonLabels.CONNECT, id=ButtonIds.CONNECT, variant="default")
67
+ self.card.ui["start"] = Button(ButtonLabels.START, id="start", variant="success")
68
+ self.card.ui["shutdown"] = Button(ButtonLabels.SHUTDOWN, id="shutdown", variant="primary")
69
+ self.card.ui["stop"] = Button(ButtonLabels.FORCE_OFF, id="stop", variant="error")
70
+ self.card.ui["pause"] = Button(ButtonLabels.PAUSE, id="pause", variant="primary")
71
+ self.card.ui["resume"] = Button(ButtonLabels.RESUME, id="resume", variant="success")
72
+ self.card.ui["configure-button"] = Button(ButtonLabels.CONFIGURE, id="configure-button", variant="primary")
73
+ self.card.ui["web_console"] = Button(ButtonLabels.WEB_CONSOLE, id="web_console", variant="default")
74
+ self.card.ui["connect"] = Button(ButtonLabels.CONNECT, id="connect", variant="default")
74
75
 
75
- self.card.ui[ButtonIds.SNAPSHOT_TAKE] = Button(ButtonLabels.SNAPSHOT, id=ButtonIds.SNAPSHOT_TAKE, variant="primary")
76
- self.card.ui[ButtonIds.SNAPSHOT_RESTORE] = Button(ButtonLabels.RESTORE_SNAPSHOT, id=ButtonIds.SNAPSHOT_RESTORE, variant="primary")
77
- self.card.ui[ButtonIds.SNAPSHOT_DELETE] = Button(ButtonLabels.DELETE_SNAPSHOT, id=ButtonIds.SNAPSHOT_DELETE, variant="error")
76
+ self.card.ui["snapshot_take"] = Button(ButtonLabels.SNAPSHOT, id="snapshot_take", variant="primary")
77
+ self.card.ui["snapshot_restore"] = Button(ButtonLabels.RESTORE_SNAPSHOT, id="snapshot_restore", variant="primary")
78
+ self.card.ui["snapshot_delete"] = Button(ButtonLabels.DELETE_SNAPSHOT, id="snapshot_delete", variant="error")
78
79
 
79
- self.card.ui[ButtonIds.DELETE] = Button(ButtonLabels.DELETE, id=ButtonIds.DELETE, variant="error", classes="delete-button")
80
- self.card.ui[ButtonIds.CLONE] = Button(ButtonLabels.CLONE, id=ButtonIds.CLONE, classes="clone-button")
81
- self.card.ui[ButtonIds.MIGRATION] = Button(ButtonLabels.MIGRATION, id=ButtonIds.MIGRATION, variant="primary", classes="migration-button")
82
- self.card.ui[ButtonIds.XML] = Button(ButtonLabels.VIEW_XML, id=ButtonIds.XML)
83
- self.card.ui[ButtonIds.RENAME_BUTTON] = Button(ButtonLabels.RENAME, id=ButtonIds.RENAME_BUTTON, variant="primary", classes="rename-button")
80
+ self.card.ui["delete"] = Button(ButtonLabels.DELETE, id="delete", variant="error", classes="delete-button")
81
+ self.card.ui["clone"] = Button(ButtonLabels.CLONE, id="clone", classes="clone-button")
82
+ self.card.ui["migration"] = Button(ButtonLabels.MIGRATION, id="migration", variant="primary", classes="migration-button")
83
+ self.card.ui["xml"] = Button(ButtonLabels.VIEW_XML, id="xml")
84
+ self.card.ui["rename-button"] = Button(ButtonLabels.RENAME, id="rename-button", variant="primary", classes="rename-button")
84
85
 
85
- self.card.ui[ButtonIds.CREATE_OVERLAY] = Button(ButtonLabels.CREATE_OVERLAY, id=ButtonIds.CREATE_OVERLAY, variant="primary")
86
- self.card.ui[ButtonIds.COMMIT_DISK] = Button(ButtonLabels.COMMIT_DISK, id=ButtonIds.COMMIT_DISK, variant="error")
87
- self.card.ui[ButtonIds.DISCARD_OVERLAY] = Button(ButtonLabels.DISCARD_OVERLAY, id=ButtonIds.DISCARD_OVERLAY, variant="error")
88
- self.card.ui[ButtonIds.SNAP_OVERLAY_HELP] = Button(ButtonLabels.SNAP_OVERLAY_HELP, id=ButtonIds.SNAP_OVERLAY_HELP, variant="default")
86
+ self.card.ui["create_overlay"] = Button(ButtonLabels.CREATE_OVERLAY, id="create_overlay", variant="primary")
87
+ self.card.ui["commit_disk"] = Button(ButtonLabels.COMMIT_DISK, id="commit_disk", variant="error")
88
+ self.card.ui["discard_overlay"] = Button(ButtonLabels.DISCARD_OVERLAY, id="discard_overlay", variant="error")
89
+ self.card.ui["snap_overlay_help"] = Button(ButtonLabels.SNAP_OVERLAY_HELP, id="snap_overlay_help", variant="default")
89
90
 
90
91
  self.card.ui["tabbed_content"] = TabbedContent(id="button-container")
91
92
 
@@ -93,37 +94,37 @@ class VMCardActions(Static):
93
94
  with TabPane(TabTitles.MANAGE, id="manage-tab"):
94
95
  with Horizontal():
95
96
  with Vertical():
96
- yield self.card.ui[ButtonIds.START]
97
- yield self.card.ui[ButtonIds.SHUTDOWN]
98
- yield self.card.ui[ButtonIds.STOP]
99
- yield self.card.ui[ButtonIds.PAUSE]
100
- yield self.card.ui[ButtonIds.RESUME]
97
+ yield self.card.ui["start"]
98
+ yield self.card.ui["shutdown"]
99
+ yield self.card.ui["stop"]
100
+ yield self.card.ui["pause"]
101
+ yield self.card.ui["resume"]
101
102
  with Vertical():
102
- yield self.card.ui[ButtonIds.CONFIGURE_BUTTON]
103
- yield self.card.ui[ButtonIds.WEB_CONSOLE]
104
- yield self.card.ui[ButtonIds.CONNECT]
103
+ yield self.card.ui["configure-button"]
104
+ yield self.card.ui["web_console"]
105
+ yield self.card.ui["connect"]
105
106
  with TabPane(self.card._get_snapshot_tab_title(num_snapshots=0), id="snapshot-tab"):
106
107
  with Horizontal():
107
108
  with Vertical():
108
- yield self.card.ui[ButtonIds.SNAPSHOT_TAKE]
109
- yield self.card.ui[ButtonIds.SNAPSHOT_RESTORE]
110
- yield self.card.ui[ButtonIds.SNAPSHOT_DELETE]
109
+ yield self.card.ui["snapshot_take"]
110
+ yield self.card.ui["snapshot_restore"]
111
+ yield self.card.ui["snapshot_delete"]
111
112
  with Vertical():
112
- yield self.card.ui[ButtonIds.CREATE_OVERLAY]
113
- yield self.card.ui[ButtonIds.COMMIT_DISK]
114
- yield self.card.ui[ButtonIds.DISCARD_OVERLAY]
115
- yield self.card.ui[ButtonIds.SNAP_OVERLAY_HELP]
113
+ yield self.card.ui["create_overlay"]
114
+ yield self.card.ui["commit_disk"]
115
+ yield self.card.ui["discard_overlay"]
116
+ yield self.card.ui["snap_overlay_help"]
116
117
  with TabPane(TabTitles.OTHER, id="special-tab"):
117
118
  with Horizontal():
118
119
  with Vertical():
119
- yield self.card.ui[ButtonIds.DELETE]
120
+ yield self.card.ui["delete"]
120
121
  yield Static(classes="button-separator")
121
- yield self.card.ui[ButtonIds.CLONE]
122
- yield self.card.ui[ButtonIds.MIGRATION]
122
+ yield self.card.ui["clone"]
123
+ yield self.card.ui["migration"]
123
124
  with Vertical():
124
- yield self.card.ui[ButtonIds.XML]
125
+ yield self.card.ui["xml"]
125
126
  yield Static(classes="button-separator")
126
- yield self.card.ui[ButtonIds.RENAME_BUTTON]
127
+ yield self.card.ui["rename-button"]
127
128
 
128
129
 
129
130
  class VMCard(Static):
@@ -228,7 +229,7 @@ class VMCard(Static):
228
229
  self.webc_status_indicator = new_indicator
229
230
 
230
231
  # Update button label and style
231
- web_console_button = self.ui.get(ButtonIds.WEB_CONSOLE)
232
+ web_console_button = self.ui.get("web_console")
232
233
  if web_console_button:
233
234
  if webc_is_running:
234
235
  web_console_button.label = "Show Console"
@@ -249,7 +250,7 @@ class VMCard(Static):
249
250
  def compose(self):
250
251
  self.ui["checkbox"] = Checkbox("", id="vm-select-checkbox", classes="vm-select-checkbox", value=self.is_selected, tooltip="Select VM")
251
252
  self.ui["vmname"] = Static(self._get_vm_display_name(), id="vmname", classes="vmname")
252
- self.ui["status"] = Static(f"{self.status}{self.webc_status_indicator}", id="status", classes=self.status.lower())
253
+ self.ui["status"] = Static(f"{self.status}{self.webc_status_indicator}", id="status")
253
254
 
254
255
  # Create all sparkline components
255
256
  self.ui["cpu_label"] = Static("", classes="sparkline-label")
@@ -864,8 +865,7 @@ class VMCard(Static):
864
865
  self._update_fast_buttons()
865
866
  self._update_webc_status()
866
867
 
867
- # Trigger background fetch for heavy data (snapshots, overlays) only if actions are visible
868
- if self.ui.get(ButtonIds.RENAME_BUTTON):
868
+ if self.ui.get("rename-button"):
869
869
  # Check if collapsible is expanded before fetching heavy data
870
870
  collapsible = self.ui.get("collapsible")
871
871
  if collapsible and not collapsible.collapsed:
@@ -885,31 +885,31 @@ class VMCard(Static):
885
885
  is_pmsuspended = self.status == StatusText.PMSUSPENDED
886
886
  is_blocked = self.status == StatusText.BLOCKED
887
887
 
888
- if not self.ui.get(ButtonIds.RENAME_BUTTON):
888
+ if not self.ui.get("rename-button"):
889
889
  return
890
890
 
891
- self.ui[ButtonIds.START].display = is_stopped
892
- self.ui[ButtonIds.SHUTDOWN].display = is_running or is_blocked
893
- self.ui[ButtonIds.STOP].display = is_running or is_paused or is_pmsuspended or is_blocked
894
- self.ui[ButtonIds.DELETE].display = is_running or is_paused or is_stopped or is_pmsuspended or is_blocked
895
- self.ui[ButtonIds.CLONE].display = is_stopped
896
- self.ui[ButtonIds.MIGRATION].display = not is_loading
897
- self.ui[ButtonIds.RENAME_BUTTON].display = is_stopped
898
- self.ui[ButtonIds.PAUSE].display = is_running
899
- self.ui[ButtonIds.RESUME].display = is_paused or is_pmsuspended
900
- self.ui[ButtonIds.CONNECT].display = self.app.r_viewer_available
901
- self.ui[ButtonIds.WEB_CONSOLE].display = (is_running or is_paused or is_blocked)
902
- self.ui[ButtonIds.CONFIGURE_BUTTON].display = not is_loading
903
- self.ui[ButtonIds.SNAP_OVERLAY_HELP].display = not is_loading
904
- self.ui[ButtonIds.SNAPSHOT_TAKE].display = not is_loading #is_running or is_paused
905
- self.ui[ButtonIds.SNAPSHOT_RESTORE].display = not is_running and not is_loading and not is_blocked
906
-
907
- xml_button = self.ui[ButtonIds.XML]
891
+ self.ui["start"].display = is_stopped
892
+ self.ui["shutdown"].display = is_running or is_blocked
893
+ self.ui["stop"].display = is_running or is_paused or is_pmsuspended or is_blocked
894
+ self.ui["delete"].display = is_running or is_paused or is_stopped or is_pmsuspended or is_blocked
895
+ self.ui["clone"].display = is_stopped
896
+ self.ui["migration"].display = not is_loading
897
+ self.ui["rename-button"].display = is_stopped
898
+ self.ui["pause"].display = is_running
899
+ self.ui["resume"].display = is_paused or is_pmsuspended
900
+ self.ui["connect"].display = self.app.r_viewer_available
901
+ self.ui["web_console"].display = (is_running or is_paused or is_blocked)
902
+ self.ui["configure-button"].display = not is_loading
903
+ self.ui["snap_overlay_help"].display = not is_loading
904
+ self.ui["snapshot_take"].display = not is_loading #is_running or is_paused
905
+ self.ui["snapshot_restore"].display = not is_running and not is_loading and not is_blocked
906
+
907
+ xml_button = self.ui["xml"]
908
908
  if is_stopped:
909
- xml_button.label = "Edit XML"
909
+ xml_button.label = ButtonLabels.EDIT_XML
910
910
  self.stats_view_mode = "resources"
911
911
  else:
912
- xml_button.label = "View XML"
912
+ xml_button.label = ButtonLabels.VIEW_XML
913
913
  xml_button.display = not is_loading
914
914
 
915
915
  def _fetch_actions_state_worker(self):
@@ -1004,7 +1004,7 @@ class VMCard(Static):
1004
1004
 
1005
1005
  def _update_slow_buttons(self, snapshot_summary: dict, has_overlay: bool):
1006
1006
  """Updates buttons that rely on heavy state."""
1007
- if not self.ui.get(ButtonIds.RENAME_BUTTON):
1007
+ if not self.ui.get("rename-button"):
1008
1008
  return
1009
1009
 
1010
1010
  snapshot_count = snapshot_summary.get('count', 0)
@@ -1031,12 +1031,12 @@ class VMCard(Static):
1031
1031
 
1032
1032
  has_snapshots = snapshot_count > 0
1033
1033
 
1034
- self.ui[ButtonIds.SNAPSHOT_RESTORE].display = has_snapshots and not is_running and not is_loading and not is_blocked
1035
- self.ui[ButtonIds.SNAPSHOT_DELETE].display = has_snapshots
1034
+ self.ui["snapshot_restore"].display = has_snapshots and not is_running and not is_loading and not is_blocked
1035
+ self.ui["snapshot_delete"].display = has_snapshots
1036
1036
 
1037
- self.ui[ButtonIds.COMMIT_DISK].display = (is_running or is_blocked) and has_overlay
1038
- self.ui[ButtonIds.DISCARD_OVERLAY].display = is_stopped and has_overlay
1039
- self.ui[ButtonIds.CREATE_OVERLAY].display = is_stopped and not has_overlay
1037
+ self.ui["commit_disk"].display = (is_running or is_blocked) and has_overlay
1038
+ self.ui["discard_overlay"].display = is_stopped and has_overlay
1039
+ self.ui["create_overlay"].display = is_stopped and not has_overlay
1040
1040
 
1041
1041
  self.update_snapshot_tab_title(snapshot_count)
1042
1042
 
@@ -1046,39 +1046,43 @@ class VMCard(Static):
1046
1046
  status_widget.remove_class("stopped", "running", "paused", "loading", "pmsuspended", "blocked")
1047
1047
  if self.status == StatusText.LOADING:
1048
1048
  status_widget.add_class("loading")
1049
+ elif self.status == StatusText.RUNNING:
1050
+ status_widget.add_class("running")
1051
+ elif self.status == StatusText.STOPPED:
1052
+ status_widget.add_class("stopped")
1053
+ elif self.status == StatusText.PAUSED:
1054
+ status_widget.add_class("paused")
1049
1055
  elif self.status == StatusText.PMSUSPENDED:
1050
1056
  status_widget.add_class("pmsuspended")
1051
1057
  elif self.status == StatusText.BLOCKED:
1052
1058
  status_widget.add_class("blocked")
1053
- else:
1054
- status_widget.add_class(self.status.lower())
1055
1059
 
1056
1060
  def on_button_pressed(self, event: Button.Pressed) -> None:
1057
1061
  """Handle button presses."""
1058
- if event.button.id == ButtonIds.START:
1062
+ if event.button.id == "start":
1059
1063
  self.post_message(VmActionRequest(self.internal_id, VmAction.START))
1060
1064
  return
1061
1065
 
1062
1066
  button_handlers = {
1063
- ButtonIds.SHUTDOWN: self._handle_shutdown_button,
1064
- ButtonIds.STOP: self._handle_stop_button,
1065
- ButtonIds.PAUSE: self._handle_pause_button,
1066
- ButtonIds.RESUME: self._handle_resume_button,
1067
- ButtonIds.XML: self._handle_xml_button,
1068
- ButtonIds.CONNECT: self._handle_connect_button,
1069
- ButtonIds.WEB_CONSOLE: self._handle_web_console_button,
1070
- ButtonIds.SNAPSHOT_TAKE: self._handle_snapshot_take_button,
1071
- ButtonIds.SNAPSHOT_RESTORE: self._handle_snapshot_restore_button,
1072
- ButtonIds.SNAPSHOT_DELETE: self._handle_snapshot_delete_button,
1073
- ButtonIds.DELETE: self._handle_delete_button,
1074
- ButtonIds.CLONE: self._handle_clone_button,
1075
- ButtonIds.MIGRATION: self._handle_migration_button,
1076
- ButtonIds.RENAME_BUTTON: self._handle_rename_button,
1077
- ButtonIds.CONFIGURE_BUTTON: self._handle_configure_button,
1078
- ButtonIds.CREATE_OVERLAY: self._handle_create_overlay,
1079
- ButtonIds.COMMIT_DISK: self._handle_commit_disk,
1080
- ButtonIds.DISCARD_OVERLAY: self._handle_discard_overlay,
1081
- ButtonIds.SNAP_OVERLAY_HELP: self._handle_overlay_help,
1067
+ "shutdown": self._handle_shutdown_button,
1068
+ "stop": self._handle_stop_button,
1069
+ "pause": self._handle_pause_button,
1070
+ "resume": self._handle_resume_button,
1071
+ "xml": self._handle_xml_button,
1072
+ "connect": self._handle_connect_button,
1073
+ "web_console": self._handle_web_console_button,
1074
+ "snapshot_take": self._handle_snapshot_take_button,
1075
+ "snapshot_restore": self._handle_snapshot_restore_button,
1076
+ "snapshot_delete": self._handle_snapshot_delete_button,
1077
+ "delete": self._handle_delete_button,
1078
+ "clone": self._handle_clone_button,
1079
+ "migration": self._handle_migration_button,
1080
+ "rename-button": self._handle_rename_button,
1081
+ "configure-button": self._handle_configure_button,
1082
+ "create_overlay": self._handle_create_overlay,
1083
+ "commit_disk": self._handle_commit_disk,
1084
+ "discard_overlay": self._handle_discard_overlay,
1085
+ "snap_overlay_help": self._handle_overlay_help,
1082
1086
  }
1083
1087
  handler = button_handlers.get(event.button.id)
1084
1088
  if handler:
@@ -1100,7 +1104,7 @@ class VMCard(Static):
1100
1104
  valid_disks = [d['path'] for d in disks if d.get('device_type') == 'disk']
1101
1105
 
1102
1106
  if not valid_disks:
1103
- self.app.show_error_message("No suitable disks found for overlay.")
1107
+ self.app.show_error_message(ErrorMessages.NO_SUITABLE_DISKS_FOR_OVERLAY)
1104
1108
  return
1105
1109
 
1106
1110
  target_disk = valid_disks[0]
@@ -1120,29 +1124,29 @@ class VMCard(Static):
1120
1124
  return
1121
1125
 
1122
1126
  if was_modified:
1123
- self.app.show_success_message(f"Input sanitized: '{overlay_name_raw}' changed to '{overlay_name}'")
1127
+ self.app.show_success_message(SuccessMessages.INPUT_SANITIZED.format(original_input=overlay_name_raw, sanitized_input=overlay_name))
1124
1128
 
1125
1129
  if not overlay_name:
1126
- self.app.show_error_message("Overlay volume name cannot be empty after sanitization.")
1130
+ self.app.show_error_message(ErrorMessages.OVERLAY_NAME_EMPTY_AFTER_SANITIZATION)
1127
1131
  return
1128
1132
 
1129
1133
  self.app.vm_service.suppress_vm_events(self.internal_id)
1130
1134
  try:
1131
1135
  create_external_overlay(self.vm, target_disk, overlay_name)
1132
- self.app.show_success_message(f"Overlay [b]{overlay_name}[/b] created and attached.")
1136
+ self.app.show_success_message(SuccessMessages.OVERLAY_CREATED.format(overlay_name=overlay_name))
1133
1137
  self.app.vm_service.invalidate_vm_state_cache(self.internal_id)
1134
1138
  self._boot_device_checked = False
1135
1139
  self.post_message(VmCardUpdateRequest(self.internal_id))
1136
1140
  self.update_button_layout()
1137
1141
  except Exception as e:
1138
- self.app.show_error_message(f"Error creating overlay: {e}")
1142
+ self.app.show_error_message(ErrorMessages.ERROR_CREATING_OVERLAY_TEMPLATE.format(error=e))
1139
1143
  finally:
1140
1144
  self.app.vm_service.unsuppress_vm_events(self.internal_id)
1141
1145
 
1142
1146
  self.app.push_screen(InputModal("Enter name for new overlay volume:", default_name, restrict=r"[a-zA-Z0-9_-]*"), on_name_input)
1143
1147
 
1144
1148
  except Exception as e:
1145
- self.app.show_error_message(f"Error preparing overlay creation: {e}")
1149
+ self.app.show_error_message(ErrorMessages.ERROR_PREPARING_OVERLAY_CREATION_TEMPLATE.format(error=e))
1146
1150
 
1147
1151
  def _handle_discard_overlay(self, event: Button.Pressed) -> None:
1148
1152
  """Handles the discard overlay button press."""
@@ -1150,7 +1154,7 @@ class VMCard(Static):
1150
1154
  overlay_disks = get_overlay_disks(self.vm)
1151
1155
 
1152
1156
  if not overlay_disks:
1153
- self.app.show_error_message("No overlay disks found.")
1157
+ self.app.show_error_message(ErrorMessages.NO_OVERLAY_DISKS_FOUND)
1154
1158
  return
1155
1159
 
1156
1160
  def proceed_with_discard(target_disk: str | None):
@@ -1162,13 +1166,13 @@ class VMCard(Static):
1162
1166
  self.app.vm_service.suppress_vm_events(self.internal_id)
1163
1167
  try:
1164
1168
  discard_overlay(self.vm, target_disk)
1165
- self.app.show_success_message(f"Overlay for [b]{target_disk}[/b] discarded and reverted to base image.")
1169
+ self.app.show_success_message(SuccessMessages.OVERLAY_DISCARDED.format(target_disk=target_disk))
1166
1170
  self.app.vm_service.invalidate_vm_state_cache(self.internal_id)
1167
1171
  self._boot_device_checked = False
1168
1172
  self.post_message(VmCardUpdateRequest(self.internal_id))
1169
1173
  self.update_button_layout()
1170
1174
  except Exception as e:
1171
- self.app.show_error_message(f"Error discarding overlay: {e}")
1175
+ self.app.show_error_message(ErrorMessages.ERROR_DISCARDING_OVERLAY_TEMPLATE.format(error=e))
1172
1176
  finally:
1173
1177
  self.app.vm_service.unsuppress_vm_events(self.internal_id)
1174
1178
 
@@ -1186,7 +1190,7 @@ class VMCard(Static):
1186
1190
  )
1187
1191
 
1188
1192
  except Exception as e:
1189
- self.app.show_error_message(f"Error preparing discard overlay: {e}")
1193
+ self.app.show_error_message(ErrorMessages.ERROR_PREPARING_DISCARD_OVERLAY_TEMPLATE.format(error=e))
1190
1194
 
1191
1195
 
1192
1196
  def _handle_commit_disk(self, event: Button.Pressed) -> None:
@@ -1202,7 +1206,7 @@ class VMCard(Static):
1202
1206
  break
1203
1207
 
1204
1208
  if not target_disk:
1205
- self.app.show_error_message("No disks found to commit.")
1209
+ self.app.show_error_message(ErrorMessages.NO_DISKS_FOUND_TO_COMMIT)
1206
1210
  return
1207
1211
 
1208
1212
  def on_confirm(confirmed: bool):
@@ -1214,11 +1218,11 @@ class VMCard(Static):
1214
1218
  self.app.vm_service.suppress_vm_events(self.internal_id)
1215
1219
  try:
1216
1220
  commit_disk_changes(self.vm, target_disk)
1217
- self.app.call_from_thread(self.app.show_success_message, "Disk changes committed successfully.")
1221
+ self.app.call_from_thread(self.app.show_success_message, SuccessMessages.DISK_COMMITTED)
1218
1222
  self.app.call_from_thread(self.app.refresh_vm_list)
1219
1223
  self.app.call_from_thread(self.update_button_layout)
1220
1224
  except Exception as e:
1221
- self.app.call_from_thread(self.app.show_error_message, f"Error committing disk: {e}")
1225
+ self.app.call_from_thread(self.app.show_error_message, ErrorMessages.ERROR_COMMITTING_DISK_TEMPLATE.format(error=e))
1222
1226
  finally:
1223
1227
  self.app.vm_service.unsuppress_vm_events(self.internal_id)
1224
1228
  self.app.call_from_thread(progress_modal.dismiss)
@@ -1231,7 +1235,7 @@ class VMCard(Static):
1231
1235
  )
1232
1236
 
1233
1237
  except Exception as e:
1234
- self.app.show_error_message(f"Error preparing commit: {e}")
1238
+ self.app.show_error_message(ErrorMessages.ERROR_PREPARING_COMMIT_TEMPLATE.format(error=e))
1235
1239
 
1236
1240
  def _handle_shutdown_button(self, event: Button.Pressed) -> None:
1237
1241
  """Handles the shutdown button press."""
@@ -1273,7 +1277,7 @@ class VMCard(Static):
1273
1277
  self.stop_background_activities()
1274
1278
  self.post_message(VmActionRequest(self.internal_id, VmAction.PAUSE))
1275
1279
  else:
1276
- self.app.show_warning_message(f"VM '{self.name}' is not in a pausable state.")
1280
+ self.app.show_warning_message(WarningMessages.LIBVIRT_XML_NO_EFFECTIVE_CHANGE.format(vm_name=self.name))
1277
1281
 
1278
1282
  def _handle_resume_button(self, event: Button.Pressed) -> None:
1279
1283
  """Handles the resume button press."""
@@ -1304,42 +1308,41 @@ class VMCard(Static):
1304
1308
  try:
1305
1309
  conn = self.vm.connect()
1306
1310
  new_domain = conn.defineXML(modified_xml)
1307
-
1311
+
1308
1312
  # Verify if changes were effectively applied
1309
1313
  new_xml = new_domain.XMLDesc(xml_flags)
1310
-
1314
+
1311
1315
  if original_xml == new_xml:
1312
- self.app.show_warning_message(f"VM [b]{self.name}[/b]: Libvirt accepted the XML but the configuration remains unchanged. Your changes may have been ignored or normalized away.")
1316
+ self.app.show_warning_message(WarningMessages.LIBVIRT_XML_NO_EFFECTIVE_CHANGE.format(vm_name=self.name))
1313
1317
  logging.warning(f"XML update for {self.name} resulted in no effective changes.")
1314
1318
  else:
1315
- self.app.show_success_message(f"VM [b]{self.name}[/b] configuration updated successfully.")
1319
+ self.app.show_success_message(SuccessMessages.VM_CONFIG_UPDATED.format(vm_name=self.name))
1316
1320
  logging.info(f"Successfully updated XML for VM: {self.name}")
1317
-
1321
+
1318
1322
  self.app.vm_service.invalidate_vm_state_cache(self.internal_id)
1319
1323
  self._boot_device_checked = False
1320
1324
  self.app.refresh_vm_list()
1321
1325
  except libvirt.libvirtError as e:
1322
- error_msg = f"Invalid XML for '{self.name}': {e}. Your changes have been discarded."
1323
- self.app.show_error_message(error_msg)
1326
+ self.app.show_error_message(ErrorMessages.INVALID_XML_TEMPLATE.format(vm_name=self.name, error=e))
1324
1327
  logging.error(error_msg)
1325
1328
  else:
1326
- self.app.show_success_message("No changes made to the XML configuration.")
1329
+ self.app.show_success_message(SuccessMessages.NO_XML_CHANGES)
1327
1330
 
1328
1331
  self.app.push_screen(
1329
1332
  XMLDisplayModal(original_xml, read_only=not is_stopped),
1330
1333
  handle_xml_modal_result
1331
1334
  )
1332
1335
  except libvirt.libvirtError as e:
1333
- self.app.show_error_message(f"Error getting XML for VM [b]{self.name}[/b]: {e}")
1336
+ self.app.show_error_message(ErrorMessages.ERROR_GETTING_XML_TEMPLATE.format(vm_name=self.name, error=e))
1334
1337
  except Exception as e:
1335
- self.app.show_error_message(f"An unexpected error occurred: {e}")
1338
+ self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE.format(error=e))
1336
1339
  logging.error(f"Unexpected error handling XML button: {traceback.format_exc()}")
1337
1340
 
1338
1341
  def _handle_connect_button(self, event: Button.Pressed) -> None:
1339
1342
  """Handles the connect button press by running the remove virt viewer in a worker."""
1340
1343
  logging.info(f"Attempting to connect to VM: {self.name}")
1341
1344
  if not hasattr(self, 'conn') or not self.conn:
1342
- self.app.show_error_message("Connection info not available for this VM.")
1345
+ self.app.show_error_message(ErrorMessages.CONNECTION_INFO_NOT_AVAILABLE)
1343
1346
  return
1344
1347
 
1345
1348
  def do_connect() -> None:
@@ -1368,7 +1371,7 @@ class VMCard(Static):
1368
1371
  logging.error(f"Failed to spawn {self.app.r_viewer} for {domain_name}: {e}")
1369
1372
  self.app.call_from_thread(
1370
1373
  self.app.show_error_message,
1371
- f"{self.app.r_viewer} failed to start for {domain_name}: {e}"
1374
+ ErrorMessages.REMOTE_VIEWER_FAILED_TO_START_TEMPLATE.format(viewer=self.app.r_viewer, domain_name=domain_name, error=e)
1372
1375
  )
1373
1376
  return
1374
1377
 
@@ -1380,13 +1383,13 @@ class VMCard(Static):
1380
1383
  except libvirt.libvirtError as e:
1381
1384
  self.app.call_from_thread(
1382
1385
  self.app.show_error_message,
1383
- f"Error getting VM details for [b]{self.name}[/b]: {e}"
1386
+ ErrorMessages.ERROR_GETTING_VM_DETAILS_TEMPLATE.format(vm_name=self.name, error=e)
1384
1387
  )
1385
1388
  except Exception as e:
1386
1389
  logging.error(f"An unexpected error occurred during connect: {e}", exc_info=True)
1387
1390
  self.app.call_from_thread(
1388
1391
  self.app.show_error_message,
1389
- "An unexpected error occurred while trying to connect."
1392
+ ErrorMessages.UNEXPECTED_ERROR_CONNECTING
1390
1393
  )
1391
1394
 
1392
1395
  self.app.worker_manager.run(do_connect, name=f"r_viewer_{self.name}")
@@ -1403,7 +1406,7 @@ class VMCard(Static):
1403
1406
  )
1404
1407
  return
1405
1408
  except Exception as e:
1406
- self.app.show_error_message(f"Error checking web console status for [b]{self.name}[/b]: {e}")
1409
+ self.app.show_error_message(ErrorMessages.ERROR_CHECKING_WEB_CONSOLE_STATUS_TEMPLATE.format(vm_name=self.name, error=e))
1407
1410
  return
1408
1411
 
1409
1412
  #is_remote = self.app.webconsole_manager.is_remote_connection(self.conn.getURI())
@@ -1468,9 +1471,9 @@ class VMCard(Static):
1468
1471
  def finalize_snapshot():
1469
1472
  loading_modal.dismiss()
1470
1473
  if error:
1471
- self.app.show_error_message(f"Snapshot error for [b]{vm_name}[/b]: {error}")
1474
+ self.app.show_error_message(ErrorMessages.SNAPSHOT_ERROR_TEMPLATE.format(vm_name=vm_name, error=error))
1472
1475
  else:
1473
- self.app.show_success_message(f"Snapshot [b]{name}[/b] created successfully.")
1476
+ self.app.show_success_message(SuccessMessages.SNAPSHOT_CREATED.format(snapshot_name=name))
1474
1477
  # Defer refresh and restart stats to avoid racing
1475
1478
  self.app.set_timer(0.5, self._refresh_snapshot_tab_async)
1476
1479
  # Restart stats timer after a delay
@@ -1498,7 +1501,7 @@ class VMCard(Static):
1498
1501
  self.app.call_from_thread(loading_modal.dismiss)
1499
1502
 
1500
1503
  if not snapshots_info:
1501
- self.app.call_from_thread(self.app.show_error_message, "No snapshots to restore.")
1504
+ self.app.call_from_thread(self.app.show_error_message, ErrorMessages.NO_SNAPSHOTS_TO_RESTORE)
1502
1505
  return
1503
1506
 
1504
1507
  def restore_snapshot_callback(snapshot_name: str | None) -> None:
@@ -1537,10 +1540,10 @@ class VMCard(Static):
1537
1540
  def finalize_ui():
1538
1541
  restore_loading_modal.dismiss()
1539
1542
  if error:
1540
- self.app.show_error_message(f"Error on VM [b]{vm_name}[/b] during 'snapshot restore': {error}")
1543
+ self.app.show_error_message(ErrorMessages.ERROR_ON_VM_DURING_ACTION.format(vm_name=vm_name, action='snapshot restore', error=error))
1541
1544
  else:
1542
1545
  self._boot_device_checked = False
1543
- self.app.show_success_message(f"Restored to snapshot [b]{snapshot_name}[/b] successfully.")
1546
+ self.app.show_success_message(SuccessMessages.SNAPSHOT_RESTORED.format(snapshot_name=snapshot_name))
1544
1547
  logging.info(f"Successfully restored snapshot [b]{snapshot_name}[/b] for VM: {vm_name}")
1545
1548
  self.app.refresh_vm_list(force=True)
1546
1549
  self.app.call_from_thread(finalize_ui)
@@ -1550,11 +1553,11 @@ class VMCard(Static):
1550
1553
  self.app.push_screen,
1551
1554
  SelectSnapshotDialog(snapshots_info, "Select snapshot to restore"),
1552
1555
  restore_snapshot_callback
1553
- )
1556
+ )
1554
1557
 
1555
1558
  except Exception as e:
1556
1559
  self.app.call_from_thread(loading_modal.dismiss)
1557
- self.app.call_from_thread(self.app.show_error_message, f"Error fetching snapshots: {e}")
1560
+ self.app.call_from_thread(self.app.show_error_message, ErrorMessages.ERROR_FETCHING_SNAPSHOTS_TEMPLATE.format(error=e))
1558
1561
 
1559
1562
  self.app.worker_manager.run(fetch_and_show_worker, name=f"snapshot_restore_fetch_{self.internal_id}")
1560
1563
 
@@ -1575,7 +1578,7 @@ class VMCard(Static):
1575
1578
  self.app.call_from_thread(loading_modal.dismiss)
1576
1579
 
1577
1580
  if not snapshots_info:
1578
- self.app.call_from_thread(self.app.show_error_message, "No snapshots to delete.")
1581
+ self.app.call_from_thread(self.app.show_error_message, ErrorMessages.NO_SNAPSHOTS_TO_DELETE)
1579
1582
  return
1580
1583
 
1581
1584
  def delete_snapshot_callback(snapshot_name: str | None) -> None:
@@ -1607,9 +1610,9 @@ class VMCard(Static):
1607
1610
  def finalize_ui():
1608
1611
  loading_modal.dismiss()
1609
1612
  if error:
1610
- self.app.show_error_message(f"Error on VM [b]{vm_name}[/b] during 'snapshot delete': {error}")
1613
+ self.app.show_error_message(ErrorMessages.ERROR_ON_VM_DURING_ACTION.format(vm_name=vm_name, action='snapshot delete', error=error))
1611
1614
  else:
1612
- self.app.show_success_message(f"Snapshot [b]{snapshot_name}[/b] deleted successfully.")
1615
+ self.app.show_success_message(SuccessMessages.SNAPSHOT_DELETED.format(snapshot_name=snapshot_name))
1613
1616
  logging.info(f"Successfully deleted snapshot '{snapshot_name}' for VM: {vm_name}")
1614
1617
  self.app.set_timer(0.1, self._refresh_snapshot_tab_async)
1615
1618
  # Restart stats timer
@@ -1631,7 +1634,7 @@ class VMCard(Static):
1631
1634
 
1632
1635
  except Exception as e:
1633
1636
  self.app.call_from_thread(loading_modal.dismiss)
1634
- self.app.call_from_thread(self.app.show_error_message, f"Error fetching snapshots: {e}")
1637
+ self.app.call_from_thread(self.app.show_error_message, ErrorMessages.ERROR_FETCHING_SNAPSHOTS_TEMPLATE.format(error=e))
1635
1638
 
1636
1639
  self.app.worker_manager.run(fetch_and_show_worker, name=f"snapshot_delete_fetch_{self.internal_id}")
1637
1640
 
@@ -1685,12 +1688,12 @@ class VMCard(Static):
1685
1688
  pass
1686
1689
 
1687
1690
  # Also update button visibility
1688
- if self.ui.get(ButtonIds.RENAME_BUTTON):
1691
+ if self.ui.get("rename-button"):
1689
1692
  has_snapshots = snapshot_count > 0
1690
1693
  is_running = self.status == StatusText.RUNNING
1691
1694
  is_loading = self.status == StatusText.LOADING
1692
- self.ui[ButtonIds.SNAPSHOT_RESTORE].display = has_snapshots and not is_running and not is_loading
1693
- self.ui[ButtonIds.SNAPSHOT_DELETE].display = has_snapshots
1695
+ self.ui["snapshot_restore"].display = has_snapshots and not is_running and not is_loading
1696
+ self.ui["snapshot_delete"].display = has_snapshots
1694
1697
 
1695
1698
  self.app.call_from_thread(update_ui)
1696
1699
 
@@ -1744,7 +1747,7 @@ class VMCard(Static):
1744
1747
  # delete_vm handles opening its own connection
1745
1748
  delete_vm(self.vm, delete_storage=delete_storage, delete_nvram=True, log_callback=log_callback)
1746
1749
 
1747
- self.app.call_from_thread(self.app.show_success_message, f"VM '{vm_name}' deleted successfully.")
1750
+ self.app.call_from_thread(self.app.show_success_message, SuccessMessages.VM_DELETED.format(vm_name=vm_name))
1748
1751
 
1749
1752
  # Invalidate cache
1750
1753
  self.app.vm_service.invalidate_vm_cache(internal_id)
@@ -1757,7 +1760,7 @@ class VMCard(Static):
1757
1760
 
1758
1761
  except Exception as e:
1759
1762
  self.app.vm_service.unsuppress_vm_events(internal_id)
1760
- self.app.call_from_thread(self.app.show_error_message, f"Error deleting VM '{vm_name}': {e}")
1763
+ self.app.call_from_thread(self.app.show_error_message, ErrorMessages.ERROR_DELETING_VM_TEMPLATE.format(vm_name=vm_name, error=e))
1761
1764
  finally:
1762
1765
  self.app.call_from_thread(progress_modal.dismiss)
1763
1766
 
@@ -1812,8 +1815,8 @@ class VMCard(Static):
1812
1815
  existing_vm_names.add(name)
1813
1816
 
1814
1817
  except libvirt.libvirtError as e:
1815
- log_callback(f"ERROR: Error getting existing VM names: {e}")
1816
- app.call_from_thread(app.show_error_message, f"Error getting existing VM names: {e}")
1818
+ log_callback(f"ERROR: {ErrorMessages.ERROR_GETTING_EXISTING_VM_NAMES_TEMPLATE.format(error=e)}")
1819
+ app.call_from_thread(app.show_error_message, ErrorMessages.ERROR_GETTING_EXISTING_VM_NAMES_TEMPLATE.format(error=e))
1817
1820
  app.call_from_thread(progress_modal.dismiss)
1818
1821
  return
1819
1822
 
@@ -1852,11 +1855,11 @@ class VMCard(Static):
1852
1855
  app.call_from_thread(lambda: progress_modal.query_one("#progress-bar").advance(1))
1853
1856
 
1854
1857
  if success_clones:
1855
- msg = f"Successfully cloned to: {', '.join(success_clones)}"
1858
+ msg = SuccessMessages.VM_CLONED.format(vm_names=', '.join(success_clones))
1856
1859
  app.call_from_thread(app.show_success_message, msg)
1857
1860
  log_callback(msg)
1858
1861
  if failed_clones:
1859
- msg = f"Failed to clone to: {', '.join(failed_clones)}"
1862
+ msg = ErrorMessages.VM_CLONE_FAILED_TEMPLATE.format(vm_names=', '.join(failed_clones))
1860
1863
  app.call_from_thread(app.show_error_message, msg)
1861
1864
  log_callback(f"ERROR: {msg}")
1862
1865
 
@@ -1895,13 +1898,13 @@ class VMCard(Static):
1895
1898
  return
1896
1899
 
1897
1900
  if was_modified:
1898
- self.app.show_success_message(f"Input sanitized: [b]{new_name_raw}[/b] changed to [b]{new_name}[/b]")
1901
+ self.app.show_success_message(SuccessMessages.INPUT_SANITIZED.format(original_input=new_name_raw, sanitized_input=new_name))
1899
1902
 
1900
1903
  if not new_name:
1901
- self.app.show_error_message("VM name cannot be empty after sanitization.")
1904
+ self.app.show_error_message(ErrorMessages.VM_NAME_EMPTY_AFTER_SANITIZATION)
1902
1905
  return
1903
1906
  if new_name == self.name:
1904
- self.app.show_success_message("New VM name is the same as the old name. No rename performed.")
1907
+ self.app.show_success_message(SuccessMessages.VM_RENAME_NO_CHANGE)
1905
1908
  return
1906
1909
 
1907
1910
  def do_rename():
@@ -1909,14 +1912,13 @@ class VMCard(Static):
1909
1912
  self.app.vm_service.suppress_vm_events(internal_id)
1910
1913
  try:
1911
1914
  rename_vm(self.vm, new_name)
1912
- msg = f"VM '{self.name}' renamed to '{new_name}' successfully."
1913
- self.app.show_success_message(msg)
1915
+ self.app.show_success_message(SuccessMessages.VM_RENAMED.format(old_name=self.name, new_name=new_name))
1914
1916
  self.app.vm_service.invalidate_domain_cache()
1915
1917
  self._boot_device_checked = False
1916
1918
  self.app.refresh_vm_list()
1917
1919
  logging.info(f"Successfully renamed VM '{self.name}' to '{new_name}'")
1918
1920
  except Exception as e:
1919
- self.app.show_error_message(f"Error renaming VM [b]{self.name}[/b]: {e}")
1921
+ self.app.show_error_message(ErrorMessages.ERROR_RENAMING_VM_TEMPLATE.format(vm_name=self.name, error=e))
1920
1922
  finally:
1921
1923
  self.app.vm_service.unsuppress_vm_events(internal_id)
1922
1924
 
@@ -1928,7 +1930,7 @@ class VMCard(Static):
1928
1930
  if delete_snapshots:
1929
1931
  self.app.set_timer(0.1, self._refresh_snapshot_tab_async)
1930
1932
  else:
1931
- self.app.show_success_message("VM rename cancelled.")
1933
+ self.app.show_success_message(SuccessMessages.VM_RENAME_CANCELLED)
1932
1934
 
1933
1935
  msg = f"Are you sure you want to rename VM {self.name} to {new_name}?\n\nWarning: This operation involves undefining and redefining the VM."
1934
1936
  self.app.push_screen(
@@ -1968,7 +1970,7 @@ class VMCard(Static):
1968
1970
  def show_details():
1969
1971
  loading_modal.dismiss()
1970
1972
  if not result:
1971
- self.app.show_error_message(f"VM [b]{vm_name}[/b] with internal ID [b]{uuid}[/b] not found on any active server.")
1973
+ self.app.show_error_message(ErrorMessages.VM_NOT_FOUND_ON_ACTIVE_SERVER_TEMPLATE.format(vm_name=vm_name, uuid=uuid))
1972
1974
  return
1973
1975
 
1974
1976
  vm_info, domain, conn_for_domain = result
@@ -1987,18 +1989,18 @@ class VMCard(Static):
1987
1989
  except Exception as e:
1988
1990
  def show_error():
1989
1991
  loading_modal.dismiss()
1990
- self.app.show_error_message(f"Error getting details for [b]{vm_name}[/b]: {e}")
1992
+ self.app.show_error_message(ErrorMessages.ERROR_GETTING_VM_DETAILS_TEMPLATE.format(vm_name=vm_name, error=e))
1991
1993
  self.app.call_from_thread(show_error)
1992
1994
 
1993
1995
  self.app.worker_manager.run(get_details_worker, name=f"get_details_{uuid}")
1994
1996
 
1995
1997
  except Exception as e:
1996
- self.app.show_error_message(f"Error getting ID for [b]{self.name}[/b]: {e}")
1998
+ self.app.show_error_message(ErrorMessages.ERROR_GETTING_ID_TEMPLATE.format(vm_name=self.name, error=e))
1997
1999
 
1998
2000
  def _handle_migration_button(self, event: Button.Pressed) -> None:
1999
2001
  """Handles the migration button press."""
2000
2002
  if len(self.app.active_uris) < 2:
2001
- self.app.show_error_message("Please select at least two servers in 'Select Servers' to enable migration.")
2003
+ self.app.show_error_message(ErrorMessages.SELECT_AT_LEAST_TWO_SERVERS_FOR_MIGRATION)
2002
2004
  return
2003
2005
 
2004
2006
  selected_vm_uuids = self.app.selected_vm_uuids
@@ -2019,8 +2021,8 @@ class VMCard(Static):
2019
2021
  found_domain = False
2020
2022
  else:
2021
2023
  found_domain = False
2022
- if not found_domain:
2023
- self.app.show_error_message(f"Selected VM with ID [b]{uuid}[/b] not found on any active server.")
2024
+ if not vm_info:
2025
+ self.app.show_error_message(ErrorMessages.SELECTED_VM_NOT_FOUND_ON_ACTIVE_SERVER_TEMPLATE.format(uuid=uuid))
2024
2026
 
2025
2027
  if not selected_vms:
2026
2028
  selected_vms = [self.vm]
@@ -2037,7 +2039,7 @@ class VMCard(Static):
2037
2039
  source_conns.add(uri)
2038
2040
 
2039
2041
  if len(source_conns) > 1:
2040
- self.app.show_error_message("Cannot migrate VMs from different source hosts at the same time.")
2042
+ self.app.show_error_message(ErrorMessages.DIFFERENT_SOURCE_HOSTS)
2041
2043
  return
2042
2044
 
2043
2045
  #active_vms = [vm for vm in selected_vms if vm.isActive()]
@@ -2058,7 +2060,7 @@ class VMCard(Static):
2058
2060
 
2059
2061
  is_live = len(active_vms) > 0
2060
2062
  if is_live and len(active_vms) < len(selected_vms):
2061
- self.app.show_error_message("Cannot migrate running/paused and stopped VMs at the same time.")
2063
+ self.app.show_error_message(ErrorMessages.MIXED_VM_STATES)
2062
2064
  return
2063
2065
 
2064
2066
  active_uris = self.app.vm_service.get_all_uris()