mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-16 09:02:00 +00:00
Add Loki and Sentinel utility classes for web API endpoints
- Implemented LokiUtils class with GET and POST endpoints for managing scripts, jobs, and payloads. - Added SentinelUtils class with GET and POST endpoints for managing events, rules, devices, and notifications. - Both classes include error handling and JSON response formatting.
This commit is contained in:
162
loki/jobs.py
Normal file
162
loki/jobs.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
Loki job manager — tracks HIDScript execution jobs.
|
||||
Each job runs in its own daemon thread.
|
||||
"""
|
||||
import uuid
|
||||
import time
|
||||
import logging
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from threading import Thread, Event
|
||||
|
||||
from logger import Logger
|
||||
|
||||
logger = Logger(name="loki.jobs", level=logging.DEBUG)
|
||||
|
||||
|
||||
class LokiJobManager:
|
||||
"""Manages HIDScript job lifecycle."""
|
||||
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
self._jobs = {} # job_id → job dict
|
||||
self._threads = {} # job_id → Thread
|
||||
self._stops = {} # job_id → Event
|
||||
|
||||
def create_job(self, script_name: str, script_content: str) -> str:
|
||||
"""Create and start a new job. Returns job_id (UUID)."""
|
||||
job_id = str(uuid.uuid4())[:8]
|
||||
now = datetime.now().isoformat()
|
||||
|
||||
job = {
|
||||
"id": job_id,
|
||||
"script_name": script_name,
|
||||
"status": "pending",
|
||||
"output": "",
|
||||
"error": "",
|
||||
"started_at": None,
|
||||
"finished_at": None,
|
||||
"created_at": now,
|
||||
}
|
||||
self._jobs[job_id] = job
|
||||
stop = Event()
|
||||
self._stops[job_id] = stop
|
||||
|
||||
# Persist to DB
|
||||
try:
|
||||
db = self.engine.shared_data.db
|
||||
db.execute(
|
||||
"INSERT INTO loki_jobs (id, script_name, status, created_at) VALUES (?, ?, ?, ?)",
|
||||
(job_id, script_name, "pending", now)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("DB insert job error: %s", e)
|
||||
|
||||
# Start execution thread
|
||||
t = Thread(
|
||||
target=self._run_job,
|
||||
args=(job_id, script_content, stop),
|
||||
daemon=True,
|
||||
name=f"loki-job-{job_id}",
|
||||
)
|
||||
self._threads[job_id] = t
|
||||
t.start()
|
||||
|
||||
logger.info("Job %s created: %s", job_id, script_name)
|
||||
return job_id
|
||||
|
||||
def cancel_job(self, job_id: str) -> bool:
|
||||
"""Cancel a running job."""
|
||||
stop = self._stops.get(job_id)
|
||||
if stop:
|
||||
stop.set()
|
||||
job = self._jobs.get(job_id)
|
||||
if job and job["status"] == "running":
|
||||
job["status"] = "cancelled"
|
||||
job["finished_at"] = datetime.now().isoformat()
|
||||
self._update_db(job_id, "cancelled", job.get("output", ""), "Cancelled by user")
|
||||
logger.info("Job %s cancelled", job_id)
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_all_jobs(self) -> list:
|
||||
"""Return list of all jobs (most recent first)."""
|
||||
jobs = list(self._jobs.values())
|
||||
jobs.sort(key=lambda j: j.get("created_at", ""), reverse=True)
|
||||
return jobs
|
||||
|
||||
def get_job(self, job_id: str) -> dict:
|
||||
"""Get a single job by ID."""
|
||||
return self._jobs.get(job_id)
|
||||
|
||||
def clear_completed(self):
|
||||
"""Remove finished/failed/cancelled jobs from memory."""
|
||||
to_remove = [
|
||||
jid for jid, j in self._jobs.items()
|
||||
if j["status"] in ("succeeded", "failed", "cancelled")
|
||||
]
|
||||
for jid in to_remove:
|
||||
self._jobs.pop(jid, None)
|
||||
self._threads.pop(jid, None)
|
||||
self._stops.pop(jid, None)
|
||||
try:
|
||||
self.engine.shared_data.db.execute(
|
||||
"DELETE FROM loki_jobs WHERE status IN ('succeeded', 'failed', 'cancelled')"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("DB clear jobs error: %s", e)
|
||||
|
||||
@property
|
||||
def running_count(self) -> int:
|
||||
return sum(1 for j in self._jobs.values() if j["status"] == "running")
|
||||
|
||||
# ── Internal ───────────────────────────────────────────────
|
||||
|
||||
def _run_job(self, job_id: str, script_content: str, stop: Event):
|
||||
"""Execute a HIDScript in this thread."""
|
||||
job = self._jobs[job_id]
|
||||
job["status"] = "running"
|
||||
job["started_at"] = datetime.now().isoformat()
|
||||
self._update_db(job_id, "running")
|
||||
|
||||
try:
|
||||
from loki.hidscript import HIDScriptParser
|
||||
parser = HIDScriptParser(self.engine.hid_controller)
|
||||
output_lines = parser.execute(script_content, stop_event=stop, job_id=job_id)
|
||||
|
||||
if stop.is_set():
|
||||
job["status"] = "cancelled"
|
||||
else:
|
||||
job["status"] = "succeeded"
|
||||
|
||||
job["output"] = "\n".join(output_lines)
|
||||
|
||||
except Exception as e:
|
||||
job["status"] = "failed"
|
||||
job["error"] = str(e)
|
||||
job["output"] = traceback.format_exc()
|
||||
logger.error("Job %s failed: %s", job_id, e)
|
||||
|
||||
finally:
|
||||
job["finished_at"] = datetime.now().isoformat()
|
||||
self._update_db(
|
||||
job_id, job["status"],
|
||||
job.get("output", ""),
|
||||
job.get("error", ""),
|
||||
)
|
||||
logger.info("Job %s finished: %s", job_id, job["status"])
|
||||
|
||||
def _update_db(self, job_id: str, status: str, output: str = "", error: str = ""):
|
||||
"""Persist job state to database."""
|
||||
try:
|
||||
db = self.engine.shared_data.db
|
||||
db.execute(
|
||||
"UPDATE loki_jobs SET status=?, output=?, error=?, "
|
||||
"started_at=?, finished_at=? WHERE id=?",
|
||||
(status, output, error,
|
||||
self._jobs.get(job_id, {}).get("started_at"),
|
||||
self._jobs.get(job_id, {}).get("finished_at"),
|
||||
job_id)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("DB update job error: %s", e)
|
||||
Reference in New Issue
Block a user