mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-15 08:52:00 +00:00
- 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.
163 lines
5.4 KiB
Python
163 lines
5.4 KiB
Python
"""
|
|
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)
|