Lokales Fenster war nicht navigierbar; halbfertige Snapshots erkennen

Zwei gemeldete Fehler.

1. Im rechten Fenster des Explorers liessen sich weder Verzeichnisse
   oeffnen noch ".." benutzen. Die Pruefung "bleibt der Pfad innerhalb der
   Wurzel?" haengte an die Wurzel ein "/" an - bei der Wurzel "/" des
   lokalen Fensters wurde daraus "//", worauf kein Pfad passt. Damit gab
   open_current() immer False zurueck. Die Pruefung steckt jetzt in
   within() und behandelt diesen Fall; die Web-Oberflaeche benutzt
   dieselbe Funktion.

2. "Snapshots vom LXC aufrufen geht nicht": der Container hatte gar keine
   brauchbaren Snapshots. In seiner Konfiguration standen nur zwei
   Eintraege mit snapstate "prepare" und "delete" - Reste aus der Zeit, in
   der jeder Snapshot am cfs-Lock scheiterte. Auf dem Storage liegt
   dahinter nichts (rbd snap ls ist leer), oeffnen kann man sie also
   nicht.

   Solche Eintraege werden jetzt als das behandelt, was sie sind:
     * Explorer und Web-Oberflaeche bieten sie nicht mehr zum Oeffnen an
       und nennen den Aufraeumbefehl.
     * "pvesnap list" markiert sie mit "!".
     * Beim Aufraeumen entfernt der Dienst sie zuerst, und zwar mit
       --force, weil sich ein Eintrag ohne Storage-Snapshot sonst nicht
       loeschen laesst. Vorher waeren sie ewig liegen geblieben und haetten
       zusaetzlich die Zahl der behaltenen Snapshots verfaelscht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
duffyduck
2026-07-31 10:21:59 +02:00
co-authored by Claude Opus 5
parent 963463a1a1
commit 3acbebce6f
6 changed files with 85 additions and 11 deletions
+23 -3
View File
@@ -31,6 +31,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from .. import __version__
from ..proxmox import Proxmox, ProxmoxError, pvesh_available
from ..explorer import within
from ..snapfs import (Session, SnapfsError, cleanup_leftovers, human_bytes,
list_snapshots)
from .assets import LOGIN_FORM, PAGE, SCRIPT, STYLE
@@ -173,8 +174,7 @@ class AppState:
if not root:
raise ValueError("Es ist kein Snapshot geoeffnet.")
candidate = os.path.realpath(os.path.join(root, (relative or "").lstrip("/")))
root_real = os.path.realpath(root)
if candidate != root_real and not candidate.startswith(root_real + os.sep):
if not within(root, candidate):
raise ValueError("Pfad liegt ausserhalb des Snapshots.")
return candidate
@@ -464,9 +464,20 @@ class Handler(BaseHTTPRequestHandler):
return
rows = []
broken = 0
for snap in list_snapshots(state.proxmox, guest):
when = (datetime.fromtimestamp(snap.snaptime).strftime("%d.%m.%Y %H:%M")
if snap.snaptime else "?")
if not snap.complete:
# Halbfertiger Eintrag aus einem abgebrochenen Lauf - dahinter
# liegt auf dem Storage nichts, was sich oeffnen liesse.
broken += 1
rows.append(
"<tr class='muted'><td class='name'><span class='icon'>⚠</span>%s</td>"
"<td class='when'>%s</td><td class='muted'>unvollstaendig (%s)</td>"
"<td class='muted'>nicht zu oeffnen</td></tr>"
% (esc(snap.name), esc(when), esc(snap.snapstate)))
continue
rows.append(
"<tr><td class='name'><span class='icon'>🕘</span>%s</td>"
"<td class='when'>%s</td><td class='muted'>%s</td>"
@@ -479,15 +490,24 @@ class Handler(BaseHTTPRequestHandler):
esc(snap.description.replace("\n", " ")[:120]),
guest.vmid, esc(snap.name)))
warning = ""
if broken:
warning = ("<div class='note warn'>%d Eintrag/Eintraege sind "
"unvollstaendig - sie stammen aus abgebrochenen Laeufen und "
"enthalten keine Daten. Aufraeumen auf dem Host mit: "
"<code>%s delsnapshot %d &lt;name&gt; --force</code></div>"
% (broken, "pct" if guest.type == "lxc" else "qm", guest.vmid))
body = (
"<p><a href='/'>&larr; Gaeste</a></p>"
"<h2>Snapshots von %s</h2>"
"<div class='note'>Das Oeffnen bindet den Snapshot schreibgeschuetzt "
"ein - das dauert ein paar Sekunden.</div>"
"%s"
"<div class='card'><table><thead><tr><th>Snapshot</th><th class='when'>"
"Erstellt</th><th>Beschreibung</th><th></th></tr></thead><tbody>%s</tbody>"
"</table></div>"
% (esc(guest.label),
% (esc(guest.label), warning,
"".join(rows) or "<tr><td colspan='4' class='empty'>"
"Dieser Gast hat keine Snapshots.</td></tr>"))
self._send(page("Snapshots %s" % guest.label, state, body,