Flag guests that no backup job covers
The guest table marks with "!" each guest that Proxmox's not-backed-up list returns. The frame's bottom border shows the count, e.g. "mainframe · 2 without backup job". This uses Proxmox's own answer, so it also counts jobs that select guests by pool. If the coverage call fails, the guests are still listed and a warning says the coverage is unknown. Closes #9 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #20.
This commit is contained in:
1 parent
5a3ca5a216
commit
520de16cc6
5 files changed
+97
-6
No files matched your search
+27
-6
@@ -659,6 +659,8 @@ class GuestApp(App):
|
||||
# bumped on every context switch; replies started earlier come from the old cluster
|
||||
self.generation = 0
|
||||
self.switching = False
|
||||
self.context_name = ""
|
||||
self.unprotected: set[int] = set() # VMIDs no backup job covers
|
||||
|
||||
CSS = """
|
||||
#guests_section {
|
||||
@@ -679,6 +681,7 @@ class GuestApp(App):
|
||||
self.watch(self, "sub_title", self._show_context_frame)
|
||||
table = self.query_one("#guests", DataTable)
|
||||
table.add_columns(
|
||||
"", # "!" = no backup job covers the guest
|
||||
"VMID",
|
||||
"Name",
|
||||
"Type",
|
||||
@@ -692,9 +695,13 @@ class GuestApp(App):
|
||||
self.action_reload()
|
||||
|
||||
def _show_context_frame(self, sub_title: str) -> None:
|
||||
self.query_one("#guests_section").border_subtitle = sub_title.removeprefix(
|
||||
"context: "
|
||||
)
|
||||
self.context_name = sub_title.removeprefix("context: ")
|
||||
self._update_frame()
|
||||
|
||||
def _update_frame(self) -> None:
|
||||
n = len(self.unprotected & {g["vmid"] for g in self.guests.values()})
|
||||
count = f" · {n} without backup job" if n else ""
|
||||
self.query_one("#guests_section").border_subtitle = self.context_name + count
|
||||
|
||||
@work(thread=True)
|
||||
def _show_context(self) -> None:
|
||||
@@ -724,21 +731,26 @@ class GuestApp(App):
|
||||
table.coordinate_to_cell_key(table.cursor_coordinate).row_key.value
|
||||
]
|
||||
|
||||
def _fill(self, guests: list[dict], gen: int) -> None:
|
||||
def _fill(
|
||||
self, guests: list[dict], gen: int, unprotected: set[int] | None = None
|
||||
) -> None:
|
||||
if gen != self.generation: # listed before a context switch: old cluster
|
||||
return
|
||||
self.unprotected = unprotected or set()
|
||||
table = self.query_one("#guests", DataTable)
|
||||
current = self._selected()
|
||||
table.clear()
|
||||
self.guests = {str(g["vmid"]): g for g in guests}
|
||||
for g in guests:
|
||||
_add_plain(table,
|
||||
_add_plain(table,
|
||||
"!" if g["vmid"] in self.unprotected else "",
|
||||
g["vmid"], g["name"], g["type"], g["node"], g["status"],
|
||||
g.get("maxcpu", "-"), _scaled(g.get("maxmem"), 2**20), _scaled(g.get("maxdisk"), 2**30),
|
||||
key=str(g["vmid"]),
|
||||
) # fmt: skip
|
||||
if current and str(current["vmid"]) in self.guests:
|
||||
table.move_cursor(row=table.get_row_index(str(current["vmid"])))
|
||||
self._update_frame()
|
||||
|
||||
def _reload(self, acted: dict | None = None) -> None:
|
||||
"""Runs in a worker thread. `acted` gets its live status: the listing lags."""
|
||||
@@ -751,7 +763,16 @@ class GuestApp(App):
|
||||
except Exception as exc: # noqa: BLE001 - any failure must reach the user, not kill the app
|
||||
self.call_from_thread(self.notify, _message(exc), severity="error")
|
||||
return
|
||||
self.call_from_thread(self._fill, guests, gen)
|
||||
unprotected = None
|
||||
try:
|
||||
unprotected = cli.not_backed_up()
|
||||
except Exception as exc: # noqa: BLE001 - the listing still works without it
|
||||
self.call_from_thread(
|
||||
self.notify,
|
||||
f"backup coverage unknown: {_message(exc)}",
|
||||
severity="warning",
|
||||
)
|
||||
self.call_from_thread(self._fill, guests, gen, unprotected)
|
||||
|
||||
def action_reload(self) -> None:
|
||||
if self.switching:
|
||||
|
||||
@@ -379,3 +379,9 @@ def set_net(guest: dict, changes: dict[str, str | None]) -> None:
|
||||
run(
|
||||
"set", kind(guest), "config", guest["node"], str(guest["vmid"]), *opts, "--wait"
|
||||
)
|
||||
|
||||
|
||||
def not_backed_up() -> set[int]:
|
||||
"""VMIDs no backup job covers (Proxmox's own answer, incl. pool-based jobs)."""
|
||||
items = json.loads(run("get", "cluster", "not-backed-up", "-o", "json"))
|
||||
return {int(i["vmid"]) for i in items if i.get("vmid") is not None}
|
||||
Reference in new issue
Block a user