"""Flask web layer for the syscall monitor.""" import json import threading from pathlib import Path from flask import Flask, jsonify, redirect, render_template, request, url_for from collector.syscall_tracer import get_tracer BASE_DIR = Path(__file__).resolve().parent.parent CONFIG_PATH = BASE_DIR / "config" / "monitors.json" _config_lock = threading.Lock() def _read_config() -> dict: if not CONFIG_PATH.exists(): return {"syscalls": []} with CONFIG_PATH.open("r", encoding="utf-8") as f: return json.load(f) def _write_config(data: dict) -> None: CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) tmp = CONFIG_PATH.with_suffix(".json.tmp") with tmp.open("w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) tmp.replace(CONFIG_PATH) def create_app() -> Flask: app = Flask(__name__, template_folder="templates", static_folder="static") tracer = get_tracer(CONFIG_PATH) @app.get("/") def index(): cfg = _read_config() return render_template("index.html", syscalls=cfg.get("syscalls", [])) @app.get("/api/counts") def api_counts(): return jsonify(tracer.get_counts()) @app.route("/config", methods=["GET", "POST"]) def config_page(): with _config_lock: cfg = _read_config() syscalls = list(cfg.get("syscalls", [])) if request.method == "POST": action = request.form.get("action", "") name = (request.form.get("name") or "").strip() if action == "add" and name and name not in syscalls: syscalls.append(name) elif action == "remove" and name in syscalls: syscalls.remove(name) cfg["syscalls"] = syscalls _write_config(cfg) return redirect(url_for("config_page")) return render_template("config.html", syscalls=syscalls) return app