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
@@ -7,6 +7,7 @@ from textual.app import ComposeResult
7
7
  from textual.containers import Vertical, Horizontal
8
8
  from textual import on
9
9
  from .base_modals import BaseModal
10
+ from ..constants import ButtonLabels, StaticText
10
11
 
11
12
  class InputModal(BaseModal[str | None]):
12
13
  """A generic modal for getting text input from the user."""
@@ -21,8 +22,8 @@ class InputModal(BaseModal[str | None]):
21
22
  yield Label(self.prompt)
22
23
  yield Input(value=self.initial_value, id="text-input", restrict=self.restrict)
23
24
  with Horizontal():
24
- yield Button("OK", variant="primary", id="ok-btn")
25
- yield Button("Cancel", variant="default", id="cancel-btn")
25
+ yield Button(ButtonLabels.OK, variant="primary", id="ok-btn")
26
+ yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn")
26
27
 
27
28
  def on_button_pressed(self, event: Button.Pressed) -> None:
28
29
  if event.button.id == "ok-btn":
@@ -40,7 +41,7 @@ class AddInputDeviceModal(BaseModal[None]):
40
41
 
41
42
  def compose(self) -> ComposeResult:
42
43
  with Vertical(id="add-input-container"):
43
- yield Label("Input Device")
44
+ yield Label(StaticText.INPUT_DEVICE)
44
45
  yield Select(
45
46
  [(t, t) for t in self.available_types],
46
47
  prompt="Input Type",
@@ -53,8 +54,8 @@ class AddInputDeviceModal(BaseModal[None]):
53
54
  )
54
55
  with Vertical():
55
56
  with Horizontal():
56
- yield Button("Add", variant="primary", id="add-input", disabled=True)
57
- yield Button("Cancel", variant="default", id="cancel-input")
57
+ yield Button(ButtonLabels.ADD, variant="primary", id="add-input", disabled=True)
58
+ yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-input")
58
59
 
59
60
  @on(Select.Changed)
60
61
  def on_select_changed(self) -> None:
@@ -82,26 +83,26 @@ class AddChannelModal(BaseModal[dict | None]):
82
83
 
83
84
  def compose(self) -> ComposeResult:
84
85
  with Vertical(id="add-channel-container"):
85
- yield Label("Add Channel Device")
86
+ yield Label(StaticText.ADD_CHANNEL_DEVICE)
86
87
  yield Select(
87
88
  [("unix", "unix"), ("virtio", "virtio"), ("spicevmc", "spicevmc")],
88
89
  prompt="Channel Type",
89
90
  id="channel-type-select",
90
91
  value="unix"
91
92
  )
92
- yield Label("Standard Target Names:")
93
+ yield Label(StaticText.STANDARD_TARGET_NAMES)
93
94
  yield Select(
94
95
  [],
95
96
  id="target-preset-select",
96
97
  prompt="Select a standard target or type below",
97
98
  value=Select.BLANK
98
99
  )
99
- yield Label("Target Name:")
100
+ yield Label(StaticText.TARGET_NAME)
100
101
  yield Input(placeholder="Target Name (e.g. org.qemu.guest_agent.0)", id="target-name-input")
101
102
 
102
103
  with Horizontal():
103
- yield Button("Add", variant="primary", id="add-channel-btn")
104
- yield Button("Cancel", variant="default", id="cancel-channel-btn")
104
+ yield Button(ButtonLabels.ADD, variant="primary", id="add-channel-btn")
105
+ yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-channel-btn")
105
106
 
106
107
  def on_mount(self) -> None:
107
108
  # Initialize presets for default type (unix)
@@ -6,6 +6,7 @@ from textual.widgets import Button, Label, TextArea
6
6
  from textual.containers import Vertical, Horizontal
7
7
 
8
8
  from .base_modals import BaseModal
9
+ from ..constants import ButtonLabels
9
10
 
10
11
  class LogModal(BaseModal[None]):
11
12
  """ Modal Screen to show Log"""
@@ -22,7 +23,7 @@ class LogModal(BaseModal[None]):
22
23
  text_area.load_text(self.log_content)
23
24
  yield text_area
24
25
  with Horizontal():
25
- yield Button("Close", variant="default", id="cancel-btn", classes="Buttonpage")
26
+ yield Button(ButtonLabels.CLOSE, variant="default", id="cancel-btn", classes="Buttonpage")
26
27
 
