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
@@ -54,6 +54,7 @@ from ..libvirt_utils import (
54
54
  get_host_usb_devices, get_host_pci_devices,
55
55
  get_host_numa_nodes
56
56
  )
57
+ from ..constants import ErrorMessages, SuccessMessages, WarningMessages, DialogMessages, ButtonLabels, StaticText
57
58
  from .utils_modals import ConfirmationDialog, ProgressModal
58
59
  from .cpu_mem_pc_modals import (
59
60
  EditCpuModal, EditMemoryModal, SelectMachineTypeModal,
@@ -69,13 +70,6 @@ from .howto_virtiofs_modal import HowToVirtIOFSModal
69
70
  from .network_modals import AddEditNetworkInterfaceModal
70
71
  from .input_modals import AddInputDeviceModal, AddChannelModal
71
72
 
72
- # Configure logging
73
- logging.basicConfig(
74
- filename=get_log_path(),
75
- level=logging.INFO,
76
- format='%(asctime)s - %(levelname)s - %(message)s'
77
- )
78
-
79
73
  BootDevice = namedtuple("BootDevice", ["type", "id", "description", "boot_order_idx"])
80
74
 
81
75
  class VMDetailModal(ModalScreen):
@@ -147,7 +141,7 @@ class VMDetailModal(ModalScreen):
147
141
  ui_update_callback()
148
142
  return
149
143
  except Exception as e:
150
- self.app.show_error_message(f"{error_msg_fmt}: {e}")
144
+ self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
151
145
  return
152
146
 
153
147
  progress = ProgressModal("Applying changes...")
@@ -236,7 +230,7 @@ class VMDetailModal(ModalScreen):
236
230
  all_networks_info = list_networks(self.conn)
237
231
  self.available_networks = [net['name'] for net in all_networks_info]
238
232
  except (libvirt.libvirtError, Exception) as e:
239
- self.app.show_error_message(f"Could not load networks: {e}")
233
+ self.app.show_error_message(ErrorMessages.VM_DETAIL_COULD_NOT_LOAD_NETWORKS.format(error=e))
240
234
  self.available_networks = []
241
235
 
242
236
  try:
@@ -545,10 +539,10 @@ class VMDetailModal(ModalScreen):
545
539
  try:
546
540
  set_boot_info(self.domain, menu_enabled, new_boot_order)
547
541
  self._invalidate_cache()
548
- self.app.show_success_message("Boot order saved successfully.")
542
+ self.app.show_success_message(SuccessMessages.BOOT_ORDER_SAVED_SUCCESSFULLY)
549
543
  self.boot_order = new_boot_order
550
544
  except libvirt.libvirtError as e:
551
- self.app.show_error_message(f"Error saving boot order: {e}")
545
+ self.app.show_error_message(ErrorMessages.ERROR_SAVING_BOOT_ORDER_TEMPLATE.format(error=e))
552
546
 
553
547
 
554
548
  def _get_bootable_devices(self) -> list[BootDevice]:
@@ -721,13 +715,13 @@ class VMDetailModal(ModalScreen):
721
715
  try:
722
716
  change_vm_network(self.domain, mac_address, new_network)
723
717
  self._invalidate_cache()
724
- self.app.show_success_message(f"Interface {mac_address} switched to {new_network}")
718
+ self.app.show_success_message(SuccessMessages.VM_NETWORK_INTERFACE_CHANGED_TEMPLATE.format(mac_address=mac_address, new_network=new_network))
725
719
  for i in self.vm_info["networks"]:
726
720
  if i["mac"] == mac_address:
727
721
  i["network"] = new_network
728
722
  break
729
723
  except (libvirt.libvirtError, ValueError, Exception) as e:
730
- self.app.show_error_message(f"Error updating network: {e}")
724
+ self.app.show_error_message(ErrorMessages.ERROR_UPDATING_NETWORK_TEMPLATE_SHORT.format(error=e))
731
725
  event.control.value = original_network
732
726
 
733
727
  self.available_networks = []
@@ -753,7 +747,7 @@ class VMDetailModal(ModalScreen):
753
747
  self._run_bulk_operation(
754
748
  targets,
755
749
  operation,
756
- f"CPU model set to {new_cpu_model}" + (" for {count} VMs ({names})" if self.is_bulk else ""),
750
+ SuccessMessages.VM_CPU_MODEL_SET_SUCCESS.format(new_cpu_model=new_cpu_model) + (" for {count} VMs ({names})" if self.is_bulk else ""),
757
751
  "Errors setting CPU model",
758
752
  ui_update
759
753
  )
@@ -783,7 +777,7 @@ class VMDetailModal(ModalScreen):
783
777
  self.query_one("#firmware-path-label").update(msg_text)
784
778
  self.vm_info['firmware']['path'] = new_uefi_path
785
779
 
786
- msg_template = f"UEFI file set to {os.path.basename(new_uefi_path) if new_uefi_path else 'BIOS'}"
780
+ msg_template = SuccessMessages.VM_UEFI_FILE_SET_SUCCESS.format(uefi_file_name=os.path.basename(new_uefi_path) if new_uefi_path else 'BIOS')
787
781
  if self.is_bulk:
788
782
  msg_template += " for {count} VMs ({names})"
789
783
 
@@ -838,7 +832,7 @@ class VMDetailModal(ModalScreen):
838
832
  self._run_bulk_operation(
839
833
  targets,
840
834
  operation,
841
- f"Video model set to {new_model}" + (" for {count} VMs ({names})" if self.is_bulk else ""),
835
+ SuccessMessages.VM_VIDEO_MODEL_SET_SUCCESS.format(new_model=new_model) + (" for {count} VMs ({names})" if self.is_bulk else ""),
842
836
  "Errors setting video model",
843
837
  ui_update
844
838
  )
@@ -862,7 +856,7 @@ class VMDetailModal(ModalScreen):
862
856
  self._run_bulk_operation(
863
857
  targets,
864
858
  operation,
865
- f"3D Acceleration {'enabled' if accel3d_enabled else 'disabled'}" + (" for {count} VMs ({names})" if self.is_bulk else ""),
859
+ SuccessMessages.VM_3D_ACCELERATION_SET_SUCCESS.format(state='enabled' if accel3d_enabled else 'disabled') + (" for {count} VMs ({names})" if self.is_bulk else ""),
866
860
  "Errors setting 3D acceleration",
867
861
  ui_update
868
862
  )
@@ -889,7 +883,7 @@ class VMDetailModal(ModalScreen):
889
883
  self._run_bulk_operation(
890
884
  targets,
891
885
  operation,
892
- f"Sound model set to {new_model}" + (" for {count} VMs ({names})" if self.is_bulk else ""),
886
+ SuccessMessages.VM_SOUND_MODEL_SET_SUCCESS.format(new_model=new_model) + (" for {count} VMs ({names})" if self.is_bulk else ""),
893
887
  "Errors setting sound model",
894
888
  ui_update
895
889
  )
@@ -900,7 +894,7 @@ class VMDetailModal(ModalScreen):
900
894
 
901
895
  current_uefi_path = self.vm_info['firmware'].get('path')
902
896
  if not current_uefi_path and event.value: # Trying to enable secure boot without a UEFI file
903
- self.app.show_error_message("Cannot enable secure boot without a UEFI file selected.")
897
+ self.app.show_error_message(ErrorMessages.CANNOT_ENABLE_SECURE_BOOT_WITHOUT_UEFI)
904
898
  event.checkbox.value = not event.value # Revert checkbox
905
899
  self._update_uefi_options() # Revert options
906
900
  return
@@ -917,7 +911,7 @@ class VMDetailModal(ModalScreen):
917
911
  self._run_bulk_operation(
918
912
  targets,
919
913
  operation,
920
- f"Secure Boot {'enabled' if event.value else 'disabled'}" + (" for {count} VMs ({names})" if self.is_bulk else ""),
914
+ SuccessMessages.VM_SECURE_BOOT_SET_SUCCESS.format(state='enabled' if event.value else 'disabled') + (" for {count} VMs ({names})" if self.is_bulk else ""),
921
915
  "Errors setting Secure Boot",
922
916
  ui_update
923
917
  )
@@ -940,7 +934,7 @@ class VMDetailModal(ModalScreen):
940
934
  self._run_bulk_operation(
941
935
  targets,
942
936
  operation,
943
- f"Shared memory {'enabled' if event.value else 'disabled'}" + (" for {count} VMs ({names})" if self.is_bulk else ""),
937
+ SuccessMessages.VM_SHARED_MEMORY_SET_SUCCESS.format(state='enabled' if event.value else 'disabled') + (" for {count} VMs ({names})" if self.is_bulk else ""),
944
938
  "Errors setting shared memory",
945
939
  ui_update
946
940
  )
@@ -998,7 +992,7 @@ class VMDetailModal(ModalScreen):
998
992
  @on(Button.Pressed, "#graphics-apply-btn")
999
993
  def on_graphics_apply_button_pressed(self, event: Button.Pressed) -> None:
1000
994
  if not self.is_vm_stopped:
1001
- self.app.show_error_message("VM must be stopped to apply graphics settings.")
995
+ self.app.show_error_message(ErrorMessages.GRAPHICS_VM_MUST_BE_STOPPED)
1002
996
  return
1003
997
 
1004
998
  original_graphics_type = self.original_graphics_info.get('type')
@@ -1054,7 +1048,7 @@ class VMDetailModal(ModalScreen):
1054
1048
  self._run_bulk_operation(
1055
1049
  targets,
1056
1050
  operation,
1057
- "Graphics settings applied successfully" + (" (Applied to {count} VMs ({names}))" if self.is_bulk else ""),
1051
+ SuccessMessages.GRAPHICS_SETTINGS_APPLIED_SUCCESSFULLY + (" (Applied to {count} VMs ({names}))" if self.is_bulk else ""),
1058
1052
  "Errors applying graphics settings",
1059
1053
  ui_update
1060
1054
  )
@@ -1080,16 +1074,16 @@ class VMDetailModal(ModalScreen):
1080
1074
  self._run_bulk_operation(
1081
1075
  targets,
1082
1076
  removal_operation,
1083
- "Removed associated SPICE devices" + (" (from {count} VMs ({names}))" if self.is_bulk else ""),
1077
+ SuccessMessages.SPICE_DEVICES_REMOVED_SUCCESS + (" (from {count} VMs ({names}))" if self.is_bulk else ""),
1084
1078
  "Error removing SPICE devices",
1085
1079
  do_apply_graphics_settings
1086
1080
  )
1087
1081
  else:
1088
1082
  do_apply_graphics_settings()
1089
1083
 
1090
- msg = "This VM has other SPICE-related devices (e.g., channels, QXL video).\nDo you want to remove them for a clean switch to VNC?"
1084
+ msg = ErrorMessages.SPICE_REMOVAL_CONFIRMATION_SINGLE
1091
1085
  if self.is_bulk:
1092
- msg = "Some selected VMs have other SPICE-related devices.\nDo you want to remove them from ALL selected VMs for a clean switch to VNC?"
1086
+ msg = ErrorMessages.SPICE_REMOVAL_CONFIRMATION_BULK
1093
1087
 
1094
1088
  self.app.push_screen(
1095
1089
  ConfirmationDialog(msg),
@@ -1102,12 +1096,12 @@ class VMDetailModal(ModalScreen):
1102
1096
  @on(Button.Pressed, "#apply-rng-btn")
1103
1097
  def on_rng_apply_button_pressed(self, event: Button.Pressed) -> None:
1104
1098
  if not self.is_vm_stopped:
1105
- self.app.show_error_message("VM must be stopped to apply RNG settings.")
1099
+ self.app.show_error_message(ErrorMessages.RNG_VM_MUST_BE_STOPPED)
1106
1100
  return
1107
1101
 
1108
1102
  rng_device = self.query_one("#rng-host-device", Input).value
1109
1103
  if not rng_device:
1110
- self.app.show_error_message("RNG device path cannot be empty.")
1104
+ self.app.show_error_message(ErrorMessages.RNG_DEVICE_PATH_EMPTY)
1111
1105
  return
1112
1106
 
1113
1107
  targets = self.selected_domains if self.is_bulk else [self.domain]
@@ -1118,7 +1112,7 @@ class VMDetailModal(ModalScreen):
1118
1112
  self._run_bulk_operation(
1119
1113
  targets,
1120
1114
  operation,
1121
- f"RNG settings applied successfully. Device: {rng_device}" + (" (Applied to {count} VMs ({names}))" if self.is_bulk else ""),
1115
+ SuccessMessages.RNG_SETTINGS_APPLIED_SUCCESSFULLY.format(rng_device=rng_device) + (" (Applied to {count} VMs ({names}))" if self.is_bulk else ""),
1122
1116
  "Error applying RNG settings"
1123
1117
  )
1124
1118
 
@@ -1141,7 +1135,7 @@ class VMDetailModal(ModalScreen):
1141
1135
  @on(Button.Pressed, "#apply-tpm-btn")
1142
1136
  def on_tpm_apply_button_pressed(self, event: Button.Pressed) -> None:
1143
1137
  if not self.is_vm_stopped:
1144
- self.app.show_error_message("VM must be stopped to apply TPM settings.")
1138
+ self.app.show_error_message(ErrorMessages.TPM_VM_MUST_BE_STOPPED)
1145
1139
  return
1146
1140
 
1147
1141
  tpm_model = self.query_one("#tpm-model-select", Select).value
@@ -1152,7 +1146,7 @@ class VMDetailModal(ModalScreen):
1152
1146
 
1153
1147
  # Basic validation for passthrough
1154
1148
  if tpm_type == 'passthrough' and not device_path:
1155
- self.app.show_error_message("Device path is required for passthrough TPM.")
1149
+ self.app.show_error_message(ErrorMessages.DEVICE_PATH_REQUIRED_FOR_PASSTHROUGH_TPM)
1156
1150
  return
1157
1151
 
1158
1152
  targets = self.selected_domains if self.is_bulk else [self.domain]
@@ -1179,14 +1173,13 @@ class VMDetailModal(ModalScreen):
1179
1173
  self.tpm_info = get_vm_tpm_info(root) # Refresh info
1180
1174
  self._update_tpm_ui()
1181
1175
 
1182
- self._run_bulk_operation(
1183
- targets,
1184
- operation,
1185
- "TPM settings applied successfully" + (" (Applied to {count} VMs ({names}))" if self.is_bulk else ""),
1186
- "Error applying TPM settings",
1187
- ui_update
1188
- )
1189
-
1176
+ self._run_bulk_operation(
1177
+ targets,
1178
+ operation,
1179
+ SuccessMessages.TPM_SETTINGS_APPLIED_SUCCESSFULLY + (" (Applied to {count} VMs ({names}))" if self.is_bulk else ""),
1180
+ "Error applying TPM settings",
1181
+ ui_update
1182
+ )
1190
1183
  @on(ListView.Highlighted, "#available-devices-list")
1191
1184
  def on_available_devices_list_highlighted(self, event: ListView.Highlighted) -> None:
1192
1185
  if not self.is_vm_stopped:
@@ -1276,11 +1269,11 @@ class VMDetailModal(ModalScreen):
1276
1269
  try:
1277
1270
  attach_usb_device(self.domain, vendor_id, product_id)
1278
1271
  self._invalidate_cache()
1279
- self.app.show_success_message(f"Attached USB device: {device_to_attach['description']}")
1272
+ self.app.show_success_message(SuccessMessages.USB_DEVICE_ATTACHED_TEMPLATE.format(description=device_to_attach['description']))
1280
1273
  self.xml_desc = self.vm_service._get_domain_xml(self.domain)
1281
1274
  self._populate_usb_lists()
1282
1275
  except libvirt.libvirtError as e:
1283
- self.app.show_error_message(f"Error attaching USB device: {e}")
1276
+ self.app.show_error_message(ErrorMessages.ERROR_ATTACHING_USB_DEVICE_TEMPLATE.format(error=e))
1284
1277
 
1285
1278
  @on(Button.Pressed, "#detach-usb-btn")
1286
1279
  def on_detach_usb_button_pressed(self, event: Button.Pressed) -> None:
@@ -1292,11 +1285,11 @@ class VMDetailModal(ModalScreen):
1292
1285
  try:
1293
1286
  detach_usb_device(self.domain, vendor_id, product_id)
1294
1287
  self._invalidate_cache()
1295
- self.app.show_success_message(f"Detached USB device: {device_to_detach['description']}")
1288
+ self.app.show_success_message(SuccessMessages.USB_DEVICE_DETACHED_TEMPLATE.format(description=device_to_detach['description']))
1296
1289
  self.xml_desc = self.vm_service._get_domain_xml(self.domain)
1297
1290
  self._populate_usb_lists()
1298
1291
  except libvirt.libvirtError as e:
1299
- self.app.show_error_message(f"Error detaching USB device: {e}")
1292
+ self.app.show_error_message(ErrorMessages.ERROR_DETACHING_USB_DEVICE_TEMPLATE.format(error=e))
1300
1293
 
1301
1294
  def _populate_pci_lists(self):
1302
1295
  """Populates the PCI device lists."""
@@ -1348,11 +1341,11 @@ class VMDetailModal(ModalScreen):
1348
1341
 
1349
1342
  @on(Button.Pressed, "#attach-pci-btn")
1350
1343
  def on_attach_pci_button_pressed(self, event: Button.Pressed) -> None:
1351
- self.app.show_error_message("PCI passthrough not implemented yet.")
1344
+ self.app.show_error_message(ErrorMessages.PCI_PASSTHROUGH_NOT_IMPLEMENTED)
1352
1345
 
1353
1346
  @on(Button.Pressed, "#detach-pci-btn")
1354
1347
  def on_detach_pci_button_pressed(self, event: Button.Pressed) -> None:
1355
- self.app.show_error_message("PCI passthrough not implemented yet.")
1348
+ self.app.show_error_message(ErrorMessages.PCI_PASSTHROUGH_NOT_IMPLEMENTED)
1356
1349
 
1357
1350
  def _populate_serial_table(self):
1358
1351
  """Populates the serial devices table."""
@@ -1555,10 +1548,10 @@ class VMDetailModal(ModalScreen):
1555
1548
  result["target_name"]
1556
1549
  )
1557
1550
  self._invalidate_cache()
1558
- self.app.show_success_message("Channel added successfully.")
1551
+ self.app.show_success_message(SuccessMessages.CHANNEL_ADDED_SUCCESSFULLY)
1559
1552
  self._update_channel_table()
1560
1553
  except libvirt.libvirtError as e:
1561
- self.app.show_error_message(f"Error adding channel: {e}")
1554
+ self.app.show_error_message(ErrorMessages.ERROR_ADDING_CHANNEL_TEMPLATE.format(error=e))
1562
1555
 
1563
1556
  self.app.push_screen(AddChannelModal(), add_channel_callback)
1564
1557
 
@@ -1567,19 +1560,19 @@ class VMDetailModal(ModalScreen):
1567
1560
  if self.selected_channel:
1568
1561
  target_name = self.selected_channel.get('name')
1569
1562
  if not target_name:
1570
- self.app.show_error_message("Selected channel has no target name.")
1563
+ self.app.show_error_message(ErrorMessages.ADD_CHANNEL_NO_TARGET_NAME)
1571
1564
  return
1572
1565
 
1573
- message = f"Are you sure you want to remove channel '{target_name}'?"
1566
+ message = DialogMessages.CONFIRM_REMOVE_CHANNEL_TEMPLATE.format(target_name=target_name)
1574
1567
  def on_confirm(confirmed: bool) -> None:
1575
1568
  if confirmed:
1576
1569
  try:
1577
1570
  remove_vm_channel(self.domain, target_name)
1578
1571
  self._invalidate_cache()
1579
- self.app.show_success_message(f"Channel '{target_name}' removed successfully.")
1572
+ self.app.show_success_message(SuccessMessages.CHANNEL_REMOVED_SUCCESSFULLY_TEMPLATE.format(target_name=target_name))
1580
1573
  self._update_channel_table()
1581
1574
  except libvirt.libvirtError as e:
1582
- self.app.show_error_message(f"Error removing channel: {e}")
1575
+ self.app.show_error_message(ErrorMessages.ERROR_REMOVING_CHANNEL_TEMPLATE.format(error=e))
1583
1576
  self.app.push_screen(ConfirmationDialog(message), on_confirm)
1584
1577
 
1585
1578
  def compose(self) -> ComposeResult:
@@ -1600,12 +1593,12 @@ class VMDetailModal(ModalScreen):
1600
1593
  if not self.is_bulk:
1601
1594
  yield Label(f"ID: {self.vm_info.get('internal_id', 'N/A')}", id="vm-details-uuid")
1602
1595
 
1603
- yield Button("Other Tabs", id="toggle-detail-button", classes="toggle-detail-button")
1596
+ yield Button(ButtonLabels.OTHER_TABS, id="toggle-detail-button", classes="toggle-detail-button")
1604
1597
  with TabbedContent(id="detail-vm"):
1605
1598
  with TabPane("CPU", id="detail-cpu-tab"):
1606
1599
  with Vertical(classes="info-details"):
1607
1600
  yield Label(f"CPU: {self.vm_info.get('cpu', 'N/A')}", id="cpu-label", classes="tabd")
1608
- yield Button("Edit", id="edit-cpu", classes="edit-detail-btn")
1601
+ yield Button(ButtonLabels.EDIT, id="edit-cpu", classes="edit-detail-btn")
1609
1602
  yield Static(classes="button-separator")
1610
1603
 
1611
1604
  # CPU Model Selection
@@ -1636,7 +1629,7 @@ class VMDetailModal(ModalScreen):
1636
1629
  # CPU Tune
1637
1630
  vcpupin_count = len(self.cputune_info.get('vcpupin', []))
1638
1631
  yield Label(f"CPU Pinning: {vcpupin_count} rules", id="cputune-label", classes="tabd")
1639
- yield Button("Edit CPU Tune", id="edit-cputune", classes="edit-detail-btn", disabled=not self.is_vm_stopped)
1632
+ yield Button(ButtonLabels.EDIT_CPU_TUNE, id="edit-cputune", classes="edit-detail-btn", disabled=not self.is_vm_stopped)
1640
1633
 
1641
1634
  yield Static(classes="button-separator")
1642
1635
 
@@ -1645,12 +1638,12 @@ class VMDetailModal(ModalScreen):
1645
1638
  yield Label(f"NUMA Mode: {numa_mode}", id="numatune-label", classes="tabd")
1646
1639
 
1647
1640
  numa_btn_disabled = not self.is_vm_stopped or self.host_numa_nodes <= 1
1648
- yield Button("Edit NUMA Tune", id="edit-numatune", classes="edit-detail-btn", disabled=numa_btn_disabled)
1641
+ yield Button(ButtonLabels.EDIT_NUMA_TUNE, id="edit-numatune", classes="edit-detail-btn", disabled=numa_btn_disabled)
1649
1642
 
1650
1643
  with TabPane("Mem", id="detail-mem-tab", ):
1651
1644
  with Vertical(classes="info-details"):
1652
1645
  yield Label(f"Memory: {self.vm_info.get('memory', 'N/A')} MB", id="memory-label", classes="tabd")
1653
- yield Button("Edit", id="edit-memory", classes="edit-detail-btn")
1646
+ yield Button(ButtonLabels.EDIT, id="edit-memory", classes="edit-detail-btn")
1654
1647
  yield Static(classes="button-separator")
1655
1648
  yield Checkbox("Shared Memory", value=self.vm_info.get('shared_memory', False), id="shared-memory-checkbox", classes="shared-memory", disabled=not self.is_vm_stopped)
1656
1649
  if not self.is_bulk:
@@ -1680,34 +1673,34 @@ class VMDetailModal(ModalScreen):
1680
1673
  allow_blank=True,
1681
1674
  )
