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
vmanager/modals/utils_modals.py
CHANGED
|
@@ -20,6 +20,7 @@ from textual.widgets import (
|
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
from .base_modals import BaseDialog, BaseModal
|
|
23
|
+
from ..constants import ButtonLabels, StaticText
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def _sanitize_message(message: str) -> str:
|
|
@@ -93,11 +94,11 @@ class DirectorySelectionModal(BaseModal[str | None]):
|
|
|
93
94
|
|
|
94
95
|
def compose(self) -> ComposeResult:
|
|
95
96
|
with Vertical(id="directory-selection-dialog"):
|
|
96
|
-
yield Label(
|
|
97
|
+
yield Label(StaticText.SELECT_A_DIRECTORY)
|
|
97
98
|
yield SafeDirectoryTree(self.start_path, id="dir-tree")
|
|
98
99
|
with Horizontal():
|
|
99
|
-
yield Button(
|
|
100
|
-
yield Button(
|
|
100
|
+
yield Button(ButtonLabels.SELECT, variant="primary", id="select-btn", disabled=True)
|
|
101
|
+
yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn")
|
|
101
102
|
|
|
102
103
|
def on_mount(self) -> None:
|
|
103
104
|
self.query_one(SafeDirectoryTree).focus()
|
|
@@ -126,11 +127,11 @@ class FileSelectionModal(BaseModal[str | None]):
|
|
|
126
127
|
|
|
127
128
|
def compose(self) -> ComposeResult:
|
|
128
129
|
with Vertical(id="file-selection-dialog", classes="file-selection-dialog"):
|
|
129
|
-
yield Label(
|
|
130
|
+
yield Label(StaticText.SELECT_A_FILE)
|
|
130
131
|
yield SafeDirectoryTree(self.start_path, id="file-tree")
|
|
131
132
|
with Horizontal():
|
|
132
|
-
yield Button(
|
|
133
|
-
yield Button(
|
|
133
|
+
yield Button(ButtonLabels.SELECT, variant="primary", id="select-btn", disabled=True)
|
|
134
|
+
yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn")
|
|
134
135
|
|
|
135
136
|
def on_mount(self) -> None:
|
|
136
137
|
self.query_one(SafeDirectoryTree).focus()
|
|
@@ -207,8 +208,8 @@ class ConfirmationDialog(BaseDialog[bool]):
|
|
|
207
208
|
yield Vertical(
|
|
208
209
|
Markdown(self.prompt, id="question"),
|
|
209
210
|
Horizontal(
|
|
210
|
-
Button(
|
|
211
|
-
Button(
|
|
211
|
+
Button(ButtonLabels.YES, variant="error", id="yes", classes="dialog-buttons"),
|
|
212
|
+
Button(ButtonLabels.NO, variant="primary", id="no", classes="dialog-buttons"),
|
|
212
213
|
id="dialog-buttons",
|
|
213
214
|
),
|
|
214
215
|
id="dialog",
|
|
@@ -237,7 +238,7 @@ class InfoModal(BaseModal[None]):
|
|
|
237
238
|
yield Label(self._title, classes="dialog-title")
|
|
238
239
|
yield Markdown(self._message, classes="dialog-message")
|
|
239
240
|
with Horizontal(classes="dialog-buttons"):
|
|
240
|
-
yield Button(
|
|
241
|
+
yield Button(ButtonLabels.OK, variant="primary", id="ok-btn")
|
|
241
242
|
|
|
242
243
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
243
244
|
self.dismiss()
|
vmanager/modals/virsh_modals.py
CHANGED
|
@@ -11,6 +11,7 @@ from textual.widgets import (
|
|
|
11
11
|
from textual.containers import Horizontal, Vertical
|
|
12
12
|
from textual.screen import ModalScreen
|
|
13
13
|
from textual import on
|
|
14
|
+
from ..constants import StaticText
|
|
14
15
|
|
|
15
16
|
class VirshShellScreen(ModalScreen):
|
|
16
17
|
"""Screen for an interactive virsh shell."""
|
|
@@ -26,8 +27,8 @@ class VirshShellScreen(ModalScreen):
|
|
|
26
27
|
def compose(self) -> ComposeResult:
|
|
27
28
|
with Vertical(id="virsh-shell-container"):
|
|
28
29
|
yield Header()
|
|
29
|
-
yield Label(
|
|
30
|
-
yield Label(
|
|
30
|
+
yield Label(StaticText.VIRSH_SHELL_TITLE, id="virsh-shell-title")
|
|
31
|
+
yield Label(StaticText.VIRSH_SHELL_NOTE, classes="virsh-shell-note")
|
|
31
32
|
yield TextArea(
|
|
32
33
|
id="virsh-output",
|
|
33
34
|
read_only=True,
|
|
@@ -6,6 +6,7 @@ from textual.containers import Horizontal, Vertical
|
|
|
6
6
|
from textual.widgets import (
|
|
7
7
|
Button, Input, Label, Checkbox,
|
|
8
8
|
)
|
|
9
|
+
from ..constants import ErrorMessages, ButtonLabels, StaticText
|
|
9
10
|
from .base_modals import BaseModal
|
|
10
11
|
|
|
11
12
|
class AddEditVirtIOFSModal(BaseModal[dict | None]):
|
|
@@ -20,13 +21,13 @@ class AddEditVirtIOFSModal(BaseModal[dict | None]):
|
|
|
20
21
|
|
|
21
22
|
def compose(self) -> ComposeResult:
|
|
22
23
|
with Vertical(id="add-edit-virtiofs-dialog"):
|
|
23
|
-
yield Label(
|
|
24
|
+
yield Label(StaticText.EDIT_VIRTIOFS_MOUNT if self.is_edit else StaticText.ADD_VIRTIOFS_MOUNT)
|
|
24
25
|
yield Input(placeholder="Source Path (e.g., /mnt/share)", id="virtiofs-source-input", value=self.source_path)
|
|
25
26
|
yield Input(placeholder="Target Path (e.g., /share)", id="virtiofs-target-input", value=self.target_path)
|
|
26
|
-
yield Checkbox(
|
|
27
|
+
yield Checkbox(StaticText.EXPORT_READONLY_MOUNT, id="virtiofs-readonly-checkbox", value=self.readonly)
|
|
27
28
|
with Horizontal():
|
|
28
|
-
yield Button(
|
|
29
|
-
yield Button(
|
|
29
|
+
yield Button(ButtonLabels.SAVE if self.is_edit else ButtonLabels.ADD, variant="primary", id="save-add-btn", classes="Buttonpage")
|
|
30
|
+
yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn", classes="Buttonpage")
|
|
30
31
|
|
|
31
32
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
32
33
|
if event.button.id == "save-add-btn":
|
|
@@ -35,7 +36,7 @@ class AddEditVirtIOFSModal(BaseModal[dict | None]):
|
|
|
35
36
|
readonly = self.query_one("#virtiofs-readonly-checkbox", Checkbox).value
|
|
36
37
|
|
|
37
38
|
if not source_path or not target_path:
|
|
38
|
-
self.app.show_error_message(
|
|
39
|
+
self.app.show_error_message(ErrorMessages.VIRTIOFS_PATH_EMPTY)
|
|
39
40
|
return
|
|
40
41
|
|
|
41
42
|
self.dismiss({'source_path': source_path, 'target_path': target_path, 'readonly': readonly})
|
|
@@ -6,6 +6,7 @@ from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
|
6
6
|
from textual.widgets import Button, Markdown
|
|
7
7
|
from textual import on
|
|
8
8
|
from .base_modals import BaseModal
|
|
9
|
+
from ..constants import ButtonLabels
|
|
9
10
|
|
|
10
11
|
VM_TYPE_INFO_TEXT = """
|
|
11
12
|
| [Storage Settings](https://www.qemu.org/docs/master/system/qemu-block-drivers.html) | Secure VM | Computation | Desktop (Linux) | Windows | Win Legacy | Server |
|
|
@@ -55,7 +56,7 @@ class VMTypeInfoModal(BaseModal[None]):
|
|
|
55
56
|
def compose(self) -> ComposeResult:
|
|
56
57
|
with ScrollableContainer(id="howto-vmtype-dialog", classes="howto-dialog"):
|
|
57
58
|
yield Markdown(VM_TYPE_INFO_TEXT, id="howto-vmtype-markdown")
|
|
58
|
-
yield Button(
|
|
59
|
+
yield Button(ButtonLabels.CLOSE, id="close-btn", variant="primary")
|
|
59
60
|
|
|
60
61
|
@on(Button.Pressed)
|
|
61
62
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
@@ -8,7 +8,7 @@ from textual.widgets import (
|
|
|
8
8
|
Button, Input, Label,
|
|
9
9
|
RadioButton, RadioSet, Checkbox
|
|
10
10
|
)
|
|
11
|
-
from ..constants import VmStatus
|
|
11
|
+
from ..constants import VmStatus, ErrorMessages, SuccessMessages, ButtonLabels, StaticText
|
|
12
12
|
from .base_modals import BaseModal
|
|
13
13
|
from .input_modals import _sanitize_input
|
|
14
14
|
|
|
@@ -33,20 +33,20 @@ class FilterModal(BaseModal[None]):
|
|
|
33
33
|
|
|
34
34
|
def compose(self) -> ComposeResult:
|
|
35
35
|
with ScrollableContainer(id="filter-dialog"):
|
|
36
|
-
yield Label(
|
|
36
|
+
yield Label(StaticText.FILTER_BY_NAME)
|
|
37
37
|
with Vertical(classes="info-details"):
|
|
38
38
|
yield Input(placeholder="Enter VM name...", id="search-input", value=self.current_search)
|
|
39
39
|
with RadioSet(id="status-radioset"):
|
|
40
|
-
yield RadioButton(
|
|
41
|
-
yield RadioButton(
|
|
42
|
-
yield RadioButton(
|
|
43
|
-
yield RadioButton(
|
|
44
|
-
yield RadioButton(
|
|
45
|
-
yield RadioButton(
|
|
46
|
-
yield RadioButton(
|
|
40
|
+
yield RadioButton(StaticText.ALL, id=f"status_{VmStatus.DEFAULT}", value=self.current_status == VmStatus.DEFAULT)
|
|
41
|
+
yield RadioButton(StaticText.RUNNING, id=f"status_{VmStatus.RUNNING}", value=self.current_status == VmStatus.RUNNING)
|
|
42
|
+
yield RadioButton(StaticText.PAUSED, id=f"status_{VmStatus.PAUSED}", value=self.current_status == VmStatus.PAUSED)
|
|
43
|
+
yield RadioButton(StaticText.PMSUSPENDED, id=f"status_{VmStatus.PMSUSPENDED}", value=self.current_status == VmStatus.PMSUSPENDED)
|
|
44
|
+
yield RadioButton(StaticText.BLOCKED, id=f"status_{VmStatus.BLOCKED}", value=self.current_status == VmStatus.BLOCKED)
|
|
45
|
+
yield RadioButton(StaticText.STOPPED, id=f"status_{VmStatus.STOPPED}", value=self.current_status == VmStatus.STOPPED)
|
|
46
|
+
yield RadioButton(StaticText.MANUALLY_SELECTED, id=f"status_{VmStatus.SELECTED}", value=self.current_status == VmStatus.SELECTED)
|
|
47
47
|
|
|
48
48
|
if self.available_servers:
|
|
49
|
-
yield Label(
|
|
49
|
+
yield Label(StaticText.SELECT_SERVERS_TO_DISPLAY)
|
|
50
50
|
|
|
51
51
|
checkboxes = []
|
|
52
52
|
for i, server in enumerate(self.available_servers):
|
|
@@ -62,8 +62,8 @@ class FilterModal(BaseModal[None]):
|
|
|
62
62
|
yield grid
|
|
63
63
|
|
|
64
64
|
with Horizontal():
|
|
65
|
-
yield Button(
|
|
66
|
-
yield Button(
|
|
65
|
+
yield Button(ButtonLabels.APPLY, id="apply-btn", variant="success")
|
|
66
|
+
yield Button(ButtonLabels.CANCEL, id="cancel-btn")
|
|
67
67
|
|
|
68
68
|
def _get_selected_servers(self) -> list[str]:
|
|
69
69
|
selected = []
|
|
@@ -81,11 +81,11 @@ class FilterModal(BaseModal[None]):
|
|
|
81
81
|
try:
|
|
82
82
|
search_text, was_modified = _sanitize_input(search_text_raw)
|
|
83
83
|
except ValueError as e:
|
|
84
|
-
self.app.show_error_message(
|
|
84
|
+
self.app.show_error_message(ErrorMessages.SANITIZATION_ERROR_TEMPLATE.format(error=e))
|
|
85
85
|
return
|
|
86
86
|
|
|
87
87
|
if was_modified and search_text_raw != search_text: # Only show if actual chars were removed, not just empty
|
|
88
|
-
self.app.show_success_message(
|
|
88
|
+
self.app.show_success_message(SuccessMessages.INPUT_SANITIZED.format(original_input=search_text_raw, sanitized_input=search_text))
|
|
89
89
|
|
|
90
90
|
radioset = self.query_one(RadioSet)
|
|
91
91
|
status_button = radioset.pressed_button
|
|
@@ -105,11 +105,11 @@ class FilterModal(BaseModal[None]):
|
|
|
105
105
|
try:
|
|
106
106
|
search_text, was_modified = _sanitize_input(search_text_raw)
|
|
107
107
|
except ValueError as e:
|
|
108
|
-
self.app.show_error_message(
|
|
108
|
+
self.app.show_error_message(ErrorMessages.SANITIZATION_ERROR_TEMPLATE.format(error=e))
|
|
109
109
|
return
|
|
110
110
|
|
|
111
111
|
if was_modified and search_text_raw != search_text: # Only show if actual chars were removed, not just empty
|
|
112
|
-
self.app.show_success_message(
|
|
112
|
+
self.app.show_success_message(SuccessMessages.INPUT_SANITIZED.format(original_input=search_text_raw, sanitized_input=search_text))
|
|
113
113
|
|
|
114
114
|
radioset = self.query_one(RadioSet)
|
|
115
115
|
status_button = radioset.pressed_button
|
|
@@ -127,15 +127,15 @@ class CreateVMModal(BaseModal[dict | None]):
|
|
|
127
127
|
|
|
128
128
|
def compose(self) -> ComposeResult:
|
|
129
129
|
with Vertical(id="create-vm-dialog"):
|
|
130
|
-
yield Label(
|
|
130
|
+
yield Label(StaticText.CREATE_NEW_VM)
|
|
131
131
|
yield Input(placeholder="VM Name", id="vm-name-input", value="new_vm")
|
|
132
132
|
yield Input(placeholder="Memory (MB, e.g., 2048)", id="vm-memory-input", value="2048")
|
|
133
133
|
yield Input(placeholder="VCPU (e.g., 2)", id="vm-vcpu-input", value="2")
|
|
134
134
|
yield Input(placeholder="Disk Image Path (e.g., /var/lib/libvirt/images/myvm.qcow2)", id="vm-disk-input", value="/var/lib/libvirt/images/new_vm.qcow2")
|
|
135
135
|
# For simplicity, we won't add network details yet.
|
|
136
136
|
with Horizontal():
|
|
137
|
-
yield Button(
|
|
138
|
-
yield Button(
|
|
137
|
+
yield Button(ButtonLabels.CREATE, variant="primary", id="create-btn", classes="Buttonpage")
|
|
138
|
+
yield Button(ButtonLabels.CANCEL, variant="default", id="cancel-btn", classes="Buttonpage")
|
|
139
139
|
|
|
140
140
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
141
141
|
if event.button.id == "create-btn":
|
vmanager/modals/vmcard_dialog.py
CHANGED
|
@@ -11,10 +11,10 @@ from textual.widgets import (
|
|
|
11
11
|
)
|
|
12
12
|
from .input_modals import _sanitize_input
|
|
13
13
|
from .utils_modals import (
|
|
14
|
-
BaseDialog
|
|
14
|
+
BaseDialog
|
|
15
15
|
)
|
|
16
16
|
from ..config import load_config, save_config
|
|
17
|
-
from ..constants import ButtonLabels,
|
|
17
|
+
from ..constants import ButtonLabels, ErrorMessages, SuccessMessages, WarningMessages, StaticText
|
|
18
18
|
from ..vm_queries import is_qemu_agent_running
|
|
19
19
|
|
|
20
20
|
class DeleteVMConfirmationDialog(BaseDialog[tuple[bool, bool]]):
|
|
@@ -27,18 +27,18 @@ class DeleteVMConfirmationDialog(BaseDialog[tuple[bool, bool]]):
|
|
|
27
27
|
def compose(self):
|
|
28
28
|
yield Vertical(
|
|
29
29
|
Markdown(f"Are you sure you want to delete VM '{self.vm_name}'?", id="question"),
|
|
30
|
-
Checkbox(
|
|
30
|
+
Checkbox(StaticText.DELETE_STORAGE_VOLUMES, id="delete-storage-checkbox", value=True),
|
|
31
31
|
Label(""),
|
|
32
32
|
Horizontal(
|
|
33
|
-
Button(ButtonLabels.YES, variant="error", id=
|
|
34
|
-
Button(ButtonLabels.NO, variant="primary", id=
|
|
33
|
+
Button(ButtonLabels.YES, variant="error", id="yes", classes="dialog-buttons"),
|
|
34
|
+
Button(ButtonLabels.NO, variant="primary", id="no", classes="dialog-buttons"),
|
|
35
35
|
id="dialog-buttons",
|
|
36
36
|
),
|
|
37
37
|
id="delete-vm-dialog",
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
41
|
-
if event.button.id ==
|
|
41
|
+
if event.button.id == "yes":
|
|
42
42
|
delete_storage = self.query_one("#delete-storage-checkbox", Checkbox).value
|
|
43
43
|
self.dismiss((True, delete_storage))
|
|
44
44
|
else:
|
|
@@ -61,15 +61,15 @@ class ChangeNetworkDialog(BaseDialog[dict | None]):
|
|
|
61
61
|
network_options = [(str(net), str(net)) for net in self.networks]
|
|
62
62
|
|
|
63
63
|
with Vertical(id="dialog"):
|
|
64
|
-
yield Label(
|
|
64
|
+
yield Label(StaticText.SELECT_INTERFACE_AND_NETWORK)
|
|
65
65
|
yield Select(interface_options, id="interface-select")
|
|
66
66
|
yield Select(network_options, id="network-select")
|
|
67
67
|
with Horizontal(id="dialog-buttons"):
|
|
68
|
-
yield Button(ButtonLabels.CHANGE, variant="success", id=
|
|
69
|
-
yield Button(ButtonLabels.CANCEL, variant="error", id=
|
|
68
|
+
yield Button(ButtonLabels.CHANGE, variant="success", id="change")
|
|
69
|
+
yield Button(ButtonLabels.CANCEL, variant="error", id="cancel")
|
|
70
70
|
|
|
71
71
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
72
|
-
if event.button.id ==
|
|
72
|
+
if event.button.id == "change":
|
|
73
73
|
interface_select = self.query_one("#interface-select", Select)
|
|
74
74
|
network_select = self.query_one("#network-select", Select)
|
|
75
75
|
|
|
@@ -77,7 +77,7 @@ class ChangeNetworkDialog(BaseDialog[dict | None]):
|
|
|
77
77
|
new_network = network_select.value
|
|
78
78
|
|
|
79
79
|
if mac_address is Select.BLANK or new_network is Select.BLANK:
|
|
80
|
-
self.app.show_error_message(
|
|
80
|
+
self.app.show_error_message(ErrorMessages.PLEASE_SELECT_INTERFACE_AND_NETWORK)
|
|
81
81
|
return
|
|
82
82
|
|
|
83
83
|
self.dismiss({"mac_address": mac_address, "new_network": new_network})
|
|
@@ -89,21 +89,21 @@ class AdvancedCloneDialog(BaseDialog[dict | None]):
|
|
|
89
89
|
|
|
90
90
|
def compose(self):
|
|
91
91
|
yield Grid(
|
|
92
|
-
Label(
|
|
92
|
+
Label(StaticText.ENTER_BASE_NAME),
|
|
93
93
|
Input(placeholder="new_vm_base_name", id="base_name_input", restrict=r"[a-zA-Z0-9_-]*"),
|
|
94
|
-
Label(
|
|
94
|
+
Label(StaticText.SUFFIX_FOR_CLONE_NAMES),
|
|
95
95
|
Input(placeholder="e.g., -clone", id="clone_suffix_input", restrict=r"[a-zA-Z0-9_-]*"),
|
|
96
|
-
Label(
|
|
96
|
+
Label(StaticText.NUMBER_OF_CLONES_TO_CREATE),
|
|
97
97
|
Input(value="1", id="clone_count_input", type="integer"),
|
|
98
|
-
Label(
|
|
98
|
+
Label(StaticText.DO_NOT_CLONE_STORAGE),
|
|
99
99
|
Checkbox("", id="skip_storage_checkbox", value=False),
|
|
100
|
-
Button(ButtonLabels.CLONE, variant="success", id=
|
|
101
|
-
Button(ButtonLabels.CANCEL, variant="error", id=
|
|
100
|
+
Button(ButtonLabels.CLONE, variant="success", id="clone"),
|
|
101
|
+
Button(ButtonLabels.CANCEL, variant="error", id="cancel"),
|
|
102
102
|
id="clone-dialog"
|
|
103
|
-
|
|
103
|
+
)
|
|
104
104
|
|
|
105
105
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
106
|
-
if event.button.id ==
|
|
106
|
+
if event.button.id == "clone":
|
|
107
107
|
base_name_input = self.query_one("#base_name_input", Input)
|
|
108
108
|
clone_count_input = self.query_one("#clone_count_input", Input)
|
|
109
109
|
clone_suffix_input = self.query_one("#clone_suffix_input", Input)
|
|
@@ -116,13 +116,13 @@ class AdvancedCloneDialog(BaseDialog[dict | None]):
|
|
|
116
116
|
try:
|
|
117
117
|
base_name, base_name_modified = _sanitize_input(base_name_raw)
|
|
118
118
|
if base_name_modified:
|
|
119
|
-
self.app.show_success_message(
|
|
119
|
+
self.app.show_success_message(SuccessMessages.BASE_NAME_SANITIZED_TEMPLATE.format(original=base_name_raw, sanitized=base_name))
|
|
120
120
|
except ValueError as e:
|
|
121
|
-
self.app.show_error_message(
|
|
121
|
+
self.app.show_error_message(ErrorMessages.SANITIZATION_ERROR_TEMPLATE.format(error=e))
|
|
122
122
|
return
|
|
123
123
|
|
|
124
124
|
if not base_name:
|
|
125
|
-
self.app.show_error_message(
|
|
125
|
+
self.app.show_error_message(ErrorMessages.BASE_NAME_EMPTY)
|
|
126
126
|
return
|
|
127
127
|
|
|
128
128
|
# Sanitize suffix only if it's provided, otherwise keep it empty string
|
|
@@ -131,9 +131,9 @@ class AdvancedCloneDialog(BaseDialog[dict | None]):
|
|
|
131
131
|
try:
|
|
132
132
|
clone_suffix, suffix_modified = _sanitize_input(clone_suffix_raw)
|
|
133
133
|
if suffix_modified:
|
|
134
|
-
self.app.show_success_message(
|
|
134
|
+
self.app.show_success_message(SuccessMessages.INPUT_SANITIZED_TEMPLATE.format(original=clone_suffix_raw, sanitized=clone_suffix))
|
|
135
135
|
except ValueError as e:
|
|
136
|
-
self.app.show_error_message(
|
|
136
|
+
self.app.show_error_message(ErrorMessages.INVALID_CHARS_IN_SUFFIX.format(error=e))
|
|
137
137
|
return
|
|
138
138
|
|
|
139
139
|
try:
|
|
@@ -141,11 +141,11 @@ class AdvancedCloneDialog(BaseDialog[dict | None]):
|
|
|
141
141
|
if clone_count < 1:
|
|
142
142
|
raise ValueError()
|
|
143
143
|
except ValueError:
|
|
144
|
-
self.app.show_error_message(
|
|
144
|
+
self.app.show_error_message(ErrorMessages.CLONE_COUNT_POSITIVE_INTEGER)
|
|
145
145
|
return
|
|
146
146
|
|
|
147
147
|
if clone_count > 1 and not clone_suffix:
|
|
148
|
-
self.app.show_error_message(
|
|
148
|
+
self.app.show_error_message(ErrorMessages.SUFFIX_MANDATORY_FOR_MULTIPLE_CLONES)
|
|
149
149
|
return
|
|
150
150
|
|
|
151
151
|
clone_storage = not skip_storage_checkbox.value
|
|
@@ -169,12 +169,12 @@ class RenameVMDialog(BaseDialog[str | None]):
|
|
|
169
169
|
|
|
170
170
|
def compose(self):
|
|
171
171
|
yield Vertical(
|
|
172
|
-
Label(
|
|
173
|
-
Label(
|
|
172
|
+
Label(StaticText.CURRENT_NAME.format(current_name=self.current_name)),
|
|
173
|
+
Label(StaticText.ENTER_NEW_VM_NAME, id="question"),
|
|
174
174
|
Input(placeholder="new_vm_name", restrict=r"[a-zA-Z0-9_-]*"),
|
|
175
175
|
Horizontal(
|
|
176
|
-
Button(ButtonLabels.RENAME, variant="success", id=
|
|
177
|
-
Button(ButtonLabels.CANCEL, variant="error", id=
|
|
176
|
+
Button(ButtonLabels.RENAME, variant="success", id="rename-button"),
|
|
177
|
+
Button(ButtonLabels.CANCEL, variant="error", id="cancel"),
|
|
178
178
|
id="dialog-buttons",
|
|
179
179
|
),
|
|
180
180
|
id="dialog",
|
|
@@ -182,21 +182,21 @@ class RenameVMDialog(BaseDialog[str | None]):
|
|
|
182
182
|
)
|
|
183
183
|
|
|
184
184
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
185
|
-
if event.button.id ==
|
|
185
|
+
if event.button.id == "rename-button":
|
|
186
186
|
input_widget = self.query_one(Input)
|
|
187
187
|
new_name_raw = input_widget.value
|
|
188
188
|
|
|
189
189
|
try:
|
|
190
190
|
new_name, was_modified = _sanitize_input(new_name_raw)
|
|
191
191
|
except ValueError as e:
|
|
192
|
-
self.app.show_error_message(
|
|
192
|
+
self.app.show_error_message(ErrorMessages.SANITIZATION_ERROR_TEMPLATE.format(error=e))
|
|
193
193
|
return
|
|
194
194
|
|
|
195
195
|
if was_modified:
|
|
196
|
-
self.app.show_success_message(
|
|
196
|
+
self.app.show_success_message(SuccessMessages.INPUT_SANITIZED_TEMPLATE.format(original=new_name_raw, sanitized=new_name))
|
|
197
197
|
|
|
198
198
|
if not new_name:
|
|
199
|
-
self.app.show_error_message(
|
|
199
|
+
self.app.show_error_message(ErrorMessages.VM_NAME_CANNOT_BE_EMPTY_RENAME)
|
|
200
200
|
return
|
|
201
201
|
|
|
202
202
|
error = self.validate_name(new_name)
|
|
@@ -220,7 +220,7 @@ class SelectSnapshotDialog(BaseDialog[str | None]):
|
|
|
220
220
|
yield Vertical(
|
|
221
221
|
Label(self.prompt, id="prompt-label"),
|
|
222
222
|
DataTable(id="snapshot-table"),
|
|
223
|
-
Button(ButtonLabels.CANCEL, variant="error", id=
|
|
223
|
+
Button(ButtonLabels.CANCEL, variant="error", id="cancel"),
|
|
224
224
|
id="dialog",
|
|
225
225
|
classes="snapshot-select-dialog"
|
|
226
226
|
)
|
|
@@ -243,7 +243,7 @@ class SelectSnapshotDialog(BaseDialog[str | None]):
|
|
|
243
243
|
self.dismiss(str(event.row_key.value))
|
|
244
244
|
|
|
245
245
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
246
|
-
if event.button.id ==
|
|
246
|
+
if event.button.id == "cancel":
|
|
247
247
|
self.dismiss(None)
|
|
248
248
|
|
|
249
249
|
class SnapshotNameDialog(BaseDialog[dict | None]):
|
|
@@ -259,22 +259,22 @@ class SnapshotNameDialog(BaseDialog[dict | None]):
|
|
|
259
259
|
agent_running = is_qemu_agent_running(self.domain) if self.domain else False
|
|
260
260
|
|
|
261
261
|
if not agent_running and self.domain:
|
|
262
|
-
|
|
262
|
+
self.app.show_warning_message(ErrorMessages.QEMU_GUEST_AGENT_RECOMMENDATION)
|
|
263
263
|
|
|
264
264
|
yield Vertical(
|
|
265
|
-
Label(
|
|
266
|
-
Label(
|
|
265
|
+
Label(StaticText.CURRENT_TIME.format(now=now), id="timestamp-label"),
|
|
266
|
+
Label(StaticText.ENTER_SNAPSHOT_NAME, id="question"),
|
|
267
267
|
Input(value=default_name, placeholder="snapshot_name", id="name-input", restrict=r"[a-zA-Z0-9_-]*"),
|
|
268
|
-
Label(
|
|
268
|
+
Label(StaticText.DESCRIPTION_OPTIONAL),
|
|
269
269
|
Input(placeholder="snapshot description", id="description-input"),
|
|
270
|
-
Checkbox(
|
|
270
|
+
Checkbox(StaticText.QUIESCE_GUEST,
|
|
271
271
|
value=agent_running,
|
|
272
272
|
disabled=not agent_running,
|
|
273
273
|
id="quiesce-checkbox",
|
|
274
274
|
tooltip="Pause the guest filesystem to ensure a clean snapshot. Requires QEMU Guest Agent to be running in the VM."),
|
|
275
275
|
Horizontal(
|
|
276
|
-
Button(ButtonLabels.CREATE, variant="success", id=
|
|
277
|
-
Button(ButtonLabels.CANCEL, variant="error", id=
|
|
276
|
+
Button(ButtonLabels.CREATE, variant="success", id="create"),
|
|
277
|
+
Button(ButtonLabels.CANCEL, variant="error", id="cancel"),
|
|
278
278
|
id="dialog-buttons",
|
|
279
279
|
),
|
|
280
280
|
id="dialog",
|
|
@@ -282,7 +282,7 @@ class SnapshotNameDialog(BaseDialog[dict | None]):
|
|
|
282
282
|
)
|
|
283
283
|
|
|
284
284
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
285
|
-
if event.button.id ==
|
|
285
|
+
if event.button.id == "create":
|
|
286
286
|
name_input = self.query_one("#name-input", Input)
|
|
287
287
|
description_input = self.query_one("#description-input", Input)
|
|
288
288
|
quiesce_checkbox = self.query_one("#quiesce-checkbox", Checkbox)
|
|
@@ -294,14 +294,14 @@ class SnapshotNameDialog(BaseDialog[dict | None]):
|
|
|
294
294
|
try:
|
|
295
295
|
snapshot_name, was_modified = _sanitize_input(snapshot_name_raw)
|
|
296
296
|
except ValueError as e:
|
|
297
|
-
self.app.show_error_message(
|
|
297
|
+
self.app.show_error_message(ErrorMessages.SANITIZATION_ERROR_TEMPLATE.format(error=e))
|
|
298
298
|
return
|
|
299
299
|
|
|
300
300
|
if was_modified:
|
|
301
|
-
self.app.show_success_message(
|
|
301
|
+
self.app.show_success_message(SuccessMessages.INPUT_SANITIZED_TEMPLATE.format(original=snapshot_name_raw, sanitized=snapshot_name))
|
|
302
302
|
|
|
303
303
|
if not snapshot_name:
|
|
304
|
-
self.app.show_error_message(
|
|
304
|
+
self.app.show_error_message(ErrorMessages.SNAPSHOT_NAME_CANNOT_BE_EMPTY)
|
|
305
305
|
return
|
|
306
306
|
|
|
307
307
|
error = self.validate_name(snapshot_name)
|
|
@@ -327,15 +327,15 @@ class WebConsoleDialog(BaseDialog[str | None]):
|
|
|
327
327
|
Markdown("Wesockify will handle a **single WebSocket** connection and exit. So it will be possible to **connect only ONE** time. If you disconnect you need to restart a new Web Console."),
|
|
328
328
|
Label(""),
|
|
329
329
|
Horizontal(
|
|
330
|
-
Button(ButtonLabels.STOP, variant="error", id=
|
|
331
|
-
Button(ButtonLabels.CLOSE, variant="primary", id=
|
|
330
|
+
Button(ButtonLabels.STOP, variant="error", id="stop"),
|
|
331
|
+
Button(ButtonLabels.CLOSE, variant="primary", id="close"),
|
|
332
332
|
id="dialog-buttons",
|
|
333
333
|
),
|
|
334
334
|
id="webconsole-dialog",
|
|
335
335
|
)
|
|
336
336
|
|
|
337
337
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
338
|
-
if event.button.id ==
|
|
338
|
+
if event.button.id == "stop":
|
|
339
339
|
self.dismiss("stop")
|
|
340
340
|
else:
|
|
341
341
|
self.dismiss(None)
|
|
@@ -351,7 +351,7 @@ class WebConsoleConfigDialog(BaseDialog[bool]):
|
|
|
351
351
|
|
|
352
352
|
def compose(self) -> ComposeResult:
|
|
353
353
|
with Vertical(id="webconsole-config-dialog"):
|
|
354
|
-
yield Label(
|
|
354
|
+
yield Label(StaticText.WEB_CONSOLE_CONFIGURATION, id="webconsole-config-title")
|
|
355
355
|
|
|
356
356
|
if self.is_remote:
|
|
357
357
|
remote_console_enabled = self.config.get('REMOTE_WEBCONSOLE', False)
|
|
@@ -365,7 +365,7 @@ class WebConsoleConfigDialog(BaseDialog[bool]):
|
|
|
365
365
|
switch_widget.add_class("switch-off")
|
|
366
366
|
|
|
367
367
|
yield Grid(
|
|
368
|
-
Label(
|
|
368
|
+
Label(StaticText.REMOTE),
|
|
369
369
|
switch_widget,
|
|
370
370
|
id="grid-remote-local"
|
|
371
371
|
)
|
|
@@ -384,10 +384,10 @@ class WebConsoleConfigDialog(BaseDialog[bool]):
|
|
|
384
384
|
id="grid-vnc-config"
|
|
385
385
|
)
|
|
386
386
|
else:
|
|
387
|
-
yield Markdown(
|
|
387
|
+
yield Markdown(StaticText.WEBCONSOLE_LOCAL_RUN)
|
|
388
388
|
|
|
389
|
-
yield Button(ButtonLabels.START, variant="primary", id=
|
|
390
|
-
yield Button(ButtonLabels.CANCEL, variant="default", id=
|
|
389
|
+
yield Button(ButtonLabels.START, variant="primary", id="start")
|
|
390
|
+
yield Button(ButtonLabels.CANCEL, variant="default", id="cancel")
|
|
391
391
|
|
|
392
392
|
def on_switch_changed(self, event: Switch.Changed) -> None:
|
|
393
393
|
if event.control.id == "remote-console-switch":
|
|
@@ -406,7 +406,7 @@ class WebConsoleConfigDialog(BaseDialog[bool]):
|
|
|
406
406
|
remote_opts.display = False
|
|
407
407
|
|
|
408
408
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
409
|
-
if event.button.id ==
|
|
409
|
+
if event.button.id == "start":
|
|
410
410
|
config_changed = False
|
|
411
411
|
if self.is_remote:
|
|
412
412
|
remote_switch = self.query_one("#remote-console-switch", Switch)
|
|
@@ -436,5 +436,5 @@ class WebConsoleConfigDialog(BaseDialog[bool]):
|
|
|
436
436
|
if config_changed:
|
|
437
437
|
save_config(self.config)
|
|
438
438
|
self.dismiss(True)
|
|
439
|
-
elif event.button.id ==
|
|
439
|
+
elif event.button.id == "cancel":
|
|
440
440
|
self.dismiss(False)
|