27
28
  def on_mount(self) -> None:
28
29
  """Called when the modal is mounted."""
@@ -11,6 +11,7 @@ from textual.screen import ModalScreen
11
11
  from textual.widgets import Button, Static, Select, Checkbox, Label, ProgressBar
12
12
  from textual import on, work
13
13
 
14
+ from ..constants import ErrorMessages, StaticText, ButtonLabels
14
15
  from ..vm_actions import check_server_migration_compatibility, check_vm_migration_compatibility
15
16
  from ..storage_manager import find_shared_storage_pools
16
17
  from ..utils import extract_server_name_from_uri
@@ -74,38 +75,39 @@ class MigrationModal(ModalScreen):
74
75
 
75
76
  with Vertical(id="migration-dialog",):
76
77
  with Vertical(id="migration-content-wrapper"):
77
- yield Label(f"[{migration_type}] Migrate VMs: [b]{vm_names}[/b]")
78
- yield Static("Select destination server:")
78
+ yield Label(StaticText.MIGRATE_VMS_TITLE.format(migration_type=migration_type, vm_names=vm_names))
79
+ yield Static(StaticText.SELECT_DESTINATION_SERVER)
79
80
  yield Select(dest_servers, id="dest-server-select", prompt="Destination...", value=default_dest_uri, allow_blank=False)
80
81
 
81
- yield Static("Migration Options:")
82
+ yield Static(StaticText.MIGRATION_OPTIONS)
82
83
  with Horizontal(classes="checkbox-container"):
83
- yield Checkbox("Copy storage all", id="copy-storage-all", tooltip="Copy all disk files during migration", value=False)
84
- yield Checkbox("Unsafe migration", id="unsafe", tooltip="Perform unsafe migration (may lose data)", disabled=not self.is_live)
85
- yield Checkbox("Persistent migration", id="persistent", tooltip="Keep VM persistent on destination", value=True)
84
+ yield Checkbox(StaticText.COPY_STORAGE_ALL, id="copy-storage-all", tooltip="Copy all disk files during migration", value=False)
85
+ yield Checkbox(StaticText.UNSAFE_MIGRATION, id="unsafe", tooltip="Perform unsafe migration (may lose data)", disabled=not self.is_live)
86
+ yield Checkbox(StaticText.PERSISTENT_MIGRATION, id="persistent", tooltip="Keep VM persistent on destination", value=True)
86
87
  with Horizontal(classes="checkbox-container"):
87
- yield Checkbox("Compress data", id="compress", tooltip="Compress data during migration", disabled=not self.is_live)
88
- yield Checkbox("Tunnelled migration", id="tunnelled", tooltip="Tunnel migration data through libvirt daemon", disabled=not self.is_live)
89
- yield Checkbox("Custom migration", id="custom", tooltip="Use custom migration workflow", value=False)
90
- yield Static("Compatibility Check Results / Migration Log:")
88
+ yield Checkbox(StaticText.COMPRESS_DATA, id="compress", tooltip="Compress data during migration", disabled=not self.is_live)
89
+ yield Checkbox(StaticText.TUNNELLED_MIGRATION, id="tunnelled", tooltip="Tunnel migration data through libvirt daemon", disabled=not self.is_live)
90
+ yield Checkbox(StaticText.CUSTOM_MIGRATION, id="custom", tooltip="Use custom migration workflow", value=False)
91
+ yield Static(StaticText.COMPATIBILITY_CHECK_RESULTS)
91
92
  yield ProgressBar(total=100, show_eta=False, id="migration-progress")
92
93
  yield Static(id="results-log")
93
94
  yield Grid(
94
95
  ScrollableContainer(
95
- Static("[b]VMs [green]Ready[/] for Migration[/b]", classes="summary-title"),
96
+ Static(StaticText.VMS_READY_FOR_MIGRATION, classes="summary-title"),
96
97
  Static(id="can-migrate-list"),
97
98
  ),
98
99
  ScrollableContainer(
99
- Static("[b]VMs [red]Not[/] Ready for Migration[/b]", classes="summary-title"),
100
+ Static(StaticText.VMS_NOT_READY_FOR_MIGRATION, classes="summary-title"),
100
101
  Static(id="cannot-migrate-list"),
101
102
  ),
102
103
  id="migration-summary-grid"
103
104
  )
