mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-12-16 11:25:48 -07:00
Initial Development of Device Filter System
This commit is contained in:
@@ -40,10 +40,11 @@ from .assemblies.execution import register_execution
|
||||
from .devices import routes as device_routes
|
||||
from .devices.approval import register_admin_endpoints
|
||||
from .devices.management import register_management
|
||||
from .filters import management as filters_management
|
||||
from .scheduled_jobs import management as scheduled_jobs_management
|
||||
from .server import info as server_info, log_management
|
||||
|
||||
DEFAULT_API_GROUPS: Sequence[str] = ("core", "auth", "tokens", "enrollment", "devices", "server", "assemblies", "scheduled_jobs")
|
||||
DEFAULT_API_GROUPS: Sequence[str] = ("core", "auth", "tokens", "enrollment", "devices", "filters", "server", "assemblies", "scheduled_jobs")
|
||||
|
||||
_SERVER_SCOPE_PATTERN = re.compile(r"\\b(?:scope|context|agent_context)=([A-Za-z0-9_-]+)", re.IGNORECASE)
|
||||
_SERVER_AGENT_ID_PATTERN = re.compile(r"\\bagent_id=([^\\s,]+)", re.IGNORECASE)
|
||||
@@ -265,6 +266,9 @@ def _register_devices(app: Flask, adapters: EngineServiceAdapters) -> None:
|
||||
register_admin_endpoints(app, adapters)
|
||||
device_routes.register_agents(app, adapters)
|
||||
|
||||
def _register_filters(app: Flask, adapters: EngineServiceAdapters) -> None:
|
||||
filters_management.register_filters(app, adapters)
|
||||
|
||||
|
||||
def _register_scheduled_jobs(app: Flask, adapters: EngineServiceAdapters) -> None:
|
||||
scheduled_jobs_management.register_management(app, adapters)
|
||||
@@ -285,6 +289,7 @@ _GROUP_REGISTRARS: Mapping[str, Callable[[Flask, EngineServiceAdapters], None]]
|
||||
"tokens": _register_tokens,
|
||||
"enrollment": _register_enrollment,
|
||||
"devices": _register_devices,
|
||||
"filters": _register_filters,
|
||||
"server": _register_server,
|
||||
"assemblies": _register_assemblies,
|
||||
"scheduled_jobs": _register_scheduled_jobs,
|
||||
@@ -309,6 +314,8 @@ def register_api(app: Flask, context: EngineContext) -> None:
|
||||
|
||||
enabled_groups: Iterable[str] = context.api_groups or DEFAULT_API_GROUPS
|
||||
normalized = [group.strip().lower() for group in enabled_groups if group]
|
||||
if "filters" not in normalized:
|
||||
normalized.append("filters")
|
||||
adapters: Optional[EngineServiceAdapters] = None
|
||||
|
||||
for group in normalized:
|
||||
|
||||
@@ -1,8 +1,196 @@
|
||||
# ======================================================
|
||||
# Data\Engine\services\API\filters\management.py
|
||||
# Description: Placeholder for filter management endpoints.
|
||||
# Description: Device filter management endpoints backed by the Engine database.
|
||||
#
|
||||
# API Endpoints (if applicable): None
|
||||
# API Endpoints (if applicable):
|
||||
# - GET /api/device_filters
|
||||
# - GET /api/device_filters/<filter_id>
|
||||
# - POST /api/device_filters
|
||||
# - PUT /api/device_filters/<filter_id>
|
||||
# ======================================================
|
||||
|
||||
"Placeholder for API module filters/management.py."
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
import time
|
||||
from typing import Any, Dict, TYPE_CHECKING
|
||||
|
||||
from flask import Blueprint, Flask, jsonify, request
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import EngineServiceAdapters
|
||||
|
||||
|
||||
def register_filters(app: Flask, adapters: "EngineServiceAdapters") -> None:
|
||||
"""
|
||||
Register device filter endpoints backed by the Engine database.
|
||||
"""
|
||||
|
||||
def _conn() -> sqlite3.Connection:
|
||||
conn = adapters.db_conn_factory()
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def _row_to_filter(row: sqlite3.Row) -> Dict[str, Any]:
|
||||
try:
|
||||
groups = json.loads(row["criteria_json"] or "[]")
|
||||
except Exception:
|
||||
groups = []
|
||||
scope = (row["site_scope"] or "global").lower()
|
||||
site_value = row["site_scope"] or row["site_name"]
|
||||
return {
|
||||
"id": row["id"],
|
||||
"name": row["name"],
|
||||
"scope": scope,
|
||||
"type": "site" if scope == "scoped" else "global",
|
||||
"applyToAllSites": scope != "scoped",
|
||||
"site": site_value,
|
||||
"site_name": site_value,
|
||||
"site_scope": site_value,
|
||||
"groups": groups,
|
||||
"last_edited_by": row["last_edited_by"],
|
||||
"last_edited": row["last_edited"],
|
||||
"created_at": row["created_at"],
|
||||
"updated_at": row["updated_at"],
|
||||
}
|
||||
|
||||
def _select_filter(filter_id: str) -> Dict[str, Any] | None:
|
||||
conn = _conn()
|
||||
try:
|
||||
cur = conn.execute(
|
||||
"""
|
||||
SELECT id, name, site_scope, site_name,
|
||||
criteria_json, last_edited_by, last_edited, created_at, updated_at
|
||||
FROM device_filters
|
||||
WHERE id = ?
|
||||
""",
|
||||
(filter_id,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return _row_to_filter(row) if row else None
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def _normalize_payload(data: Dict[str, Any], existing: Dict[str, Any] | None = None) -> Dict[str, Any]:
|
||||
now_iso = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||
base = existing or {}
|
||||
scope = (data.get("site_scope") or data.get("scope") or data.get("type") or base.get("scope") or "global").lower()
|
||||
site_name = (
|
||||
data.get("site")
|
||||
or data.get("site_name")
|
||||
or data.get("target_site")
|
||||
or base.get("site")
|
||||
or base.get("site_name")
|
||||
)
|
||||
site_scope = scope if scope in ("global", "scoped") else (base.get("site_scope") or "global")
|
||||
groups = data.get("groups")
|
||||
if not isinstance(groups, list):
|
||||
groups = base.get("groups") if isinstance(base.get("groups"), list) else []
|
||||
last_edited = data.get("last_edited") or base.get("last_edited") or now_iso
|
||||
return {
|
||||
"name": (data.get("name") or base.get("name") or "Unnamed Filter").strip(),
|
||||
"site_scope": site_scope,
|
||||
"site_name": site_name,
|
||||
"site_scope": site_scope,
|
||||
"criteria_json": json.dumps(groups or []),
|
||||
"last_edited_by": data.get("last_edited_by") or data.get("owner") or base.get("last_edited_by") or "Unknown",
|
||||
"last_edited": last_edited,
|
||||
"created_at": base.get("created_at") or now_iso,
|
||||
"updated_at": now_iso,
|
||||
}
|
||||
|
||||
blueprint = Blueprint("device_filters", __name__, url_prefix="/api/device_filters")
|
||||
|
||||
@blueprint.route("", methods=["GET"])
|
||||
def list_filters() -> Any:
|
||||
conn = _conn()
|
||||
try:
|
||||
cur = conn.execute(
|
||||
"""
|
||||
SELECT id, name, site_scope, site_name,
|
||||
criteria_json, last_edited_by, last_edited, created_at, updated_at
|
||||
FROM device_filters
|
||||
ORDER BY COALESCE(updated_at, created_at) DESC, id DESC
|
||||
"""
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return jsonify({"filters": [_row_to_filter(r) for r in rows]})
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@blueprint.route("/<filter_id>", methods=["GET"])
|
||||
def get_filter(filter_id: str) -> Any:
|
||||
record = _select_filter(filter_id)
|
||||
if not record:
|
||||
return jsonify({"error": "Filter not found"}), 404
|
||||
return jsonify({"filter": record})
|
||||
|
||||
@blueprint.route("", methods=["POST"])
|
||||
def create_filter() -> Any:
|
||||
data = request.get_json(silent=True) or {}
|
||||
payload = _normalize_payload(data)
|
||||
conn = _conn()
|
||||
try:
|
||||
cur = conn.execute(
|
||||
"""
|
||||
INSERT INTO device_filters (
|
||||
name, site_scope, site_name, criteria_json,
|
||||
last_edited_by, last_edited, created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
payload["name"],
|
||||
payload["site_scope"],
|
||||
payload["site_name"],
|
||||
payload["criteria_json"],
|
||||
payload["last_edited_by"],
|
||||
payload["last_edited"],
|
||||
payload["created_at"],
|
||||
payload["updated_at"],
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
record = _select_filter(cur.lastrowid)
|
||||
adapters.service_log("device_filters", f"Created device filter '{payload['name']}'.")
|
||||
return jsonify({"filter": record or payload}), 201
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@blueprint.route("/<filter_id>", methods=["PUT"])
|
||||
def update_filter(filter_id: str) -> Any:
|
||||
existing = _select_filter(filter_id)
|
||||
if not existing:
|
||||
return jsonify({"error": "Filter not found"}), 404
|
||||
data = request.get_json(silent=True) or {}
|
||||
payload = _normalize_payload(data, existing=existing)
|
||||
conn = _conn()
|
||||
try:
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE device_filters
|
||||
SET name = ?, site_scope = ?, site_name = ?, criteria_json = ?,
|
||||
last_edited_by = ?, last_edited = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
""",
|
||||
(
|
||||
payload["name"],
|
||||
payload["site_scope"],
|
||||
payload["site_name"],
|
||||
payload["criteria_json"],
|
||||
payload["last_edited_by"],
|
||||
payload["last_edited"],
|
||||
payload["updated_at"],
|
||||
filter_id,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
record = _select_filter(filter_id)
|
||||
adapters.service_log("device_filters", f"Updated device filter '{payload['name']}' (id={filter_id}).")
|
||||
return jsonify({"filter": record or payload})
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
app.register_blueprint(blueprint)
|
||||
adapters.service_log("device_filters", "Registered device filter endpoints.")
|
||||
|
||||
Reference in New Issue
Block a user