Spaces:
Configuration error
Configuration error
Debug for storage management made
Browse files- index.html +7 -4
- main.py +82 -29
index.html
CHANGED
|
@@ -1596,7 +1596,7 @@
|
|
| 1596 |
</label>
|
| 1597 |
</div>
|
| 1598 |
<div class="admin-actions">
|
| 1599 |
-
<button type="button" id="wipeDataBtn" class="danger-btn">Delete
|
| 1600 |
</div>
|
| 1601 |
</div>
|
| 1602 |
<div id="adminPanelStatus"></div>
|
|
@@ -3626,7 +3626,7 @@
|
|
| 3626 |
}
|
| 3627 |
|
| 3628 |
const proceed = window.confirm(
|
| 3629 |
-
'This will permanently delete all
|
| 3630 |
);
|
| 3631 |
if (!proceed) {
|
| 3632 |
return;
|
|
@@ -3644,12 +3644,15 @@
|
|
| 3644 |
|
| 3645 |
const data = await response.json();
|
| 3646 |
if (!response.ok || !data.ok) {
|
| 3647 |
-
setAdminStatus(data.message || 'Failed to delete
|
| 3648 |
return;
|
| 3649 |
}
|
| 3650 |
|
|
|
|
|
|
|
|
|
|
| 3651 |
setAdminStatus(
|
| 3652 |
-
`Deleted
|
| 3653 |
);
|
| 3654 |
adminDeleteConfirmInput.value = '';
|
| 3655 |
} catch (error) {
|
|
|
|
| 1596 |
</label>
|
| 1597 |
</div>
|
| 1598 |
<div class="admin-actions">
|
| 1599 |
+
<button type="button" id="wipeDataBtn" class="danger-btn">Delete managed storage contents</button>
|
| 1600 |
</div>
|
| 1601 |
</div>
|
| 1602 |
<div id="adminPanelStatus"></div>
|
|
|
|
| 3626 |
}
|
| 3627 |
|
| 3628 |
const proceed = window.confirm(
|
| 3629 |
+
'This will permanently delete all managed storage files/folders. Continue?'
|
| 3630 |
);
|
| 3631 |
if (!proceed) {
|
| 3632 |
return;
|
|
|
|
| 3644 |
|
| 3645 |
const data = await response.json();
|
| 3646 |
if (!response.ok || !data.ok) {
|
| 3647 |
+
setAdminStatus(data.message || 'Failed to delete managed storage contents.', true);
|
| 3648 |
return;
|
| 3649 |
}
|
| 3650 |
|
| 3651 |
+
const targetInfo = Array.isArray(data.deleted_targets) && data.deleted_targets.length > 0
|
| 3652 |
+
? ` Targets: ${data.deleted_targets.join(', ')}.`
|
| 3653 |
+
: '';
|
| 3654 |
setAdminStatus(
|
| 3655 |
+
`Deleted storage contents (files: ${data.deleted_files ?? 0}, folders: ${data.deleted_dirs ?? 0}).${targetInfo}`
|
| 3656 |
);
|
| 3657 |
adminDeleteConfirmInput.value = '';
|
| 3658 |
} catch (error) {
|
main.py
CHANGED
|
@@ -2890,20 +2890,66 @@ def get_admin_password() -> Optional[str]:
|
|
| 2890 |
return password.strip()
|
| 2891 |
return None
|
| 2892 |
|
| 2893 |
-
def
|
| 2894 |
-
|
| 2895 |
-
|
| 2896 |
-
|
| 2897 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2898 |
|
| 2899 |
def is_safe_admin_purge_root(path: str) -> bool:
|
| 2900 |
normalized = os.path.abspath(path)
|
| 2901 |
# Must be an absolute subpath, never root.
|
| 2902 |
if normalized in {"/", ""}:
|
| 2903 |
return False
|
| 2904 |
-
if
|
| 2905 |
-
return
|
| 2906 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2907 |
|
| 2908 |
def delete_directory_contents(path: str) -> Dict[str, int]:
|
| 2909 |
deleted_files = 0
|
|
@@ -2962,39 +3008,46 @@ async def admin_wipe_data(
|
|
| 2962 |
status_code=400
|
| 2963 |
)
|
| 2964 |
|
| 2965 |
-
|
| 2966 |
-
|
|
|
|
| 2967 |
return JSONResponse(
|
| 2968 |
-
content={"ok": False, "message":
|
| 2969 |
status_code=400
|
| 2970 |
)
|
| 2971 |
|
| 2972 |
-
if not
|
|
|
|
| 2973 |
return JSONResponse(
|
| 2974 |
-
content={
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2975 |
status_code=404
|
| 2976 |
)
|
| 2977 |
|
| 2978 |
-
if not os.path.isdir(purge_root):
|
| 2979 |
-
return JSONResponse(
|
| 2980 |
-
content={"ok": False, "message": f"Purge path is not a directory: {purge_root}"},
|
| 2981 |
-
status_code=400
|
| 2982 |
-
)
|
| 2983 |
-
|
| 2984 |
try:
|
| 2985 |
-
|
| 2986 |
-
|
| 2987 |
-
|
| 2988 |
-
|
| 2989 |
-
result
|
| 2990 |
-
|
| 2991 |
-
|
|
|
|
|
|
|
| 2992 |
return JSONResponse(
|
| 2993 |
content={
|
| 2994 |
"ok": True,
|
| 2995 |
-
"message": f"Deleted contents
|
| 2996 |
-
"deleted_files":
|
| 2997 |
-
"deleted_dirs":
|
|
|
|
|
|
|
|
|
|
| 2998 |
},
|
| 2999 |
status_code=200
|
| 3000 |
)
|
|
|
|
| 2890 |
return password.strip()
|
| 2891 |
return None
|
| 2892 |
|
| 2893 |
+
def get_root_database_dirs() -> List[str]:
|
| 2894 |
+
roots = [os.path.abspath(os.path.join(get_base_directory(), "root databases"))]
|
| 2895 |
+
try:
|
| 2896 |
+
from vectorstore import CHROMA_DB_BASE
|
| 2897 |
+
roots.append(os.path.abspath(CHROMA_DB_BASE))
|
| 2898 |
+
except Exception:
|
| 2899 |
+
pass
|
| 2900 |
+
# Preserve order while removing duplicates.
|
| 2901 |
+
return list(dict.fromkeys(roots))
|
| 2902 |
|
| 2903 |
def is_safe_admin_purge_root(path: str) -> bool:
|
| 2904 |
normalized = os.path.abspath(path)
|
| 2905 |
# Must be an absolute subpath, never root.
|
| 2906 |
if normalized in {"/", ""}:
|
| 2907 |
return False
|
| 2908 |
+
if normalized.startswith("/data"):
|
| 2909 |
+
return True
|
| 2910 |
+
allowed_names = {"uploaded files", "root databases"}
|
| 2911 |
+
return os.path.basename(normalized) in allowed_names
|
| 2912 |
+
|
| 2913 |
+
def resolve_admin_purge_targets() -> Tuple[List[str], List[str]]:
|
| 2914 |
+
"""
|
| 2915 |
+
Resolve safe directories to purge.
|
| 2916 |
+
Priority:
|
| 2917 |
+
1) ADMIN_PURGE_PATH when configured
|
| 2918 |
+
2) /data when mounted
|
| 2919 |
+
3) known storage folders used by this app
|
| 2920 |
+
Returns (existing_targets, skipped_non_existing_targets)
|
| 2921 |
+
"""
|
| 2922 |
+
configured = (os.environ.get("ADMIN_PURGE_PATH") or "").strip()
|
| 2923 |
+
if configured:
|
| 2924 |
+
configured_path = os.path.abspath(configured)
|
| 2925 |
+
if not is_safe_admin_purge_root(configured_path):
|
| 2926 |
+
raise ValueError(f"Unsafe ADMIN_PURGE_PATH resolved to '{configured_path}'.")
|
| 2927 |
+
if os.path.exists(configured_path):
|
| 2928 |
+
if not os.path.isdir(configured_path):
|
| 2929 |
+
raise ValueError(f"ADMIN_PURGE_PATH is not a directory: {configured_path}")
|
| 2930 |
+
return [configured_path], []
|
| 2931 |
+
return [], [configured_path]
|
| 2932 |
+
|
| 2933 |
+
data_path = os.path.abspath(DEFAULT_ADMIN_PURGE_PATH)
|
| 2934 |
+
if os.path.isdir(data_path):
|
| 2935 |
+
return [data_path], []
|
| 2936 |
+
|
| 2937 |
+
candidates: List[str] = [os.path.abspath(UPLOAD_DIR)]
|
| 2938 |
+
candidates.extend(get_root_database_dirs())
|
| 2939 |
+
# Remove duplicates while preserving order.
|
| 2940 |
+
candidates = list(dict.fromkeys(candidates))
|
| 2941 |
+
|
| 2942 |
+
existing_targets: List[str] = []
|
| 2943 |
+
skipped_targets: List[str] = []
|
| 2944 |
+
for candidate in candidates:
|
| 2945 |
+
if not is_safe_admin_purge_root(candidate):
|
| 2946 |
+
continue
|
| 2947 |
+
if os.path.exists(candidate):
|
| 2948 |
+
if os.path.isdir(candidate):
|
| 2949 |
+
existing_targets.append(candidate)
|
| 2950 |
+
else:
|
| 2951 |
+
skipped_targets.append(candidate)
|
| 2952 |
+
return existing_targets, skipped_targets
|
| 2953 |
|
| 2954 |
def delete_directory_contents(path: str) -> Dict[str, int]:
|
| 2955 |
deleted_files = 0
|
|
|
|
| 3008 |
status_code=400
|
| 3009 |
)
|
| 3010 |
|
| 3011 |
+
try:
|
| 3012 |
+
targets, skipped_targets = resolve_admin_purge_targets()
|
| 3013 |
+
except ValueError as e:
|
| 3014 |
return JSONResponse(
|
| 3015 |
+
content={"ok": False, "message": str(e)},
|
| 3016 |
status_code=400
|
| 3017 |
)
|
| 3018 |
|
| 3019 |
+
if not targets:
|
| 3020 |
+
skipped_text = ", ".join(skipped_targets) if skipped_targets else "none"
|
| 3021 |
return JSONResponse(
|
| 3022 |
+
content={
|
| 3023 |
+
"ok": False,
|
| 3024 |
+
"message": (
|
| 3025 |
+
"No existing managed storage directories found to purge. "
|
| 3026 |
+
f"Checked: {skipped_text}"
|
| 3027 |
+
)
|
| 3028 |
+
},
|
| 3029 |
status_code=404
|
| 3030 |
)
|
| 3031 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3032 |
try:
|
| 3033 |
+
total_deleted_files = 0
|
| 3034 |
+
total_deleted_dirs = 0
|
| 3035 |
+
per_target: Dict[str, Dict[str, int]] = {}
|
| 3036 |
+
for target in targets:
|
| 3037 |
+
result = delete_directory_contents(target)
|
| 3038 |
+
per_target[target] = result
|
| 3039 |
+
total_deleted_files += result["deleted_files"]
|
| 3040 |
+
total_deleted_dirs += result["deleted_dirs"]
|
| 3041 |
+
|
| 3042 |
return JSONResponse(
|
| 3043 |
content={
|
| 3044 |
"ok": True,
|
| 3045 |
+
"message": f"Deleted managed storage contents from {len(targets)} target(s).",
|
| 3046 |
+
"deleted_files": total_deleted_files,
|
| 3047 |
+
"deleted_dirs": total_deleted_dirs,
|
| 3048 |
+
"deleted_targets": targets,
|
| 3049 |
+
"per_target": per_target,
|
| 3050 |
+
"skipped_missing_targets": skipped_targets
|
| 3051 |
},
|
| 3052 |
status_code=200
|
| 3053 |
)
|