104
105
 
105
106
  with Horizontal(classes="modal-buttons"):
106
- yield Button("Check Compatibility", variant="primary", id="check", classes="Buttonpage")
107
- yield Button("Start Migration", variant="success", id="start", disabled=True, classes="Buttonpage")
108
- yield Button("Close", variant="default", id="close", disabled=False, classes="close-button")
107
+ yield Button(ButtonLabels.CHECK_COMPATIBILITY, variant="primary", id="check", classes="Buttonpage")
108
+ yield Button(ButtonLabels.START_MIGRATION, variant="success", id="start", disabled=True, classes="Buttonpage")
109
+ yield Button(ButtonLabels.CLOSE, variant="default", id="close", disabled=False, classes="close-button")
110
+
109
111
 
110
112
  def _lock_controls(self, lock: bool):
111
113
  self.query_one("#check").disabled = lock
@@ -403,17 +405,17 @@ class MigrationModal(ModalScreen):
403
405
  def on_button_pressed(self, event: Button.Pressed):
404
406
  if event.button.id == "check":
405
407
  if not self.dest_conn:
406
- self.app.show_error_message("Please select a destination server.")
408
+ self.app.show_error_message(ErrorMessages.SELECT_DESTINATION_SERVER)
407
409
  return
408
410
  self._clear_log()
409
411
  self.run_compatibility_checks()
410
412
 
411
413
  elif event.button.id == "start":
412
414
  if not self.compatibility_checked:
413
- self.app.show_error_message("Please run compatibility check first.")
415
+ self.app.show_error_message(ErrorMessages.RUN_COMPATIBILITY_CHECK_FIRST)
414
416
  return
415
417
  if not self.checks_passed:
416
- self.app.show_error_message("Cannot start migration due to compatibility errors.")
418
+ self.app.show_error_message(ErrorMessages.MIGRATION_COMPATIBILITY_ERRORS)
417
419
  return
418
420
 
419
421
  self._clear_log()
@@ -8,6 +8,7 @@ from textual.widgets.text_area import LanguageDoesNotExist
8
8
  from textual.containers import Vertical, Horizontal, ScrollableContainer
9
9
  from textual import on
10
10
 
11
+ from ..constants import ErrorMessages, SuccessMessages, ButtonLabels, StaticText
11
12
  from .base_modals import BaseModal, BaseDialog
12
13
  from ..network_manager import (
13
14
  create_network, get_host_network_interfaces, get_existing_subnets
@@ -35,7 +36,7 @@ class AddEditNetworkInterfaceModal(BaseDialog[dict | None]):
35
36
  if self.is_edit and self.interface_info:
36
37
  network_value = self.interface_info.get("network")
37
38
  if network_value not in self.networks:
38
- self.app.show_error_message(f"Network '{network_value}' not found. Please select an available network.")
39
+ self.app.show_error_message(ErrorMessages.NETWORK_NOT_FOUND_TEMPLATE.format(network=network_value))
39
40
  network_value = self.networks[0] if self.networks else None # Set to first available network if any, otherwise None
40
41
  model_value = self.interface_info.get("model", "virtio")
41
42
  mac_value = self.interface_info.get("mac", "")
@@ -43,7 +44,7 @@ class AddEditNetworkInterfaceModal(BaseDialog[dict | None]):
43
44
  network_value = self.networks[0]
44
45
 
45
46
  with Vertical(id="add-edit-network-dialog"):
46
- yield Label("Select network and model")
47
+ yield Label(StaticText.SELECT_NETWORK_AND_MODEL)
47
48
 
48
49
  if self.networks:
49
50
  yield Select(network_options, id="network-select", prompt="Select a network", value=network_value)
@@ -60,8 +61,8 @@ class AddEditNetworkInterfaceModal(BaseDialog[dict | None]):
60
61
  )
61
62
 
62
63
  with Horizontal(id="dialog-buttons"):
63
- yield Button("Save" if self.is_edit else "Add", variant="success", id="save")
64
- yield Button("Cancel", variant="error", id="cancel")
64
+ yield Button(ButtonLabels.SAVE if self.is_edit else ButtonLabels.ADD, variant="success", id="save")
65
+ yield Button(ButtonLabels.CANCEL, variant="error", id="cancel")
65
66
 
