UI changes

This commit is contained in:
2026-05-27 10:29:26 -04:00
parent 1ba2aa39d8
commit d560b2e0b4
14 changed files with 237 additions and 28 deletions
+90 -3
View File
@@ -1,4 +1,5 @@
import os
import subprocess
from pathlib import Path
from flask import Flask, Response, jsonify, request, send_from_directory
@@ -29,6 +30,7 @@ CORS(app)
def get_tests_route():
completed = request.args.get("completed")
interference = request.args.get("interference")
throttled = request.args.get("throttled")
device = request.args.get("device")
rotation = request.args.get("rotation")
test_point = request.args.get("testPoint")
@@ -46,6 +48,8 @@ def get_tests_route():
tests = [t for t in tests if t.get("completed") == completed_value]
if interference:
tests = [t for t in tests if t.get("interference") == interference]
if throttled:
tests = [t for t in tests if t.get("throttled") == throttled]
if device:
tests = [t for t in tests if t.get("device") == device]
if rotation:
@@ -72,6 +76,7 @@ def get_tests_route():
@app.get("/api/stats")
def get_stats_route():
tests = get_all_tests()
types = ["COE", "P2P", "P3P"]
total_completed = sum(1 for t in tests if t.get("completed"))
overall = {
@@ -87,10 +92,21 @@ def get_stats_route():
continue
if name not in device_map:
device_map[name] = {"total": 0, "completed": 0}
device_map[name] = {
"total": 0,
"completed": 0,
"byType": {t: {"total": 0, "completed": 0} for t in types},
}
device_map[name]["total"] += 1
interference = test.get("interference")
if interference in device_map[name]["byType"]:
device_map[name]["byType"][interference]["total"] += 1
if test.get("completed"):
device_map[name]["completed"] += 1
if interference in device_map[name]["byType"]:
device_map[name]["byType"][interference]["completed"] += 1
devices = []
for name in sorted(device_map.keys()):
@@ -101,6 +117,7 @@ def get_stats_route():
"total": stats["total"],
"completed": stats["completed"],
"completionRate": (stats["completed"] / stats["total"]) if stats["total"] else 0,
"byType": stats["byType"],
}
)
@@ -110,7 +127,6 @@ def get_stats_route():
if test.get("completed") and duration is not None:
elapsed_seconds += duration
types = ["COE", "P2P", "P3P"]
by_type = {}
estimate_possible = True
estimated_remaining_seconds = 0
@@ -234,6 +250,56 @@ def rescan_route():
@app.get("/api/browse")
def browse_route():
def _normalize_request_path(path):
if not path:
return path
p = path.strip()
if os.name == "nt":
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:
@@ -260,8 +326,19 @@ 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 = request.args.get("path")
req_path = _normalize_request_path(request.args.get("path"))
if not req_path:
if roots:
@@ -282,6 +359,16 @@ 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):