mirror of
https://github.com/smittix/intercept.git
synced 2026-07-08 01:28:13 -07:00
96172ca593
First-time run of ruff-format via pre-commit hook normalises quote style, trailing commas, and whitespace across 188 Python files. No logic changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
184 lines
6.3 KiB
Python
184 lines
6.3 KiB
Python
"""
|
|
TSCM Schedule Routes
|
|
|
|
Handles /schedules/* endpoints for automated sweep scheduling.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
|
|
from flask import jsonify, request
|
|
|
|
from routes.tscm import (
|
|
_get_schedule_timezone,
|
|
_next_run_from_cron,
|
|
_start_sweep_internal,
|
|
tscm_bp,
|
|
)
|
|
from utils.database import (
|
|
create_tscm_schedule,
|
|
delete_tscm_schedule,
|
|
get_all_tscm_schedules,
|
|
get_tscm_schedule,
|
|
update_tscm_schedule,
|
|
)
|
|
|
|
logger = logging.getLogger("intercept.tscm")
|
|
|
|
|
|
@tscm_bp.route("/schedules", methods=["GET"])
|
|
def list_schedules():
|
|
"""List all TSCM sweep schedules."""
|
|
enabled_param = request.args.get("enabled")
|
|
enabled = None
|
|
if enabled_param is not None:
|
|
enabled = enabled_param.lower() in ("1", "true", "yes")
|
|
|
|
schedules = get_all_tscm_schedules(enabled=enabled, limit=200)
|
|
return jsonify(
|
|
{
|
|
"status": "success",
|
|
"count": len(schedules),
|
|
"schedules": schedules,
|
|
}
|
|
)
|
|
|
|
|
|
@tscm_bp.route("/schedules", methods=["POST"])
|
|
def create_schedule():
|
|
"""Create a new sweep schedule."""
|
|
data = request.get_json() or {}
|
|
name = (data.get("name") or "").strip()
|
|
cron_expression = (data.get("cron_expression") or "").strip()
|
|
sweep_type = data.get("sweep_type", "standard")
|
|
baseline_id = data.get("baseline_id")
|
|
zone_name = data.get("zone_name")
|
|
enabled = bool(data.get("enabled", True))
|
|
notify_on_threat = bool(data.get("notify_on_threat", True))
|
|
notify_email = data.get("notify_email")
|
|
|
|
if not name:
|
|
return jsonify({"status": "error", "message": "Schedule name required"}), 400
|
|
if not cron_expression:
|
|
return jsonify({"status": "error", "message": "cron_expression required"}), 400
|
|
|
|
next_run = None
|
|
if enabled:
|
|
try:
|
|
tz = _get_schedule_timezone(zone_name)
|
|
next_local = _next_run_from_cron(cron_expression, datetime.now(tz))
|
|
next_run = next_local.astimezone(timezone.utc).isoformat() if next_local else None
|
|
except Exception as e:
|
|
return jsonify({"status": "error", "message": f"Invalid cron: {e}"}), 400
|
|
|
|
schedule_id = create_tscm_schedule(
|
|
name=name,
|
|
cron_expression=cron_expression,
|
|
sweep_type=sweep_type,
|
|
baseline_id=baseline_id,
|
|
zone_name=zone_name,
|
|
enabled=enabled,
|
|
notify_on_threat=notify_on_threat,
|
|
notify_email=notify_email,
|
|
next_run=next_run,
|
|
)
|
|
schedule = get_tscm_schedule(schedule_id)
|
|
return jsonify({"status": "success", "message": "Schedule created", "schedule": schedule})
|
|
|
|
|
|
@tscm_bp.route("/schedules/<int:schedule_id>", methods=["PUT", "PATCH"])
|
|
def update_schedule(schedule_id: int):
|
|
"""Update a sweep schedule."""
|
|
schedule = get_tscm_schedule(schedule_id)
|
|
if not schedule:
|
|
return jsonify({"status": "error", "message": "Schedule not found"}), 404
|
|
|
|
data = request.get_json() or {}
|
|
updates: dict[str, Any] = {}
|
|
|
|
for key in ("name", "cron_expression", "sweep_type", "baseline_id", "zone_name", "notify_email"):
|
|
if key in data:
|
|
updates[key] = data[key]
|
|
|
|
if "baseline_id" in updates and updates["baseline_id"] in ("", None):
|
|
updates["baseline_id"] = None
|
|
|
|
if "enabled" in data:
|
|
updates["enabled"] = 1 if data["enabled"] else 0
|
|
if "notify_on_threat" in data:
|
|
updates["notify_on_threat"] = 1 if data["notify_on_threat"] else 0
|
|
|
|
# Recalculate next_run when cron/zone/enabled changes
|
|
if any(k in updates for k in ("cron_expression", "zone_name", "enabled")):
|
|
if updates.get("enabled", schedule.get("enabled", 1)):
|
|
cron_expr = updates.get("cron_expression", schedule.get("cron_expression", ""))
|
|
zone_name = updates.get("zone_name", schedule.get("zone_name"))
|
|
try:
|
|
tz = _get_schedule_timezone(zone_name)
|
|
next_local = _next_run_from_cron(cron_expr, datetime.now(tz))
|
|
updates["next_run"] = next_local.astimezone(timezone.utc).isoformat() if next_local else None
|
|
except Exception as e:
|
|
return jsonify({"status": "error", "message": f"Invalid cron: {e}"}), 400
|
|
else:
|
|
updates["next_run"] = None
|
|
|
|
if not updates:
|
|
return jsonify({"status": "error", "message": "No updates provided"}), 400
|
|
|
|
update_tscm_schedule(schedule_id, **updates)
|
|
schedule = get_tscm_schedule(schedule_id)
|
|
return jsonify({"status": "success", "schedule": schedule})
|
|
|
|
|
|
@tscm_bp.route("/schedules/<int:schedule_id>", methods=["DELETE"])
|
|
def delete_schedule(schedule_id: int):
|
|
"""Delete a sweep schedule."""
|
|
success = delete_tscm_schedule(schedule_id)
|
|
if not success:
|
|
return jsonify({"status": "error", "message": "Schedule not found"}), 404
|
|
return jsonify({"status": "success", "message": "Schedule deleted"})
|
|
|
|
|
|
@tscm_bp.route("/schedules/<int:schedule_id>/run", methods=["POST"])
|
|
def run_schedule_now(schedule_id: int):
|
|
"""Trigger a scheduled sweep immediately."""
|
|
schedule = get_tscm_schedule(schedule_id)
|
|
if not schedule:
|
|
return jsonify({"status": "error", "message": "Schedule not found"}), 404
|
|
|
|
result = _start_sweep_internal(
|
|
sweep_type=schedule.get("sweep_type") or "standard",
|
|
baseline_id=schedule.get("baseline_id"),
|
|
wifi_enabled=True,
|
|
bt_enabled=True,
|
|
rf_enabled=True,
|
|
wifi_interface="",
|
|
bt_interface="",
|
|
sdr_device=None,
|
|
verbose_results=False,
|
|
)
|
|
|
|
if result.get("status") != "success":
|
|
status_code = result.pop("http_status", 400)
|
|
return jsonify(result), status_code
|
|
|
|
# Update schedule run timestamps
|
|
cron_expr = schedule.get("cron_expression") or ""
|
|
tz = _get_schedule_timezone(schedule.get("zone_name"))
|
|
now_utc = datetime.now(timezone.utc)
|
|
try:
|
|
next_local = _next_run_from_cron(cron_expr, datetime.now(tz))
|
|
except Exception:
|
|
next_local = None
|
|
|
|
update_tscm_schedule(
|
|
schedule_id,
|
|
last_run=now_utc.isoformat(),
|
|
next_run=next_local.astimezone(timezone.utc).isoformat() if next_local else None,
|
|
)
|
|
|
|
return jsonify(result)
|