virtui-manager 1.1.5__tar.gz → 1.1.6__tar.gz
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.5/src/virtui_manager.egg-info → virtui_manager-1.1.6}/PKG-INFO +1 -1
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/pyproject.toml +1 -1
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/setup.cfg +1 -1
- {virtui_manager-1.1.5 → virtui_manager-1.1.6/src/virtui_manager.egg-info}/PKG-INFO +1 -1
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/virtui_manager.egg-info/SOURCES.txt +0 -1
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/constants.py +1 -1
- virtui_manager-1.1.6/src/vmanager/modals/howto_disk_modal.py +33 -0
- virtui_manager-1.1.6/src/vmanager/modals/howto_network_modal.py +33 -0
- virtui_manager-1.1.6/src/vmanager/modals/howto_overlay_modal.py +33 -0
- virtui_manager-1.1.6/src/vmanager/modals/howto_ssh_modal.py +32 -0
- virtui_manager-1.1.6/src/vmanager/modals/howto_virtiofs_modal.py +33 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/server_prefs_modals.py +25 -16
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/storage_manager.py +25 -3
- virtui_manager-1.1.5/FEATURES.md +0 -256
- virtui_manager-1.1.5/src/vmanager/modals/howto_disk_modal.py +0 -93
- virtui_manager-1.1.5/src/vmanager/modals/howto_network_modal.py +0 -60
- virtui_manager-1.1.5/src/vmanager/modals/howto_overlay_modal.py +0 -73
- virtui_manager-1.1.5/src/vmanager/modals/howto_ssh_modal.py +0 -88
- virtui_manager-1.1.5/src/vmanager/modals/howto_virtiofs_modal.py +0 -85
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/LICENSE +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/MANIFEST.in +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/README.md +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/requirements.txt +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/virtui_manager.egg-info/dependency_links.txt +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/virtui_manager.egg-info/entry_points.txt +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/virtui_manager.egg-info/requires.txt +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/virtui_manager.egg-info/top_level.txt +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/__init__.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/config.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/connection_manager.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/dialog.css +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/events.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/firmware_manager.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/libvirt_error_handler.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/libvirt_utils.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/__init__.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/base_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/bulk_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/cache_stats_modal.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/config_modal.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/cpu_mem_pc_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/custom_migration_modal.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/disk_pool_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/input_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/log_modal.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/migration_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/network_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/provisioning_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/select_server_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/selection_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/server_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/utils_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/virsh_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/virtiofs_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/vm_type_info_modal.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/vmanager_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/vmcard_dialog.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/vmdetails_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/modals/xml_modals.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/network_manager.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/remote_viewer.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/remote_viewer_gtk4.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/utils.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/virtui_dev.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vm_actions.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vm_cache.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vm_migration.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vm_provisioner.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vm_queries.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vm_service.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vmanager.css +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vmanager.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vmanager_cmd.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vmcard.css +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vmcard.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/vmcard_pool.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/webconsole_manager.py +0 -0
- {virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/vmanager/wrapper.py +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modal to show how to manage VM disks.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
8
|
+
from textual.widgets import Button, Markdown
|
|
9
|
+
from textual import on
|
|
10
|
+
from .base_modals import BaseModal
|
|
11
|
+
|
|
12
|
+
class HowToDiskModal(BaseModal[None]):
|
|
13
|
+
"""A modal to display instructions for managing VM disks."""
|
|
14
|
+
|
|
15
|
+
def compose(self) -> ComposeResult:
|
|
16
|
+
# Load markdown from external file
|
|
17
|
+
docs_path = Path(__file__).parent.parent / "appdocs" / "howto_disk.md"
|
|
18
|
+
try:
|
|
19
|
+
with open(docs_path, "r") as f:
|
|
20
|
+
content = f.read()
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
content = "# Error: Documentation file not found."
|
|
23
|
+
|
|
24
|
+
with Vertical(id="howto-disk-dialog"):
|
|
25
|
+
with ScrollableContainer(id="howto-disk-content"):
|
|
26
|
+
yield Markdown(content, id="howto-disk-markdown")
|
|
27
|
+
with Horizontal(id="dialog-buttons"):
|
|
28
|
+
yield Button("Close", id="close-btn", variant="primary")
|
|
29
|
+
|
|
30
|
+
@on(Button.Pressed)
|
|
31
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
32
|
+
"""Handle button presses."""
|
|
33
|
+
self.dismiss()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modal to show how to configure networks.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
8
|
+
from textual.widgets import Button, Markdown
|
|
9
|
+
from textual import on
|
|
10
|
+
from .base_modals import BaseModal
|
|
11
|
+
|
|
12
|
+
class HowToNetworkModal(BaseModal[None]):
|
|
13
|
+
"""A modal to display instructions for network configuration."""
|
|
14
|
+
|
|
15
|
+
def compose(self) -> ComposeResult:
|
|
16
|
+
# Load markdown from external file
|
|
17
|
+
docs_path = Path(__file__).parent.parent / "appdocs" / "howto_network.md"
|
|
18
|
+
try:
|
|
19
|
+
with open(docs_path, "r") as f:
|
|
20
|
+
content = f.read()
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
content = "# Error: Documentation file not found."
|
|
23
|
+
|
|
24
|
+
with Vertical(id="howto-network-dialog"):
|
|
25
|
+
with ScrollableContainer(id="howto-network-content"):
|
|
26
|
+
yield Markdown(content, id="howto-network-markdown")
|
|
27
|
+
with Horizontal(id="dialog-buttons"):
|
|
28
|
+
yield Button("Close", id="close-btn", variant="primary")
|
|
29
|
+
|
|
30
|
+
@on(Button.Pressed)
|
|
31
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
32
|
+
"""Handle button presses."""
|
|
33
|
+
self.dismiss()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modal to show how overlay disks work.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
8
|
+
from textual.widgets import Button, Markdown
|
|
9
|
+
from textual import on
|
|
10
|
+
from .base_modals import BaseModal
|
|
11
|
+
|
|
12
|
+
class HowToOverlayModal(BaseModal[None]):
|
|
13
|
+
"""A modal to display instructions for disk overlays."""
|
|
14
|
+
|
|
15
|
+
def compose(self) -> ComposeResult:
|
|
16
|
+
# Load markdown from external file
|
|
17
|
+
docs_path = Path(__file__).parent.parent / "appdocs" / "howto_overlay.md"
|
|
18
|
+
try:
|
|
19
|
+
with open(docs_path, "r") as f:
|
|
20
|
+
content = f.read()
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
content = "# Error: Documentation file not found."
|
|
23
|
+
|
|
24
|
+
with Vertical(id="howto-overlay-dialog", classes="howto-dialog"):
|
|
25
|
+
with ScrollableContainer(id="howto-overlay-content"):
|
|
26
|
+
yield Markdown(content, id="howto-overlay-markdown")
|
|
27
|
+
with Horizontal(id="dialog-buttons"):
|
|
28
|
+
yield Button("Close", id="close-btn", variant="primary")
|
|
29
|
+
|
|
30
|
+
@on(Button.Pressed)
|
|
31
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
32
|
+
"""Handle button presses."""
|
|
33
|
+
self.dismiss()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modal to show how to use ssh-agent.
|
|
3
|
+
"""
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from textual.app import ComposeResult
|
|
6
|
+
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
7
|
+
from textual.widgets import Button, Markdown
|
|
8
|
+
from textual import on
|
|
9
|
+
from .base_modals import BaseModal
|
|
10
|
+
|
|
11
|
+
class HowToSSHModal(BaseModal[None]):
|
|
12
|
+
"""A modal to display instructions for using an ssh-agent."""
|
|
13
|
+
|
|
14
|
+
def compose(self) -> ComposeResult:
|
|
15
|
+
# Load markdown from external file
|
|
16
|
+
docs_path = Path(__file__).parent.parent / "appdocs" / "howto_ssh.md"
|
|
17
|
+
try:
|
|
18
|
+
with open(docs_path, "r") as f:
|
|
19
|
+
content = f.read()
|
|
20
|
+
except FileNotFoundError:
|
|
21
|
+
content = "# Error: Documentation file not found."
|
|
22
|
+
|
|
23
|
+
with Vertical(id="howto-ssh-dialog"):
|
|
24
|
+
with ScrollableContainer(id="howto-ssh-content"):
|
|
25
|
+
yield Markdown(content, id="howto-ssh-markdown")
|
|
26
|
+
with Horizontal(id="dialog-buttons"):
|
|
27
|
+
yield Button("Close", id="close-btn", variant="primary")
|
|
28
|
+
|
|
29
|
+
@on(Button.Pressed)
|
|
30
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
31
|
+
"""Handle button presses."""
|
|
32
|
+
self.dismiss()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modal to show how to use VirtIO-FS.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
8
|
+
from textual.widgets import Button, Markdown
|
|
9
|
+
from textual import on
|
|
10
|
+
from .base_modals import BaseModal
|
|
11
|
+
|
|
12
|
+
class HowToVirtIOFSModal(BaseModal[None]):
|
|
13
|
+
"""A modal to display instructions for using VirtIO-FS."""
|
|
14
|
+
|
|
15
|
+
def compose(self) -> ComposeResult:
|
|
16
|
+
# Load markdown from external file
|
|
17
|
+
docs_path = Path(__file__).parent.parent / "appdocs" / "howto_virtiofs.md"
|
|
18
|
+
try:
|
|
19
|
+
with open(docs_path, "r") as f:
|
|
20
|
+
content = f.read()
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
content = "# Error: Documentation file not found."
|
|
23
|
+
|
|
24
|
+
with Vertical(id="howto-virtiofs-dialog"):
|
|
25
|
+
with ScrollableContainer(id="howto-virtiofs-content"):
|
|
26
|
+
yield Markdown(content, id="howto-virtiofs-markdown")
|
|
27
|
+
with Horizontal(id="dialog-buttons"):
|
|
28
|
+
yield Button("Close", id="close-btn", variant="primary")
|
|
29
|
+
|
|
30
|
+
@on(Button.Pressed)
|
|
31
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
32
|
+
"""Handle button presses."""
|
|
33
|
+
self.dismiss()
|
|
@@ -248,20 +248,28 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
248
248
|
pools = storage_manager.list_storage_pools(self.conn)
|
|
249
249
|
node_to_select = None
|
|
250
250
|
for pool_data in pools:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
251
|
+
try:
|
|
252
|
+
pool_name = pool_data['name']
|
|
253
|
+
status = pool_data['status']
|
|
254
|
+
autostart = "autostart" if pool_data['autostart'] else "no autostart"
|
|
255
|
+
label = f"{pool_name} [{status}, {autostart}]"
|
|
256
|
+
pool_node = tree.root.add(label, data=pool_data)
|
|
257
|
+
pool_node.data["type"] = "pool"
|
|
258
|
+
# Add a dummy node to make the pool node expandable
|
|
259
|
+
pool_node.add_leaf("Loading volumes...")
|
|
260
|
+
|
|
261
|
+
if expand_pools and pool_name in expand_pools:
|
|
262
|
+
self.app.call_later(pool_node.expand)
|
|
263
|
+
|
|
264
|
+
if select_pool and pool_name == select_pool:
|
|
265
|
+
node_to_select = pool_node
|
|
266
|
+
except libvirt.libvirtError:
|
|
267
|
+
# Handle cases where pool might be listed but inaccessible (e.g. NFS down)
|
|
268
|
+
pool_name = pool_data.get('name', 'Unknown')
|
|
269
|
+
label = f"{pool_name} [unavailable]"
|
|
270
|
+
pool_node = tree.root.add(label, data=pool_data)
|
|
271
|
+
pool_node.data["type"] = "pool"
|
|
272
|
+
pool_node.add_leaf("Pool unavailable")
|
|
265
273
|
|
|
266
274
|
if node_to_select:
|
|
267
275
|
self.app.call_later(tree.select_node, node_to_select)
|
|
@@ -308,7 +316,8 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
308
316
|
if len(node.children) == 1 and node.children[0].data is None:
|
|
309
317
|
node.remove_children()
|
|
310
318
|
pool = node_data.get('pool')
|
|
311
|
-
|
|
319
|
+
# Check cached status instead of calling pool.isActive() to avoid blocking
|
|
320
|
+
if pool and node_data.get('status') == 'active':
|
|
312
321
|
volumes = storage_manager.list_storage_volumes(pool)
|
|
313
322
|
for vol_data in volumes:
|
|
314
323
|
vol_name = vol_data['name']
|
|
@@ -404,7 +413,7 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
404
413
|
self.app.show_error_message("Could not find pool object to edit.")
|
|
405
414
|
return
|
|
406
415
|
|
|
407
|
-
if
|
|
416
|
+
if node_data.get('status') == 'active':
|
|
408
417
|
self.app.show_error_message("Pool must be inactive to edit its XML definition.")
|
|
409
418
|
return
|
|
410
419
|
|
|
@@ -29,18 +29,40 @@ def list_storage_pools(conn: libvirt.virConnect) -> List[Dict[str, Any]]:
|
|
|
29
29
|
pools = conn.listAllStoragePools(0)
|
|
30
30
|
for pool in pools:
|
|
31
31
|
try:
|
|
32
|
+
# Try to get basic info
|
|
33
|
+
try:
|
|
34
|
+
name = pool.name()
|
|
35
|
+
except libvirt.libvirtError:
|
|
36
|
+
name = "Unknown Pool"
|
|
37
|
+
|
|
32
38
|
is_active = pool.isActive()
|
|
33
39
|
info = pool.info()
|
|
34
40
|
pools_info.append({
|
|
35
|
-
'name':
|
|
41
|
+
'name': name,
|
|
36
42
|
'pool': pool,
|
|
37
43
|
'status': 'active' if is_active else 'inactive',
|
|
38
44
|
'autostart': pool.autostart() == 1,
|
|
39
45
|
'capacity': info[1],
|
|
40
46
|
'allocation': info[2],
|
|
41
47
|
})
|
|
42
|
-
except libvirt.libvirtError:
|
|
43
|
-
|
|
48
|
+
except libvirt.libvirtError as e:
|
|
49
|
+
# If we fail to get details (e.g. NFS down), still list the pool but as unavailable
|
|
50
|
+
if 'name' not in locals():
|
|
51
|
+
try:
|
|
52
|
+
name = pool.name()
|
|
53
|
+
except:
|
|
54
|
+
name = "Unknown Pool"
|
|
55
|
+
|
|
56
|
+
logging.warning(f"Failed to get details for pool '{name}': {e}")
|
|
57
|
+
pools_info.append({
|
|
58
|
+
'name': name,
|
|
59
|
+
'pool': pool,
|
|
60
|
+
'status': 'unavailable',
|
|
61
|
+
'autostart': False,
|
|
62
|
+
'capacity': 0,
|
|
63
|
+
'allocation': 0,
|
|
64
|
+
'error': str(e)
|
|
65
|
+
})
|
|
44
66
|
except libvirt.libvirtError:
|
|
45
67
|
return []
|
|
46
68
|
|
virtui_manager-1.1.5/FEATURES.md
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
# Virtui Manager - Features
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
A Textual-based TUI (Terminal User Interface) application for managing QEMU/KVM virtual machines using the libvirt Python API. It provides a comprehensive interface for VM management with features that go beyond basic management.
|
|
5
|
-
|
|
6
|
-
## Main Interface Features
|
|
7
|
-
|
|
8
|
-
### Multi-server Management
|
|
9
|
-
- Connect to multiple libvirt servers simultaneously
|
|
10
|
-
- Transhypervisor view showing VMs from different servers
|
|
11
|
-
- Server selection and management interface
|
|
12
|
-
|
|
13
|
-
### VM Grid Display
|
|
14
|
-
- VMs displayed in a responsive grid layout (up to 5x3 = 15 VM)
|
|
15
|
-
- Color-coded status indicators (Running, Paused, Stopped)
|
|
16
|
-
- CPU, Memory, Disk, Net usage sparklines for running VMs
|
|
17
|
-
- Pagination controls for large VM lists
|
|
18
|
-
|
|
19
|
-
### VM Management Actions
|
|
20
|
-
- Start, Shutdown, Force Off (destroy), Pause, Resume
|
|
21
|
-
- Delete VM with optional storage cleanup: Now more robust, always using libvirt API for managed storage volumes, preventing permission errors. Automatically deletes VM snapshot metadata.
|
|
22
|
-
- Hide restore snapshot if the VM is running or loading
|
|
23
|
-
- Clone VM functionality: Clone VMs with advanced options, including specifying a custom suffix, cloning multiple instances at once, and automatic creation of storage for the new clones.
|
|
24
|
-
- Rename VM with snapshot handling
|
|
25
|
-
- Take, restore, and delete VM snapshots
|
|
26
|
-
- View/Edit XML configuration
|
|
27
|
-
- Connect to VM via virt-viewer
|
|
28
|
-
- Web console access via novnc (when available)
|
|
29
|
-
- **Multi-session Support**: Support for multiple concurrent remote web console sessions via automatic port allocation/checking on the remote server.
|
|
30
|
-
- When connecting to a remote libvirt server via SSH, the web console can be configured to run either locally (default) or directly on the remote server.
|
|
31
|
-
- To enable running the web console on the remote server, set `REMOTE_WEBCONSOLE: True` in your `config.yaml`.
|
|
32
|
-
- When `REMOTE_WEBCONSOLE` is enabled, `websockify` and `novnc` assets must be installed on the remote server at the paths specified in `config.yaml` (default: `/usr/bin/websockify` and `/usr/share/novnc/`).
|
|
33
|
-
- For secure (HTTPS) remote web console access, `cert.pem` and `key.pem` files must also be present on the remote server in `~/.config/virtui-manager/`.
|
|
34
|
-
- Bulk actions on selected VMs (start, stop, force off, pause, delete)
|
|
35
|
-
- Edit Configuration Bulk action available
|
|
36
|
-
- VM Migration (Live and Offline) with pre-checking server and VM configuration
|
|
37
|
-
- Custom migration: migrate offline VMs with their volumes, snapshots, and overlays, allowing users to select where to place volumes on the destination server pool.
|
|
38
|
-
- Always copy storage in custom migration by default
|
|
39
|
-
|
|
40
|
-
### Disk Overlay Management (External Snapshots)
|
|
41
|
-
- **Create Overlay**: Create a new QCOW2 overlay on top of the current disk (freezes base image).
|
|
42
|
-
- **Discard Overlay**: Revert to the backing file and delete the overlay (discard changes).
|
|
43
|
-
- **Commit Disk**: Merge changes from the overlay back into the base image (make changes permanent).
|
|
44
|
-
- Visual management of overlay state.
|
|
45
|
-
- Comparison guide between Internal Snapshots and External Overlays included in the UI.
|
|
46
|
-
|
|
47
|
-
### Advanced Features
|
|
48
|
-
- Filter VMs by status (All, Running, Paused, Stopped) and search by name
|
|
49
|
-
- Filter VMs to show only VMs from a specific hypervisor
|
|
50
|
-
- Server preferences configuration
|
|
51
|
-
- Virsh shell access
|
|
52
|
-
- Detailed VM information view
|
|
53
|
-
- Web console management with automatic port allocation
|
|
54
|
-
- Configuration file management for server lists
|
|
55
|
-
- Create new VMs (with single server connection)
|
|
56
|
-
- Bulk operations on multiple VMs
|
|
57
|
-
- VMs sorted in human-readable order
|
|
58
|
-
- Select which server to autoconnect at startup
|
|
59
|
-
|
|
60
|
-
## Configure VM Features
|
|
61
|
-
|
|
62
|
-
### CPU Configuration
|
|
63
|
-
- Edit CPU count
|
|
64
|
-
- Select CPU model from available models (including host-passthrough and default options)
|
|
65
|
-
- CPU model selection is disabled when VM is running
|
|
66
|
-
|
|
67
|
-
### Memory Configuration
|
|
68
|
-
- Edit memory size in MB
|
|
69
|
-
- Enable/disable shared memory (disabled when VM is running)
|
|
70
|
-
|
|
71
|
-
### Firmware Configuration
|
|
72
|
-
- Select firmware type (BIOS or UEFI)
|
|
73
|
-
- For UEFI firmware:
|
|
74
|
-
- Enable/disable Secure Boot
|
|
75
|
-
- Enable/disable AMD-SEV and AMD-SEV-ES (when supported)
|
|
76
|
-
- Select UEFI file from available options
|
|
77
|
-
- Machine type selection (disabled when VM is running)
|
|
78
|
-
|
|
79
|
-
### Boot Configuration
|
|
80
|
-
- Enable/disable boot menu
|
|
81
|
-
- Boot device management
|
|
82
|
-
- Set boot order for devices
|
|
83
|
-
|
|
84
|
-
### Disk Management
|
|
85
|
-
- View all disks in a table format
|
|
86
|
-
- Add new disk (create new or attach existing)
|
|
87
|
-
- Create new disk in specific storage pool
|
|
88
|
-
- Attach existing volume to VM
|
|
89
|
-
- Attach existing disk from storage pools
|
|
90
|
-
- Remove disk
|
|
91
|
-
- Disable disk
|
|
92
|
-
- Enable disk
|
|
93
|
-
- Edit disk properties (cache mode and discard mode)
|
|
94
|
-
- Disk status indicators (enabled/disabled)
|
|
95
|
-
- Set disk cache and discard modes
|
|
96
|
-
- Display disk bus information
|
|
97
|
-
|
|
98
|
-
### Network Configuration
|
|
99
|
-
- View network interfaces with MAC addresses and IP addresses
|
|
100
|
-
- Change network interface to a different network
|
|
101
|
-
- View network DNS and gateway information
|
|
102
|
-
- Add new network interface
|
|
103
|
-
- Remove network interface
|
|
104
|
-
- Change network interface model
|
|
105
|
-
|
|
106
|
-
### VirtIO-FS Configuration
|
|
107
|
-
- View existing VirtIO-FS mounts
|
|
108
|
-
- Add new VirtIO-FS mount
|
|
109
|
-
- Edit existing VirtIO-FS mount
|
|
110
|
-
- Delete VirtIO-FS mount
|
|
111
|
-
- Requires shared memory to be enabled
|
|
112
|
-
|
|
113
|
-
### Video Configuration
|
|
114
|
-
- Select video model (virtio, qxl, vga, cirrus, bochs, ramfb, none, default)
|
|
115
|
-
- Enable/disable 3D acceleration (virtio only)
|
|
116
|
-
- Video model selection is disabled when VM is running
|
|
117
|
-
|
|
118
|
-
### Graphics Configuration
|
|
119
|
-
- Select graphics type (VNC, Spice, or None)
|
|
120
|
-
- Configure listen type (Address or None)
|
|
121
|
-
- Set address (Hypervisor default, Localhost only, All interfaces)
|
|
122
|
-
- Enable/disable auto port allocation
|
|
123
|
-
- Set port number (when auto port is disabled)
|
|
124
|
-
- Enable/disable password protection
|
|
125
|
-
- Set password for graphics access
|
|
126
|
-
- Apply graphics settings (disabled when VM is running)
|
|
127
|
-
- When switching from Spice to VNC: If other SPICE-related devices (channels, audio, QXL video) are detected, the user is prompted to remove them for a clean switch. This process automatically removes SPICE channels and USB redirection, changes SPICE audio to 'none', and converts QXL video to 'virtio'. A default VNC graphics device is added if no other graphics device exists after removal.
|
|
128
|
-
|
|
129
|
-
### TPM Configuration
|
|
130
|
-
- Select TPM model (tpm-crb, tpm-tis, or none)
|
|
131
|
-
- Select TPM type (emulated or passthrough)
|
|
132
|
-
- Configure device path for passthrough TPM
|
|
133
|
-
- Configure backend type and path for passthrough TPM
|
|
134
|
-
- Apply TPM settings (disabled when VM is running)
|
|
135
|
-
|
|
136
|
-
### RNG Configuration
|
|
137
|
-
- Configure Random Number Generator (RNG) host device.
|
|
138
|
-
- Apply RNG settings (disabled when VM is running).
|
|
139
|
-
|
|
140
|
-
### Sound Configuration
|
|
141
|
-
- Select sound model (ac97, ich6, sb16, pcspk, es1370, hda, default)
|
|
142
|
-
- Sound model selection is disabled when VM is running
|
|
143
|
-
|
|
144
|
-
### Watchdog Configuration
|
|
145
|
-
- Configure Watchdog device for VM
|
|
146
|
-
- Set watchdog model and action (reset, shutdown, poweroff)
|
|
147
|
-
- Watchdog configuration is disabled when VM is running
|
|
148
|
-
|
|
149
|
-
### Input Configuration
|
|
150
|
-
- Configure input devices (keyboard, mouse, tablet)
|
|
151
|
-
- Set input device type and bus (usb, ps2, virtio)
|
|
152
|
-
- Input configuration is disabled when VM is running
|
|
153
|
-
|
|
154
|
-
### USB Host Configuration
|
|
155
|
-
- Add/Remove USB host devices
|
|
156
|
-
- Configure vendor ID, product ID, and address
|
|
157
|
-
|
|
158
|
-
### PCI Passthrough Configuration
|
|
159
|
-
- View available PCI host devices
|
|
160
|
-
- Attach PCI devices to VM (experimental)
|
|
161
|
-
- Detach PCI devices from VM (experimental)
|
|
162
|
-
- PCI device status indicators (attached/disconnected)
|
|
163
|
-
|
|
164
|
-
### Serial Configuration
|
|
165
|
-
- Add/Remove serial devices
|
|
166
|
-
- Configure serial device type (Pty)
|
|
167
|
-
|
|
168
|
-
### Additional Features
|
|
169
|
-
- Tabbed interface for organized configuration
|
|
170
|
-
- Toggle between main and extended configuration tabs
|
|
171
|
-
- Real-time status indicators
|
|
172
|
-
- Confirmation dialogs for destructive actions
|
|
173
|
-
- Error handling and user feedback
|
|
174
|
-
- VM status validation (prevents configuration changes when VM is running)
|
|
175
|
-
|
|
176
|
-
## Server Management Features
|
|
177
|
-
|
|
178
|
-
### Network Management
|
|
179
|
-
- View all networks in a table format
|
|
180
|
-
- Create new network with NAT or routed type
|
|
181
|
-
- Edit network properties including DHCP settings
|
|
182
|
-
- Delete network with confirmation
|
|
183
|
-
- Toggle network active state
|
|
184
|
-
- Toggle network autostart state
|
|
185
|
-
- View network XML details
|
|
186
|
-
- Get list of VMs using a specific network
|
|
187
|
-
|
|
188
|
-
### Storage Management
|
|
189
|
-
- View storage pools in a tree format
|
|
190
|
-
- Create new storage pool (directory or network file system)
|
|
191
|
-
- Edit storage pool properties (path)
|
|
192
|
-
- Delete storage pool with confirmation
|
|
193
|
-
- Create new storage volume
|
|
194
|
-
- Delete storage volume with confirmation
|
|
195
|
-
- Toggle storage pool active state
|
|
196
|
-
- Toggle storage pool autostart state
|
|
197
|
-
- List unused storage volumes
|
|
198
|
-
- Get all storage volumes across all pools
|
|
199
|
-
- Move storage volume between pools
|
|
200
|
-
|
|
201
|
-
## User Interface Features
|
|
202
|
-
|
|
203
|
-
### Keyboard Shortcuts
|
|
204
|
-
- `v` - View Log
|
|
205
|
-
- `f` - Filter VM
|
|
206
|
-
- `p` - Pattern Selection of VM
|
|
207
|
-
- `m` - Servers List
|
|
208
|
-
- `s` - Select Servers
|
|
209
|
-
- `ctrl+a` - Select/Deselect All VMs on current page
|
|
210
|
-
- `ctrl+u` - Unselect all VMs
|
|
211
|
-
- `c` - User Application Configuration
|
|
212
|
-
- `ctrl+v` - Virsh Shell
|
|
213
|
-
- `q` - Quit
|
|
214
|
-
|
|
215
|
-
For Debugging the app:
|
|
216
|
-
- `ctrl+l` - Start/Stop Log Stats
|
|
217
|
-
- `ctrl+s` - Show Cache Stats
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
### Visual Elements
|
|
221
|
-
- Color-coded server identification
|
|
222
|
-
- Status indicators with color coding (Running, Paused, Stopped)
|
|
223
|
-
- Sparkline graphs for CPU, Memory, Disk, Network usage
|
|
224
|
-
- Responsive layout that adapts to terminal size
|
|
225
|
-
- Tabbed interface for organized information display
|
|
226
|
-
- Selection indicators for multiple VMs
|
|
227
|
-
|
|
228
|
-
## Technical Capabilities
|
|
229
|
-
|
|
230
|
-
### Connection Management
|
|
231
|
-
- Support for multiple libvirt connection types (local, SSH)
|
|
232
|
-
- Automatic detection of virt-viewer, websockify, and novnc availability
|
|
233
|
-
- Error handling and logging
|
|
234
|
-
- Responsive UI that adapts to terminal size
|
|
235
|
-
- Command-line mode support (--cmd flag)
|
|
236
|
-
- Improved cache invalidation mechanism using UUID@URI for better accuracy
|
|
237
|
-
|
|
238
|
-
## User Experience
|
|
239
|
-
- Visual feedback through notifications
|
|
240
|
-
- Confirmation dialogs for destructive actions
|
|
241
|
-
- Loading indicators for long-running operations
|
|
242
|
-
- Detailed error messages
|
|
243
|
-
- Command-line mode for advanced users
|
|
244
|
-
- Bulk operations
|
|
245
|
-
- Real-time VM status updates
|
|
246
|
-
|
|
247
|
-
## Extra Command-Line Tool (vmanager_cmd.py)
|
|
248
|
-
|
|
249
|
-
In addition to the main TUI application, `vmanager` also provides a command-line interface (`vmanager_cmd.py`) for managing virtual machines and storage. This tool offers the following key features:
|
|
250
|
-
|
|
251
|
-
* **Multi-server Management**: Connect to and manage multiple `libvirt` servers simultaneously from a single shell.
|
|
252
|
-
* **Bulk VM Operations**: Execute commands like `start`, `stop`, `status`, `pause`, `resume`, `force_off`, and `delete` on multiple VMs across different connected servers at once.
|
|
253
|
-
* **Advanced VM Selection**: Select VMs for operations using direct names or powerful regular expression patterns.
|
|
254
|
-
* **Interactive VM Deletion**: Delete VMs with interactive confirmation, including an option to also remove associated storage volumes.
|
|
255
|
-
* **Storage Management**: List storage pools and identify unused storage volumes across all connected servers.
|
|
256
|
-
* **Tab Autocompletion**: Enjoy context-aware autocompletion for server names, VM names, and storage pool names, enhancing usability and speed.
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Modal to show how to manage VM disks.
|
|
3
|
-
"""
|
|
4
|
-
from textual.app import ComposeResult
|
|
5
|
-
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
6
|
-
from textual.widgets import Button, Markdown
|
|
7
|
-
from textual import on
|
|
8
|
-
from .base_modals import BaseModal
|
|
9
|
-
|
|
10
|
-
HOW_TO_DISK_TEXT = """
|
|
11
|
-
# Managing VM Disks
|
|
12
|
-
|
|
13
|
-
This guide explains the functions of the buttons available in the "Disks" tab.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
### Add Disk
|
|
18
|
-
|
|
19
|
-
This button allows you to create a **brand new virtual disk** and attach it to the VM.
|
|
20
|
-
|
|
21
|
-
- You will be prompted to select a **Storage Pool** where the new disk image file will be created.
|
|
22
|
-
- You must provide a **Volume Name** for the new disk (e.g., `new-data-disk.qcow2`).
|
|
23
|
-
- You must specify its **Size** and **Format** (`qcow2` or `raw`).
|
|
24
|
-
- The VM does **not** need to be stopped to add a new disk.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
### Attach Existing Disk
|
|
29
|
-
|
|
30
|
-
This attaches a **pre-existing disk image** to your VM. This is useful if you have an existing `.qcow2`, `.raw`, or `.iso` file you want to use.
|
|
31
|
-
|
|
32
|
-
- The system will first ask you to select a **Storage Pool** that contains the disk volume you want to attach.
|
|
33
|
-
- If the disk file is not part of a libvirt storage pool yet, you should first use the "Attach" button in the **Server Preferences -> Storage** tab to make it known to libvirt. This might involve creating a new storage pool for the directory containing your disk file.
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
### Edit Disk
|
|
38
|
-
|
|
39
|
-
*(This button is enabled only when a disk is selected in the table)*
|
|
40
|
-
|
|
41
|
-
Allows you to modify properties of an attached disk, such as:
|
|
42
|
-
- **Bus Type:** `virtio`, `sata`, `scsi`, etc.
|
|
43
|
-
- **Cache Mode:** `none`, `writeback`, etc.
|
|
44
|
-
- **Discard Mode:** `unmap`, `ignore`.
|
|
45
|
-
|
|
46
|
-
> **Important:** The VM must be **stopped** to edit disk properties.
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
### Remove Disk
|
|
51
|
-
|
|
52
|
-
*(This button is enabled only when a disk is selected)*
|
|
53
|
-
|
|
54
|
-
This **detaches** the selected disk from the virtual machine.
|
|
55
|
-
|
|
56
|
-
- **This action does NOT delete the disk image file.** The file remains in its storage pool or on your filesystem.
|
|
57
|
-
- It only removes the disk from this specific VM's configuration.
|
|
58
|
-
- The VM must be **stopped** to remove most types of disks.
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
### Disable Disk
|
|
63
|
-
|
|
64
|
-
*(This button is enabled for active disks)*
|
|
65
|
-
|
|
66
|
-
This temporarily "unplugs" the disk from the VM **without removing its configuration**.
|
|
67
|
-
|
|
68
|
-
- The disk will become invisible to the guest operating system but remains in the disk list with a `(disabled)` status.
|
|
69
|
-
- This is useful for troubleshooting or temporarily preventing access to a disk without fully removing it.
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
### Enable Disk
|
|
74
|
-
|
|
75
|
-
*(This button is enabled for disabled disks)*
|
|
76
|
-
|
|
77
|
-
This "re-plugs" a disabled disk back into the VM, making it available to the guest operating system again.
|
|
78
|
-
"""
|
|
79
|
-
|
|
80
|
-
class HowToDiskModal(BaseModal[None]):
|
|
81
|
-
"""A modal to display instructions for managing VM disks."""
|
|
82
|
-
|
|
83
|
-
def compose(self) -> ComposeResult:
|
|
84
|
-
with Vertical(id="howto-disk-dialog"):
|
|
85
|
-
with ScrollableContainer(id="howto-disk-content"):
|
|
86
|
-
yield Markdown(HOW_TO_DISK_TEXT, id="howto-disk-markdown")
|
|
87
|
-
with Horizontal(id="dialog-buttons"):
|
|
88
|
-
yield Button("Close", id="close-btn", variant="primary")
|
|
89
|
-
|
|
90
|
-
@on(Button.Pressed)
|
|
91
|
-
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
92
|
-
"""Handle button presses."""
|
|
93
|
-
self.dismiss()
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Modal to show how to configure networks.
|
|
3
|
-
"""
|
|
4
|
-
from textual.app import ComposeResult
|
|
5
|
-
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
6
|
-
from textual.widgets import Button, Markdown
|
|
7
|
-
from textual import on
|
|
8
|
-
from .base_modals import BaseModal
|
|
9
|
-
|
|
10
|
-
HOW_TO_NETWORK_TEXT = """
|
|
11
|
-
# Understanding Network Configuration in libvirt
|
|
12
|
-
|
|
13
|
-
libvirt provides flexible networking capabilities for virtual machines, allowing them to communicate with each other, the host, and external networks.
|
|
14
|
-
|
|
15
|
-
### Types of Networks
|
|
16
|
-
|
|
17
|
-
1. **NAT (Network Address Translation) Network:**
|
|
18
|
-
* **Purpose:** Allows VMs to access the external network (internet) but prevents external machines from directly initiating connections to the VMs.
|
|
19
|
-
* **Mechanism:** libvirt creates a virtual bridge (e.g., `virbr0`) on the host, and VMs connect to this bridge. The host acts as a router, performing NAT for outgoing connections from VMs. VMs get IP addresses from a DHCP server managed by libvirt.
|
|
20
|
-
* **Use Cases:** Most common setup for general VM usage where VMs just need internet access.
|
|
21
|
-
|
|
22
|
-
2. **Routed Network:**
|
|
23
|
-
* **Purpose:** VMs can communicate with other machines on the host's physical network, and potentially external networks, with proper routing configured on the host and potentially external routers. VMs will have IP addresses on the same subnet as the host's physical network, or a dedicated routed subnet.
|
|
24
|
-
* **Mechanism:** Similar to NAT, a virtual bridge is used, but without NAT. The host needs to be configured to route traffic between the virtual bridge and the physical interface. VMs typically get IP addresses from a DHCP server on the physical network or static IPs.
|
|
25
|
-
* **Use Cases:** When VMs need to be directly accessible from the physical network, or participate as full members of an existing network.
|
|
26
|
-
|
|
27
|
-
3. **Isolated Network:**
|
|
28
|
-
* **Purpose:** VMs on this network can only communicate with each other and the host, but not with external networks.
|
|
29
|
-
* **Mechanism:** A virtual bridge is created, but no routing or NAT is configured to connect it to physical interfaces.
|
|
30
|
-
* **Use Cases:** Testing environments, isolated services, or when you need a private network segment for VMs.
|
|
31
|
-
|
|
32
|
-
### Key Concepts
|
|
33
|
-
|
|
34
|
-
* **Bridge:** A software-based network device that connects multiple network segments at the data link layer. VMs connect to a virtual bridge, which then connects to a physical interface (for NAT/routed) or remains isolated.
|
|
35
|
-
* **DHCP:** Dynamic Host Configuration Protocol. Automatically assigns IP addresses to VMs within a network.
|
|
36
|
-
* **Forward Device:** The physical network interface on the host through which the virtual network's traffic is forwarded (for NAT or routed modes).
|
|
37
|
-
* **MAC Address:** Media Access Control address. A unique identifier assigned to network interfaces. For KVM/QEMU, MAC addresses often start with `52:54:00:`.
|
|
38
|
-
|
|
39
|
-
### Common Tasks
|
|
40
|
-
|
|
41
|
-
* **Create/Edit Network:** Define network parameters like name, IP range, DHCP settings, and forward mode.
|
|
42
|
-
* **Activate/Deactivate Network:** Start or stop a virtual network.
|
|
43
|
-
* **Autostart Network:** Configure a network to start automatically when the libvirt daemon starts.
|
|
44
|
-
* **View XML:** Examine the underlying libvirt XML definition of a network, which provides full details of its configuration.
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
class HowToNetworkModal(BaseModal[None]):
|
|
48
|
-
"""A modal to display instructions for network configuration."""
|
|
49
|
-
|
|
50
|
-
def compose(self) -> ComposeResult:
|
|
51
|
-
with Vertical(id="howto-network-dialog"):
|
|
52
|
-
with ScrollableContainer(id="howto-network-content"):
|
|
53
|
-
yield Markdown(HOW_TO_NETWORK_TEXT, id="howto-network-markdown")
|
|
54
|
-
with Horizontal(id="dialog-buttons"):
|
|
55
|
-
yield Button("Close", id="close-btn", variant="primary")
|
|
56
|
-
|
|
57
|
-
@on(Button.Pressed)
|
|
58
|
-
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
59
|
-
"""Handle button presses."""
|
|
60
|
-
self.dismiss()
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Modal to show how overlay disks work.
|
|
3
|
-
"""
|
|
4
|
-
from textual.app import ComposeResult
|
|
5
|
-
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
6
|
-
from textual.widgets import Button, Markdown
|
|
7
|
-
from textual import on
|
|
8
|
-
from .base_modals import BaseModal
|
|
9
|
-
|
|
10
|
-
HOW_TO_OVERLAY_TEXT = """
|
|
11
|
-
# Understanding Snapshots and Disk Overlays
|
|
12
|
-
|
|
13
|
-
Virtual machines in this manager support two ways to preserve states and manage changes: **Snapshots** and **Disk Overlays**. While they share similar goals, they work differently and are suited for different use cases.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
### 1. Snapshots (Internal)
|
|
18
|
-
Snapshots are managed directly by libvirt and are typically stored **inside** the disk image (if using QCOW2 format).
|
|
19
|
-
|
|
20
|
-
* **How they work:** When you take a snapshot, libvirt records the current state of the VM's disks and, if the VM is running, its memory (RAM). You can have many snapshots for a single VM, creating a timeline you can jump back and forth in.
|
|
21
|
-
* **Operations:**
|
|
22
|
-
* **Take Snapshot:** Creates a new restore point.
|
|
23
|
-
* **Restore Snapshot:** Reverts the VM to a previous state.
|
|
24
|
-
* **Delete Snapshot:** Removes the restore point from the timeline.
|
|
25
|
-
* **Best for:** Quick restore points before risky operations, and preserving the full "live" state of a running VM.
|
|
26
|
-
|
|
27
|
-
### 2. Disk Overlays (External)
|
|
28
|
-
Overlays are **new files** created on top of a base disk image. This is also known as "External Snapshots" or "Backing Files".
|
|
29
|
-
|
|
30
|
-
* **Key Concepts:**
|
|
31
|
-
* **Base Image (Backing File):** The original disk image that becomes read-only.
|
|
32
|
-
* **Overlay Image:** A new QCOW2 file that records only the changes made *after* its creation.
|
|
33
|
-
* **Backing Chain:** The relationship between layers (e.g., Base -> Overlay 1 -> Overlay 2).
|
|
34
|
-
* **Operations:**
|
|
35
|
-
* **New Overlay:** Freezes the current disk and starts a new layer.
|
|
36
|
-
* **Discard Overlay (Revert):** Deletes the overlay file and reverts to the base image.
|
|
37
|
-
* **Commit Disk (Merge):** Merges changes from the overlay into the base image, making them permanent.
|
|
38
|
-
* **Best for:** Maintaining "Golden Images", branching multiple VMs from a single base, and isolating large changes in separate files.
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
### Comparison: Snapshot vs. Overlay
|
|
43
|
-
|
|
44
|
-
| Feature | Snapshots (Internal) | Disk Overlays (External) |
|
|
45
|
-
| :--- | :--- | :--- |
|
|
46
|
-
| **Storage** | Inside the existing disk file | In a new, separate file |
|
|
47
|
-
| **VM State** | Can include RAM (Live state) | Disk only (requires VM stop) |
|
|
48
|
-
| **Management** | Timeline (multiple points) | Layered (Base + Changes) |
|
|
49
|
-
| **Primary Use** | Quick restore points | Permanent branching / Golden images |
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
### How Overlays Work (Technical)
|
|
54
|
-
|
|
55
|
-
1. **Creation:** When you create a "New Overlay", the current disk image is set as the backing file for a newly created QCOW2 file. The VM is then updated to point to this new overlay file instead of the original disk.
|
|
56
|
-
2. **Read Operations:** When the VM needs to read data, it first checks the overlay. If the data has been modified, it reads from the overlay. If not, it transparently reads from the backing file.
|
|
57
|
-
3. **Write Operations:** All writes are directed to the overlay file. The backing file remains untouched and pristine.
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
class HowToOverlayModal(BaseModal[None]):
|
|
61
|
-
"""A modal to display instructions for disk overlays."""
|
|
62
|
-
|
|
63
|
-
def compose(self) -> ComposeResult:
|
|
64
|
-
with Vertical(id="howto-overlay-dialog", classes="howto-dialog"):
|
|
65
|
-
with ScrollableContainer(id="howto-overlay-content"):
|
|
66
|
-
yield Markdown(HOW_TO_OVERLAY_TEXT, id="howto-overlay-markdown")
|
|
67
|
-
with Horizontal(id="dialog-buttons"):
|
|
68
|
-
yield Button("Close", id="close-btn", variant="primary")
|
|
69
|
-
|
|
70
|
-
@on(Button.Pressed)
|
|
71
|
-
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
72
|
-
"""Handle button presses."""
|
|
73
|
-
self.dismiss()
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Modal to show how to use ssh-agent.
|
|
3
|
-
"""
|
|
4
|
-
from textual.app import ComposeResult
|
|
5
|
-
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
6
|
-
from textual.widgets import Button, Markdown
|
|
7
|
-
from textual import on
|
|
8
|
-
from .base_modals import BaseModal
|
|
9
|
-
|
|
10
|
-
HOW_TO_SSH_TEXT = """
|
|
11
|
-
# Using an SSH Agent for Passwordless Connections
|
|
12
|
-
|
|
13
|
-
Using an `ssh-agent` is the standard and most secure way to handle passphrase-protected SSH keys, allowing you to connect without repeatedly entering your passphrase.
|
|
14
|
-
|
|
15
|
-
### What is an SSH Agent?
|
|
16
|
-
|
|
17
|
-
An `ssh-agent` is a background program that securely stores your private SSH keys in memory. When you try to connect to a remote server, SSH can ask the agent for the key, and the agent provides it. You only need to "unlock" your key once.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
### Step 1: Start the `ssh-agent`
|
|
22
|
-
|
|
23
|
-
On most modern desktop environments, an agent is often started automatically. If not, run this in your terminal:
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
eval "$(ssh-agent -s)"
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
> To make this permanent, add the command to your shell's startup file (e.g., `~/.bashrc` or `~/.zshrc`).
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
### Step 2: Add Your SSH Key to the Agent
|
|
34
|
-
|
|
35
|
-
Use the `ssh-add` command. If your key is in a default location (`~/.ssh/id_rsa`, etc.), you can just run:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
ssh-add
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
If your key is elsewhere, specify the path to the **private key**:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
ssh-add /path/to/your/private_key
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
You will be prompted for your key's passphrase **one time**.
|
|
48
|
-
|
|
49
|
-
To verify the key was added, list the agent's keys:
|
|
50
|
-
```bash
|
|
51
|
-
ssh-add -l
|
|
52
|
-
```
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
### Step 3: Connect
|
|
56
|
-
|
|
57
|
-
That's it! `Virtui Manager` will now use the agent to authenticate for any `qemu+ssh://` connections without any more prompts.
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
### SSH Compression for Performance
|
|
62
|
-
|
|
63
|
-
For connections over slower networks, enabling SSH compression can significantly improve performance. This is configured in your SSH client's configuration file.
|
|
64
|
-
|
|
65
|
-
To enable compression for a specific host, add the following to your `~/.ssh/config` file:
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
Host your_remote_host_name
|
|
69
|
-
Compression yes
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Replace `your_remote_host_name` with the actual hostname or IP address you use in your `qemu+ssh://` URI. If you want to enable compression for all SSH connections, you can use `Host *`.
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
class HowToSSHModal(BaseModal[None]):
|
|
76
|
-
"""A modal to display instructions for using an ssh-agent."""
|
|
77
|
-
|
|
78
|
-
def compose(self) -> ComposeResult:
|
|
79
|
-
with Vertical(id="howto-ssh-dialog"):
|
|
80
|
-
with ScrollableContainer(id="howto-ssh-content"):
|
|
81
|
-
yield Markdown(HOW_TO_SSH_TEXT, id="howto-ssh-markdown")
|
|
82
|
-
with Horizontal(id="dialog-buttons"):
|
|
83
|
-
yield Button("Close", id="close-btn", variant="primary")
|
|
84
|
-
|
|
85
|
-
@on(Button.Pressed)
|
|
86
|
-
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
87
|
-
"""Handle button presses."""
|
|
88
|
-
self.dismiss()
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Modal to show how to use VirtIO-FS.
|
|
3
|
-
"""
|
|
4
|
-
from textual.app import ComposeResult
|
|
5
|
-
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
6
|
-
from textual.widgets import Button, Markdown
|
|
7
|
-
from textual import on
|
|
8
|
-
from .base_modals import BaseModal
|
|
9
|
-
|
|
10
|
-
HOW_TO_VIRTIOFS_TEXT = """
|
|
11
|
-
# Using VirtIO-FS for Host-Guest File Sharing
|
|
12
|
-
|
|
13
|
-
VirtIO-FS is a high-performance shared filesystem that lets you share a directory from your host machine directly with a guest VM.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
### Host Prerequisites
|
|
18
|
-
|
|
19
|
-
1. **Shared Memory:** VirtIO-FS requires shared memory to be enabled for the VM. You can enable this in the **"Mem"** tab.
|
|
20
|
-
2. **Permissions:** The user running QEMU/libvirt on the host must have the necessary permissions to read (and write, if needed) the source directory you want to share.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
### Adding a VirtIO-FS Mount
|
|
25
|
-
|
|
26
|
-
- **Source Path:** The absolute path to the directory on your **host machine** that you want to share.
|
|
27
|
-
- **Target Path:** This is a "mount tag" or a label that the guest VM will use to identify the shared directory. It is **not** a path inside the guest. For example, you could use `shared-data`.
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
### Mounting in a Linux Guest
|
|
32
|
-
|
|
33
|
-
Most modern Linux distributions include the necessary VirtIO-FS drivers.
|
|
34
|
-
|
|
35
|
-
**1. Create a Mount Point:**
|
|
36
|
-
This is the directory inside your VM where the shared files will appear.
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
sudo mkdir /mnt/my_host_share
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**2. Mount the Share:**
|
|
43
|
-
Use the `mount` command with the filesystem type `virtiofs` and the **Target Path (mount tag)** you defined.
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
sudo mount -t virtiofs 'your-target-path' /mnt/my_host_share
|
|
47
|
-
```
|
|
48
|
-
*(Replace `'your-target-path'` with the actual tag you set)*
|
|
49
|
-
|
|
50
|
-
**3. Automount on Boot (Optional):**
|
|
51
|
-
To make the share available automatically every time the VM boots, add an entry to `/etc/fstab`:
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
your-target-path /mnt/my_host_share virtiofs defaults,nofail 0 0
|
|
55
|
-
```
|
|
56
|
-
> The `nofail` option is recommended to prevent boot issues if the share is not available.
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
### Mounting in a Windows Guest
|
|
61
|
-
|
|
62
|
-
**1. Install Drivers:**
|
|
63
|
-
You must install the VirtIO-FS drivers in the Windows guest. These are included in the **"VirtIO-Win Guest Tools"** package, which you can typically download as an ISO file.
|
|
64
|
-
- Download the latest stable `virtio-win.iso` from the [Fedora VirtIO-Win project](https://github.com/virtio-win/virtio-win-pkg-scripts/blob/master/README.md).
|
|
65
|
-
- Attach the ISO to your VM as a CD-ROM.
|
|
66
|
-
- Open the CD-ROM in Windows and run the `virtio-win-guest-tools.exe` installer, ensuring the **"VirtIO-FS"** feature is selected.
|
|
67
|
-
|
|
68
|
-
**2. Access the Share:**
|
|
69
|
-
After installation and a reboot, the VirtIO-FS service will start. The shared folder will automatically appear as a network drive in **This PC** (or My Computer). The drive will be named after the **Target Path (mount tag)** you set.
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
class HowToVirtIOFSModal(BaseModal[None]):
|
|
73
|
-
"""A modal to display instructions for using VirtIO-FS."""
|
|
74
|
-
|
|
75
|
-
def compose(self) -> ComposeResult:
|
|
76
|
-
with Vertical(id="howto-virtiofs-dialog"):
|
|
77
|
-
with ScrollableContainer(id="howto-virtiofs-content"):
|
|
78
|
-
yield Markdown(HOW_TO_VIRTIOFS_TEXT, id="howto-virtiofs-markdown")
|
|
79
|
-
with Horizontal(id="dialog-buttons"):
|
|
80
|
-
yield Button("Close", id="close-btn", variant="primary")
|
|
81
|
-
|
|
82
|
-
@on(Button.Pressed)
|
|
83
|
-
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
84
|
-
"""Handle button presses."""
|
|
85
|
-
self.dismiss()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{virtui_manager-1.1.5 → virtui_manager-1.1.6}/src/virtui_manager.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|