feat: enhance permission logging and display detailed errors in UI
All checks were successful
Docker Build / build (push) Successful in 14s
All checks were successful
Docker Build / build (push) Successful in 14s
This commit is contained in:
parent
85f7a18883
commit
45a1f0ae93
2 changed files with 67 additions and 7 deletions
|
|
@ -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):
|
||||||
os.remove(source_path)
|
try:
|
||||||
log(f"☢️ [DESTRUCT] Deleted source: {source_path}")
|
os.remove(source_path)
|
||||||
source_deleted = True
|
log(f"☢️ [DESTRUCT] Deleted source: {source_path}")
|
||||||
break
|
source_deleted = True
|
||||||
|
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()):
|
||||||
parent.rmdir()
|
try:
|
||||||
log(f"🧹 [CLEANUP] Removed empty folder: {parent}")
|
parent.rmdir()
|
||||||
|
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():
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue