Initial commit
This commit is contained in:
commit
9c7df76fbd
91 files changed
+36572
No files matched your search
+137
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Scan stream, start/stop, checkpoint, settings, delta
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import threading
|
||||
from flask import Blueprint, jsonify, request
|
||||
from routes import state
|
||||
from app_config import (
|
||||
_save_settings, _load_settings,
|
||||
_load_src_toggles, _save_src_toggles,
|
||||
)
|
||||
from checkpoint import (
|
||||
_checkpoint_key, _load_checkpoint, _clear_checkpoint,
|
||||
_load_delta_tokens, _DELTA_PATH,
|
||||
)
|
||||
|
||||
bp = Blueprint("scan", __name__)
|
||||
|
||||
|
||||
@bp.route("/api/scan/status")
|
||||
def scan_status():
|
||||
"""Lightweight status check — is a scan running? What scan_id?"""
|
||||
import sse as _sse_mod
|
||||
acquired = state._scan_lock.acquire(blocking=False)
|
||||
if acquired:
|
||||
state._scan_lock.release()
|
||||
return jsonify({
|
||||
"running": not acquired,
|
||||
"scan_id": _sse_mod._current_scan_id or None,
|
||||
})
|
||||
|
||||
|
||||
@bp.route("/api/src_toggles", methods=["GET", "POST"])
|
||||
def src_toggles():
|
||||
"""GET: return source toggle state. POST: save."""
|
||||
if request.method == "POST":
|
||||
_save_src_toggles(request.get_json() or {})
|
||||
return jsonify({"ok": True})
|
||||
return jsonify(_load_src_toggles())
|
||||
|
||||
|
||||
@bp.route("/api/scan/start", methods=["POST"])
|
||||
def scan_start():
|
||||
if not state.connector:
|
||||
return jsonify({"error": "not authenticated"}), 401
|
||||
if not state._scan_lock.acquire(blocking=False):
|
||||
return jsonify({"error": "scan already running"}), 409
|
||||
options = request.get_json() or {}
|
||||
state._scan_abort.clear()
|
||||
profile_id = options.pop("profile_id", None)
|
||||
_save_settings({
|
||||
"sources": options.get("sources", []),
|
||||
"user_ids": options.get("user_ids", []),
|
||||
"options": options.get("options", {}),
|
||||
}, profile_id=profile_id)
|
||||
def _run():
|
||||
from scan_engine import run_scan
|
||||
try:
|
||||
run_scan(options)
|
||||
finally:
|
||||
state._scan_lock.release()
|
||||
threading.Thread(target=_run, daemon=True).start()
|
||||
return jsonify({"status": "started"})
|
||||
|
||||
|
||||
@bp.route("/api/scan/stop", methods=["POST"])
|
||||
def scan_stop():
|
||||
state._scan_abort.set()
|
||||
return jsonify({"status": "stopping"})
|
||||
|
||||
|
||||
@bp.route("/api/scan/checkpoint", methods=["POST"])
|
||||
def scan_checkpoint_info():
|
||||
"""Return info about any saved checkpoint for the given scan options.
|
||||
If check_only=true, just reports whether a scan is currently running."""
|
||||
options = request.get_json() or {}
|
||||
if options.get("check_only"):
|
||||
acquired = state._scan_lock.acquire(blocking=False)
|
||||
if acquired:
|
||||
state._scan_lock.release()
|
||||
return jsonify({"running": not acquired})
|
||||
key = _checkpoint_key(options)
|
||||
cp = _load_checkpoint(key)
|
||||
if not cp:
|
||||
return jsonify({"exists": False})
|
||||
return jsonify({
|
||||
"exists": True,
|
||||
"scanned_count": len(cp.get("scanned_ids", [])),
|
||||
"flagged_count": len(cp.get("flagged", [])),
|
||||
"started_at": cp.get("meta", {}).get("started_at"),
|
||||
})
|
||||
|
||||
|
||||
@bp.route("/api/scan/clear_checkpoint", methods=["POST"])
|
||||
def scan_clear_checkpoint():
|
||||
"""Discard any saved checkpoint so the next scan starts fresh."""
|
||||
_clear_checkpoint()
|
||||
return jsonify({"status": "cleared"})
|
||||
|
||||
|
||||
@bp.route("/api/settings/save", methods=["POST"])
|
||||
def settings_save():
|
||||
"""Persist scan settings so they can be reused by --headless mode."""
|
||||
payload = request.get_json() or {}
|
||||
_save_settings(payload)
|
||||
return jsonify({"status": "saved"})
|
||||
|
||||
|
||||
@bp.route("/api/settings/load")
|
||||
def settings_load():
|
||||
"""Return previously saved scan settings (for --headless setup guidance)."""
|
||||
s = _load_settings()
|
||||
if not s:
|
||||
return jsonify({"exists": False})
|
||||
return jsonify({"exists": True, "settings": s})
|
||||
|
||||
|
||||
@bp.route("/api/delta/status")
|
||||
def delta_status():
|
||||
"""Return info about stored delta tokens."""
|
||||
tokens = _load_delta_tokens()
|
||||
return jsonify({
|
||||
"count": len(tokens),
|
||||
"keys": list(tokens.keys()),
|
||||
"exists": len(tokens) > 0,
|
||||
})
|
||||
|
||||
|
||||
@bp.route("/api/delta/clear", methods=["POST"])
|
||||
def delta_clear():
|
||||
"""Discard all stored delta tokens (next scan will be a full scan)."""
|
||||
try:
|
||||
if _DELTA_PATH.exists():
|
||||
_DELTA_PATH.unlink()
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return jsonify({"status": "cleared"})
|
||||
Reference in new issue
Block a user