fix: allow network paths

This commit is contained in:
2026-05-27 15:02:32 -04:00
parent 5996443f38
commit ba86f5830f
9 changed files with 342 additions and 196 deletions
+51 -69
View File
@@ -1,12 +1,11 @@
import os
import subprocess
from pathlib import Path
from flask import Flask, Response, jsonify, request, send_from_directory
from flask_cors import CORS
from db_py import count_tests, del_config, get_all_tests, get_config, set_config
from scanner import full_scan
from scanner import full_scan, scan_results_only
from sse_py import broadcast, stream_events
from watcher import start_watching
@@ -14,9 +13,13 @@ PORT = int(os.getenv("PORT", "3001"))
ALLOWED_KEYS = {
"target_dir",
"results_dir",
"results_dir_ref",
"avg_time_coe",
"avg_time_p2p",
"avg_time_p3p",
"smb_username",
"smb_password",
"smb_domain",
}
BASE_DIR = Path(__file__).resolve().parent
@@ -26,6 +29,20 @@ app = Flask(__name__, static_folder=str(DIST_DIR), static_url_path="")
CORS(app)
def _apply_smb_env_from_config():
mapping = {
"SMB_USERNAME": get_config("smb_username"),
"SMB_PASSWORD": get_config("smb_password"),
"SMB_DOMAIN": get_config("smb_domain"),
}
for env_key, value in mapping.items():
if value in (None, ""):
os.environ.pop(env_key, None)
else:
os.environ[env_key] = str(value)
@app.get("/api/tests")
def get_tests_route():
completed = request.args.get("completed")
@@ -205,14 +222,16 @@ def set_config_route():
else:
set_config(key, str(value))
if key in {"target_dir", "results_dir"}:
if key in {"target_dir", "results_dir", "results_dir_ref"}:
dirs_changed = True
if dirs_changed:
_apply_smb_env_from_config()
target_dir = get_config("target_dir")
results_dir = get_config("results_dir")
results_dir_ref = get_config("results_dir_ref")
try:
full_scan(target_dir, results_dir)
full_scan(target_dir, results_dir, results_dir_ref)
start_watching(target_dir, results_dir)
except Exception as exc:
print(f"[config] fullScan error: {exc}")
@@ -224,18 +243,21 @@ def set_config_route():
broadcast({"type": "update"})
return jsonify({"ok": True, "testCount": len(tests), "completedCount": completed})
broadcast({"type": "update"})
return jsonify({"ok": True, "testCount": None, "completedCount": None})
@app.post("/api/config/rescan")
def rescan_route():
_apply_smb_env_from_config()
target_dir = get_config("target_dir")
results_dir = get_config("results_dir")
results_dir_ref = get_config("results_dir_ref")
if not target_dir or not results_dir:
return jsonify({"error": "Directories not configured"}), 400
try:
full_scan(target_dir, results_dir)
full_scan(target_dir, results_dir, results_dir_ref)
start_watching(target_dir, results_dir)
except Exception as exc:
print(f"[config] rescan error: {exc}")
@@ -248,6 +270,27 @@ def rescan_route():
return jsonify({"ok": True, "testCount": len(tests), "completedCount": completed})
@app.post("/api/config/rescan-results")
def rescan_results_route():
_apply_smb_env_from_config()
results_dir = get_config("results_dir")
results_dir_ref = get_config("results_dir_ref")
if not results_dir:
return jsonify({"error": "Results directory not configured"}), 400
try:
scan_results_only(results_dir, results_dir_ref)
except Exception as exc:
print(f"[config] rescan-results error: {exc}")
return jsonify({"error": f"Scan failed: {exc}"}), 500
tests = get_all_tests()
completed = len([t for t in tests if t.get("completed")])
print(f"[config] Results rescan complete -> {len(tests)} tests, {completed} completed")
broadcast({"type": "update"})
return jsonify({"ok": True, "testCount": len(tests), "completedCount": completed})
@app.get("/api/browse")
def browse_route():
def _normalize_request_path(path):
@@ -258,48 +301,6 @@ def browse_route():
p = p.replace("/", "\\")
return p
def _is_unc_host_only(path):
if os.name != "nt" or not path:
return False
p = path.rstrip("\\")
if not p.startswith("\\\\"):
return False
remainder = p[2:]
return bool(remainder) and "\\" not in remainder
def _shares_for_unc_host(host):
# Enumerate SMB shares with native Windows tooling for host-only UNC paths.
proc = subprocess.run(
["net", "view", f"\\\\{host}"],
capture_output=True,
text=True,
timeout=10,
check=False,
)
if proc.returncode != 0:
raise RuntimeError(proc.stderr.strip() or proc.stdout.strip() or "Cannot query network host")
shares = []
in_table = False
for raw_line in proc.stdout.splitlines():
line = raw_line.strip()
if not line:
continue
if line.startswith("---"):
in_table = True
continue
if not in_table:
continue
if line.lower().startswith("the command completed successfully"):
break
first = line.split()[0]
if first and first.lower() not in {"share", "name"}:
shares.append(first)
unique = sorted(set(shares), key=str.lower)
return [{"name": share, "path": f"\\\\{host}\\{share}"} for share in unique]
def _configured_roots():
raw = os.getenv("BROWSE_ROOTS", "").strip()
if not raw:
@@ -326,17 +327,6 @@ def browse_route():
continue
return False
def _is_unc_host_allowed(host, roots):
if not roots:
return True
host_prefix = os.path.normcase(f"\\\\{host}\\")
host_exact = host_prefix.rstrip("\\")
for root in roots:
normalized_root = os.path.normcase(root)
if normalized_root == host_exact or normalized_root.startswith(host_prefix):
return True
return False
roots = _configured_roots()
req_path = _normalize_request_path(request.args.get("path"))
@@ -359,16 +349,6 @@ def browse_route():
return jsonify({"path": None, "parent": None, "dirs": dirs})
if _is_unc_host_only(req_path):
host = req_path.rstrip("\\")[2:]
if not _is_unc_host_allowed(host, roots):
return jsonify({"error": "Path is outside allowed browse roots"}), 403
try:
dirs = _shares_for_unc_host(host)
except Exception as exc:
return jsonify({"error": f"Cannot list shares for host: {exc}"}), 403
return jsonify({"path": f"\\\\{host}", "parent": None, "dirs": dirs})
if not os.path.exists(req_path):
return jsonify({"error": "Path does not exist"}), 400
if not os.path.isdir(req_path):
@@ -416,8 +396,10 @@ def static_or_spa(path=""):
def bootstrap():
_apply_smb_env_from_config()
target_dir = get_config("target_dir")
results_dir = get_config("results_dir")
results_dir_ref = get_config("results_dir_ref")
if target_dir and results_dir:
existing = count_tests()
@@ -425,7 +407,7 @@ def bootstrap():
print(f"[server] Resuming from DB -> {existing} tests already loaded.")
else:
print("[server] No cached data, scanning directories...")
full_scan(target_dir, results_dir)
full_scan(target_dir, results_dir, results_dir_ref)
tests = get_all_tests()
completed = len([t for t in tests if t.get("completed")])
print(f"[server] Scan complete -> {len(tests)} tests found, {completed} completed")