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