66
67
  def on_button_pressed(self, event: Button.Pressed) -> None:
67
68
  if event.button.id == "save":
@@ -72,7 +73,7 @@ class AddEditNetworkInterfaceModal(BaseDialog[dict | None]):
72
73
  new_model = model_select.value
73
74
 
74
75
  if new_network is Select.BLANK:
75
- self.app.show_error_message("Please select a network.")
76
+ self.app.show_error_message(ErrorMessages.SELECT_NETWORK)
76
77
  return
77
78
 
78
79
  result = {"network": new_network, "model": new_model}
@@ -94,7 +95,7 @@ class AddEditNetworkModal(BaseModal[None]):
94
95
 
95
96
  def compose(self) -> ComposeResult:
96
97
  title = "Edit Network" if self.is_edit else "Create New Network"
97
- button_label = "Save Changes" if self.is_edit else "Create Network"
98
+ button_label = ButtonLabels.SAVE_CHANGES if self.is_edit else ButtonLabels.CREATE_NETWORK
98
99
 
99
100
  name_val = ""
100
101
  forward_mode = "nat"
@@ -137,7 +138,6 @@ class AddEditNetworkModal(BaseModal[None]):
137
138
  dhcp_end_val = "192.168.11.30"
138
139
 
139
140
 
140
-
141
141
  with Vertical(id="create-network-dialog"):
142
142
  yield Label(title, id="create-network-title")
143
143
 
@@ -150,8 +150,8 @@ class AddEditNetworkModal(BaseModal[None]):
150
150
  disabled=self.is_edit
151
151
  )
152
152
  with RadioSet(id="type-network", classes="type-network-radioset"):
153
- yield RadioButton("Nat network", id="type-network-nat", value=(forward_mode == "nat"))
154
- yield RadioButton("Routed network", id="type-network-routed", value=(forward_mode == "route"))
153
+ yield RadioButton(StaticText.NAT_NETWORK, id="type-network-nat", value=(forward_mode == "nat"))
154
+ yield RadioButton(StaticText.ROUTED_NETWORK, id="type-network-routed", value=(forward_mode == "route"))
155
155
  yield Select(
156
156
  [("Loading...", "")],
157
157
  prompt="Select Forward Interface",
@@ -160,33 +160,33 @@ class AddEditNetworkModal(BaseModal[None]):
160
160
  disabled=True
161
161
  )
162
162
  yield Input(
163
- placeholder="IPv4 Network (e.g., 192.168.100.0/24)", id="net-ip-input", value=ip_val
163
+ placeholder=StaticText.IPV4_NETWORK_EXAMPLE, id="net-ip-input", value=ip_val
164
164
  )
165
- yield Checkbox("Enable DHCPv4", id="dhcp-checkbox", value=dhcp_val)
165
+ yield Checkbox(StaticText.ENABLE_DHCPV4, id="dhcp-checkbox", value=dhcp_val)
166
166
  with Vertical(id="dhcp-inputs-horizontal"):
167
167
  dhcp_options_classes = "" if dhcp_val else "hidden"
168
168
  with Horizontal(id="dhcp-options", classes=dhcp_options_classes):
169
169
  yield Input(
170
- placeholder="DHCP Start (e.192.168.100.100)",
170
+ placeholder=StaticText.DHCP_START_EXAMPLE,
171
171
  id="dhcp-start-input",
172
172
  classes="dhcp-input",
173
173
  value=dhcp_start_val
174
174
  )
175
175
  yield Input(
176
- placeholder="DHCP End (e.g., 192.168.100.254)",
176
+ placeholder=StaticText.DHCP_END_EXAMPLE,
177
177
  id="dhcp-end-input",
178
178
  classes="dhcp-input",
179
179
  value=dhcp_end_val
180
180
  )
181
181
  with RadioSet(id="dns-domain-radioset", classes="dns-domain-radioset"):
182
182
  yield RadioButton(
183
- "Use Network Name for DNS Domain", id="dns-use-net-name", value=not use_custom_domain
183
+ StaticText.USE_NETWORK_NAME_FOR_DNS, id="dns-use-net-name", value=not use_custom_domain
184
184
  )