1682
1675
  yield Static(classes="button-separator")
1683
- yield Button("Switch to BIOS", id="switch-to-bios", disabled=not self.is_vm_stopped)
1676
+ yield Button(ButtonLabels.SWITCH_TO_BIOS, id="switch-to-bios", disabled=not self.is_vm_stopped)
1684
1677
  else:
1685
- yield Button("Switch to UEFI", id="switch-to-uefi", disabled=not self.is_vm_stopped)
1678
+ yield Button(ButtonLabels.SWITCH_TO_UEFI, id="switch-to-uefi", disabled=not self.is_vm_stopped)
1686
1679
 
1687
1680
 
1688
1681
  if "machine_type" in self.vm_info:
1689
1682
  yield Static(classes="button-separator")
1690
1683
  yield Label(f"Machine Type: {self.vm_info['machine_type']}", id="machine-type-label", classes="tabd")
1691
- yield Button("Edit", id="edit-machine-type", classes="edit-detail-btn", disabled=not self.is_vm_stopped)
1684
+ yield Button(ButtonLabels.EDIT, id="edit-machine-type", classes="edit-detail-btn", disabled=not self.is_vm_stopped)
1692
1685
 
1693
1686
 
1694
1687
  with TabPane("Boot", id="detail-boot-tab"):
