Specs stay at repo root (cross-VM). Move deploy and code into logical projects with README per domain, updated manifest.yaml, and symlinks at legacy paths for VM122 backward compatibility.
105 lines
3.1 KiB
Python
105 lines
3.1 KiB
Python
"""Persistência e consulta de módulos activos."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from app.modules.registry import MODULE_BY_ID, MODULES, role_module_defaults
|
|
|
|
MODULES_PATH = Path(os.getenv("DESK_MODULES_PATH", "/data/desk_modules.json"))
|
|
|
|
|
|
def _disabled_from_env() -> set[str]:
|
|
raw = os.getenv("DESK_MODULES_DISABLED", "").strip()
|
|
if not raw:
|
|
return set()
|
|
return {part.strip() for part in raw.split(",") if part.strip()}
|
|
|
|
|
|
def _load_overrides() -> dict[str, bool]:
|
|
if not MODULES_PATH.exists():
|
|
return {}
|
|
try:
|
|
data = json.loads(MODULES_PATH.read_text(encoding="utf-8"))
|
|
except (json.JSONDecodeError, OSError):
|
|
return {}
|
|
overrides: dict[str, bool] = {}
|
|
for key, val in data.items():
|
|
if isinstance(val, dict) and "enabled" in val:
|
|
overrides[key] = bool(val["enabled"])
|
|
elif isinstance(val, bool):
|
|
overrides[key] = val
|
|
return overrides
|
|
|
|
|
|
def _save_overrides(overrides: dict[str, bool]) -> None:
|
|
MODULES_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
payload = {mid: {"enabled": overrides[mid]} for mid in overrides if mid in MODULE_BY_ID}
|
|
MODULES_PATH.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
|
|
|
|
def is_module_enabled(module_id: str) -> bool:
|
|
mod = MODULE_BY_ID.get(module_id)
|
|
if not mod:
|
|
return False
|
|
if mod.locked:
|
|
return True
|
|
env_disabled = _disabled_from_env()
|
|
if module_id in env_disabled:
|
|
return False
|
|
overrides = _load_overrides()
|
|
if module_id in overrides:
|
|
return overrides[module_id]
|
|
return mod.default_enabled
|
|
|
|
|
|
def set_module_enabled(module_id: str, enabled: bool) -> None:
|
|
mod = MODULE_BY_ID.get(module_id)
|
|
if not mod:
|
|
raise KeyError(f"unknown module: {module_id}")
|
|
if mod.locked:
|
|
raise ValueError(f"module {module_id} is locked")
|
|
overrides = _load_overrides()
|
|
for m in MODULES:
|
|
if m.id not in overrides:
|
|
overrides[m.id] = is_module_enabled(m.id)
|
|
overrides[module_id] = enabled
|
|
_save_overrides(overrides)
|
|
|
|
|
|
def is_module_enabled_for_role(module_id: str, role: str) -> bool:
|
|
if not is_module_enabled(module_id):
|
|
return False
|
|
defaults = role_module_defaults(role)
|
|
if defaults is None:
|
|
return True
|
|
return module_id in defaults
|
|
|
|
|
|
def list_modules(role: str | None = None) -> list[dict]:
|
|
items = []
|
|
for mod in MODULES:
|
|
global_enabled = is_module_enabled(mod.id)
|
|
role_enabled = is_module_enabled_for_role(mod.id, role) if role else global_enabled
|
|
items.append(
|
|
{
|
|
"id": mod.id,
|
|
"label": mod.label,
|
|
"description": mod.description,
|
|
"locked": mod.locked,
|
|
"nav_views": list(mod.nav_views),
|
|
"enabled": global_enabled,
|
|
"enabled_for_role": role_enabled,
|
|
}
|
|
)
|
|
return items
|
|
|
|
|
|
def enabled_nav_views() -> set[str]:
|
|
views: set[str] = set()
|
|
for mod in MODULES:
|
|
if is_module_enabled(mod.id):
|
|
views.update(mod.nav_views)
|
|
return views
|