185
- yield RadioButton("Use Custom DNS Domain", id="dns-use-custom", value=use_custom_domain)
185
+ yield RadioButton(StaticText.USE_CUSTOM_DNS_DOMAIN, id="dns-use-custom", value=use_custom_domain)
186
186
 
187
187
  custom_domain_classes = "hidden" if not use_custom_domain else ""
188
188
  yield Input(
189
- placeholder="Custom DNS Domain",
189
+ placeholder=StaticText.CUSTOM_DNS_DOMAIN,
190
190
  id="dns-custom-domain-input",
191
191
  value=domain_name,
192
192
  classes=custom_domain_classes
@@ -195,9 +195,9 @@ class AddEditNetworkModal(BaseModal[None]):
195
195
  with Horizontal(classes="action-buttons"):
196
196
  yield Button(
197
197
  button_label, variant="primary", id="create-net-btn", classes="create-net-btn"
198
- )
199
- yield Button("Close", variant="default", id="close-btn", classes="close-button")
200
-
198
+ )
199
+ yield Button(ButtonLabels.CLOSE, variant="default", id="close-btn", classes="close-button")
200
+
201
201
  def on_mount(self) -> None:
202
202
  """Called when the modal is mounted to populate network interfaces."""
203
203
  self.run_worker(self.populate_interfaces, thread=True)
@@ -225,7 +225,7 @@ class AddEditNetworkModal(BaseModal[None]):
225
225
  else:
226
226
  select.clear()
227
227
  self.app.show_error_message(
228
- f"Warning: Forward device '{forward_dev}' not found on host."
228
+ ErrorMessages.FORWARD_DEVICE_NOT_FOUND_TEMPLATE.format(device=forward_dev)
229
229
  )
230
230
  else:
231
231
  select.clear()
@@ -234,7 +234,7 @@ class AddEditNetworkModal(BaseModal[None]):
234
234
  except Exception as e:
235
235
  self.app.call_from_thread(
236
236
  self.app.show_error_message,
237
- f"Error getting host interfaces: {e}"
237
+ ErrorMessages.ERROR_GETTING_HOST_INTERFACES_TEMPLATE.format(error=e)
238
238
  )
239
239
 
240
240
  @on(Checkbox.Changed, "#dhcp-checkbox")
@@ -270,13 +270,13 @@ class AddEditNetworkModal(BaseModal[None]):
270
270
  dhcp_end = self.query_one("#dhcp-end-input", Input).value
271
271
 
272
272
  domain_radio = self.query_one("#dns-domain-radioset", RadioSet).pressed_button.id
273
-
273
+
274
274
  try:
275
275
  name, name_modified = _sanitize_input(name_raw)
276
276
  if name_modified:
277
- self.app.show_success_message(f"Network name sanitized: '{name_raw}' changed to '{name}'")
277
+ self.app.show_success_message(SuccessMessages.INPUT_SANITIZED.format(original_input=name_raw, sanitized_input=name))
278
278
  except ValueError as e:
279
- self.app.show_error_message(f"Invalid Network Name: {e}")
279
+ self.app.show_error_message(ErrorMessages.INVALID_NETWORK_NAME_TEMPLATE.format(error=e))
280
280
  return
281
281
 
282
282
  domain_name_raw = self.query_one("#dns-custom-domain-input", Input).value
@@ -285,13 +285,13 @@ class AddEditNetworkModal(BaseModal[None]):
285
285
  try:
286
286
  domain_name, domain_name_modified = _sanitize_domain_name(domain_name_raw)
287
287
  if domain_name_modified:
288
- self.app.show_success_message(f"Custom DNS Domain sanitized: '{domain_name_raw}' changed to '{domain_name}'")
288
+ self.app.show_success_message(SuccessMessages.INPUT_SANITIZED.format(original_input=domain_name_raw, sanitized_input=domain_name))
289
289
  except ValueError as e:
290
- self.app.show_error_message(f"Invalid Custom DNS Domain: {e}")
290
+ self.app.show_error_message(ErrorMessages.INVALID_CUSTOM_DNS_DOMAIN_TEMPLATE.format(error=e))
291
291
  return
292
292
 
293
293
  if not name:
294
- self.app.show_error_message("Network Name cannot be empty.")
294
+ self.app.show_error_message(ErrorMessages.NETWORK_NAME_REQUIRED)
295
295
  return
296
296
 
297
297
  if ip:
@@ -302,16 +302,16 @@ class AddEditNetworkModal(BaseModal[None]):
302
302
  dhcp_start_ip = ipaddress.ip_address(dhcp_start)
303
303
  dhcp_end_ip = ipaddress.ip_address(dhcp_end)
304
304
  if dhcp_start_ip not in ip_network or dhcp_end_ip not in ip_network:
305
- self.app.show_error_message(f"DHCP IPs are not in the network {ip_network}")
305
+ self.app.show_error_message(ErrorMessages.DHCP_IPS_NOT_IN_NETWORK_TEMPLATE.format(network=ip_network))
306
306
  return
307
307
  if dhcp_start_ip >= dhcp_end_ip:
308
- self.app.show_error_message("DHCP start IP must be before the end IP.")
308
+ self.app.show_error_message(ErrorMessages.DHCP_START_BEFORE_END)
309
309
  return
310
310
  except ValueError as e:
311
- self.app.show_error_message(f"Invalid IP address or network: {e}")
311
+ self.app.show_error_message(ErrorMessages.INVALID_IP_OR_NETWORK_TEMPLATE.format(error=e))
312
312
  return
313
313
  elif dhcp:
314
- self.app.show_error_message("DHCP cannot be enabled without an IP network.")
314
+ self.app.show_error_message(ErrorMessages.DHCP_REQUIRES_IP)
315
315
  return
316
316
 
317
317
  def do_create_or_update_network():
@@ -344,20 +344,29 @@ class AddEditNetworkModal(BaseModal[None]):
344
344
  if ip_network.overlaps(existing_subnet):
345
345
  self.app.call_from_thread(
346
346
  self.app.show_error_message,
347
- f"Subnet {ip_network} overlaps with an existing network."
347
+ ErrorMessages.SUBNET_OVERLAPS_TEMPLATE.format(subnet=ip_network)
348
348
  )
349
349
  return
350
350
 
351
351
  uuid = self.network_info.get('uuid') if self.is_edit and self.network_info else None
352
352
  create_network(self.conn, name, typenet, forward, ip, dhcp, dhcp_start, dhcp_end, domain_name, uuid=uuid)
353
353
 
354
- message = f"Network {name} {'updated' if self.is_edit else 'created'} successfully."
354
+ if self.is_edit:
355
+ message = SuccessMessages.NETWORK_UPDATED_TEMPLATE.format(name=name)
356
+ else:
357
+ message = SuccessMessages.NETWORK_CREATED_TEMPLATE.format(name=name)
358
+
355
359
  self.app.call_from_thread(self.app.show_success_message, message)
356
360
  self.app.call_from_thread(self.dismiss, True)
357
361
  except Exception as e:
362
+ if self.is_edit:
363
+ err_msg = ErrorMessages.ERROR_UPDATING_NETWORK_TEMPLATE.format(error=e)
364
+ else:
365
+ err_msg = ErrorMessages.ERROR_CREATING_NETWORK_TEMPLATE.format(error=e)
366
+
358
367
  self.app.call_from_thread(
359
368
  self.app.show_error_message,
360
- f"Error {'updating' if self.is_edit else 'creating'} network: {e}"
369
+ err_msg
361
370
  )
362
371
 
363
372
  self.app.worker_manager.run(
@@ -374,7 +383,7 @@ class NetworkXMLModal(BaseModal[None]):
374
383
 
375
384
  def compose(self) -> ComposeResult:
376
385
  with Vertical(id="network-detail-dialog"):
377
- yield Label(f"Network Details: {self.network_name}", id="title")
386
+ yield Label(StaticText.NETWORK_DETAILS.format(network_name=self.network_name), id="title")
378
387
  with ScrollableContainer():
379
388
  text_area = TextArea(self.network_xml, read_only=True)
380
389
  try:
@@ -384,7 +393,7 @@ class NetworkXMLModal(BaseModal[None]):
384
393
  text_area.styles.height = "auto"
385
394
  yield text_area
386
395
  with Horizontal():
387
- yield Button("Close", variant="default", id="close-btn", classes="close-btn")
396
+ yield Button(ButtonLabels.CLOSE, variant="default", id="close-btn", classes="close-btn")
388
397
 
389
398
  def on_button_pressed(self, event: Button.Pressed) -> None:
390
399
  if event.button.id == "close-btn":