1695
1688
  with Vertical():
1696
- yield Checkbox("Enable boot menu", id="boot-menu-enable", disabled=not self.is_vm_stopped)
1689
+ yield Checkbox(StaticText.ENABLE_BOOT_MENU, id="boot-menu-enable", disabled=not self.is_vm_stopped)
1697
1690
  with Horizontal(classes="boot-manager"):
1698
1691
  with Vertical(classes="boot-main-container"):
1699
- yield Label("Boot Order")
1692
+ yield Label(StaticText.BOOT_ORDER)
1700
1693
  yield ListView(id="boot-order-list", classes="boot-list-container")
1701
1694
  with Vertical(classes="boot-buttons-container"):
1702
- yield Label("")
1703
- yield Button("<", id="boot-add", disabled=not self.is_vm_stopped)
1704
- yield Button(">", id="boot-remove", disabled=not self.is_vm_stopped)
1705
- yield Button("Up", id="boot-up", disabled=not self.is_vm_stopped)
1706
- yield Button("Down", id="boot-down", disabled=not self.is_vm_stopped)
1695
+ yield Label(StaticText.EMPTY_LABEL)
1696
+ yield Button(ButtonLabels.BOOT_ADD, id="boot-add", disabled=not self.is_vm_stopped)
1697
+ yield Button(ButtonLabels.BOOT_REMOVE, id="boot-remove", disabled=not self.is_vm_stopped)
1698
+ yield Button(ButtonLabels.BOOT_UP, id="boot-up", disabled=not self.is_vm_stopped)
1699
+ yield Button(ButtonLabels.BOOT_DOWN, id="boot-down", disabled=not self.is_vm_stopped)
1707
1700
  with Vertical(classes="boot-main-container"):
