virtui-manager 1.1.6__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {virtui_manager-1.1.6.dist-info → virtui_manager-1.4.0.dist-info}/METADATA +1 -1
- virtui_manager-1.4.0.dist-info/RECORD +76 -0
- vmanager/constants.py +739 -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.mo +0 -0
- vmanager/locales/de/LC_MESSAGES/virtui-manager.po +3158 -0
- vmanager/locales/fr/LC_MESSAGES/virtui-manager.mo +0 -0
- vmanager/locales/fr/LC_MESSAGES/virtui-manager.po +3155 -0
- vmanager/locales/it/LC_MESSAGES/virtui-manager.mo +0 -0
- vmanager/locales/it/LC_MESSAGES/virtui-manager.po +3132 -0
- vmanager/locales/virtui-manager.pot +3033 -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/host_stats.py +199 -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 +21 -19
- 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 +54 -7
- vmanager/vm_actions.py +48 -24
- vmanager/vm_migration.py +4 -1
- vmanager/vm_queries.py +67 -25
- vmanager/vm_service.py +8 -5
- vmanager/vmanager.css +55 -1
- vmanager/vmanager.py +247 -120
- vmanager/vmcard.css +3 -1
- vmanager/vmcard.py +270 -205
- vmanager/webconsole_manager.py +22 -22
- virtui_manager-1.1.6.dist-info/RECORD +0 -65
- {virtui_manager-1.1.6.dist-info → virtui_manager-1.4.0.dist-info}/WHEEL +0 -0
- {virtui_manager-1.1.6.dist-info → virtui_manager-1.4.0.dist-info}/entry_points.txt +0 -0
- {virtui_manager-1.1.6.dist-info → virtui_manager-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {virtui_manager-1.1.6.dist-info → virtui_manager-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -8,6 +8,7 @@ from textual.containers import Horizontal, Vertical, VerticalScroll, Grid
|
|
|
8
8
|
from textual.widgets import Button, Input, Label, DataTable, Checkbox
|
|
9
9
|
from textual import on
|
|
10
10
|
from .base_modals import BaseModal
|
|
11
|
+
from ..constants import StaticText, ButtonLabels
|
|
11
12
|
|
|
12
13
|
class PatternSelectModal(BaseModal[set[str] | None]):
|
|
13
14
|
"""Modal for selecting VMs by pattern across servers."""
|
|
@@ -23,7 +24,7 @@ class PatternSelectModal(BaseModal[set[str] | None]):
|
|
|
23
24
|
|
|
24
25
|
def compose(self) -> ComposeResult:
|
|
25
26
|
with Vertical(id="pattern-select-container", classes="modal-container"):
|
|
26
|
-
yield Label(
|
|
27
|
+
yield Label(StaticText.SELECT_VMS_BY_PATTERN, id="pattern-select-title")
|
|
27
28
|
|
|
28
29
|
with Horizontal(classes="pattern-input-row"):
|
|
29
30
|
yield Input(
|
|
@@ -31,7 +32,7 @@ class PatternSelectModal(BaseModal[set[str] | None]):
|
|
|
31
32
|
id="pattern-input",
|
|
32
33
|
restrict=r"[a-zA-Z0-9_\-\*\?\.\^\|\$\( \[ \] \+\{\}\\]*"
|
|
33
34
|
)
|
|
34
|
-
yield Checkbox(
|
|
35
|
+
yield Checkbox(StaticText.REGEX, id="regex-checkbox")
|
|
35
36
|
|
|
36
37
|
if self.available_servers:
|
|
37
38
|
#yield Label("Search in Servers:")
|
|
@@ -48,15 +49,15 @@ class PatternSelectModal(BaseModal[set[str] | None]):
|
|
|
48
49
|
grid.styles.grid_gutter_horizontal = 0
|
|
49
50
|
yield grid
|
|
50
51
|
|
|
51
|
-
yield Button(
|
|
52
|
+
yield Button(ButtonLabels.SEARCH_VMS, variant="primary", id="search-vms-btn")
|
|
52
53
|
|
|
53
|
-
yield Label(
|
|
54
|
+
yield Label(StaticText.MATCHING_VMS, id="results-label")
|
|
54
55
|
with VerticalScroll(id="results-container"):
|
|
55
56
|
yield DataTable(id="results-table", cursor_type="row")
|
|
56
57
|
|
|
57
58
|
with Horizontal(id="pattern-action-buttons"):
|
|
58
|
-
yield Button(
|
|
59
|
-
yield Button(
|
|
59
|
+
yield Button(ButtonLabels.SELECT_MATCHING, variant="success", id="select-btn", disabled=True)
|
|
60
|
+
yield Button(ButtonLabels.CANCEL, variant="error", id="cancel-btn")
|
|
60
61
|
|
|
61
62
|
def on_mount(self) -> None:
|
|
62
63
|
table = self.query_one("#results-table", DataTable)
|
vmanager/modals/server_modals.py
CHANGED
|
@@ -11,19 +11,20 @@ from .howto_ssh_modal import HowToSSHModal
|
|
|
11
11
|
from .base_modals import BaseModal
|
|
12
12
|
|
|
13
13
|
from ..config import save_config
|
|
14
|
+
from ..constants import ErrorMessages, SuccessMessages, ButtonLabels, StaticText
|
|
14
15
|
|
|
15
16
|
class ConnectionModal(BaseModal[str | None]):
|
|
16
17
|
|
|
17
18
|
def compose(self) -> ComposeResult:
|
|
18
19
|
with Vertical(id="connection-dialog"):
|
|
19
|
-
yield Label(
|
|
20
|
+
yield Label(StaticText.ENTER_QEMU_CONNECTION_URI)
|
|
20
21
|
yield Input(
|
|
21
22
|
placeholder="qemu+ssh://user@host/system or qemu:///system",
|
|
22
23
|
id="uri-input",
|
|
23
24
|
)
|
|
24
25
|
with Horizontal():
|
|
25
|
-
yield Button(
|
|
26
|
-
yield Button(
|
|
26
|
+
yield Button(ButtonLabels.CONNECT, variant="primary", id="connect-btn", classes="Buttonpage")
|
|
27
|
+
yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn", classes="Buttonpage")
|
|
27
28
|
|
|
28
29
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
29
30
|
if event.button.id == "connect-btn":
|
|
@@ -37,14 +38,14 @@ class AddServerModal(BaseModal[Tuple[str, str] | None]):
|
|
|
37
38
|
"""Modal for adding a new server with autoconnect option."""
|
|
38
39
|
def compose(self) -> ComposeResult:
|
|
39
40
|
with Vertical(id="add-server-dialog"):
|
|
40
|
-
yield Label(
|
|
41
|
+
yield Label(StaticText.ADD_NEW_SERVER)
|
|
41
42
|
yield Input(placeholder="Server Name", id="server-name-input")
|
|
42
43
|
yield Input(placeholder="qemu+ssh://user@host/system", id="server-uri-input")
|
|
43
|
-
yield Label(
|
|
44
|
-
yield Checkbox(
|
|
44
|
+
yield Label(StaticText.EMPTY_LABEL)
|
|
45
|
+
yield Checkbox(StaticText.AUTOCONNECT_AT_STARTUP, id="autoconnect-checkbox", value=False)
|
|
45
46
|
with Horizontal():
|
|
46
|
-
yield Button(
|
|
47
|
-
yield Button(
|
|
47
|
+
yield Button(ButtonLabels.SAVE, variant="primary", id="save-btn", classes="Buttonpage")
|
|
48
|
+
yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn", classes="Buttonpage")
|
|
48
49
|
|
|
49
50
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
50
51
|
if event.button.id == "save-btn":
|
|
@@ -69,14 +70,14 @@ class EditServerModal(BaseModal[Tuple[str, str, bool] | None]):
|
|
|
69
70
|
|
|
70
71
|
def compose(self) -> ComposeResult:
|
|
71
72
|
with Vertical(id="edit-server-dialog"):
|
|
72
|
-
yield Label(
|
|
73
|
+
yield Label(StaticText.EDIT_SERVER)
|
|
73
74
|
yield Input(value=self.server_name, id="server-name-input")
|
|
74
75
|
yield Input(value=self.server_uri, id="server-uri-input")
|
|
75
|
-
yield Label(
|
|
76
|
-
yield Checkbox(
|
|
76
|
+
yield Label(StaticText.EMPTY_LABEL)
|
|
77
|
+
yield Checkbox(StaticText.AUTOCONNECT_AT_STARTUP, id="autoconnect-checkbox", value=self.autoconnect)
|
|
77
78
|
with Horizontal():
|
|
78
|
-
yield Button(
|
|
79
|
-
yield Button(
|
|
79
|
+
yield Button(ButtonLabels.SAVE, variant="primary", id="save-btn", classes="Buttonpage")
|
|
80
|
+
yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn", classes="Buttonpage")
|
|
80
81
|
|
|
81
82
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
82
83
|
if event.button.id == "save-btn":
|
|
@@ -102,18 +103,18 @@ class ServerManagementModal(BaseModal [str | None]):
|
|
|
102
103
|
|
|
103
104
|
def compose(self) -> ComposeResult:
|
|
104
105
|
with Vertical(id="server-management-dialog"): #, classes="info-details"):
|
|
105
|
-
yield Label(
|
|
106
|
+
yield Label(StaticText.SERVER_LIST_MANAGEMENT) #, id="server-list-title")
|
|
106
107
|
with ScrollableContainer(classes="info-details"):
|
|
107
108
|
yield DataTable(id="server-table", classes="server-list")
|
|
108
109
|
with Vertical(classes="server-list"):
|
|
109
110
|
with Horizontal():
|
|
110
|
-
yield Button(
|
|
111
|
-
yield Button(
|
|
112
|
-
yield Button(
|
|
111
|
+
yield Button(ButtonLabels.ADD, id="add-server-btn", classes="add-button", variant="success")
|
|
112
|
+
yield Button(ButtonLabels.EDIT, id="edit-server-btn", disabled=True, classes="edit-button")
|
|
113
|
+
yield Button(ButtonLabels.DELETE, id="delete-server-btn", disabled=True,)
|
|
113
114
|
with Horizontal():
|
|
114
|
-
yield Button(
|
|
115
|
-
yield Button(
|
|
116
|
-
yield Button(
|
|
115
|
+
yield Button(ButtonLabels.CONNECT, id="select-btn", variant="primary", disabled=True, classes="Buttonpage")
|
|
116
|
+
yield Button(ButtonLabels.CUSTOM_URL, id="custom-conn-btn", classes="Buttonpage")
|
|
117
|
+
yield Button(ButtonLabels.SSH_HELP, id="ssh-help-btn", classes="Buttonpage")
|
|
117
118
|
#yield Button("Close", id="close-btn", classes="close-button")
|
|
118
119
|
|
|
119
120
|
def on_mount(self) -> None:
|
|
@@ -193,13 +194,13 @@ class ServerManagementModal(BaseModal [str | None]):
|
|
|
193
194
|
self.query_one("#edit-server-btn").disabled = True
|
|
194
195
|
self.query_one("#delete-server-btn").disabled = True
|
|
195
196
|
self.query_one("#select-btn").disabled = True
|
|
196
|
-
self.app.show_success_message(
|
|
197
|
+
self.app.show_success_message(SuccessMessages.SERVER_DELETED_TEMPLATE.format(server_name=server_name_to_delete))
|
|
197
198
|
logging.info(f"Successfully deleted Server '{server_name_to_delete}'")
|
|
198
199
|
except Exception as e:
|
|
199
|
-
self.app.show_error_message(
|
|
200
|
+
self.app.show_error_message(ErrorMessages.ERROR_DELETING_SERVER_TEMPLATE.format(server_name=server_name_to_delete, error=e))
|
|
200
201
|
|
|
201
202
|
self.app.push_screen(
|
|
202
|
-
ConfirmationDialog(
|
|
203
|
+
ConfirmationDialog(ErrorMessages.DELETE_SERVER_CONFIRMATION_TEMPLATE.format(server_name=server_name_to_delete)), on_confirm)
|
|
203
204
|
|
|
204
205
|
elif event.button.id == "custom-conn-btn":
|
|
205
206
|
def connection_callback(uri: str | None):
|
|
@@ -25,7 +25,11 @@ from ..network_manager import (
|
|
|
25
25
|
from .. import storage_manager
|
|
26
26
|
from ..storage_manager import list_storage_volumes
|
|
27
27
|
|
|
28
|
-
from ..constants import
|
|
28
|
+
from ..constants import (
|
|
29
|
+
AppCacheTimeout, ErrorMessages, SuccessMessages,
|
|
30
|
+
ButtonLabels, WarningMessages,
|
|
31
|
+
StaticText
|
|
32
|
+
)
|
|
29
33
|
from .base_modals import BaseModal
|
|
30
34
|
from .network_modals import AddEditNetworkModal, NetworkXMLModal
|
|
31
35
|
from .disk_pool_modals import (
|
|
@@ -51,7 +55,7 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
51
55
|
|
|
52
56
|
def compose(self) -> ComposeResult:
|
|
53
57
|
with Vertical(id="server-pref-dialog",):
|
|
54
|
-
yield Label(
|
|
58
|
+
yield Label(StaticText.SERVER_PREFERENCES, id="server-pref-title")
|
|
55
59
|
yield Static(classes="button-separator")
|
|
56
60
|
with TabbedContent(id="server-pref-tabs"):
|
|
57
61
|
with TabPane("Network", id="tab-network"):
|
|
@@ -65,19 +69,19 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
65
69
|
uri_to_connect = self.uri
|
|
66
70
|
if uri_to_connect is None:
|
|
67
71
|
if len(self.app.active_uris) == 0:
|
|
68
|
-
self.app.show_error_message(
|
|
72
|
+
self.app.show_error_message(ErrorMessages.NOT_CONNECTED_TO_ANY_SERVER_PREFS)
|
|
69
73
|
self.dismiss()
|
|
70
74
|
return
|
|
71
75
|
if len(self.app.active_uris) > 1:
|
|
72
76
|
# This should not happen if the app logic uses the server selection modal
|
|
73
|
-
self.app.show_error_message(
|
|
77
|
+
self.app.show_error_message(ErrorMessages.MULTIPLE_SERVERS_ACTIVE_NONE_SELECTED)
|
|
74
78
|
self.dismiss()
|
|
75
79
|
return
|
|
76
80
|
uri_to_connect = self.app.active_uris[0]
|
|
77
81
|
|
|
78
82
|
self.conn = self.app.vm_service.connect(uri_to_connect)
|
|
79
83
|
if not self.conn:
|
|
80
|
-
self.app.show_error_message(
|
|
84
|
+
self.app.show_error_message(ErrorMessages.FAILED_TO_GET_CONNECTION_PREFS_TEMPLATE.format(uri=uri_to_connect))
|
|
81
85
|
self.dismiss()
|
|
82
86
|
return
|
|
83
87
|
|
|
@@ -103,21 +107,21 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
103
107
|
|
|
104
108
|
button_container = Vertical(
|
|
105
109
|
Horizontal(
|
|
106
|
-
Button(
|
|
110
|
+
Button(ButtonLabels.DE_ACTIVE, id="toggle-net-active-btn", classes="toggle-detail-button",
|
|
107
111
|
variant="primary", disabled=True, tooltip="Activate or Deactivate the selected network."),
|
|
108
|
-
Button(
|
|
112
|
+
Button(ButtonLabels.AUTOSTART, id="toggle-net-autostart-btn", classes="toggle-detail-button",
|
|
109
113
|
variant="primary", disabled=True, tooltip="Enable or Disable autostart for the selected network."),
|
|
110
114
|
),
|
|
111
115
|
Horizontal(
|
|
112
|
-
Button(
|
|
116
|
+
Button(ButtonLabels.ADD, id="add-net-btn", variant="success", classes="toggle-detail-button",
|
|
113
117
|
tooltip="Add a new network."),
|
|
114
|
-
Button(
|
|
118
|
+
Button(ButtonLabels.EDIT, id="edit-net-btn", variant="success", classes="toggle-detail-button",
|
|
115
119
|
disabled=True, tooltip="Edit the selected network."),
|
|
116
|
-
Button(
|
|
120
|
+
Button(ButtonLabels.VIEW, id="view-net-btn", variant="success", classes="toggle-detail-button",
|
|
117
121
|
disabled=True, tooltip="View XML details of the selected network."),
|
|
118
|
-
Button(
|
|
122
|
+
Button(ButtonLabels.DELETE, id="delete-net-btn", variant="error", classes="toggle-detail-button",
|
|
119
123
|
disabled=True, tooltip="Delete the selected network."),
|
|
120
|
-
Button(
|
|
124
|
+
Button(ButtonLabels.HELP, id="help-net-btn", variant="success", classes="toggle-detail-button",
|
|
121
125
|
tooltip="Show network configuration help."),
|
|
122
126
|
),
|
|
123
127
|
classes="server-pref-button"
|
|
@@ -145,9 +149,9 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
145
149
|
|
|
146
150
|
|
|
147
151
|
if num_vms > AppCacheTimeout.DONT_DISPLAY_DISK_USAGE:
|
|
148
|
-
self.app.show_warning_message(
|
|
152
|
+
self.app.show_warning_message(WarningMessages.TOO_MANY_VMS_DISK_USAGE_WARNING_TEMPLATE.format(count=AppCacheTimeout.DONT_DISPLAY_DISK_USAGE))
|
|
149
153
|
else:
|
|
150
|
-
self.app.show_warning_message(
|
|
154
|
+
#self.app.show_warning_message(WarningMessages.RUNNING_DISK_USAGE_SCAN_WARNING)
|
|
151
155
|
disk_map = get_all_vm_disk_usage(self.conn)
|
|
152
156
|
nvram_map = get_all_vm_nvram_usage(self.conn)
|
|
153
157
|
#overlay_map = get_all_vm_overlay_usage(self.conn)
|
|
@@ -177,25 +181,25 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
177
181
|
Button(id="toggle-autostart-pool-btn", variant="primary", classes="toggle-detail-button",
|
|
178
182
|
tooltip="Enable or Disable autostart for the selected storage pool."),
|
|
179
183
|
Vertical(
|
|
180
|
-
Button(
|
|
184
|
+
Button(ButtonLabels.ADD_POOL, id="add-pool-btn", variant="success", classes="toggle-detail-button",
|
|
181
185
|
tooltip="Add a new storage pool."),
|
|
182
|
-
Button(
|
|
186
|
+
Button(ButtonLabels.DELETE_POOL, id="del-pool-btn", variant="error", classes="toggle-detail-button",
|
|
183
187
|
tooltip="Delete the selected storage pool."),
|
|
184
188
|
),
|
|
185
189
|
Vertical(
|
|
186
|
-
Button(
|
|
190
|
+
Button(ButtonLabels.NEW_VOLUME, id="add-vol-btn", variant="success", classes="toggle-detail-button",
|
|
187
191
|
tooltip="Create a new volume in the selected pool."),
|
|
188
|
-
Button(
|
|
192
|
+
Button(ButtonLabels.ATTACH_VOL, id="attach-vol-btn", variant="success", classes="toggle-detail-button",
|
|
189
193
|
tooltip="Attach an existing disk file as a volume."),
|
|
190
|
-
Button(
|
|
194
|
+
Button(ButtonLabels.MOVE_VOLUME, id="move-vol-btn", variant="success", classes="toggle-detail-button",
|
|
191
195
|
tooltip="Move the selected volume to another pool."),
|
|
192
|
-
Button(
|
|
196
|
+
Button(ButtonLabels.DELETE_VOLUME, id="del-vol-btn", variant="error", classes="toggle-detail-button",
|
|
193
197
|
tooltip="Delete the selected volume."),
|
|
194
198
|
),
|
|
195
199
|
Vertical(
|
|
196
|
-
Button(
|
|
200
|
+
Button(ButtonLabels.VIEW_XML, id="view-storage-xml-btn", variant="primary", classes="toggle-detail-button",
|
|
197
201
|
tooltip="View XML details of the selected pool or volume."),
|
|
198
|
-
Button(
|
|
202
|
+
Button(ButtonLabels.EDIT_XML, id="edit-pool-xml-btn", variant="primary", classes="toggle-detail-button",
|
|
199
203
|
tooltip="Edit XML of the selected pool."),
|
|
200
204
|
),
|
|
201
205
|
),
|
|
@@ -286,7 +290,7 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
286
290
|
|
|
287
291
|
table.clear()
|
|
288
292
|
|
|
289
|
-
self.app.show_warning_message(
|
|
293
|
+
#self.app.show_warning_message(WarningMessages.RUNNING_NETWORK_USAGE_SCAN_WARNING)
|
|
290
294
|
network_usage = get_all_network_usage(self.conn)
|
|
291
295
|
self.networks_list = list_networks(self.conn)
|
|
292
296
|
|
|
@@ -388,14 +392,14 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
388
392
|
return
|
|
389
393
|
|
|
390
394
|
if not target_obj:
|
|
391
|
-
self.app.show_error_message(
|
|
395
|
+
self.app.show_error_message(ErrorMessages.COULD_NOT_FIND_OBJECT_XML)
|
|
392
396
|
return
|
|
393
397
|
|
|
394
398
|
try:
|
|
395
399
|
xml_content = target_obj.XMLDesc(0)
|
|
396
400
|
self.app.push_screen(XMLDisplayModal(xml_content, read_only=True))
|
|
397
401
|
except libvirt.libvirtError as e:
|
|
398
|
-
self.app.show_error_message(
|
|
402
|
+
self.app.show_error_message(ErrorMessages.ERROR_GETTING_XML_FOR_TYPE_TEMPLATE.format(obj_type=node_type, error=e))
|
|
399
403
|
|
|
400
404
|
@on(Button.Pressed, "#edit-pool-xml-btn")
|
|
401
405
|
def on_edit_pool_xml_button_pressed(self, event: Button.Pressed) -> None:
|
|
@@ -410,11 +414,11 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
410
414
|
|
|
411
415
|
pool = node_data.get('pool')
|
|
412
416
|
if not pool:
|
|
413
|
-
self.app.show_error_message(
|
|
417
|
+
self.app.show_error_message(ErrorMessages.COULD_NOT_FIND_POOL_EDIT)
|
|
414
418
|
return
|
|
415
419
|
|
|
416
420
|
if node_data.get('status') == 'active':
|
|
417
|
-
self.app.show_error_message(
|
|
421
|
+
self.app.show_error_message(ErrorMessages.POOL_MUST_BE_INACTIVE_FOR_XML_EDIT)
|
|
418
422
|
return
|
|
419
423
|
|
|
420
424
|
def on_edit_confirm(confirmed: bool) -> None:
|
|
@@ -424,7 +428,7 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
424
428
|
try:
|
|
425
429
|
xml_content = pool.XMLDesc(0)
|
|
426
430
|
except libvirt.libvirtError as e:
|
|
427
|
-
self.app.show_error_message(
|
|
431
|
+
self.app.show_error_message(ErrorMessages.ERROR_GETTING_XML_FOR_POOL_TEMPLATE.format(error=e))
|
|
428
432
|
return
|
|
429
433
|
|
|
430
434
|
def on_xml_save(new_xml: str | None) -> None:
|
|
@@ -434,21 +438,17 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
434
438
|
try:
|
|
435
439
|
# Redefine the pool with the new XML
|
|
436
440
|
self.conn.storagePoolDefineXML(new_xml, 0)
|
|
437
|
-
self.app.show_success_message(
|
|
441
|
+
self.app.show_success_message(SuccessMessages.STORAGE_POOL_UPDATED_SUCCESSFULLY_TEMPLATE.format(pool_name=pool.name()))
|
|
438
442
|
storage_manager.list_storage_pools.cache_clear()
|
|
439
443
|
self._load_storage_pools() # Refresh the tree
|
|
440
444
|
except libvirt.libvirtError as e:
|
|
441
|
-
self.app.show_error_message(
|
|
445
|
+
self.app.show_error_message(ErrorMessages.ERROR_UPDATING_POOL_XML_TEMPLATE.format(error=e))
|
|
442
446
|
except Exception as e:
|
|
443
|
-
self.app.show_error_message(
|
|
447
|
+
self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
|
|
444
448
|
|
|
445
449
|
self.app.push_screen(XMLDisplayModal(xml_content, read_only=False), on_xml_save)
|
|
446
450
|
|
|
447
|
-
warning_message =
|
|
448
|
-
"Editing a pool's XML definition is an advanced operation.\n"
|
|
449
|
-
"An invalid configuration may make its volumes inaccessible to VMs.\n\n"
|
|
450
|
-
"Are you sure you want to proceed?"
|
|
451
|
-
)
|
|
451
|
+
warning_message = ErrorMessages.EDIT_POOL_XML_WARNING
|
|
452
452
|
self.app.push_screen(ConfirmationDialog(warning_message), on_edit_confirm)
|
|
453
453
|
|
|
454
454
|
@on(Button.Pressed, "#toggle-active-pool-btn")
|
|
@@ -467,12 +467,13 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
467
467
|
is_active = node_data.get('status') == 'active'
|
|
468
468
|
try:
|
|
469
469
|
storage_manager.set_pool_active(pool, not is_active)
|
|
470
|
-
|
|
470
|
+
status = 'inactive' if is_active else 'active'
|
|
471
|
+
self.app.show_success_message(SuccessMessages.POOL_ACTIVATION_CHANGE_TEMPLATE.format(pool_name=pool.name(), status=status))
|
|
471
472
|
node_data['status'] = 'inactive' if is_active else 'active'
|
|
472
473
|
storage_manager.list_storage_pools.cache_clear()
|
|
473
474
|
self._load_storage_pools(select_pool=pool_name) # Refresh the tree
|
|
474
475
|
except Exception as e:
|
|
475
|
-
self.app.show_error_message(
|
|
476
|
+
self.app.show_error_message(ErrorMessages.ERROR_TOGGLE_POOL_ACTIVE_TEMPLATE.format(error=e))
|
|
476
477
|
|
|
477
478
|
@on(Button.Pressed, "#toggle-autostart-pool-btn")
|
|
478
479
|
def on_toggle_autostart_pool_button_pressed(self, event: Button.Pressed) -> None:
|
|
@@ -490,12 +491,13 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
490
491
|
has_autostart = node_data.get('autostart', False)
|
|
491
492
|
try:
|
|
492
493
|
storage_manager.set_pool_autostart(pool, not has_autostart)
|
|
493
|
-
|
|
494
|
+
status = 'off' if has_autostart else 'on'
|
|
495
|
+
self.app.show_success_message(SuccessMessages.POOL_AUTOSTART_CHANGE_TEMPLATE.format(pool_name=pool.name(), status=status))
|
|
494
496
|
node_data['autostart'] = not has_autostart
|
|
495
497
|
storage_manager.list_storage_pools.cache_clear()
|
|
496
498
|
self._load_storage_pools(select_pool=pool_name) # Refresh the tree
|
|
497
499
|
except Exception as e:
|
|
498
|
-
self.app.show_error_message(
|
|
500
|
+
self.app.show_error_message(ErrorMessages.ERROR_TOGGLE_POOL_AUTOSTART_TEMPLATE.format(error=e))
|
|
499
501
|
|
|
500
502
|
@on(Button.Pressed, "#add-vol-btn")
|
|
501
503
|
def on_add_volume_button_pressed(self, event: Button.Pressed) -> None:
|
|
@@ -519,7 +521,7 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
519
521
|
result['size_gb'],
|
|
520
522
|
result['format']
|
|
521
523
|
)
|
|
522
|
-
self.app.show_success_message(
|
|
524
|
+
self.app.show_success_message(SuccessMessages.VOLUME_CREATED_SUCCESSFULLY_TEMPLATE.format(name=result['name'], size_gb=result['size_gb'], format=result['format']))
|
|
523
525
|
storage_manager.list_storage_volumes.cache_clear()
|
|
524
526
|
pool_node.add_leaf(result['name'])
|
|
525
527
|
|
|
@@ -536,7 +538,7 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
536
538
|
|
|
537
539
|
volume_path = result.get('path')
|
|
538
540
|
if not volume_path or not os.path.exists(volume_path):
|
|
539
|
-
self.app.show_error_message(
|
|
541
|
+
self.app.show_error_message(ErrorMessages.INVALID_OR_NON_EXISTENT_PATH_TEMPLATE.format(path=volume_path))
|
|
540
542
|
return
|
|
541
543
|
|
|
542
544
|
volume_dir = os.path.dirname(volume_path)
|
|
@@ -547,11 +549,10 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
547
549
|
existing_pool.refresh(0)
|
|
548
550
|
storage_manager.list_storage_volumes.cache_clear()
|
|
549
551
|
self.app.show_success_message(
|
|
550
|
-
|
|
551
|
-
"Refreshed pool to include the new volume."
|
|
552
|
+
SuccessMessages.POOL_MANAGED_BY_EXISTING_POOL_TEMPLATE.format(pool_name=existing_pool.name())
|
|
552
553
|
)
|
|
553
554
|
except libvirt.libvirtError as e:
|
|
554
|
-
self.app.show_error_message(
|
|
555
|
+
self.app.show_error_message(ErrorMessages.ERROR_REFRESHING_POOL_TEMPLATE.format(error=e))
|
|
555
556
|
|
|
556
557
|
self._load_storage_pools(expand_pools=[existing_pool.name()])
|
|
557
558
|
return
|
|
@@ -563,17 +564,16 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
563
564
|
try:
|
|
564
565
|
storage_manager.create_storage_pool(self.conn, new_pool_name, 'dir', volume_dir)
|
|
565
566
|
self.app.show_success_message(
|
|
566
|
-
|
|
567
|
+
SuccessMessages.STORAGE_POOL_CREATED_TEMPLATE.format(name=new_pool_name)
|
|
567
568
|
)
|
|
568
569
|
storage_manager.list_storage_pools.cache_clear()
|
|
569
570
|
self._load_storage_pools(expand_pools=[new_pool_name])
|
|
570
571
|
except Exception as e:
|
|
571
|
-
self.app.show_error_message(
|
|
572
|
+
self.app.show_error_message(ErrorMessages.ERROR_CREATING_STORAGE_POOL_FOR_DIR_TEMPLATE.format(error=e))
|
|
572
573
|
|
|
573
574
|
self.app.push_screen(
|
|
574
575
|
ConfirmationDialog(
|
|
575
|
-
|
|
576
|
-
f"Create a new pool named '{new_pool_name}'?"
|
|
576
|
+
ErrorMessages.NO_POOL_FOR_DIRECTORY_CONFIRM_TEMPLATE.format(directory=volume_dir, pool_name=new_pool_name)
|
|
577
577
|
),
|
|
578
578
|
on_confirm_create
|
|
579
579
|
)
|
|
@@ -606,14 +606,14 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
606
606
|
if confirmed:
|
|
607
607
|
try:
|
|
608
608
|
storage_manager.delete_storage_pool(pool)
|
|
609
|
-
self.app.show_success_message(
|
|
609
|
+
self.app.show_success_message(SuccessMessages.STORAGE_POOL_DELETED_SUCCESSFULLY_TEMPLATE.format(pool_name=pool_name))
|
|
610
610
|
storage_manager.list_storage_pools.cache_clear()
|
|
611
611
|
self._load_storage_pools() # Refresh the tree
|
|
612
612
|
except Exception as e:
|
|
613
|
-
self.app.show_error_message(
|
|
613
|
+
self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
|
|
614
614
|
|
|
615
615
|
self.app.push_screen(
|
|
616
|
-
ConfirmationDialog(
|
|
616
|
+
ConfirmationDialog(ErrorMessages.DELETE_STORAGE_POOL_CONFIRMATION_TEMPLATE.format(pool_name=pool_name)), on_confirm)
|
|
617
617
|
|
|
618
618
|
@on(Button.Pressed, "#del-vol-btn")
|
|
619
619
|
def on_delete_volume_button_pressed(self, event: Button.Pressed) -> None:
|
|
@@ -632,7 +632,7 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
632
632
|
if confirmed:
|
|
633
633
|
try:
|
|
634
634
|
storage_manager.delete_volume(vol)
|
|
635
|
-
self.app.show_success_message(
|
|
635
|
+
self.app.show_success_message(SuccessMessages.VOLUME_DELETED_SUCCESSFULLY_TEMPLATE.format(volume_name=vol_name))
|
|
636
636
|
# Clear the cache to force a refresh
|
|
637
637
|
list_storage_volumes.cache_clear()
|
|
638
638
|
# Refresh the parent node
|
|
@@ -642,10 +642,10 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
642
642
|
parent_node.add_leaf("No volumes")
|
|
643
643
|
|
|
644
644
|
except Exception as e:
|
|
645
|
-
self.app.show_error_message(
|
|
645
|
+
self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
|
|
646
646
|
|
|
647
647
|
self.app.push_screen(
|
|
648
|
-
ConfirmationDialog(
|
|
648
|
+
ConfirmationDialog(ErrorMessages.DELETE_VOLUME_CONFIRMATION_TEMPLATE.format(vol_name=vol_name)),
|
|
649
649
|
on_confirm
|
|
650
650
|
)
|
|
651
651
|
|
|
@@ -661,7 +661,7 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
661
661
|
|
|
662
662
|
volume_name = node_data.get('name')
|
|
663
663
|
if not tree.cursor_node.parent or not tree.cursor_node.parent.data:
|
|
664
|
-
self.app.show_error_message(
|
|
664
|
+
self.app.show_error_message(ErrorMessages.COULD_NOT_DETERMINE_SOURCE_POOL)
|
|
665
665
|
return
|
|
666
666
|
source_pool_name = tree.cursor_node.parent.data.get('name')
|
|
667
667
|
|
|
@@ -717,7 +717,7 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
717
717
|
self._load_storage_pools()
|
|
718
718
|
|
|
719
719
|
return {
|
|
720
|
-
"message":
|
|
720
|
+
"message": SuccessMessages.VOLUME_MOVED_SUCCESSFULLY_TEMPLATE.format(volume_name=volume_name, dest_pool_name=dest_pool_name),
|
|
721
721
|
"source_pool": source_pool_name,
|
|
722
722
|
"dest_pool": dest_pool_name,
|
|
723
723
|
"updated_vms": updated_vms
|
|
@@ -759,10 +759,10 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
759
759
|
updated_vms = result.get("updated_vms", [])
|
|
760
760
|
if updated_vms:
|
|
761
761
|
vm_list = ", ".join(updated_vms)
|
|
762
|
-
self.app.show_success_message(
|
|
762
|
+
self.app.show_success_message(SuccessMessages.UPDATED_VM_CONFIGURATIONS_TEMPLATE.format(vm_list=vm_list))
|
|
763
763
|
self._load_storage_pools(expand_pools=[result["source_pool"], result["dest_pool"]])
|
|
764
764
|
elif event.worker.state == "ERROR":
|
|
765
|
-
self.app.show_error_message(
|
|
765
|
+
self.app.show_error_message(ErrorMessages.MOVE_OPERATION_FAILED_TEMPLATE.format(error=event.worker.error))
|
|
766
766
|
self._load_storage_pools()
|
|
767
767
|
|
|
768
768
|
@on(DataTable.RowSelected, "#networks-table")
|
|
@@ -795,10 +795,12 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
795
795
|
if net_info:
|
|
796
796
|
try:
|
|
797
797
|
set_network_active(self.conn, net_name, not net_info['active'])
|
|
798
|
-
|
|
798
|
+
status = 'inactive' if net_info['active'] else 'active'
|
|
799
|
+
self.app.show_success_message(SuccessMessages.NETWORK_ACTIVATION_CHANGE_TEMPLATE.format(net_name=net_name, status=status))
|
|
800
|
+
list_networks.cache_clear()
|
|
799
801
|
self._load_networks()
|
|
800
802
|
except Exception as e:
|
|
801
|
-
self.app.show_error_message(
|
|
803
|
+
self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
|
|
802
804
|
|
|
803
805
|
@on(Button.Pressed, "#toggle-net-autostart-btn")
|
|
804
806
|
def on_toggle_net_autostart_pressed(self, event: Button.Pressed) -> None:
|
|
@@ -813,10 +815,12 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
813
815
|
if net_info:
|
|
814
816
|
try:
|
|
815
817
|
set_network_autostart(self.conn, net_name, not net_info['autostart'])
|
|
816
|
-
|
|
818
|
+
status = 'off' if net_info['autostart'] else 'on'
|
|
819
|
+
self.app.show_success_message(SuccessMessages.NETWORK_AUTOSTART_CHANGE_TEMPLATE.format(net_name=net_name, status=status))
|
|
820
|
+
list_networks.cache_clear()
|
|
817
821
|
self._load_networks()
|
|
818
822
|
except Exception as e:
|
|
819
|
-
self.app.show_error_message(
|
|
823
|
+
self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
|
|
820
824
|
|
|
821
825
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
822
826
|
if event.button.id == "close-btn":
|
|
@@ -831,15 +835,15 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
831
835
|
try:
|
|
832
836
|
conn = self.conn
|
|
833
837
|
if conn is None:
|
|
834
|
-
self.app.show_error_message(
|
|
838
|
+
self.app.show_error_message(ErrorMessages.NOT_CONNECTED_TO_LIBVIRT)
|
|
835
839
|
return
|
|
836
840
|
net = conn.networkLookupByName(network_name)
|
|
837
841
|
network_xml = net.XMLDesc(0)
|
|
838
842
|
self.app.push_screen(NetworkXMLModal(network_name, network_xml))
|
|
839
843
|
except libvirt.libvirtError as e:
|
|
840
|
-
self.app.show_error_message(
|
|
844
|
+
self.app.show_error_message(ErrorMessages.ERROR_GETTING_NETWORK_XML_TEMPLATE.format(error=e))
|
|
841
845
|
except Exception as e:
|
|
842
|
-
self.app.show_error_message(
|
|
846
|
+
self.app.show_error_message(ErrorMessages.UNEXPECTED_ERROR_OCCURRED_TEMPLATE_XML.format(error=e))
|
|
843
847
|
|
|
844
848
|
elif event.button.id == "edit-net-btn":
|
|
845
849
|
table = self.query_one("#networks-table", DataTable)
|
|
@@ -851,17 +855,19 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
851
855
|
|
|
852
856
|
network_info = get_network_info(self.conn, network_name)
|
|
853
857
|
if not network_info:
|
|
854
|
-
self.app.show_error_message(
|
|
858
|
+
self.app.show_error_message(ErrorMessages.COULD_NOT_RETRIEVE_NETWORK_INFO_TEMPLATE.format(network_name=network_name))
|
|
855
859
|
return
|
|
856
860
|
|
|
857
861
|
def on_create(success: bool):
|
|
858
862
|
if success:
|
|
863
|
+
list_networks.cache_clear()
|
|
859
864
|
self._load_networks()
|
|
860
865
|
self.app.push_screen(AddEditNetworkModal(self.conn, network_info=network_info), on_create)
|
|
861
866
|
|
|
862
867
|
elif event.button.id == "add-net-btn":
|
|
863
868
|
def on_create(success: bool):
|
|
864
869
|
if success:
|
|
870
|
+
list_networks.cache_clear()
|
|
865
871
|
self._load_networks()
|
|
866
872
|
self.app.push_screen(AddEditNetworkModal(self.conn), on_create)
|
|
867
873
|
|
|
@@ -874,19 +880,20 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
874
880
|
network_name = row_key.value
|
|
875
881
|
vms_using_network = get_vms_using_network(self.conn, network_name)
|
|
876
882
|
|
|
877
|
-
confirm_message =
|
|
883
|
+
confirm_message = ErrorMessages.DELETE_NETWORK_CONFIRM_TEMPLATE.format(network_name=network_name)
|
|
878
884
|
if vms_using_network:
|
|
879
885
|
vm_list = ", ".join(vms_using_network)
|
|
880
|
-
confirm_message +=
|
|
886
|
+
confirm_message += ErrorMessages.NETWORK_IN_USE_WARNING_TEMPLATE.format(vm_list=vm_list)
|
|
881
887
|
|
|
882
888
|
def on_confirm(confirmed: bool) -> None:
|
|
883
889
|
if confirmed:
|
|
884
890
|
try:
|
|
885
891
|
delete_network(self.conn, network_name)
|
|
886
|
-
self.app.show_success_message(
|
|
892
|
+
self.app.show_success_message(SuccessMessages.NETWORK_DELETED_SUCCESSFULLY_TEMPLATE.format(network_name=network_name))
|
|
893
|
+
list_networks.cache_clear()
|
|
887
894
|
self._load_networks()
|
|
888
895
|
except Exception as e:
|
|
889
|
-
self.app.show_error_message(
|
|
896
|
+
self.app.show_error_message(ErrorMessages.ERROR_DELETING_NETWORK_TEMPLATE.format(network_name=network_name, error=e))
|
|
890
897
|
|
|
891
898
|
self.app.push_screen(
|
|
892
899
|
ConfirmationDialog(confirm_message), on_confirm
|