feat: enhance permission logging and display detailed errors in UI
All checks were successful
Docker Build / build (push) Successful in 14s

This commit is contained in:
wander 2026-03-08 05:25:11 -04:00
parent 85f7a18883
commit 45a1f0ae93
2 changed files with 67 additions and 7 deletions

View file

@ -23,6 +23,7 @@ SOURCE_DIR = Path("/app/source")
TARGET_DIR = Path("/app/target") TARGET_DIR = Path("/app/target")
HIDDEN_DIR = Path("/app/hidden") HIDDEN_DIR = Path("/app/hidden")
IMPORT_DIR = Path("/app/import") IMPORT_DIR = Path("/app/import")
DATA_DIR = Path("/app/data")
HEADERS = {"Authorization": f"Token {API_TOKEN}"} HEADERS = {"Authorization": f"Token {API_TOKEN}"}
# Serve static files from ui/dist # Serve static files from ui/dist
@ -1147,6 +1148,8 @@ def api_recovery_delete_batch():
fail_count = 0 fail_count = 0
errors = [] errors = []
log(f"🔥 Batch Delete started. Items: {len(paths)}, Destruct: {destruct}")
# Refresh metadata for destruct mode # Refresh metadata for destruct mode
video_map = fetch_all_metadata() if destruct else {} video_map = fetch_all_metadata() if destruct else {}
@ -1159,10 +1162,15 @@ def api_recovery_delete_batch():
if meta.get('path') == path or meta.get('filesystem_path') == path: if meta.get('path') == path or meta.get('filesystem_path') == path:
source_path = meta.get('filesystem_path') source_path = meta.get('filesystem_path')
if source_path and os.path.exists(source_path): if source_path and os.path.exists(source_path):
try:
os.remove(source_path) os.remove(source_path)
log(f"☢️ [DESTRUCT] Deleted source: {source_path}") log(f"☢️ [DESTRUCT] Deleted source: {source_path}")
source_deleted = True source_deleted = True
break break
except Exception as se:
log(f"❌ [DESTRUCT] Failed to delete source {source_path}: {se}")
raise Exception(f"Source deletion failed: {se}")
if not source_deleted: if not source_deleted:
log(f"⚠️ [DESTRUCT] Source not found for: {path}") log(f"⚠️ [DESTRUCT] Source not found for: {path}")
@ -1173,13 +1181,16 @@ def api_recovery_delete_batch():
shutil.rmtree(p) shutil.rmtree(p)
else: else:
p.unlink() p.unlink()
log(f"🗑️ Deleted target: {path}")
# 3. Cleanup empty parent # 3. Cleanup empty parent
parent = p.parent parent = p.parent
if parent != Path(TARGET_DIR) and parent != Path(HIDDEN_DIR): if parent != Path(TARGET_DIR) and parent != Path(HIDDEN_DIR):
if parent.exists() and not any(parent.iterdir()): if parent.exists() and not any(parent.iterdir()):
try:
parent.rmdir() parent.rmdir()
log(f"🧹 [CLEANUP] Removed empty folder: {parent}") log(f"🧹 [CLEANUP] Removed empty folder: {parent}")
except: pass
success_count += 1 success_count += 1
except Exception as e: except Exception as e:
@ -1195,6 +1206,52 @@ def api_recovery_delete_batch():
"errors": errors[:5] "errors": errors[:5]
}) })
@app.route("/api/system/check-permissions", methods=["GET"])
@requires_auth
def api_check_permissions():
results = {}
test_dirs = [
("source", SOURCE_DIR),
("target", TARGET_DIR),
("hidden", HIDDEN_DIR),
("data", DATA_DIR)
]
log("🔍 Running System Permission Check...")
for name, path in test_dirs:
if not path:
results[name] = {"status": "unset", "writeable": False}
continue
p = Path(path)
if not p.exists():
results[name] = {"status": "missing", "writeable": False, "message": "Directory does not exist"}
log(f"{name} ({path}): MISSING")
continue
test_file = p / f".write_test_{os.getpid()}"
try:
# Try to write
log(f" 🧪 Testing write on {name}...")
if test_file.exists(): test_file.unlink() # Cleanup old failure
with open(test_file, "w") as f:
f.write("test")
# Try to delete
test_file.unlink()
results[name] = {"status": "ok", "writeable": True}
log(f"{name} ({path}): WRITEABLE")
except Exception as e:
msg = str(e)
results[name] = {"status": "error", "writeable": False, "message": msg}
log(f"{name} ({path}): READ-ONLY or PERMISSION DENIED - {msg}")
# Identify if it is literally "Read-only file system"
if "Read-only file system" in msg:
log(f" 🚨 POSITIVE R/O MOUNT DETECTED for {name}")
return jsonify(results)
@app.route("/api/recovery/delete", methods=["POST"]) @app.route("/api/recovery/delete", methods=["POST"])
@requires_auth @requires_auth
def api_recovery_delete(): def api_recovery_delete():

View file

@ -248,11 +248,14 @@
<div class="text-xs text-red-200"> <div class="text-xs text-red-200">
<span class="font-bold uppercase">Filesystem Alert:</span> <span class="font-bold uppercase">Filesystem Alert:</span>
{#if sourceRO && targetRO} {#if sourceRO && targetRO}
Both Source AND Target directories are currently <span class="text-white underline">READ-ONLY</span>. Deletion and reorganization will fail. Both Source AND Target directories are currently <span class="text-white underline">READ-ONLY</span>.
<div class="mt-1 opacity-70 italic">Errors: {permissions?.source?.message} | {permissions?.target?.message}</div>
{:else if sourceRO} {:else if sourceRO}
Source archive is <span class="text-white underline">READ-ONLY</span>. Destruct Mode will fail. Source archive is <span class="text-white underline">READ-ONLY</span>. Destruct Mode will fail.
<div class="mt-1 opacity-70 italic">Error: {permissions?.source?.message}</div>
{:else} {:else}
Target library is <span class="text-white underline">READ-ONLY</span>. Symlink cleanup will fail. Target library is <span class="text-white underline">READ-ONLY</span>. Symlink cleanup will fail.
<div class="mt-1 opacity-70 italic">Error: {permissions?.target?.message}</div>
{/if} {/if}
</div> </div>
<button on:click={checkPermissions} class="ml-auto text-[10px] bg-red-500/20 hover:bg-red-500/40 px-2 py-1 rounded border border-red-500/30 transition-colors"> <button on:click={checkPermissions} class="ml-auto text-[10px] bg-red-500/20 hover:bg-red-500/40 px-2 py-1 rounded border border-red-500/30 transition-colors">