1708
- yield Label("Available Devices")
1701
+ yield Label(StaticText.AVAILABLE_DEVICES)
1709
1702
  yield ListView(id="available-devices-list", classes="boot-list-container")
1710
- yield Button("Save Boot Order", id="save-boot-order", disabled=not self.is_vm_stopped, variant="primary")
1703
+ yield Button(ButtonLabels.SAVE_BOOT_ORDER, id="save-boot-order", disabled=not self.is_vm_stopped, variant="primary")
1711
1704
 
1712
1705
  with TabPane("Disks", id="detail-disk-tab"):
1713
1706
  with ScrollableContainer(classes="info-details"):
@@ -1716,20 +1709,20 @@ class VMDetailModal(ModalScreen):
1716
1709
  disks_info = self.vm_info.get("disks", [])
1717
1710
  has_enabled_disks = any(d['status'] == 'enabled' for d in disks_info)
1718
1711
  has_disabled_disks = any(d['status'] == 'disabled' for d in disks_info)
1719
- remove_button = Button("Remove Disk", id="detail_remove_disk", classes="detail-disks")
1720
- disable_button = Button("Disable Disk", id="detail_disable_disk", classes="detail-disks")
1721
- enable_button = Button("Enable Disk", id="detail_enable_disk", classes="detail-disks")
1712
+ remove_button = Button(ButtonLabels.REMOVE_DISK, id="detail_remove_disk", classes="detail-disks")
1713
+ disable_button = Button(ButtonLabels.DISABLE_DISK, id="detail_disable_disk", classes="detail-disks")
1714
+ enable_button = Button(ButtonLabels.ENABLE_DISK, id="detail_enable_disk", classes="detail-disks")
1722
1715
  remove_button.display = has_enabled_disks
