From 62490af91342826a80a10d93f8175cd305241684 Mon Sep 17 00:00:00 2001 From: Per-Arne Hellarvik Date: Mon, 2 Nov 2026 21:53:00 +0100 Subject: [PATCH] Separate backups and network on the info screen Put backups and network in their own bordered sections titled "Backups" and "Network". Inside Network, the configured settings and the addresses in use are labelled "Configured" and "In use". The "unavailable" note is hidden when it is empty, so it no longer leaves a blank line. Co-Authored-By: Claude Opus 5.5 --- src/cv4pve_tui/app.py | 32 ++++++++++++++++++++++++-------- tests/test_app.py | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/cv4pve_tui/app.py b/src/cv4pve_tui/app.py index 173da82..8984d52 100644 --- a/src/cv4pve_tui/app.py +++ b/src/cv4pve_tui/app.py @@ -334,7 +334,15 @@ class InfoScreen(ModalScreen): ("n", "net", "Edit network"), ("u", "undo", "Undo network"), ] - DEFAULT_CSS = "InfoScreen DataTable { height: auto; max-height: 12; }" + DEFAULT_CSS = """ + InfoScreen DataTable { height: auto; max-height: 12; } + InfoScreen .section { + height: auto; border: round $accent; border-title-style: bold; + padding: 0 1; margin-bottom: 1; + } + InfoScreen .label { text-style: bold; margin-top: 1; } + InfoScreen .label.first { margin-top: 0; } + """ def __init__(self, guest: dict) -> None: super().__init__() @@ -349,12 +357,18 @@ class InfoScreen(ModalScreen): def compose(self) -> ComposeResult: with Vertical(): yield Static(f"{self.guest['name']} ({self.guest['vmid']})", markup=False) - yield Static("", id="jobs", markup=False) - yield DataTable(id="backups") - yield Static("", id="hidden", markup=False) - yield DataTable(id="nets") - yield Static("", id="unavailable", markup=False) - yield DataTable(id="addresses") + with Vertical(id="backup_section", classes="section") as backup: + backup.border_title = "Backups" + yield Static("", id="jobs", markup=False) + yield DataTable(id="backups") + yield Static("", id="hidden", markup=False) + with Vertical(id="network_section", classes="section") as network: + network.border_title = "Network" + yield Static("Configured", classes="label first") + yield DataTable(id="nets") + yield Static("In use", classes="label") + yield Static("", id="unavailable", markup=False) + yield DataTable(id="addresses") yield Footer() def on_mount(self) -> None: @@ -442,7 +456,9 @@ class InfoScreen(ModalScreen): ip.get("ip6", "-"), "yes" if net.get("firewall") == "1" else "no", ) - self.query_one("#unavailable", Static).update(unavailable) + note = self.query_one("#unavailable", Static) + note.update(unavailable) + note.display = bool(unavailable) # no empty line when the addresses are known table = self.query_one("#addresses", DataTable).clear() for i in addresses: table.add_row( diff --git a/tests/test_app.py b/tests/test_app.py index 6f56ba0..9ca8dd5 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1864,3 +1864,22 @@ async def test_link_only_change_on_running_ct_does_not_mention_reboot(fake): await setv(app, pilot, tag="99") text = await apply_to_confirm(app, pilot) assert "reboot" not in text.lower() + + +# --- info screen layout: backups and network clearly separated -------------------- + + +async def test_info_has_separate_titled_backup_and_network_sections(fake): + app = make_app() + async with app.run_test() as pilot: + await open_info(app, pilot) + backup = app.screen.query_one("#backup_section") + network = app.screen.query_one("#network_section") + assert "Backups" in str(backup.border_title) + assert "Network" in str(network.border_title) + assert {w.id for w in backup.query("*")} >= {"jobs", "backups", "hidden"} + assert {w.id for w in network.query("*")} >= { + "nets", + "unavailable", + "addresses", + } -- 2.39.5