mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-15 17:01:58 +00:00
BREAKING CHANGE: Complete refactor of architecture to prepare BJORN V2 release, APIs, assets, and UI, webapp, logics, attacks, a lot of new features...
This commit is contained in:
293
db_utils/actions.py
Normal file
293
db_utils/actions.py
Normal file
@@ -0,0 +1,293 @@
|
||||
# db_utils/actions.py
|
||||
# Action definition and management operations
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
from functools import lru_cache
|
||||
from typing import Any, Dict, List, Optional
|
||||
import logging
|
||||
|
||||
from logger import Logger
|
||||
|
||||
logger = Logger(name="db_utils.actions", level=logging.DEBUG)
|
||||
|
||||
|
||||
class ActionOps:
|
||||
"""Action definition and configuration operations"""
|
||||
|
||||
def __init__(self, base):
|
||||
self.base = base
|
||||
|
||||
def create_tables(self):
|
||||
"""Create actions table"""
|
||||
self.base.execute("""
|
||||
CREATE TABLE IF NOT EXISTS actions (
|
||||
b_class TEXT PRIMARY KEY,
|
||||
b_module TEXT NOT NULL,
|
||||
b_port INTEGER,
|
||||
b_status TEXT,
|
||||
b_parent TEXT,
|
||||
b_args TEXT,
|
||||
b_description TEXT,
|
||||
b_name TEXT,
|
||||
b_author TEXT,
|
||||
b_version TEXT,
|
||||
b_icon TEXT,
|
||||
b_docs_url TEXT,
|
||||
b_examples TEXT,
|
||||
b_action TEXT DEFAULT 'normal',
|
||||
b_service TEXT,
|
||||
b_trigger TEXT,
|
||||
b_requires TEXT,
|
||||
b_priority INTEGER DEFAULT 50,
|
||||
b_tags TEXT,
|
||||
b_timeout INTEGER DEFAULT 300,
|
||||
b_max_retries INTEGER DEFAULT 3,
|
||||
b_cooldown INTEGER DEFAULT 0,
|
||||
b_rate_limit TEXT,
|
||||
b_stealth_level INTEGER DEFAULT 5,
|
||||
b_risk_level TEXT DEFAULT 'medium',
|
||||
b_enabled INTEGER DEFAULT 1
|
||||
);
|
||||
""")
|
||||
logger.debug("Actions table created/verified")
|
||||
|
||||
# =========================================================================
|
||||
# ACTION CRUD OPERATIONS
|
||||
# =========================================================================
|
||||
|
||||
def sync_actions(self, actions):
|
||||
"""Sync action definitions to database"""
|
||||
if not actions:
|
||||
return
|
||||
|
||||
def _as_int(x, default=None):
|
||||
if x is None:
|
||||
return default
|
||||
if isinstance(x, (list, tuple)):
|
||||
x = x[0] if x else default
|
||||
try:
|
||||
return int(x)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
def _as_str(x, default=None):
|
||||
if x is None:
|
||||
return default
|
||||
if isinstance(x, (list, tuple, set, dict)):
|
||||
try:
|
||||
return json.dumps(list(x) if not isinstance(x, dict) else x, ensure_ascii=False)
|
||||
except Exception:
|
||||
return default
|
||||
return str(x)
|
||||
|
||||
def _as_json(x):
|
||||
if x is None:
|
||||
return None
|
||||
if isinstance(x, str):
|
||||
xs = x.strip()
|
||||
if (xs.startswith("{") and xs.endswith("}")) or (xs.startswith("[") and xs.endswith("]")):
|
||||
return xs
|
||||
return json.dumps(x, ensure_ascii=False)
|
||||
try:
|
||||
return json.dumps(x, ensure_ascii=False)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
with self.base.transaction():
|
||||
for a in actions:
|
||||
# Normalize fields
|
||||
b_service = a.get("b_service")
|
||||
if isinstance(b_service, (list, tuple, set, dict)):
|
||||
b_service = json.dumps(list(b_service) if not isinstance(b_service, dict) else b_service, ensure_ascii=False)
|
||||
|
||||
b_tags = a.get("b_tags")
|
||||
if isinstance(b_tags, (list, tuple, set, dict)):
|
||||
b_tags = json.dumps(list(b_tags) if not isinstance(b_tags, dict) else b_tags, ensure_ascii=False)
|
||||
|
||||
b_trigger = a.get("b_trigger")
|
||||
if isinstance(b_trigger, (list, tuple, set, dict)):
|
||||
b_trigger = json.dumps(b_trigger, ensure_ascii=False)
|
||||
|
||||
b_requires = a.get("b_requires")
|
||||
if isinstance(b_requires, (list, tuple, set, dict)):
|
||||
b_requires = json.dumps(b_requires, ensure_ascii=False)
|
||||
|
||||
b_args_json = _as_json(a.get("b_args"))
|
||||
|
||||
# Enriched metadata
|
||||
b_name = _as_str(a.get("b_name"))
|
||||
b_description = _as_str(a.get("b_description"))
|
||||
b_author = _as_str(a.get("b_author"))
|
||||
b_version = _as_str(a.get("b_version"))
|
||||
b_icon = _as_str(a.get("b_icon"))
|
||||
b_docs_url = _as_str(a.get("b_docs_url"))
|
||||
b_examples = _as_json(a.get("b_examples"))
|
||||
|
||||
# Typed fields
|
||||
b_port = _as_int(a.get("b_port"))
|
||||
b_priority = _as_int(a.get("b_priority"), 50)
|
||||
b_timeout = _as_int(a.get("b_timeout"), 300)
|
||||
b_max_retries = _as_int(a.get("b_max_retries"), 3)
|
||||
b_cooldown = _as_int(a.get("b_cooldown"), 0)
|
||||
b_stealth_level = _as_int(a.get("b_stealth_level"), 5)
|
||||
b_enabled = _as_int(a.get("b_enabled"), 1)
|
||||
b_rate_limit = _as_str(a.get("b_rate_limit"))
|
||||
b_risk_level = _as_str(a.get("b_risk_level"), "medium")
|
||||
|
||||
self.base.execute("""
|
||||
INSERT INTO actions (
|
||||
b_class,b_module,b_port,b_status,b_parent,
|
||||
b_action,b_service,b_trigger,b_requires,b_priority,
|
||||
b_tags,b_timeout,b_max_retries,b_cooldown,b_rate_limit,
|
||||
b_stealth_level,b_risk_level,b_enabled,
|
||||
b_args,
|
||||
b_name, b_description, b_author, b_version, b_icon, b_docs_url, b_examples
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
|
||||
?,?,?,?,?,?,?)
|
||||
ON CONFLICT(b_class) DO UPDATE SET
|
||||
b_module = excluded.b_module,
|
||||
b_port = COALESCE(excluded.b_port, actions.b_port),
|
||||
b_status = COALESCE(excluded.b_status, actions.b_status),
|
||||
b_parent = COALESCE(excluded.b_parent, actions.b_parent),
|
||||
b_action = COALESCE(excluded.b_action, actions.b_action),
|
||||
b_service = COALESCE(excluded.b_service, actions.b_service),
|
||||
b_trigger = COALESCE(excluded.b_trigger, actions.b_trigger),
|
||||
b_requires = COALESCE(excluded.b_requires, actions.b_requires),
|
||||
b_priority = COALESCE(excluded.b_priority, actions.b_priority),
|
||||
b_tags = COALESCE(excluded.b_tags, actions.b_tags),
|
||||
b_timeout = COALESCE(excluded.b_timeout, actions.b_timeout),
|
||||
b_max_retries = COALESCE(excluded.b_max_retries, actions.b_max_retries),
|
||||
b_cooldown = COALESCE(excluded.b_cooldown, actions.b_cooldown),
|
||||
b_rate_limit = COALESCE(excluded.b_rate_limit, actions.b_rate_limit),
|
||||
b_stealth_level = COALESCE(excluded.b_stealth_level, actions.b_stealth_level),
|
||||
b_risk_level = COALESCE(excluded.b_risk_level, actions.b_risk_level),
|
||||
b_enabled = COALESCE(excluded.b_enabled, actions.b_enabled),
|
||||
b_args = COALESCE(excluded.b_args, actions.b_args),
|
||||
b_name = COALESCE(excluded.b_name, actions.b_name),
|
||||
b_description = COALESCE(excluded.b_description, actions.b_description),
|
||||
b_author = COALESCE(excluded.b_author, actions.b_author),
|
||||
b_version = COALESCE(excluded.b_version, actions.b_version),
|
||||
b_icon = COALESCE(excluded.b_icon, actions.b_icon),
|
||||
b_docs_url = COALESCE(excluded.b_docs_url, actions.b_docs_url),
|
||||
b_examples = COALESCE(excluded.b_examples, actions.b_examples)
|
||||
""", (
|
||||
a.get("b_class"),
|
||||
a.get("b_module"),
|
||||
b_port,
|
||||
a.get("b_status"),
|
||||
a.get("b_parent"),
|
||||
a.get("b_action", "normal"),
|
||||
b_service,
|
||||
b_trigger,
|
||||
b_requires,
|
||||
b_priority,
|
||||
b_tags,
|
||||
b_timeout,
|
||||
b_max_retries,
|
||||
b_cooldown,
|
||||
b_rate_limit,
|
||||
b_stealth_level,
|
||||
b_risk_level,
|
||||
b_enabled,
|
||||
b_args_json,
|
||||
b_name,
|
||||
b_description,
|
||||
b_author,
|
||||
b_version,
|
||||
b_icon,
|
||||
b_docs_url,
|
||||
b_examples
|
||||
))
|
||||
|
||||
# Update action counter in stats
|
||||
action_count_row = self.base.query_one("SELECT COUNT(*) as cnt FROM actions WHERE b_enabled = 1")
|
||||
if action_count_row:
|
||||
try:
|
||||
self.base.execute("""
|
||||
UPDATE stats
|
||||
SET actions_count = ?
|
||||
WHERE id = 1
|
||||
""", (action_count_row['cnt'],))
|
||||
except sqlite3.OperationalError:
|
||||
# Column doesn't exist yet, add it
|
||||
self.base.execute("ALTER TABLE stats ADD COLUMN actions_count INTEGER DEFAULT 0")
|
||||
self.base.execute("""
|
||||
UPDATE stats
|
||||
SET actions_count = ?
|
||||
WHERE id = 1
|
||||
""", (action_count_row['cnt'],))
|
||||
|
||||
logger.info(f"Synchronized {len(actions)} actions")
|
||||
|
||||
def list_actions(self):
|
||||
"""List all action definitions ordered by class name"""
|
||||
return self.base.query("SELECT * FROM actions ORDER BY b_class;")
|
||||
|
||||
def list_studio_actions(self):
|
||||
"""List all studio action definitions"""
|
||||
return self.base.query("SELECT * FROM actions_studio ORDER BY b_class;")
|
||||
|
||||
def get_action_by_class(self, b_class: str) -> dict | None:
|
||||
"""Get action by class name"""
|
||||
rows = self.base.query("SELECT * FROM actions WHERE b_class=? LIMIT 1;", (b_class,))
|
||||
return rows[0] if rows else None
|
||||
|
||||
def delete_action(self, b_class: str) -> None:
|
||||
"""Delete action by class name"""
|
||||
self.base.execute("DELETE FROM actions WHERE b_class=?;", (b_class,))
|
||||
|
||||
def upsert_simple_action(self, *, b_class: str, b_module: str, **kw) -> None:
|
||||
"""Minimal upsert of an action by reusing sync_actions"""
|
||||
rec = {"b_class": b_class, "b_module": b_module}
|
||||
rec.update(kw)
|
||||
self.sync_actions([rec])
|
||||
|
||||
def list_action_cards(self) -> list[dict]:
|
||||
"""Lightweight descriptor of actions for card-based UIs"""
|
||||
rows = self.base.query("""
|
||||
SELECT b_class, COALESCE(b_enabled, 0) AS b_enabled
|
||||
FROM actions
|
||||
ORDER BY b_class;
|
||||
""")
|
||||
out = []
|
||||
for r in rows:
|
||||
cls = r["b_class"]
|
||||
enabled = int(r["b_enabled"]) # 0 reste 0
|
||||
out.append({
|
||||
"name": cls,
|
||||
"image": f"/actions/actions_icons/{cls}.png",
|
||||
"enabled": enabled,
|
||||
})
|
||||
return out
|
||||
|
||||
# def list_action_cards(self) -> list[dict]:
|
||||
# """Lightweight descriptor of actions for card-based UIs"""
|
||||
# rows = self.base.query("""
|
||||
# SELECT b_class, b_enabled
|
||||
# FROM actions
|
||||
# ORDER BY b_class;
|
||||
# """)
|
||||
# out = []
|
||||
# for r in rows:
|
||||
# cls = r["b_class"]
|
||||
# out.append({
|
||||
# "name": cls,
|
||||
# "image": f"/actions/actions_icons/{cls}.png",
|
||||
# "enabled": int(r.get("b_enabled", 1) or 1),
|
||||
# })
|
||||
# return out
|
||||
|
||||
@lru_cache(maxsize=32)
|
||||
def get_action_definition(self, b_class: str) -> Optional[Dict[str, Any]]:
|
||||
"""Cached lookup of an action definition by class name"""
|
||||
row = self.base.query("SELECT * FROM actions WHERE b_class=? LIMIT 1;", (b_class,))
|
||||
if not row:
|
||||
return None
|
||||
r = row[0]
|
||||
if r.get("b_args"):
|
||||
try:
|
||||
r["b_args"] = json.loads(r["b_args"])
|
||||
except Exception:
|
||||
pass
|
||||
return r
|
||||
Reference in New Issue
Block a user