1723
1716
  disable_button.display = has_enabled_disks
1724
1717
  enable_button.display = has_disabled_disks
1725
1718
 
1726
1719
  with Vertical(classes="button-details"):
1727
1720
  with Horizontal():
1728
- yield Button("Add Disk", id="detail_add_disk", classes="detail-disks")
1729
- yield Button("Attach Existing Disk", id="detail_attach_disk", classes="detail-disks")
1730
- yield Button("Edit Disk", id="detail_edit_disk", classes="detail-disks", disabled=True)
1721
+ yield Button(ButtonLabels.ADD_DISK, id="detail_add_disk", classes="detail-disks")
1722
+ yield Button(ButtonLabels.ATTACH_EXISTING_DISK, id="detail_attach_disk", classes="detail-disks")
1723
+ yield Button(ButtonLabels.EDIT_DISK, id="detail_edit_disk", classes="detail-disks", disabled=True)
1731
1724
  yield remove_button
1732
- yield Button("Help", id="detail_disk_help", classes="detail-disks")
1725
+ yield Button(ButtonLabels.DISK_HELP, id="detail_disk_help", classes="detail-disks")
1733
1726
 
1734
1727
  with Horizontal(classes="button-details"):
1735
1728
  yield disable_button
@@ -1748,14 +1741,14 @@ class VMDetailModal(ModalScreen):
1748
1741
 
1749
1742
  with Vertical(classes="button-details"):
1750
1743
  with Horizontal():
