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.
- {virtui_manager-1.1.6.dist-info → virtui_manager-1.3.0.dist-info}/METADATA +1 -1
- virtui_manager-1.3.0.dist-info/RECORD +73 -0
- vmanager/constants.py +737 -108
- vmanager/dialog.css +24 -0
- vmanager/firmware_manager.py +4 -1
- vmanager/i18n.py +32 -0
- vmanager/libvirt_utils.py +132 -3
- vmanager/locales/de/LC_MESSAGES/virtui-manager.po +3012 -0
- vmanager/locales/fr/LC_MESSAGES/virtui-manager.mo +0 -0
- vmanager/locales/fr/LC_MESSAGES/virtui-manager.po +3124 -0
- vmanager/locales/it/LC_MESSAGES/virtui-manager.po +3012 -0
- vmanager/locales/virtui-manager.pot +3012 -0
- vmanager/modals/bulk_modals.py +13 -12
- vmanager/modals/cache_stats_modal.py +6 -5
- vmanager/modals/capabilities_modal.py +133 -0
- vmanager/modals/config_modal.py +25 -24
- vmanager/modals/cpu_mem_pc_modals.py +22 -21
- vmanager/modals/custom_migration_modal.py +10 -9
- vmanager/modals/disk_pool_modals.py +60 -59
- vmanager/modals/host_dashboard_modal.py +137 -0
- vmanager/modals/howto_disk_modal.py +2 -1
- vmanager/modals/howto_network_modal.py +2 -1
- vmanager/modals/howto_overlay_modal.py +2 -1
- vmanager/modals/howto_ssh_modal.py +2 -1
- vmanager/modals/howto_virtiofs_modal.py +2 -1
- vmanager/modals/input_modals.py +11 -10
- vmanager/modals/log_modal.py +2 -1
- vmanager/modals/migration_modals.py +20 -18
- vmanager/modals/network_modals.py +45 -36
- vmanager/modals/provisioning_modals.py +56 -56
- vmanager/modals/select_server_modals.py +8 -7
- vmanager/modals/selection_modals.py +7 -6
- vmanager/modals/server_modals.py +24 -23
- vmanager/modals/server_prefs_modals.py +78 -71
- vmanager/modals/utils_modals.py +10 -9
- vmanager/modals/virsh_modals.py +3 -2
- vmanager/modals/virtiofs_modals.py +6 -5
- vmanager/modals/vm_type_info_modal.py +2 -1
- vmanager/modals/vmanager_modals.py +19 -19
- vmanager/modals/vmcard_dialog.py +57 -57
- vmanager/modals/vmdetails_modals.py +115 -123
- vmanager/modals/xml_modals.py +3 -2
- vmanager/network_manager.py +4 -1
- vmanager/storage_manager.py +157 -39
- vmanager/utils.py +39 -6
- vmanager/vm_actions.py +28 -24
- vmanager/vm_queries.py +67 -25
- vmanager/vm_service.py +8 -5
- vmanager/vmanager.css +46 -0
- vmanager/vmanager.py +178 -112
- vmanager/vmcard.py +161 -159
- vmanager/webconsole_manager.py +21 -21
- virtui_manager-1.1.6.dist-info/RECORD +0 -65
- {virtui_manager-1.1.6.dist-info → virtui_manager-1.3.0.dist-info}/WHEEL +0 -0
- {virtui_manager-1.1.6.dist-info → virtui_manager-1.3.0.dist-info}/entry_points.txt +0 -0
- {virtui_manager-1.1.6.dist-info → virtui_manager-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {virtui_manager-1.1.6.dist-info → virtui_manager-1.3.0.dist-info}/top_level.txt +0 -0
vmanager/vmanager.py
CHANGED
|
@@ -5,6 +5,7 @@ import os
|
|
|
5
5
|
import sys
|
|
6
6
|
import re
|
|
7
7
|
from threading import RLock
|
|
8
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
8
9
|
import logging
|
|
9
10
|
import argparse
|
|
10
11
|
from collections import deque
|
|
@@ -22,8 +23,10 @@ from textual.worker import Worker, WorkerState
|
|
|
22
23
|
|
|
23
24
|
from .config import load_config, save_config, get_log_path
|
|
24
25
|
from .constants import (
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
WarningMessages,
|
|
27
|
+
SuccessMessages,
|
|
28
|
+
VmAction, VmStatus, ButtonLabels, BindingDescriptions,
|
|
29
|
+
ErrorMessages, AppInfo, StatusText, ServerPallette, QuickMessages, ProgressMessages,
|
|
27
30
|
)
|
|
28
31
|
from .events import VmActionRequest, VMSelectionChanged, VmCardUpdateRequest #,VMNameClicked
|
|
29
32
|
from .libvirt_error_handler import register_error_handler
|
|
@@ -34,6 +37,7 @@ from .modals.server_modals import ServerManagementModal
|
|
|
34
37
|
from .modals.server_prefs_modals import ServerPrefModal
|
|
35
38
|
from .modals.select_server_modals import SelectOneServerModal, SelectServerModal
|
|
36
39
|
from .modals.selection_modals import PatternSelectModal
|
|
40
|
+
from .modals.capabilities_modal import CapabilitiesTreeModal
|
|
37
41
|
from .modals.cache_stats_modal import CacheStatsModal
|
|
38
42
|
from .modals.utils_modals import (
|
|
39
43
|
show_error_message,
|
|
@@ -49,6 +53,7 @@ from .modals.vmanager_modals import (
|
|
|
49
53
|
)
|
|
50
54
|
from .modals.virsh_modals import VirshShellScreen
|
|
51
55
|
from .modals.provisioning_modals import InstallVMModal
|
|
56
|
+
from .modals.host_dashboard_modal import HostDashboardModal
|
|
52
57
|
from .utils import (
|
|
53
58
|
check_novnc_path,
|
|
54
59
|
check_r_viewer,
|
|
@@ -56,21 +61,21 @@ from .utils import (
|
|
|
56
61
|
generate_webconsole_keys_if_needed,
|
|
57
62
|
get_server_color_cached,
|
|
58
63
|
setup_cache_monitoring,
|
|
64
|
+
setup_logging
|
|
59
65
|
)
|
|
60
|
-
from .libvirt_utils import get_internal_id
|
|
61
66
|
from .vm_queries import (
|
|
62
67
|
get_status,
|
|
63
68
|
)
|
|
69
|
+
from .libvirt_utils import (
|
|
70
|
+
get_internal_id, get_host_resources,
|
|
71
|
+
get_total_vm_allocation, get_active_vm_allocation
|
|
72
|
+
)
|
|
64
73
|
from .vm_service import VMService
|
|
65
74
|
from .vmcard import VMCard
|
|
66
75
|
from .vmcard_pool import VMCardPool
|
|
67
76
|
from .webconsole_manager import WebConsoleManager
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
log_level_str = log_config.get("LOG_LEVEL", "INFO")
|
|
71
|
-
log_level = getattr(logging, log_level_str, logging.INFO)
|
|
72
|
-
file_handler = logging.FileHandler(get_log_path())
|
|
73
|
-
file_handler.setLevel(log_level)
|
|
78
|
+
setup_logging()
|
|
74
79
|
|
|
75
80
|
class WorkerManager:
|
|
76
81
|
"""A class to manage and track Textual workers."""
|
|
@@ -158,26 +163,28 @@ class VMManagerTUI(App):
|
|
|
158
163
|
"""A Textual application to manage VMs."""
|
|
159
164
|
|
|
160
165
|
BINDINGS = [
|
|
161
|
-
Binding(key="v", action="view_log", description=
|
|
162
|
-
Binding(key="f", action="filter_view", description=
|
|
163
|
-
Binding(key="k", action="compact_view", description=
|
|
164
|
-
#Binding(key="p", action="server_preferences", description=
|
|
165
|
-
Binding(key="c", action="config", description=
|
|
166
|
-
Binding(key="b", action="bulk_cmd", description=
|
|
167
|
-
Binding(key="s", action="select_server", description=
|
|
168
|
-
Binding(key="l", action="manage_server", description=
|
|
169
|
-
Binding(key="p", action="pattern_select", description=
|
|
170
|
-
Binding(key="ctrl+a", action="toggle_select_all", description=
|
|
171
|
-
Binding(key="ctrl+u", action="unselect_all", description=
|
|
172
|
-
Binding(key="left", action="previous_page", description=
|
|
173
|
-
Binding(key="right", action="next_page", description=
|
|
174
|
-
Binding(key="up", action="filter_running", description=
|
|
175
|
-
Binding(key="down", action="filter_all", description=
|
|
176
|
-
Binding(key="ctrl+v", action="virsh_shell", description=
|
|
177
|
-
Binding(key="
|
|
178
|
-
Binding(key="
|
|
179
|
-
Binding(key="
|
|
180
|
-
Binding(key="
|
|
166
|
+
Binding(key="v", action="view_log", description=BindingDescriptions.LOG),
|
|
167
|
+
Binding(key="f", action="filter_view", description=BindingDescriptions.FILTER, show=False),
|
|
168
|
+
Binding(key="k", action="compact_view", description=BindingDescriptions.COMPACT_VIEW, show=True),
|
|
169
|
+
#Binding(key="p", action="server_preferences", description=BindingDescriptions.SERVER_PREFS),
|
|
170
|
+
Binding(key="c", action="config", description=BindingDescriptions.CONFIG, show=True),
|
|
171
|
+
Binding(key="b", action="bulk_cmd", description=BindingDescriptions.BULK_CMD, show=False),
|
|
172
|
+
Binding(key="s", action="select_server", description=BindingDescriptions.SELECT_SERVERS, show=False),
|
|
173
|
+
Binding(key="l", action="manage_server", description=BindingDescriptions.MANAGE_SERVERS, show=False),
|
|
174
|
+
Binding(key="p", action="pattern_select", description=BindingDescriptions.PATTERN_SELECT, show=False),
|
|
175
|
+
Binding(key="ctrl+a", action="toggle_select_all", description=BindingDescriptions.SELECT_ALL),
|
|
176
|
+
Binding(key="ctrl+u", action="unselect_all", description=BindingDescriptions.UNSELECT_ALL),
|
|
177
|
+
Binding(key="left", action="previous_page", description=BindingDescriptions.PREVIOUS_PAGE, show=False),
|
|
178
|
+
Binding(key="right", action="next_page", description=BindingDescriptions.NEXT_PAGE, show=False),
|
|
179
|
+
Binding(key="up", action="filter_running", description=BindingDescriptions.RUNNING_VMS, show=False),
|
|
180
|
+
Binding(key="down", action="filter_all", description=BindingDescriptions.ALL_VMS, show=False),
|
|
181
|
+
Binding(key="ctrl+v", action="virsh_shell", description=BindingDescriptions.VIRSH_SHELL, show=False ),
|
|
182
|
+
Binding(key="h", action="host_capabilities", description=BindingDescriptions.HOST_CAPABILITIES, show=False),
|
|
183
|
+
Binding(key="H", action="host_dashboard", description=BindingDescriptions.HOST_DASHBOARD, show=False),
|
|
184
|
+
Binding(key="i", action="install_vm", description=BindingDescriptions.INSTALL_VM, show=True),
|
|
185
|
+
Binding(key="ctrl+l", action="toggle_stats_logging", description=BindingDescriptions.TOGGLE_STATS, show=False),
|
|
186
|
+
Binding(key="ctrl+s", action="show_cache_stats", description=BindingDescriptions.CACHE_STATS, show=False),
|
|
187
|
+
Binding(key="q", action="quit", description=BindingDescriptions.QUIT),
|
|
181
188
|
]
|
|
182
189
|
|
|
183
190
|
config = load_config()
|
|
@@ -337,10 +344,10 @@ class VMManagerTUI(App):
|
|
|
337
344
|
self.ui["error_footer"] = Static(id="error-footer", classes="error-message")
|
|
338
345
|
self.ui["page_info"] = Label("", id="page-info", classes="")
|
|
339
346
|
self.ui["prev_button"] = Button(
|
|
340
|
-
ButtonLabels.PREVIOUS_PAGE, id=
|
|
347
|
+
ButtonLabels.PREVIOUS_PAGE, id="prev-button", variant="primary", classes="ctrlpage"
|
|
341
348
|
)
|
|
342
349
|
self.ui["next_button"] = Button(
|
|
343
|
-
ButtonLabels.NEXT_PAGE, id=
|
|
350
|
+
ButtonLabels.NEXT_PAGE, id="next-button", variant="primary", classes="ctrlpage"
|
|
344
351
|
)
|
|
345
352
|
self.ui["pagination_controls"] = Horizontal(
|
|
346
353
|
self.ui["prev_button"],
|
|
@@ -356,20 +363,20 @@ class VMManagerTUI(App):
|
|
|
356
363
|
yield Header()
|
|
357
364
|
with Horizontal(classes="top-controls"):
|
|
358
365
|
yield Button(
|
|
359
|
-
ButtonLabels.SELECT_SERVER, id=
|
|
366
|
+
ButtonLabels.SELECT_SERVER, id="select_server_button", classes="Buttonpage"
|
|
360
367
|
)
|
|
361
|
-
yield Button(ButtonLabels.MANAGE_SERVERS, id=
|
|
368
|
+
yield Button(ButtonLabels.MANAGE_SERVERS, id="manage_servers_button", classes="Buttonpage")
|
|
362
369
|
yield Button(
|
|
363
|
-
ButtonLabels.SERVER_PREFERENCES, id=
|
|
370
|
+
ButtonLabels.SERVER_PREFERENCES, id="server_preferences_button", classes="Buttonpage"
|
|
364
371
|
)
|
|
365
|
-
yield Button(ButtonLabels.FILTER_VM, id=
|
|
366
|
-
#yield Button(ButtonLabels.VIEW_LOG, id=
|
|
372
|
+
yield Button(ButtonLabels.FILTER_VM, id="filter_button", classes="Buttonpage")
|
|
373
|
+
#yield Button(ButtonLabels.VIEW_LOG, id="view-log-button", classes="Buttonpage")
|
|
367
374
|
# yield Button("Virsh Shell", id="virsh_shell_button", classes="Buttonpage")
|
|
368
|
-
yield Button(ButtonLabels.BULK_CMD, id=
|
|
369
|
-
yield Button(ButtonLabels.PATTERN_SELECT, id=
|
|
370
|
-
#yield Button(ButtonLabels.CONFIG, id=
|
|
375
|
+
yield Button(ButtonLabels.BULK_CMD, id="bulk_selected_vms", classes="Buttonpage")
|
|
376
|
+
yield Button(ButtonLabels.PATTERN_SELECT, id="pattern_select_button", classes="Buttonpage")
|
|
377
|
+
#yield Button(ButtonLabels.CONFIG, id="config-button", classes="Buttonpage")
|
|
371
378
|
#yield Button(
|
|
372
|
-
# ButtonLabels.COMPACT_VIEW, id=
|
|
379
|
+
# ButtonLabels.COMPACT_VIEW, id="compact-view-button", classes="Buttonpage"
|
|
373
380
|
#)
|
|
374
381
|
yield Link("About", url="https://aginies.github.io/virtui-manager/")
|
|
375
382
|
|
|
@@ -377,9 +384,7 @@ class VMManagerTUI(App):
|
|
|
377
384
|
yield self.ui["vms_container"]
|
|
378
385
|
yield self.ui["error_footer"]
|
|
379
386
|
yield Footer()
|
|
380
|
-
self.show_success_message(
|
|
381
|
-
"In some Terminal use [b]Shift[/b] key while selecting text with the mouse to copy it."
|
|
382
|
-
)
|
|
387
|
+
self.show_success_message(SuccessMessages.TERMINAL_COPY_HINT)
|
|
383
388
|
|
|
384
389
|
def reload_servers(self, new_servers):
|
|
385
390
|
self.servers = new_servers
|
|
@@ -398,7 +403,7 @@ class VMManagerTUI(App):
|
|
|
398
403
|
)
|
|
399
404
|
self.r_viewer_available = False
|
|
400
405
|
else:
|
|
401
|
-
self.show_quick_message(
|
|
406
|
+
self.show_quick_message(QuickMessages.REMOTE_VIEWER_SELECTED.format(viewer=self.r_viewer))
|
|
402
407
|
|
|
403
408
|
if not check_websockify():
|
|
404
409
|
self.show_error_message(
|
|
@@ -426,9 +431,7 @@ class VMManagerTUI(App):
|
|
|
426
431
|
vms_container.styles.grid_size_columns = 2
|
|
427
432
|
|
|
428
433
|
if not self.servers:
|
|
429
|
-
self.show_success_message(
|
|
430
|
-
"No servers configured. Please add one via 'Servers List'."
|
|
431
|
-
)
|
|
434
|
+
self.show_success_message(SuccessMessages.NO_SERVERS_CONFIGURED)
|
|
432
435
|
else:
|
|
433
436
|
# Launch initial connection and cache loading in background
|
|
434
437
|
if self.active_uris:
|
|
@@ -441,10 +444,10 @@ class VMManagerTUI(App):
|
|
|
441
444
|
"""Connects to servers in background and then triggers cache loading."""
|
|
442
445
|
if self.active_uris:
|
|
443
446
|
for uri in self.active_uris:
|
|
444
|
-
self.call_from_thread(self.show_in_progress_message,
|
|
447
|
+
self.call_from_thread(self.show_in_progress_message, ProgressMessages.CONNECTING_TO_SERVER.format(uri=uri))
|
|
445
448
|
success = self.connect_libvirt(uri)
|
|
446
449
|
if success:
|
|
447
|
-
self.call_from_thread(self.show_success_message,
|
|
450
|
+
self.call_from_thread(self.show_success_message, SuccessMessages.CONNECTED_TO_SERVER.format(uri=uri))
|
|
448
451
|
else:
|
|
449
452
|
error_msg = self.vm_service.connection_manager.get_connection_error(uri)
|
|
450
453
|
if error_msg:
|
|
@@ -461,7 +464,7 @@ class VMManagerTUI(App):
|
|
|
461
464
|
# Log libvirt call statistics
|
|
462
465
|
call_stats = self.vm_service.connection_manager.get_stats()
|
|
463
466
|
if call_stats:
|
|
464
|
-
logging.
|
|
467
|
+
logging.debug("=== Libvirt Call Statistics ===")
|
|
465
468
|
for uri, methods in sorted(call_stats.items()):
|
|
466
469
|
server_name = uri
|
|
467
470
|
for s in self.servers:
|
|
@@ -479,7 +482,7 @@ class VMManagerTUI(App):
|
|
|
479
482
|
if total_increase > 0:
|
|
480
483
|
increase_pct = 100 - (previous_how_many_more*100 / total_increase)
|
|
481
484
|
|
|
482
|
-
logging.
|
|
485
|
+
logging.debug(f"{server_name} ({uri}): {total_calls} calls | +{total_increase} ({increase_pct:.1f}%)")
|
|
483
486
|
previous_how_many_more = how_many_more
|
|
484
487
|
|
|
485
488
|
# Initialize previous method calls dict for this URI if needed
|
|
@@ -493,7 +496,7 @@ class VMManagerTUI(App):
|
|
|
493
496
|
|
|
494
497
|
self.last_method_calls[uri][method] = count
|
|
495
498
|
how_many_more_count = count - prev_method_count
|
|
496
|
-
logging.
|
|
499
|
+
logging.debug(f" - {method}: {count} calls (+{how_many_more_count})")
|
|
497
500
|
|
|
498
501
|
self.last_increase[uri] = how_many_more
|
|
499
502
|
self.last_total_calls[uri] = total_calls
|
|
@@ -510,13 +513,13 @@ class VMManagerTUI(App):
|
|
|
510
513
|
self._stats_interval_timer = None
|
|
511
514
|
self._stats_logging_active = False
|
|
512
515
|
setup_cache_monitoring(enable=False)
|
|
513
|
-
self.show_success_message(
|
|
516
|
+
self.show_success_message(SuccessMessages.STATS_LOGGING_DISABLED)
|
|
514
517
|
else:
|
|
515
518
|
setup_cache_monitoring(enable=True)
|
|
516
519
|
self._log_cache_statistics()
|
|
517
520
|
self._stats_interval_timer = self.set_interval(10, self._log_cache_statistics)
|
|
518
521
|
self._stats_logging_active = True
|
|
519
|
-
self.show_success_message(
|
|
522
|
+
self.show_success_message(SuccessMessages.STATS_LOGGING_ENABLED)
|
|
520
523
|
|
|
521
524
|
def _initial_cache_worker(self):
|
|
522
525
|
"""Pre-loads VM cache before displaying the UI."""
|
|
@@ -545,7 +548,7 @@ class VMManagerTUI(App):
|
|
|
545
548
|
if s['uri'] == uri:
|
|
546
549
|
server_name = s['name']
|
|
547
550
|
break
|
|
548
|
-
self.call_from_thread(self.show_error_message,
|
|
551
|
+
self.call_from_thread(self.show_error_message, ErrorMessages.SERVER_CONNECTION_ERROR.format(server_name=server_name, error_msg=error_msg))
|
|
549
552
|
|
|
550
553
|
if self.vm_service.connection_manager.is_max_retries_reached(uri):
|
|
551
554
|
self.call_from_thread(self.remove_active_uri, uri)
|
|
@@ -573,14 +576,14 @@ class VMManagerTUI(App):
|
|
|
573
576
|
|
|
574
577
|
if active_vms_on_page:
|
|
575
578
|
vms_list_str = ", ".join(active_vms_on_page)
|
|
576
|
-
self.call_from_thread(self.show_quick_message,
|
|
579
|
+
self.call_from_thread(self.show_quick_message, QuickMessages.CACHING_VM_STATE.format(vms_list=vms_list_str))
|
|
577
580
|
|
|
578
581
|
self.call_from_thread(self._on_initial_cache_complete)
|
|
579
582
|
|
|
580
583
|
except Exception as e:
|
|
581
584
|
self.call_from_thread(
|
|
582
585
|
self.show_error_message,
|
|
583
|
-
|
|
586
|
+
ErrorMessages.ERROR_DURING_INITIAL_CACHE_LOADING.format(error=e)
|
|
584
587
|
)
|
|
585
588
|
|
|
586
589
|
def _on_initial_cache_complete(self):
|
|
@@ -588,7 +591,7 @@ class VMManagerTUI(App):
|
|
|
588
591
|
self.initial_cache_loading = False
|
|
589
592
|
self.initial_cache_complete = True
|
|
590
593
|
if self.servers:
|
|
591
|
-
self.show_quick_message(
|
|
594
|
+
self.show_quick_message(QuickMessages.VM_DATA_LOADED)
|
|
592
595
|
self.refresh_vm_list()
|
|
593
596
|
|
|
594
597
|
def _update_layout_for_size(self):
|
|
@@ -646,9 +649,7 @@ class VMManagerTUI(App):
|
|
|
646
649
|
self.vm_card_pool.prefill_pool()
|
|
647
650
|
|
|
648
651
|
if self.VMS_PER_PAGE > 9 and old_vms_per_page <= 9 and not self.compact_view:
|
|
649
|
-
self.show_warning_message(
|
|
650
|
-
f"Displaying [b]{self.VMS_PER_PAGE}[/b] VMs per page. CPU usage may increase; 9 is recommended for optimal performance."
|
|
651
|
-
)
|
|
652
|
+
self.show_warning_message(WarningMessages.VMS_PER_PAGE_PERFORMANCE_WARNING.format(vms_per_page=self.VMS_PER_PAGE))
|
|
652
653
|
|
|
653
654
|
self.refresh_vm_list(force=True)
|
|
654
655
|
|
|
@@ -676,7 +677,7 @@ class VMManagerTUI(App):
|
|
|
676
677
|
if conn:
|
|
677
678
|
yield conn
|
|
678
679
|
else:
|
|
679
|
-
self.show_error_message(
|
|
680
|
+
self.show_error_message(ErrorMessages.FAILED_TO_OPEN_CONNECTION.format(uri=uri))
|
|
680
681
|
|
|
681
682
|
def connect_libvirt(self, uri: str) -> None:
|
|
682
683
|
"""Connects to libvirt."""
|
|
@@ -705,11 +706,11 @@ class VMManagerTUI(App):
|
|
|
705
706
|
def show_warning_message(self, message: str):
|
|
706
707
|
show_warning_message(self, message)
|
|
707
708
|
|
|
708
|
-
@on(Button.Pressed, f"#
|
|
709
|
+
@on(Button.Pressed, f"#compact-view-button")
|
|
709
710
|
def action_compact_view(self) -> None:
|
|
710
711
|
"""Toggle compact view."""
|
|
711
712
|
if self.bulk_operation_in_progress:
|
|
712
|
-
self.show_warning_message(
|
|
713
|
+
self.show_warning_message(WarningMessages.COMPACT_VIEW_LOCKED)
|
|
713
714
|
return
|
|
714
715
|
|
|
715
716
|
if not self.compact_view:
|
|
@@ -754,8 +755,7 @@ class VMManagerTUI(App):
|
|
|
754
755
|
uris_to_connect = [uri for uri in selected_uris if uri not in self.active_uris]
|
|
755
756
|
# Show connecting message for each new server
|
|
756
757
|
for uri in uris_to_connect:
|
|
757
|
-
self.show_in_progress_message(
|
|
758
|
-
|
|
758
|
+
self.show_in_progress_message(ProgressMessages.CONNECTING_TO_SERVER.format(uri=uri))
|
|
759
759
|
for uri in uris_to_disconnect:
|
|
760
760
|
# Cleanup UI caches for VMs on this server
|
|
761
761
|
uuids_to_release = [
|
|
@@ -795,11 +795,11 @@ class VMManagerTUI(App):
|
|
|
795
795
|
server_name = s['name']
|
|
796
796
|
break
|
|
797
797
|
if success:
|
|
798
|
-
self.call_from_thread(self.show_success_message,
|
|
798
|
+
self.call_from_thread(self.show_success_message, SuccessMessages.SERVER_CONNECTED.format(name=server_name))
|
|
799
799
|
else:
|
|
800
800
|
error_msg = self.vm_service.connection_manager.get_connection_error(uri)
|
|
801
801
|
if error_msg:
|
|
802
|
-
self.call_from_thread(self.show_error_message,
|
|
802
|
+
self.call_from_thread(self.show_error_message, ErrorMessages.SERVER_FAILED_TO_CONNECT.format(server_name=server_name, error_msg=error_msg))
|
|
803
803
|
|
|
804
804
|
if uris_to_connect:
|
|
805
805
|
self.worker_manager.run(show_connection_results, name="show_connection_results")
|
|
@@ -868,7 +868,7 @@ class VMManagerTUI(App):
|
|
|
868
868
|
if self.sort_by != VmStatus.RUNNING:
|
|
869
869
|
self.sort_by = VmStatus.RUNNING
|
|
870
870
|
self.current_page = 0
|
|
871
|
-
self.show_quick_message(
|
|
871
|
+
self.show_quick_message(QuickMessages.FILTER_RUNNING_VMS)
|
|
872
872
|
self.refresh_vm_list()
|
|
873
873
|
|
|
874
874
|
def action_filter_all(self) -> None:
|
|
@@ -876,7 +876,7 @@ class VMManagerTUI(App):
|
|
|
876
876
|
if self.sort_by != VmStatus.DEFAULT:
|
|
877
877
|
self.sort_by = VmStatus.DEFAULT
|
|
878
878
|
self.current_page = 0
|
|
879
|
-
self.show_quick_message(
|
|
879
|
+
self.show_quick_message(QuickMessages.FILTER_ALL_VMS)
|
|
880
880
|
self.refresh_vm_list()
|
|
881
881
|
|
|
882
882
|
@on(FilterModal.FilterChanged)
|
|
@@ -899,7 +899,7 @@ class VMManagerTUI(App):
|
|
|
899
899
|
self.search_text = new_search
|
|
900
900
|
self.filtered_server_uris = new_selected_servers
|
|
901
901
|
self.current_page = 0
|
|
902
|
-
self.show_in_progress_message(
|
|
902
|
+
self.show_in_progress_message(ProgressMessages.LOADING_VM_DATA_FROM_REMOTE_SERVERS)
|
|
903
903
|
self.refresh_vm_list()
|
|
904
904
|
|
|
905
905
|
def action_config(self) -> None:
|
|
@@ -919,7 +919,7 @@ class VMManagerTUI(App):
|
|
|
919
919
|
logging.getLogger().setLevel(new_log_level)
|
|
920
920
|
for handler in logging.getLogger().handlers:
|
|
921
921
|
handler.setLevel(new_log_level)
|
|
922
|
-
self.show_success_message(
|
|
922
|
+
self.show_success_message(SuccessMessages.LOG_LEVEL_CHANGED.format(level=new_log_level_str))
|
|
923
923
|
|
|
924
924
|
# Update remote viewer if changed
|
|
925
925
|
self.r_viewer = check_r_viewer(self.config.get("REMOTE_VIEWER"))
|
|
@@ -930,10 +930,10 @@ class VMManagerTUI(App):
|
|
|
930
930
|
self.r_viewer_available = True
|
|
931
931
|
|
|
932
932
|
if (self.config.get("STATS_INTERVAL") != old_stats_interval):
|
|
933
|
-
self.show_in_progress_message(
|
|
933
|
+
self.show_in_progress_message(ProgressMessages.CONFIG_UPDATED_REFRESHING_VM_LIST)
|
|
934
934
|
self.refresh_vm_list(force=False, optimize_for_current_page=True)
|
|
935
935
|
else:
|
|
936
|
-
self.show_success_message(
|
|
936
|
+
self.show_success_message(SuccessMessages.CONFIG_UPDATED)
|
|
937
937
|
|
|
938
938
|
@on(Button.Pressed, "#config_button")
|
|
939
939
|
def on_config_button_pressed(self, event: Button.Pressed) -> None:
|
|
@@ -985,7 +985,7 @@ class VMManagerTUI(App):
|
|
|
985
985
|
self.call_from_thread(self.push_screen, modal)
|
|
986
986
|
except Exception as e:
|
|
987
987
|
self.call_from_thread(loading.dismiss)
|
|
988
|
-
self.call_from_thread(self.show_error_message,
|
|
988
|
+
self.call_from_thread(self.show_error_message, ErrorMessages.PREFERENCES_LAUNCH_ERROR.format(error=e))
|
|
989
989
|
|
|
990
990
|
self.worker_manager.run(show_prefs, name="launch_server_prefs")
|
|
991
991
|
else:
|
|
@@ -1004,7 +1004,7 @@ class VMManagerTUI(App):
|
|
|
1004
1004
|
Handles 0, 1, or multiple active servers.
|
|
1005
1005
|
"""
|
|
1006
1006
|
if len(self.active_uris) == 0:
|
|
1007
|
-
self.show_error_message(
|
|
1007
|
+
self.show_error_message(ErrorMessages.NOT_CONNECTED_TO_ANY_SERVER)
|
|
1008
1008
|
return
|
|
1009
1009
|
|
|
1010
1010
|
if len(self.active_uris) == 1:
|
|
@@ -1038,6 +1038,34 @@ class VMManagerTUI(App):
|
|
|
1038
1038
|
"""Callback for the virsh shell button."""
|
|
1039
1039
|
self.action_virsh_shell()
|
|
1040
1040
|
|
|
1041
|
+
def action_host_dashboard(self) -> None:
|
|
1042
|
+
"""Show Host Resource Dashboard."""
|
|
1043
|
+
def launch_dashboard_modal(uri: str):
|
|
1044
|
+
conn = self.vm_service.connect(uri)
|
|
1045
|
+
if conn:
|
|
1046
|
+
# Find server name
|
|
1047
|
+
server_name = uri
|
|
1048
|
+
for s in self.servers:
|
|
1049
|
+
if s['uri'] == uri:
|
|
1050
|
+
server_name = s['name']
|
|
1051
|
+
break
|
|
1052
|
+
self.push_screen(HostDashboardModal(conn, server_name))
|
|
1053
|
+
else:
|
|
1054
|
+
self.show_error_message(ErrorMessages.COULD_NOT_CONNECT_TO_SERVER.format(uri=uri))
|
|
1055
|
+
|
|
1056
|
+
self._select_server_and_run(launch_dashboard_modal, "Select a server for Dashboard", "View Dashboard")
|
|
1057
|
+
|
|
1058
|
+
def action_host_capabilities(self) -> None:
|
|
1059
|
+
"""Show Host Capabilities."""
|
|
1060
|
+
def launch_caps_modal(uri: str):
|
|
1061
|
+
conn = self.vm_service.connect(uri)
|
|
1062
|
+
if conn:
|
|
1063
|
+
self.push_screen(CapabilitiesTreeModal(conn))
|
|
1064
|
+
else:
|
|
1065
|
+
self.show_error_message(ErrorMessages.COULD_NOT_CONNECT_TO_SERVER.format(uri=uri))
|
|
1066
|
+
|
|
1067
|
+
self._select_server_and_run(launch_caps_modal, "Select a server for Capabilities", "View")
|
|
1068
|
+
|
|
1041
1069
|
def action_install_vm(self) -> None:
|
|
1042
1070
|
"""Launch the VM Installation Modal."""
|
|
1043
1071
|
def launch_install_modal(uri: str):
|
|
@@ -1057,7 +1085,7 @@ class VMManagerTUI(App):
|
|
|
1057
1085
|
def action_worker():
|
|
1058
1086
|
domain = self.vm_service.find_domain_by_uuid(self.active_uris, message.internal_id)
|
|
1059
1087
|
if not domain:
|
|
1060
|
-
self.call_from_thread(self.show_error_message,
|
|
1088
|
+
self.call_from_thread(self.show_error_message, ErrorMessages.VM_NOT_FOUND_BY_ID.format(vm_id=message.internal_id))
|
|
1061
1089
|
return
|
|
1062
1090
|
|
|
1063
1091
|
#vm_name = domain.name()
|
|
@@ -1069,6 +1097,41 @@ class VMManagerTUI(App):
|
|
|
1069
1097
|
try:
|
|
1070
1098
|
# Message are done by events
|
|
1071
1099
|
if message.action == VmAction.START:
|
|
1100
|
+
# Check resources
|
|
1101
|
+
try:
|
|
1102
|
+
conn = domain.connect()
|
|
1103
|
+
host_res = get_host_resources(conn)
|
|
1104
|
+
current_alloc = get_active_vm_allocation(conn)
|
|
1105
|
+
|
|
1106
|
+
# domain.info() -> [state, maxMem(KB), memory(KB), nrVirtCpu, cpuTime]
|
|
1107
|
+
vm_info = domain.info()
|
|
1108
|
+
vm_mem_mb = vm_info[1] // 1024
|
|
1109
|
+
vm_vcpus = vm_info[3]
|
|
1110
|
+
|
|
1111
|
+
host_mem_mb = host_res.get('available_memory', 0)
|
|
1112
|
+
host_cpus = host_res.get('total_cpus', 0)
|
|
1113
|
+
|
|
1114
|
+
active_mem_mb = current_alloc.get('active_allocated_memory', 0)
|
|
1115
|
+
active_vcpus = current_alloc.get('active_allocated_vcpus', 0)
|
|
1116
|
+
|
|
1117
|
+
overcommit_mem = (active_mem_mb + vm_mem_mb) > host_mem_mb
|
|
1118
|
+
overcommit_cpu = (active_vcpus + vm_vcpus) > host_cpus
|
|
1119
|
+
|
|
1120
|
+
if overcommit_mem or overcommit_cpu:
|
|
1121
|
+
warnings = []
|
|
1122
|
+
if overcommit_mem:
|
|
1123
|
+
warnings.append(f"Memory: {active_mem_mb + vm_mem_mb} MB > {host_mem_mb} MB")
|
|
1124
|
+
if overcommit_cpu:
|
|
1125
|
+
warnings.append(f"vCPUs: {active_vcpus + vm_vcpus} > {host_cpus}")
|
|
1126
|
+
|
|
1127
|
+
warning_msg = (
|
|
1128
|
+
f"Starting VM '{vm_name}' will exceed host capacity (Active Allocation):\n"
|
|
1129
|
+
f"{chr(10).join(warnings)}"
|
|
1130
|
+
)
|
|
1131
|
+
self.show_warning_message(warning_msg)
|
|
1132
|
+
except Exception as e:
|
|
1133
|
+
logging.error(f"Error checking resources before start: {e}")
|
|
1134
|
+
|
|
1072
1135
|
self.vm_service.start_vm(domain)
|
|
1073
1136
|
elif message.action == VmAction.STOP:
|
|
1074
1137
|
self.vm_service.stop_vm(domain)
|
|
@@ -1088,7 +1151,7 @@ class VMManagerTUI(App):
|
|
|
1088
1151
|
except Exception as e:
|
|
1089
1152
|
self.call_from_thread(
|
|
1090
1153
|
self.show_error_message,
|
|
1091
|
-
|
|
1154
|
+
ErrorMessages.ERROR_ON_VM_DURING_ACTION.format(vm_name=vm_name, action=message.action, error=e),
|
|
1092
1155
|
)
|
|
1093
1156
|
finally:
|
|
1094
1157
|
self.vm_service.unsuppress_vm_events(message.internal_id)
|
|
@@ -1129,7 +1192,7 @@ class VMManagerTUI(App):
|
|
|
1129
1192
|
for card in self.query(VMCard):
|
|
1130
1193
|
card.is_selected = False
|
|
1131
1194
|
|
|
1132
|
-
self.show_quick_message(
|
|
1195
|
+
self.show_quick_message(QuickMessages.ALL_VMS_UNSELECTED)
|
|
1133
1196
|
|
|
1134
1197
|
@on(VMSelectionChanged)
|
|
1135
1198
|
def on_vm_selection_changed(self, message: VMSelectionChanged) -> None:
|
|
@@ -1150,7 +1213,7 @@ class VMManagerTUI(App):
|
|
|
1150
1213
|
delete_storage_flag = result.get('delete_storage', False)
|
|
1151
1214
|
|
|
1152
1215
|
if not action_type:
|
|
1153
|
-
self.show_error_message(
|
|
1216
|
+
self.show_error_message(ErrorMessages.NO_ACTION_TYPE_BULK_MODAL)
|
|
1154
1217
|
return
|
|
1155
1218
|
|
|
1156
1219
|
selected_uuids_copy = list(self.selected_vm_uuids) # Take a copy for the worker
|
|
@@ -1162,7 +1225,7 @@ class VMManagerTUI(App):
|
|
|
1162
1225
|
selected_domains = list(found_domains_map.values())
|
|
1163
1226
|
|
|
1164
1227
|
if not selected_domains:
|
|
1165
|
-
self.show_error_message(
|
|
1228
|
+
self.show_error_message(ErrorMessages.VM_NOT_FOUND_FOR_EDITING)
|
|
1166
1229
|
return
|
|
1167
1230
|
|
|
1168
1231
|
# Check if all selected VMs are stopped
|
|
@@ -1172,7 +1235,7 @@ class VMManagerTUI(App):
|
|
|
1172
1235
|
active_vms.append(domain.name())
|
|
1173
1236
|
|
|
1174
1237
|
if active_vms:
|
|
1175
|
-
self.show_error_message(
|
|
1238
|
+
self.show_error_message(ErrorMessages.VMS_MUST_BE_STOPPED_FOR_BULK_EDITING.format(running_vms=', '.join(active_vms)))
|
|
1176
1239
|
# Restore selection since we are aborting
|
|
1177
1240
|
self.selected_vm_uuids = set(selected_uuids_copy)
|
|
1178
1241
|
return
|
|
@@ -1209,9 +1272,9 @@ class VMManagerTUI(App):
|
|
|
1209
1272
|
# Clear selection after launching modal
|
|
1210
1273
|
self.selected_vm_uuids.clear()
|
|
1211
1274
|
else:
|
|
1212
|
-
self.show_error_message(
|
|
1275
|
+
self.show_error_message(ErrorMessages.COULD_NOT_LOAD_DETAILS_FOR_REFERENCE_VM)
|
|
1213
1276
|
except Exception as e:
|
|
1214
|
-
self.app.show_error_message(
|
|
1277
|
+
self.app.show_error_message(ErrorMessages.BULK_EDIT_PREP_ERROR.format(error=e))
|
|
1215
1278
|
|
|
1216
1279
|
warning_message = "This will apply configuration changes to all selected VMs based on the settings you choose.\n\nSome changes modify the VM's XML directly. All change cannot be undone.\n\nAre you sure you want to proceed?"
|
|
1217
1280
|
self.app.push_screen(ConfirmationDialog(warning_message), on_confirm)
|
|
@@ -1260,13 +1323,13 @@ class VMManagerTUI(App):
|
|
|
1260
1323
|
logging.info(summary)
|
|
1261
1324
|
|
|
1262
1325
|
if successful_vms:
|
|
1263
|
-
self.call_from_thread(self.show_success_message,
|
|
1326
|
+
self.call_from_thread(self.show_success_message, SuccessMessages.BULK_ACTION_SUCCESS_TEMPLATE.format(action_type=action_type, count=len(successful_vms)))
|
|
1264
1327
|
if failed_vms:
|
|
1265
|
-
self.call_from_thread(self.show_error_message,
|
|
1328
|
+
self.call_from_thread(self.show_error_message, ErrorMessages.BULK_ACTION_FAILED_TEMPLATE.format(action_type=action_type, count=len(failed_vms)))
|
|
1266
1329
|
|
|
1267
1330
|
except Exception as e:
|
|
1268
1331
|
logging.error(f"An unexpected error occurred during bulk action service call: {e}", exc_info=True)
|
|
1269
|
-
self.call_from_thread(self.show_error_message,
|
|
1332
|
+
self.call_from_thread(self.show_error_message, ErrorMessages.FATAL_ERROR_BULK_ACTION.format(error=e))
|
|
1270
1333
|
|
|
1271
1334
|
finally:
|
|
1272
1335
|
# Ensure these are called on the main thread
|
|
@@ -1310,7 +1373,7 @@ class VMManagerTUI(App):
|
|
|
1310
1373
|
vms_per_page,
|
|
1311
1374
|
uris_to_query,
|
|
1312
1375
|
force=force,
|
|
1313
|
-
optimize_for_current_page=
|
|
1376
|
+
optimize_for_current_page=optimize_for_current_page,
|
|
1314
1377
|
on_complete=on_complete,
|
|
1315
1378
|
),
|
|
1316
1379
|
name="list_vms"
|
|
@@ -1353,22 +1416,18 @@ class VMManagerTUI(App):
|
|
|
1353
1416
|
end_index = start_index + vms_per_page
|
|
1354
1417
|
paginated_domains = domains_to_display[start_index:end_index]
|
|
1355
1418
|
|
|
1356
|
-
#
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
for domain, conn in paginated_domains:
|
|
1419
|
+
# Parallelize fetching of VM info
|
|
1420
|
+
def fetch_vm_data(item):
|
|
1421
|
+
domain, conn = item
|
|
1361
1422
|
try:
|
|
1362
1423
|
uri = self.vm_service.get_uri_for_connection(conn) or conn.getURI()
|
|
1363
1424
|
uuid, vm_name = self.vm_service.get_vm_identity(domain, conn, known_uri=uri)
|
|
1364
|
-
page_uuids.add(uuid)
|
|
1365
1425
|
|
|
1366
|
-
# Get info from cache or fetch if not present
|
|
1426
|
+
# Get info from cache or fetch if not present
|
|
1367
1427
|
info = self.vm_service._get_domain_info(domain)
|
|
1368
1428
|
cached_details = self.vm_service.get_cached_vm_details(uuid)
|
|
1369
1429
|
|
|
1370
|
-
# Explicitly get state from cache/service
|
|
1371
|
-
# This avoids flickering if domain.info() (fetched by _get_domain_info) lags behind events
|
|
1430
|
+
# Explicitly get state from cache/service
|
|
1372
1431
|
state_tuple = self.vm_service._get_domain_state(domain, internal_id=uuid)
|
|
1373
1432
|
|
|
1374
1433
|
effective_state = None
|
|
@@ -1394,7 +1453,7 @@ class VMManagerTUI(App):
|
|
|
1394
1453
|
cpu = 0
|
|
1395
1454
|
memory = 0
|
|
1396
1455
|
|
|
1397
|
-
|
|
1456
|
+
return {
|
|
1398
1457
|
'uuid': uuid,
|
|
1399
1458
|
'name': vm_name,
|
|
1400
1459
|
'status': status,
|
|
@@ -1405,19 +1464,28 @@ class VMManagerTUI(App):
|
|
|
1405
1464
|
'conn': conn,
|
|
1406
1465
|
'uri': uri
|
|
1407
1466
|
}
|
|
1408
|
-
vm_data_list.append(vm_data)
|
|
1409
|
-
|
|
1410
1467
|
except libvirt.libvirtError as e:
|
|
1411
1468
|
if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
|
|
1412
1469
|
logging.warning(f"Skipping display of non-existent VM during refresh.")
|
|
1413
|
-
|
|
1470
|
+
return None
|
|
1414
1471
|
else:
|
|
1415
1472
|
try:
|
|
1416
1473
|
name_for_error = vm_name if 'vm_name' in locals() else domain.name()
|
|
1417
1474
|
except:
|
|
1418
1475
|
name_for_error = "Unknown"
|
|
1419
|
-
self.call_from_thread(self.show_error_message,
|
|
1420
|
-
|
|
1476
|
+
self.call_from_thread(self.show_error_message, ErrorMessages.VM_INFO_ERROR.format(vm_name=name_for_error, error=e))
|
|
1477
|
+
return None
|
|
1478
|
+
|
|
1479
|
+
vm_data_list = []
|
|
1480
|
+
page_uuids = set()
|
|
1481
|
+
|
|
1482
|
+
with ThreadPoolExecutor(max_workers=20) as executor:
|
|
1483
|
+
results = list(executor.map(fetch_vm_data, paginated_domains))
|
|
1484
|
+
|
|
1485
|
+
for result in results:
|
|
1486
|
+
if result:
|
|
1487
|
+
vm_data_list.append(result)
|
|
1488
|
+
page_uuids.add(result['uuid'])
|
|
1421
1489
|
|
|
1422
1490
|
# Cleanup cache: remove cards for VMs that no longer exist at all
|
|
1423
1491
|
all_uuids_from_libvirt = set(all_active_uuids)
|
|
@@ -1550,7 +1618,7 @@ class VMManagerTUI(App):
|
|
|
1550
1618
|
self.filtered_server_uris = [u for u in self.filtered_server_uris if u not in uris_to_remove]
|
|
1551
1619
|
|
|
1552
1620
|
if removed_names:
|
|
1553
|
-
self.show_error_message(
|
|
1621
|
+
self.show_error_message(ErrorMessages.SERVER_DISCONNECTED_AUTOCONNECT_DISABLED.format(names=', '.join(removed_names)))
|
|
1554
1622
|
|
|
1555
1623
|
if config_changed:
|
|
1556
1624
|
self.config['servers'] = self.servers
|
|
@@ -1563,7 +1631,7 @@ class VMManagerTUI(App):
|
|
|
1563
1631
|
self.call_from_thread(update_ui_on_main_thread)
|
|
1564
1632
|
|
|
1565
1633
|
except Exception as e:
|
|
1566
|
-
self.call_from_thread(self.show_error_message,
|
|
1634
|
+
self.call_from_thread(self.show_error_message, ErrorMessages.ERROR_FETCHING_VM_DATA.format(error=e))
|
|
1567
1635
|
finally:
|
|
1568
1636
|
if on_complete:
|
|
1569
1637
|
self.call_from_thread(on_complete)
|
|
@@ -1613,7 +1681,7 @@ class VMManagerTUI(App):
|
|
|
1613
1681
|
def action_pattern_select(self) -> None:
|
|
1614
1682
|
"""Handles the 'Pattern Sel' button press."""
|
|
1615
1683
|
if not self.active_uris:
|
|
1616
|
-
self.show_error_message(
|
|
1684
|
+
self.show_error_message(ErrorMessages.NO_ACTIVE_SERVERS)
|
|
1617
1685
|
return
|
|
1618
1686
|
|
|
1619
1687
|
# Gather all known VMs from cache
|
|
@@ -1638,7 +1706,7 @@ class VMManagerTUI(App):
|
|
|
1638
1706
|
continue
|
|
1639
1707
|
|
|
1640
1708
|
if not available_vms:
|
|
1641
|
-
self.show_error_message(
|
|
1709
|
+
self.show_error_message(ErrorMessages.NO_VMS_IN_CACHE)
|
|
1642
1710
|
return
|
|
1643
1711
|
|
|
1644
1712
|
# Prepare server list for the modal, matching FilterModal logic
|
|
@@ -1661,7 +1729,7 @@ class VMManagerTUI(App):
|
|
|
1661
1729
|
if selected_uuids:
|
|
1662
1730
|
# Add found UUIDs to current selection
|
|
1663
1731
|
self.selected_vm_uuids.update(selected_uuids)
|
|
1664
|
-
self.show_success_message(
|
|
1732
|
+
self.show_success_message(SuccessMessages.VMS_SELECTED_BY_PATTERN.format(count=len(selected_uuids)))
|
|
1665
1733
|
self.refresh_vm_list()
|
|
1666
1734
|
|
|
1667
1735
|
self.push_screen(PatternSelectModal(available_vms, available_servers, selected_servers), handle_result)
|
|
@@ -1672,7 +1740,7 @@ class VMManagerTUI(App):
|
|
|
1672
1740
|
"""Handles the 'Bulk Selected' button press."""
|
|
1673
1741
|
self._collapse_all_action_collapsibles()
|
|
1674
1742
|
if not self.selected_vm_uuids:
|
|
1675
|
-
self.show_error_message(
|
|
1743
|
+
self.show_error_message(ErrorMessages.NO_VMS_SELECTED)
|
|
1676
1744
|
return
|
|
1677
1745
|
|
|
1678
1746
|
uuids_snapshot = list(self.selected_vm_uuids)
|
|
@@ -1683,7 +1751,7 @@ class VMManagerTUI(App):
|
|
|
1683
1751
|
|
|
1684
1752
|
# Use the service to find specific domains by their internal ID (UUID@URI)
|
|
1685
1753
|
# This correctly handles cases where identical UUIDs exist on different servers
|
|
1686
|
-
found_domains_map = self.vm_service.find_domains_by_uuids(self.active_uris, uuids)
|
|
1754
|
+
found_domains_map = self.vm_service.find_domains_by_uuids(self.active_uris, uuids, check_validity=False)
|
|
1687
1755
|
|
|
1688
1756
|
all_names = set()
|
|
1689
1757
|
for domain in found_domains_map.values():
|
|
@@ -1701,9 +1769,7 @@ class VMManagerTUI(App):
|
|
|
1701
1769
|
self.push_screen, BulkActionModal(vm_names_list), self.handle_bulk_action_result
|
|
1702
1770
|
)
|
|
1703
1771
|
else:
|
|
1704
|
-
self.call_from_thread(
|
|
1705
|
-
self.show_error_message, "Could not retrieve names for selected VMs."
|
|
1706
|
-
)
|
|
1772
|
+
self.call_from_thread(self.show_error_message, ErrorMessages.BULK_ACTION_VM_NAMES_RETRIEVAL_FAILED)
|
|
1707
1773
|
|
|
1708
1774
|
self.worker_manager.run(
|
|
1709
1775
|
get_names_and_show_modal,
|