virtui-manager 1.1.6__py3-none-any.whl → 1.4.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 (62) hide show
  1. {virtui_manager-1.1.6.dist-info → virtui_manager-1.4.0.dist-info}/METADATA +1 -1
  2. virtui_manager-1.4.0.dist-info/RECORD +76 -0
  3. vmanager/constants.py +739 -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.mo +0 -0
  9. vmanager/locales/de/LC_MESSAGES/virtui-manager.po +3158 -0
  10. vmanager/locales/fr/LC_MESSAGES/virtui-manager.mo +0 -0
  11. vmanager/locales/fr/LC_MESSAGES/virtui-manager.po +3155 -0
  12. vmanager/locales/it/LC_MESSAGES/virtui-manager.mo +0 -0
  13. vmanager/locales/it/LC_MESSAGES/virtui-manager.po +3132 -0
  14. vmanager/locales/virtui-manager.pot +3033 -0
  15. vmanager/modals/bulk_modals.py +13 -12
  16. vmanager/modals/cache_stats_modal.py +6 -5
  17. vmanager/modals/capabilities_modal.py +133 -0
  18. vmanager/modals/config_modal.py +25 -24
  19. vmanager/modals/cpu_mem_pc_modals.py +22 -21
  20. vmanager/modals/custom_migration_modal.py +10 -9
  21. vmanager/modals/disk_pool_modals.py +60 -59
  22. vmanager/modals/host_dashboard_modal.py +137 -0
  23. vmanager/modals/host_stats.py +199 -0
  24. vmanager/modals/howto_disk_modal.py +2 -1
  25. vmanager/modals/howto_network_modal.py +2 -1
  26. vmanager/modals/howto_overlay_modal.py +2 -1
  27. vmanager/modals/howto_ssh_modal.py +2 -1
  28. vmanager/modals/howto_virtiofs_modal.py +2 -1
  29. vmanager/modals/input_modals.py +11 -10
  30. vmanager/modals/log_modal.py +2 -1
  31. vmanager/modals/migration_modals.py +21 -19
  32. vmanager/modals/network_modals.py +45 -36
  33. vmanager/modals/provisioning_modals.py +56 -56
  34. vmanager/modals/select_server_modals.py +8 -7
  35. vmanager/modals/selection_modals.py +7 -6
  36. vmanager/modals/server_modals.py +24 -23
  37. vmanager/modals/server_prefs_modals.py +78 -71
  38. vmanager/modals/utils_modals.py +10 -9
  39. vmanager/modals/virsh_modals.py +3 -2
  40. vmanager/modals/virtiofs_modals.py +6 -5
  41. vmanager/modals/vm_type_info_modal.py +2 -1
  42. vmanager/modals/vmanager_modals.py +19 -19
  43. vmanager/modals/vmcard_dialog.py +57 -57
  44. vmanager/modals/vmdetails_modals.py +115 -123
  45. vmanager/modals/xml_modals.py +3 -2
  46. vmanager/network_manager.py +4 -1
  47. vmanager/storage_manager.py +157 -39
  48. vmanager/utils.py +54 -7
  49. vmanager/vm_actions.py +48 -24
  50. vmanager/vm_migration.py +4 -1
  51. vmanager/vm_queries.py +67 -25
  52. vmanager/vm_service.py +8 -5
  53. vmanager/vmanager.css +55 -1
  54. vmanager/vmanager.py +247 -120
  55. vmanager/vmcard.css +3 -1
  56. vmanager/vmcard.py +270 -205
  57. vmanager/webconsole_manager.py +22 -22
  58. virtui_manager-1.1.6.dist-info/RECORD +0 -65
  59. {virtui_manager-1.1.6.dist-info → virtui_manager-1.4.0.dist-info}/WHEEL +0 -0
  60. {virtui_manager-1.1.6.dist-info → virtui_manager-1.4.0.dist-info}/entry_points.txt +0 -0
  61. {virtui_manager-1.1.6.dist-info → virtui_manager-1.4.0.dist-info}/licenses/LICENSE +0 -0
  62. {virtui_manager-1.1.6.dist-info → virtui_manager-1.4.0.dist-info}/top_level.txt +0 -0
@@ -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):
@@ -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