1751
- yield Button("Edit Interface", id="edit-network-interface-button", classes="detail-disks", variant="primary", disabled=True)
1752
- yield Button("Add Interface", id="add-network-interface-button", classes="detail-disks", variant="primary")
1753
- yield Button("Remove Interface", id="remove-network-interface-button", classes="detail-disks", variant="error", disabled=True)
1744
+ yield Button(ButtonLabels.EDIT_INTERFACE, id="edit-network-interface-button", classes="detail-disks", variant="primary", disabled=True)
1745
+ yield Button(ButtonLabels.ADD_INTERFACE, id="add-network-interface-button", classes="detail-disks", variant="primary")
1746
+ yield Button(ButtonLabels.REMOVE_INTERFACE, id="remove-network-interface-button", classes="detail-disks", variant="error", disabled=True)
1754
1747
 
1755
1748
  if self.vm_info.get("devices"):
1756
1749
  with TabPane("VirtIO-FS", id="detail-virtiofs-tab"):
1757
1750
  # Always create label, toggle visibility
1758
- lbl = Label("! Shared Memory is Mandatory to use VirtIO-FS.\n! Enable it in Mem tab.", id="virtiofs-shared-mem-warning", classes="tabd-warning")
1751
+ lbl = Label(WarningMessages.VIRTIOFS_SHARED_MEM_WARNING, id="virtiofs-shared-mem-warning", classes="tabd-warning")
1759
1752
  lbl.display = not self.vm_info.get('shared_memory')
1760
1753
  yield lbl
1761
1754
  with ScrollableContainer(classes="info-details"):
@@ -1774,10 +1767,10 @@ class VMDetailModal(ModalScreen):
1774
1767
  yield virtiofs_table
1775
1768
  with Vertical(classes="button-details"):
1776
1769
  with Horizontal():
1777
- yield Button("Add", variant="primary", id="add-virtiofs-btn", classes="detail-disks")
1778
- yield Button("Edit", variant="default", id="edit-virtiofs-btn", disabled=True, classes="detail-disks")
1779
- yield Button("Delete", variant="error", id="delete-virtiofs-btn", disabled=True, classes="detail-disks")
1780
- yield Button("Help", id="detail_virtiofs_help", classes="detail-disks")
1770
+ yield Button(ButtonLabels.ADD, variant="primary", id="add-virtiofs-btn", classes="detail-disks")
1771
+ yield Button(ButtonLabels.EDIT, variant="default", id="edit-virtiofs-btn", disabled=True, classes="detail-disks")
1772
+ yield Button(ButtonLabels.DELETE, variant="error", id="delete-virtiofs-btn", disabled=True, classes="detail-disks")
1773
+ yield Button(ButtonLabels.VIRTIOFS_HELP, id="detail_virtiofs_help", classes="detail-disks")
1781
1774
 
1782
1775
  with TabPane("Video", id="detail-video-tab"):
1783
1776
  with Vertical(classes="info-details"):
@@ -1854,25 +1847,25 @@ class VMDetailModal(ModalScreen):
1854
1847
 
1855
1848
  with TabPane("Graphics", id="detail-graphics-tab"):
1856
1849
  with ScrollableContainer(classes="info-details"):
1857
- yield Label("Type:")
1850
+ yield Label(StaticText.TYPE_LABEL)
1858
1851
  yield Select(
1859
1852
  [("VNC", "vnc"), ("Spice", "spice"), ("None", "")],
1860
1853
  value=self.graphics_info['type'],
1861
1854
  id="graphics-type-select",
1862
1855
  disabled=not self.is_vm_stopped
1863
1856
  )
1864
- yield Label("Listen Type:")
1857
+ yield Label(StaticText.LISTEN_TYPE)
1865
1858
  yield Select(
1866
1859
  [("Address", "address"), ("None", "none")],
1867
1860
  value=self.graphics_info['listen_type'],
1868
1861
  id="graphics-listen-type-select",
1869
1862
  disabled=not self.is_vm_stopped
1870
1863
  )
1871
- yield Label("Address:")
1864
+ yield Label(StaticText.ADDRESS_LABEL)
1872
1865
  with RadioSet(id="graphics-address-radioset", disabled=not self.is_vm_stopped or self.graphics_info['listen_type'] != 'address'):
1873
- yield RadioButton("Hypervisor default", id="graphics-address-default", value=self.graphics_info['address'] not in ['127.0.0.1', '0.0.0.0'])
1874
- yield RadioButton("Localhost only", id="graphics-address-localhost", value=self.graphics_info['address'] == '127.0.0.1')
1875
- yield RadioButton("All interfaces", id="graphics-address-all", value=self.graphics_info['address'] == '0.0.0.0')
1866
+ yield RadioButton(StaticText.HYPERVISOR_DEFAULT, id="graphics-address-default", value=self.graphics_info['address'] not in ['127.0.0.1', '0.0.0.0'])
1867
+ yield RadioButton(StaticText.LOCALHOST_ONLY, id="graphics-address-localhost", value=self.graphics_info['address'] == '127.0.0.1')
1868
+ yield RadioButton(StaticText.ALL_INTERFACES, id="graphics-address-all", value=self.graphics_info['address'] == '0.0.0.0')
1876
1869
  yield Checkbox(
1877
1870
  "Auto Port",
1878
1871
  value=self.graphics_info['autoport'],
@@ -1899,7 +1892,7 @@ class VMDetailModal(ModalScreen):
1899
1892
  password=True, # Hide password input
1900
1893
  disabled=not self.is_vm_stopped or not self.graphics_info['password_enabled']
1901
1894
  )
1902
- yield Button("Apply Graphics Settings", id="graphics-apply-btn", variant="primary", disabled=not self.is_vm_stopped)
1895
+ yield Button(ButtonLabels.APPLY_GRAPHICS_SETTINGS, id="graphics-apply-btn", variant="primary", disabled=not self.is_vm_stopped)
1903
1896
  with TabPane("TPM", id="detail-tpm-tab"):
1904
1897
  tpm_model = self.tpm_info[0].get('model') if self.tpm_info else 'none'
1905
1898
  tpm_type = self.tpm_info[0].get('type') if self.tpm_info else 'emulated'
@@ -1908,7 +1901,7 @@ class VMDetailModal(ModalScreen):
1908
1901
  tpm_backend_path = self.tpm_info[0].get('backend_path', '') if self.tpm_info else ''
1909
1902
 
1910
1903
  with Vertical(classes="info-details"):
