dashboard-nanobot/backend/api/platform_router.py

697 lines
30 KiB
Python
Raw Normal View History

import time
import shlex
from typing import Any, Dict, Optional
import logging
2026-03-17 19:52:50 +00:00
import httpx
2026-03-17 19:52:50 +00:00
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlmodel import Session, select
2026-03-17 19:52:50 +00:00
from clients.edge.errors import log_edge_failure, summarize_edge_exception
from clients.edge.http import HttpEdgeClient
2026-03-17 19:52:50 +00:00
from core.cache import cache
from core.database import get_session
from models.bot import BotInstance
from providers.target import ProviderTarget
from providers.selector import get_runtime_provider
from schemas.platform import (
ManagedNodeConnectivityResult,
ManagedNodeNativePreflightResult,
ManagedNodePayload,
PlatformSettingsPayload,
SystemSettingPayload,
)
from services.node_registry_service import ManagedNode
2026-03-17 19:52:50 +00:00
from services.platform_service import (
build_node_resource_overview,
2026-03-17 19:52:50 +00:00
build_platform_overview,
create_or_update_system_setting,
delete_system_setting,
get_platform_settings,
list_system_settings,
list_activity_events,
list_usage,
save_platform_settings,
)
router = APIRouter()
logger = logging.getLogger(__name__)
PLATFORM_OVERVIEW_CACHE_KEY = "platform:overview"
PLATFORM_OVERVIEW_CACHE_TTL_SECONDS = 15
PLATFORM_NODES_CACHE_KEY = "platform:nodes:list"
PLATFORM_NODES_CACHE_TTL_SECONDS = 20
def _cached_platform_overview_payload() -> Optional[Dict[str, Any]]:
cached = cache.get_json(PLATFORM_OVERVIEW_CACHE_KEY)
return cached if isinstance(cached, dict) else None
def _store_platform_overview_payload(payload: Dict[str, Any]) -> Dict[str, Any]:
cache.set_json(PLATFORM_OVERVIEW_CACHE_KEY, payload, ttl=PLATFORM_OVERVIEW_CACHE_TTL_SECONDS)
return payload
def _invalidate_platform_overview_cache() -> None:
cache.delete(PLATFORM_OVERVIEW_CACHE_KEY)
def _cached_platform_nodes_payload() -> Optional[Dict[str, Any]]:
cached = cache.get_json(PLATFORM_NODES_CACHE_KEY)
if not isinstance(cached, dict):
return None
items = cached.get("items")
if not isinstance(items, list):
return None
return {"items": items}
def _store_platform_nodes_payload(items: list[Dict[str, Any]]) -> Dict[str, Any]:
payload = {"items": items}
cache.set_json(PLATFORM_NODES_CACHE_KEY, payload, ttl=PLATFORM_NODES_CACHE_TTL_SECONDS)
return payload
def _invalidate_platform_nodes_cache() -> None:
cache.delete(PLATFORM_NODES_CACHE_KEY)
def _normalize_node_payload(payload: ManagedNodePayload) -> ManagedNodePayload:
normalized_node_id = str(payload.node_id or "").strip().lower()
if not normalized_node_id:
raise HTTPException(status_code=400, detail="node_id is required")
transport_kind = str(payload.transport_kind or "edge").strip().lower() or "edge"
if transport_kind != "edge":
raise HTTPException(status_code=400, detail="Only edge transport is supported")
runtime_kind = str(payload.runtime_kind or "docker").strip().lower() or "docker"
core_adapter = str(payload.core_adapter or "nanobot").strip().lower() or "nanobot"
native_sandbox_mode = _normalize_native_sandbox_mode(payload.native_sandbox_mode)
base_url = str(payload.base_url or "").strip()
if transport_kind == "edge" and not base_url:
raise HTTPException(status_code=400, detail="base_url is required for edge nodes")
return payload.model_copy(
update={
"node_id": normalized_node_id,
"display_name": str(payload.display_name or normalized_node_id).strip() or normalized_node_id,
"base_url": base_url,
"auth_token": str(payload.auth_token or "").strip(),
"transport_kind": transport_kind,
"runtime_kind": runtime_kind,
"core_adapter": core_adapter,
"workspace_root": str(payload.workspace_root or "").strip(),
"native_command": str(payload.native_command or "").strip(),
"native_workdir": str(payload.native_workdir or "").strip(),
"native_sandbox_mode": native_sandbox_mode,
}
)
def _normalize_native_sandbox_mode(raw_value: Any) -> str:
text = str(raw_value or "").strip().lower()
if text in {"workspace", "sandbox", "strict"}:
return "workspace"
if text in {"full_access", "full-access", "danger-full-access", "escape"}:
return "full_access"
return "inherit"
def _managed_node_from_payload(payload: ManagedNodePayload) -> ManagedNode:
normalized = _normalize_node_payload(payload)
return ManagedNode(
node_id=normalized.node_id,
display_name=normalized.display_name,
base_url=normalized.base_url,
enabled=bool(normalized.enabled),
auth_token=normalized.auth_token,
metadata={
"transport_kind": normalized.transport_kind,
"runtime_kind": normalized.runtime_kind,
"core_adapter": normalized.core_adapter,
"workspace_root": normalized.workspace_root,
"native_command": normalized.native_command,
"native_workdir": normalized.native_workdir,
"native_sandbox_mode": normalized.native_sandbox_mode,
},
)
def _node_status(node: ManagedNode, *, refresh_failed: bool = False) -> str:
if not bool(node.enabled):
return "disabled"
transport_kind = str((node.metadata or {}).get("transport_kind") or "edge").strip().lower()
if transport_kind != "edge":
return "unknown"
if refresh_failed:
return "offline"
return "online" if node.last_seen_at else "unknown"
def _serialize_node(node: ManagedNode, *, refresh_failed: bool = False) -> Dict[str, Any]:
metadata = dict(node.metadata or {})
return {
"node_id": node.node_id,
"display_name": node.display_name,
"base_url": node.base_url,
"enabled": bool(node.enabled),
"transport_kind": str(metadata.get("transport_kind") or ""),
"runtime_kind": str(metadata.get("runtime_kind") or ""),
"core_adapter": str(metadata.get("core_adapter") or ""),
"workspace_root": str(metadata.get("workspace_root") or ""),
"native_command": str(metadata.get("native_command") or ""),
"native_workdir": str(metadata.get("native_workdir") or ""),
"native_sandbox_mode": str(metadata.get("native_sandbox_mode") or "inherit"),
"metadata": metadata,
"capabilities": dict(node.capabilities or {}),
"resources": dict(getattr(node, "resources", {}) or {}),
"last_seen_at": node.last_seen_at,
"status": _node_status(node, refresh_failed=refresh_failed),
}
def _test_edge_connectivity(resolve_edge_client, node: ManagedNode) -> ManagedNodeConnectivityResult:
started = time.perf_counter()
try:
client = resolve_edge_client(
ProviderTarget(
node_id=node.node_id,
transport_kind="edge",
runtime_kind=str((node.metadata or {}).get("runtime_kind") or "docker"),
core_adapter=str((node.metadata or {}).get("core_adapter") or "nanobot"),
)
)
node_self = _edge_node_self_with_native_preflight(client=client, node=node)
latency_ms = max(1, int((time.perf_counter() - started) * 1000))
return ManagedNodeConnectivityResult(
ok=True,
status="online",
latency_ms=latency_ms,
detail="dashboard-edge reachable",
node_self=node_self,
)
except Exception as exc:
latency_ms = max(1, int((time.perf_counter() - started) * 1000))
return ManagedNodeConnectivityResult(
ok=False,
status="offline",
latency_ms=latency_ms,
detail=summarize_edge_exception(exc),
node_self=None,
)
def _split_native_command(raw_command: Optional[str]) -> list[str]:
text = str(raw_command or "").strip()
if not text:
return []
try:
return [str(item or "").strip() for item in shlex.split(text) if str(item or "").strip()]
except Exception:
return [text]
def _runtime_native_supported(node_self: Dict[str, Any]) -> bool:
capabilities = dict(node_self.get("capabilities") or {})
runtime_caps = dict(capabilities.get("runtime") or {})
return bool(runtime_caps.get("native") is True)
def _test_edge_native_preflight(
resolve_edge_client,
node: ManagedNode,
*,
native_command: Optional[str] = None,
native_workdir: Optional[str] = None,
) -> ManagedNodeNativePreflightResult:
started = time.perf_counter()
command_hint = _split_native_command(native_command)
workdir_hint = str(native_workdir or "").strip()
try:
client = resolve_edge_client(
ProviderTarget(
node_id=node.node_id,
transport_kind="edge",
runtime_kind=str((node.metadata or {}).get("runtime_kind") or "docker"),
core_adapter=str((node.metadata or {}).get("core_adapter") or "nanobot"),
)
)
node_self = dict(client.heartbeat_node() or {})
preflight = dict(
client.preflight_native(
native_command=native_command,
native_workdir=native_workdir,
)
or {}
)
latency_ms = max(1, int((time.perf_counter() - started) * 1000))
command = [str(item or "").strip() for item in list(preflight.get("command") or []) if str(item or "").strip()]
workdir = str(preflight.get("workdir") or "")
detail = str(preflight.get("detail") or "")
if not detail:
detail = "native launcher ready" if bool(preflight.get("ok")) else "native launcher not ready"
return ManagedNodeNativePreflightResult(
ok=bool(preflight.get("ok")),
status="online",
latency_ms=latency_ms,
detail=detail,
command=command,
workdir=workdir,
command_available=bool(preflight.get("command_available")),
workdir_exists=bool(preflight.get("workdir_exists")),
runtime_native_supported=_runtime_native_supported(node_self),
node_self=node_self,
)
except Exception as exc:
latency_ms = max(1, int((time.perf_counter() - started) * 1000))
return ManagedNodeNativePreflightResult(
ok=False,
status="offline",
latency_ms=latency_ms,
detail=summarize_edge_exception(exc),
command=command_hint,
workdir=workdir_hint,
command_available=False,
workdir_exists=False if workdir_hint else True,
runtime_native_supported=False,
node_self=None,
)
def _edge_node_self_with_native_preflight(*, client: HttpEdgeClient, node: ManagedNode) -> Dict[str, Any]:
node_self = dict(client.heartbeat_node() or {})
metadata = dict(node.metadata or {})
native_command = str(metadata.get("native_command") or "").strip() or None
native_workdir = str(metadata.get("native_workdir") or "").strip() or None
runtime_kind = str(metadata.get("runtime_kind") or "docker").strip().lower()
should_probe = bool(native_command or native_workdir or runtime_kind == "native")
if not should_probe:
return node_self
try:
preflight = dict(client.preflight_native(native_command=native_command, native_workdir=native_workdir) or {})
except Exception as exc:
log_edge_failure(
logger,
key=f"platform-node-native-preflight:{node.node_id}",
exc=exc,
message=f"Failed to run native preflight for node_id={node.node_id}",
)
return node_self
caps = dict(node_self.get("capabilities") or {})
process_caps = dict(caps.get("process") or {})
if preflight.get("command"):
process_caps["command"] = list(preflight.get("command") or [])
process_caps["available"] = bool(preflight.get("ok"))
process_caps["command_available"] = bool(preflight.get("command_available"))
process_caps["workdir_exists"] = bool(preflight.get("workdir_exists"))
process_caps["workdir"] = str(preflight.get("workdir") or "")
process_caps["detail"] = str(preflight.get("detail") or "")
caps["process"] = process_caps
node_self["capabilities"] = caps
node_self["native_preflight"] = preflight
return node_self
2026-03-17 19:52:50 +00:00
def _apply_platform_runtime_changes(request: Request) -> None:
_invalidate_platform_overview_cache()
_invalidate_platform_nodes_cache()
2026-03-17 19:52:50 +00:00
speech_service = getattr(request.app.state, "speech_service", None)
if speech_service is not None and hasattr(speech_service, "reset_runtime"):
speech_service.reset_runtime()
@router.get("/api/platform/overview")
def get_platform_overview(request: Request, session: Session = Depends(get_session)):
cached_payload = _cached_platform_overview_payload()
if cached_payload is not None:
return cached_payload
def _read_runtime(bot):
provider = get_runtime_provider(request.app.state, bot)
status = str(provider.get_runtime_status(bot_id=str(bot.id or "")) or "STOPPED").upper()
runtime = dict(provider.get_resource_snapshot(bot_id=str(bot.id or "")) or {})
runtime.setdefault("docker_status", status)
return status, runtime
payload = build_platform_overview(session, read_runtime=_read_runtime)
return _store_platform_overview_payload(payload)
@router.get("/api/platform/nodes")
def list_platform_nodes(request: Request, session: Session = Depends(get_session)):
cached_payload = _cached_platform_nodes_payload()
if cached_payload is not None:
return cached_payload
node_registry = getattr(request.app.state, "node_registry_service", None)
if node_registry is None or not hasattr(node_registry, "list_nodes"):
return {"items": []}
resolve_edge_client = getattr(request.app.state, "resolve_edge_client", None)
refreshed_items = []
for node in node_registry.list_nodes():
metadata = dict(node.metadata or {})
refresh_failed = False
if (
callable(resolve_edge_client)
and str(metadata.get("transport_kind") or "").strip().lower() == "edge"
and bool(node.enabled)
):
try:
client = resolve_edge_client(
ProviderTarget(
node_id=node.node_id,
transport_kind="edge",
runtime_kind=str(metadata.get("runtime_kind") or "docker"),
core_adapter=str(metadata.get("core_adapter") or "nanobot"),
)
)
node_self = _edge_node_self_with_native_preflight(client=client, node=node)
node = node_registry.mark_node_seen(
session,
node_id=node.node_id,
display_name=str(node.display_name or node_self.get("display_name") or node.node_id),
capabilities=dict(node_self.get("capabilities") or {}),
resources=dict(node_self.get("resources") or {}),
)
except Exception as exc:
refresh_failed = True
log_edge_failure(
logger,
key=f"platform-node-refresh:{node.node_id}",
exc=exc,
message=f"Failed to refresh edge node metadata for node_id={node.node_id}",
)
refreshed_items.append((node, refresh_failed))
items = []
for node, refresh_failed in refreshed_items:
items.append(_serialize_node(node, refresh_failed=refresh_failed))
return _store_platform_nodes_payload(items)
@router.get("/api/platform/nodes/{node_id}")
def get_platform_node(node_id: str, request: Request, session: Session = Depends(get_session)):
normalized_node_id = str(node_id or "").strip().lower()
node_registry = getattr(request.app.state, "node_registry_service", None)
if node_registry is None or not hasattr(node_registry, "get_node"):
raise HTTPException(status_code=500, detail="node registry is unavailable")
node = node_registry.get_node(normalized_node_id)
if node is None:
raise HTTPException(status_code=404, detail=f"Managed node not found: {normalized_node_id}")
return _serialize_node(node)
@router.post("/api/platform/nodes")
def create_platform_node(payload: ManagedNodePayload, request: Request, session: Session = Depends(get_session)):
node_registry = getattr(request.app.state, "node_registry_service", None)
if node_registry is None or not hasattr(node_registry, "get_node"):
raise HTTPException(status_code=500, detail="node registry is unavailable")
normalized = _normalize_node_payload(payload)
if node_registry.get_node(normalized.node_id) is not None:
raise HTTPException(status_code=409, detail=f"Node already exists: {normalized.node_id}")
node = node_registry.upsert_node(session, _managed_node_from_payload(normalized))
_invalidate_platform_overview_cache()
_invalidate_platform_nodes_cache()
return _serialize_node(node)
@router.put("/api/platform/nodes/{node_id}")
def update_platform_node(node_id: str, payload: ManagedNodePayload, request: Request, session: Session = Depends(get_session)):
normalized_node_id = str(node_id or "").strip().lower()
node_registry = getattr(request.app.state, "node_registry_service", None)
if node_registry is None or not hasattr(node_registry, "get_node"):
raise HTTPException(status_code=500, detail="node registry is unavailable")
existing = node_registry.get_node(normalized_node_id)
if existing is None:
raise HTTPException(status_code=404, detail=f"Managed node not found: {normalized_node_id}")
normalized = _normalize_node_payload(payload)
if normalized.node_id != normalized_node_id:
raise HTTPException(status_code=400, detail="node_id cannot be changed")
node = node_registry.upsert_node(
session,
ManagedNode(
node_id=normalized_node_id,
display_name=normalized.display_name,
base_url=normalized.base_url,
enabled=bool(normalized.enabled),
auth_token=normalized.auth_token or existing.auth_token,
metadata={
"transport_kind": normalized.transport_kind,
"runtime_kind": normalized.runtime_kind,
"core_adapter": normalized.core_adapter,
"workspace_root": normalized.workspace_root,
"native_command": normalized.native_command,
"native_workdir": normalized.native_workdir,
"native_sandbox_mode": normalized.native_sandbox_mode,
},
capabilities=dict(existing.capabilities or {}),
resources=dict(existing.resources or {}),
last_seen_at=existing.last_seen_at,
),
)
_invalidate_platform_overview_cache()
_invalidate_platform_nodes_cache()
return _serialize_node(node)
@router.delete("/api/platform/nodes/{node_id}")
def delete_platform_node(node_id: str, request: Request, session: Session = Depends(get_session)):
normalized_node_id = str(node_id or "").strip().lower()
if normalized_node_id == "local":
raise HTTPException(status_code=400, detail="Local node cannot be deleted")
node_registry = getattr(request.app.state, "node_registry_service", None)
if node_registry is None or not hasattr(node_registry, "get_node"):
raise HTTPException(status_code=500, detail="node registry is unavailable")
if node_registry.get_node(normalized_node_id) is None:
raise HTTPException(status_code=404, detail=f"Managed node not found: {normalized_node_id}")
attached_bot_ids = session.exec(select(BotInstance.id).where(BotInstance.node_id == normalized_node_id)).all()
if attached_bot_ids:
raise HTTPException(
status_code=400,
detail=f"Node {normalized_node_id} still has bots assigned: {', '.join(str(item) for item in attached_bot_ids[:5])}",
)
node_registry.delete_node(session, normalized_node_id)
_invalidate_platform_overview_cache()
_invalidate_platform_nodes_cache()
return {"status": "deleted", "node_id": normalized_node_id}
@router.post("/api/platform/nodes/test")
def test_platform_node(payload: ManagedNodePayload, request: Request):
normalized = _normalize_node_payload(payload)
temp_node = _managed_node_from_payload(normalized)
result = _test_edge_connectivity(
lambda _target: HttpEdgeClient(
node=temp_node,
http_client_factory=lambda: httpx.Client(timeout=10.0, trust_env=False),
async_http_client_factory=lambda: httpx.AsyncClient(timeout=10.0, trust_env=False),
),
temp_node,
)
return result.model_dump()
@router.post("/api/platform/nodes/native/preflight")
def test_platform_node_native_preflight(payload: ManagedNodePayload, request: Request):
normalized = _normalize_node_payload(payload)
temp_node = _managed_node_from_payload(normalized)
result = _test_edge_native_preflight(
lambda _target: HttpEdgeClient(
node=temp_node,
http_client_factory=lambda: httpx.Client(timeout=10.0, trust_env=False),
async_http_client_factory=lambda: httpx.AsyncClient(timeout=10.0, trust_env=False),
),
temp_node,
native_command=str(normalized.native_command or "").strip() or None,
native_workdir=str(normalized.native_workdir or "").strip() or None,
)
return result.model_dump()
@router.post("/api/platform/nodes/{node_id}/test")
def test_saved_platform_node(node_id: str, request: Request, session: Session = Depends(get_session)):
normalized_node_id = str(node_id or "").strip().lower()
node_registry = getattr(request.app.state, "node_registry_service", None)
if node_registry is None or not hasattr(node_registry, "get_node"):
raise HTTPException(status_code=500, detail="node registry is unavailable")
node = node_registry.get_node(normalized_node_id)
if node is None:
raise HTTPException(status_code=404, detail=f"Managed node not found: {normalized_node_id}")
transport_kind = str((node.metadata or {}).get("transport_kind") or "edge").strip().lower()
if transport_kind != "edge":
_invalidate_platform_nodes_cache()
raise HTTPException(status_code=400, detail="Only edge transport is supported")
result = _test_edge_connectivity(
lambda target: HttpEdgeClient(
node=node,
http_client_factory=lambda: httpx.Client(timeout=10.0, trust_env=False),
async_http_client_factory=lambda: httpx.AsyncClient(timeout=10.0, trust_env=False),
),
node,
)
if result.ok:
node_registry.mark_node_seen(
session,
node_id=node.node_id,
display_name=str(node.display_name or result.node_self.get("display_name") or node.node_id) if result.node_self else node.display_name,
capabilities=dict(result.node_self.get("capabilities") or {}) if result.node_self else dict(node.capabilities or {}),
resources=dict(result.node_self.get("resources") or {}) if result.node_self else dict(getattr(node, "resources", {}) or {}),
)
_invalidate_platform_nodes_cache()
return result.model_dump()
@router.post("/api/platform/nodes/{node_id}/native/preflight")
def test_saved_platform_node_native_preflight(node_id: str, request: Request, session: Session = Depends(get_session)):
normalized_node_id = str(node_id or "").strip().lower()
node_registry = getattr(request.app.state, "node_registry_service", None)
if node_registry is None or not hasattr(node_registry, "get_node"):
raise HTTPException(status_code=500, detail="node registry is unavailable")
node = node_registry.get_node(normalized_node_id)
if node is None:
raise HTTPException(status_code=404, detail=f"Managed node not found: {normalized_node_id}")
transport_kind = str((node.metadata or {}).get("transport_kind") or "edge").strip().lower()
if transport_kind != "edge":
_invalidate_platform_nodes_cache()
raise HTTPException(status_code=400, detail="Only edge transport is supported")
metadata = dict(node.metadata or {})
result = _test_edge_native_preflight(
lambda _target: HttpEdgeClient(
node=node,
http_client_factory=lambda: httpx.Client(timeout=10.0, trust_env=False),
async_http_client_factory=lambda: httpx.AsyncClient(timeout=10.0, trust_env=False),
),
node,
native_command=str(metadata.get("native_command") or "").strip() or None,
native_workdir=str(metadata.get("native_workdir") or "").strip() or None,
)
if result.status == "online" and result.node_self:
node_registry.mark_node_seen(
session,
node_id=node.node_id,
display_name=str(node.display_name or result.node_self.get("display_name") or node.node_id),
capabilities=dict(result.node_self.get("capabilities") or {}),
resources=dict(result.node_self.get("resources") or {}),
)
_invalidate_platform_nodes_cache()
return result.model_dump()
@router.get("/api/platform/nodes/{node_id}/resources")
def get_platform_node_resources(node_id: str, request: Request, session: Session = Depends(get_session)):
normalized_node_id = str(node_id or "").strip().lower()
node_registry = getattr(request.app.state, "node_registry_service", None)
if node_registry is not None and hasattr(node_registry, "get_node"):
node = node_registry.get_node(normalized_node_id)
if node is not None:
metadata = dict(getattr(node, "metadata", {}) or {})
if str(metadata.get("transport_kind") or "").strip().lower() == "edge":
resolve_edge_client = getattr(request.app.state, "resolve_edge_client", None)
if callable(resolve_edge_client):
from providers.target import ProviderTarget
base = build_node_resource_overview(session, node_id=normalized_node_id, read_runtime=None)
client = resolve_edge_client(
ProviderTarget(
node_id=normalized_node_id,
transport_kind="edge",
runtime_kind=str(metadata.get("runtime_kind") or "docker"),
core_adapter=str(metadata.get("core_adapter") or "nanobot"),
)
)
try:
resource_report = dict(client.get_node_resources() or {})
except Exception as exc:
log_edge_failure(
logger,
key=f"platform-node-resources:{normalized_node_id}",
exc=exc,
message=f"Failed to load edge node resources for node_id={normalized_node_id}",
)
return base
base["resources"] = dict(resource_report.get("resources") or resource_report)
if resource_report:
base["node_report"] = resource_report
return base
def _read_runtime(bot):
provider = get_runtime_provider(request.app.state, bot)
status = str(provider.get_runtime_status(bot_id=str(bot.id or "")) or "STOPPED").upper()
runtime = dict(provider.get_resource_snapshot(bot_id=str(bot.id or "")) or {})
runtime.setdefault("docker_status", status)
return status, runtime
return build_node_resource_overview(session, node_id=normalized_node_id, read_runtime=_read_runtime)
2026-03-17 19:52:50 +00:00
@router.get("/api/platform/settings")
def get_platform_settings_api(session: Session = Depends(get_session)):
return get_platform_settings(session).model_dump()
@router.put("/api/platform/settings")
def update_platform_settings_api(payload: PlatformSettingsPayload, request: Request, session: Session = Depends(get_session)):
result = save_platform_settings(session, payload).model_dump()
_apply_platform_runtime_changes(request)
return result
@router.post("/api/platform/cache/clear")
def clear_platform_cache():
_invalidate_platform_overview_cache()
_invalidate_platform_nodes_cache()
2026-03-17 19:52:50 +00:00
return {"status": "cleared"}
@router.post("/api/platform/reload")
def reload_platform_runtime(request: Request):
_apply_platform_runtime_changes(request)
return {"status": "reloaded"}
@router.get("/api/platform/usage")
def get_platform_usage(
bot_id: Optional[str] = None,
limit: int = 100,
offset: int = 0,
session: Session = Depends(get_session),
):
return list_usage(session, bot_id=bot_id, limit=limit, offset=offset)
@router.get("/api/platform/events")
def get_platform_events(bot_id: Optional[str] = None, limit: int = 100, session: Session = Depends(get_session)):
return {"items": list_activity_events(session, bot_id=bot_id, limit=limit)}
@router.get("/api/platform/system-settings")
def get_system_settings(search: str = "", session: Session = Depends(get_session)):
return {"items": list_system_settings(session, search=search)}
@router.post("/api/platform/system-settings")
def create_system_setting(payload: SystemSettingPayload, request: Request, session: Session = Depends(get_session)):
try:
result = create_or_update_system_setting(session, payload)
_apply_platform_runtime_changes(request)
return result
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@router.put("/api/platform/system-settings/{key}")
def update_system_setting(key: str, payload: SystemSettingPayload, request: Request, session: Session = Depends(get_session)):
try:
result = create_or_update_system_setting(session, payload.model_copy(update={"key": key}))
_apply_platform_runtime_changes(request)
return result
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@router.delete("/api/platform/system-settings/{key}")
def remove_system_setting(key: str, request: Request, session: Session = Depends(get_session)):
try:
delete_system_setting(session, key)
_apply_platform_runtime_changes(request)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {"status": "deleted", "key": key}