1911
- yield Label("TPM Model:")
1904
+ yield Label(StaticText.TPM_MODEL)
1912
1905
  yield Select(
1913
1906
  [("None", "none"), ("tpm-crb", "tpm-crb"), ("tpm-tis", "tpm-tis")],
1914
1907
  value=tpm_model,
@@ -1916,7 +1909,7 @@ class VMDetailModal(ModalScreen):
1916
1909
  disabled=not self.is_vm_stopped,
1917
1910
  allow_blank=False,
1918
1911
  )
1919
- yield Label("TPM Type:")
1912
+ yield Label(StaticText.TPM_TYPE)
1920
1913
  yield Select(
1921
1914
  [("Emulated", "emulated"), ("Passthrough", "passthrough")],
1922
1915
  value=tpm_type,
@@ -1924,51 +1917,51 @@ class VMDetailModal(ModalScreen):
1924
1917
  disabled=not self.is_vm_stopped,
1925
1918
  allow_blank=False,
1926
1919
  )
1927
- yield Label("Device Path (for passthrough):")
1920
+ yield Label(StaticText.DEVICE_PATH_PASSTHROUGH)
1928
1921
  yield Input(
1929
1922
  value=tpm_device_path,
1930
1923
  id="tpm-device-path-input",
1931
1924
  disabled=not self.is_vm_stopped or tpm_type != 'passthrough',
1932
- placeholder="/dev/tpm0"
1925
+ placeholder="=/dev/tpm0"
1933
1926
  )
1934
- yield Label("Backend Type (for passthrough):")
1927
+ yield Label(StaticText.BACKEND_TYPE_PASSTHROUGH)
1935
1928
  yield Input(
1936
1929
  value=tpm_backend_type,
1937
1930
  id="tpm-backend-type-input",
1938
1931
  disabled=not self.is_vm_stopped or tpm_type != 'passthrough',
1939
1932
  placeholder="emulator or passthrough"
1940
1933
  )
1941
- yield Label("Backend Path (for passthrough):")
1934
+ yield Label(StaticText.BACKEND_PATH_PASSTHROUGH)
1942
1935
  yield Input(
1943
1936
  value=tpm_backend_path,
1944
1937
  id="tpm-backend-path-input",
1945
1938
  disabled=not self.is_vm_stopped or tpm_type != 'passthrough',
1946
1939
  placeholder="/dev/tpmrm0"
1947
1940
  )
1948
- yield Button("Apply TPM Settings", id="apply-tpm-btn", variant="primary", disabled=not self.is_vm_stopped)
1941
+ yield Button(ButtonLabels.APPLY_TPM_SETTINGS, id="apply-tpm-btn", variant="primary", disabled=not self.is_vm_stopped)
1949
1942
 
1950
1943
 
1951
1944
  with TabbedContent(id="detail2-vm"):
1952
1945
  with TabPane("RNG", id="detail-rng-tab"):
1953
1946
  with Vertical(classes="info-details"):
1954
1947
  current_path = self.rng_info["backend_path"]
1955
- yield Label("Host device")
1948
+ yield Label(StaticText.HOST_DEVICE)
1956
1949
  yield Input(value=current_path, id="rng-host-device")
1957
- yield Button("Apply RNG Settings", id="apply-rng-btn", variant="primary")
1950
+ yield Button(ButtonLabels.APPLY_RNG_SETTINGS, id="apply-rng-btn", variant="primary")
1958
1951
 
1959
1952
  with TabPane("Serial", id="detail-serial-tab"):
1960
1953
  with ScrollableContainer(classes="info-details"):
1961
1954
  yield DataTable(id="serial-table", cursor_type="row")
1962
1955
  with Vertical(classes="button-details"):
1963
1956
  with Horizontal():
1964
- yield Button("Add PTY Console", id="add-serial-btn", variant="primary", disabled=not self.is_vm_stopped)
1965
- yield Button("Remove Console", id="remove-serial-btn", variant="error", disabled=True)
1957
+ yield Button(ButtonLabels.ADD_PTY_CONSOLE, id="add-serial-btn", variant="primary", disabled=not self.is_vm_stopped)
1958
+ yield Button(ButtonLabels.REMOVE_CONSOLE, id="remove-serial-btn", variant="error", disabled=True)
1966
1959
  with TabPane("Watchdog", id="detail-watchdog-tab"):
1967
1960
  watchdog_model = self.watchdog_info.get('model') if self.watchdog_info and self.watchdog_info.get('model') else 'none'
1968
1961
  watchdog_action = self.watchdog_info.get('action') if self.watchdog_info and self.watchdog_info.get('action') else 'reset'
1969
1962
 
1970
1963
  with Vertical(classes="info-details"):
1971
- yield Label("Watchdog Model:")
1964
+ yield Label(StaticText.WATCHDOG_MODEL)
1972
1965
 
1973
1966
  watchdog_models = [("None", "none"), ("i6300esb", "i6300esb"), ("ib700", "ib700"), ("diag288", "diag288")]
1974
1967
 
@@ -1984,7 +1977,7 @@ class VMDetailModal(ModalScreen):
1984
1977
  disabled=not self.is_vm_stopped,
1985
1978
  allow_blank=False
1986
1979
  )
1987
- yield Label("Action:")
1980
+ yield Label(StaticText.ACTION_LABEL)
1988
1981
  yield Select(
1989
1982
  [("Reset", "reset"), ("Shutdown", "shutdown"), ("Poweroff", "poweroff"), ("Pause", "pause"), ("None", "none"), ("Dump", "dump"), ("Inject-NMI", "inject-nmi")],
1990
1983
  value=watchdog_action,
@@ -1994,8 +1987,8 @@ class VMDetailModal(ModalScreen):
1994
1987
  )
1995
1988
  with Vertical(classes="button-details"):
1996
1989
  with Horizontal():
1997
- yield Button("Apply Watchdog Settings", id="apply-watchdog-btn", variant="primary", disabled=not self.is_vm_stopped)
1998
- yield Button("Remove Watchdog", id="remove-watchdog-btn", variant="error", disabled=not self.is_vm_stopped or watchdog_model == 'none')
1990
+ yield Button(ButtonLabels.APPLY_WATCHDOG_SETTINGS, id="apply-watchdog-btn", variant="primary", disabled=not self.is_vm_stopped)
1991
+ yield Button(ButtonLabels.REMOVE_WATCHDOG, id="remove-watchdog-btn", variant="error", disabled=not self.is_vm_stopped or watchdog_model == 'none')
1999
1992
 
2000
1993
  if not self.is_bulk:
2001
1994
  with TabPane("Input", id="detail-input-tab"):
@@ -2003,41 +1996,40 @@ class VMDetailModal(ModalScreen):
2003
1996
  yield DataTable(id="input-table", cursor_type="row")
2004
1997
  with Vertical(classes="button-details"):
2005
1998
  with Horizontal():
2006
- yield Button("Add Input", id="add-input-btn", variant="primary", disabled=not self.is_vm_stopped)
2007
- yield Button("Remove Input", id="remove-input-btn", variant="error", disabled=True)
1999
+ yield Button(ButtonLabels.ADD_INPUT, id="add-input-btn", variant="primary", disabled=not self.is_vm_stopped)
2000
+ yield Button(ButtonLabels.REMOVE_INPUT, id="remove-input-btn", variant="error", disabled=True)
2008
2001
 
2009
2002
  with TabPane("Controller", id="detail-controler-tab"):
2010
2003
  with ScrollableContainer(classes="info-details"):
2011
2004
  yield DataTable(id="controller-table", cursor_type="row")
2012
2005
  with Vertical(classes="button-details"):
2013
2006
  with Horizontal():
2014
- yield Button("Add USB2", id="add-usb2-controller-btn", variant="primary", disabled=not self.is_vm_stopped)
2015
- yield Button("Add USB3", id="add-usb3-controller-btn", variant="primary", disabled=not self.is_vm_stopped)
2016
- yield Button("Add SCSI", id="add-scsi-controller-btn", variant="primary", disabled=not self.is_vm_stopped)
2017
- yield Button("Remove", id="remove-controller-btn", variant="error", disabled=True)
2018
-
2007
+ yield Button(ButtonLabels.ADD_USB2, id="add-usb2-controller-btn", variant="primary", disabled=not self.is_vm_stopped)
2008
+ yield Button(ButtonLabels.ADD_USB3, id="add-usb3-controller-btn", variant="primary", disabled=not self.is_vm_stopped)
2009
+ yield Button(ButtonLabels.ADD_SCSI, id="add-scsi-controller-btn", variant="primary", disabled=not self.is_vm_stopped)
2010
+ yield Button(ButtonLabels.REMOVE, id="remove-controller-btn", variant="error", disabled=True)
2019
2011
  if not self.is_bulk:
2020
2012
  with TabPane("USB Host", id="detail-usbhost-tab"):
2021
2013
  with Horizontal(classes="boot-manager"):
2022
2014
  with Vertical(classes="boot-main-container"):
2023
- yield Label("Available Host USB Devices")
2015
+ yield Label(StaticText.AVAILABLE_HOST_USB)
2024
2016
  yield ListView(id="available-usb-list", classes="boot-list-container")
2025
2017
  with Vertical(classes="boot-buttons-container"):
2026
- yield Button("Attach >", id="attach-usb-btn", disabled=True)
2027
- yield Button("< Detach", id="detach-usb-btn", disabled=True)
2018
+ yield Button(ButtonLabels.ATTACH_ARROW, id="attach-usb-btn", disabled=True)
2019
+ yield Button(ButtonLabels.DETACH_ARROW, id="detach-usb-btn", disabled=True)
2028
2020
  with Vertical(classes="boot-main-container"):
2029
- yield Label("Attached to VM")
2021
+ yield Label(StaticText.ATTACHED_TO_VM)
2030
2022
  yield ListView(id="attached-usb-list", classes="boot-list-container")
2031
2023
  with TabPane("PCI Host", id="detail-PCIhost-tab"):
2032
2024
  with Horizontal(classes="boot-manager"):
2033
2025
  with Vertical(classes="boot-main-container"):
2034
- yield Label("Available Host PCI Devices")
2026
+ yield Label(StaticText.AVAILABLE_HOST_PCI)
2035
2027
  yield ListView(id="available-pci-list", classes="boot-list-container")
2036
2028
  with Vertical(classes="boot-buttons-container"):
2037
- yield Button("Attach >", id="attach-pci-btn", disabled=True)
2038
- yield Button("< Detach", id="detach-pci-btn", disabled=True)
2029
+ yield Button(ButtonLabels.ATTACH_ARROW, id="attach-pci-btn", disabled=True)
2030
+ yield Button(ButtonLabels.DETACH_ARROW, id="detach-pci-btn", disabled=True)
2039
2031
  with Vertical(classes="boot-main-container"):
2040
- yield Label("Attached to VM")
2032
+ yield Label(StaticText.ATTACHED_TO_VM)
2041
2033
  yield ListView(id="attached-pci-list", classes="boot-list-container")
2042
2034
  #with TabPane("PCIe", id="detail-pcie-tab"):
2043
2035
  # yield Label("PCIe")
@@ -2048,10 +2040,10 @@ class VMDetailModal(ModalScreen):
2048
2040
  yield DataTable(id="channel-table", cursor_type="row")
2049
2041
  with Vertical(classes="button-details"):
2050
2042
  with Horizontal():
2051
- yield Button("Add Channel", id="add-channel-btn", variant="primary", disabled=not self.is_vm_stopped)
2052
- yield Button("Remove Channel", id="remove-channel-btn", variant="error", disabled=True)
2043
+ yield Button(ButtonLabels.ADD_CHANNEL, id="add-channel-btn", variant="primary", disabled=not self.is_vm_stopped)
2044
+ yield Button(ButtonLabels.REMOVE_CHANNEL, id="remove-channel-btn", variant="error", disabled=True)
2053
2045
 
2054
- yield Button("Close", variant="default", id="close-btn", classes="close-button")
2046
+ yield Button(ButtonLabels.CLOSE, variant="default", id="close-btn", classes="close-button")
2055
2047
 
2056
2048
  def _update_tpm_ui(self) -> None:
2057
2049
  """Updates the UI elements for the TPM tab based on self.tpm_info."""