Files
Agentic-OS/backend/app/services/vessel_devices.py
nearxos 6185b9b85a Initial commit: Agentic OS troubleshooting platform
Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend +
LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini ->
DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials,
RBAC, audit log, project-memory + Obsidian integrations, and editable
troubleshooting decision rules tuned for the GeneseasX vessel stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 22:11:07 +03:00

79 lines
2.7 KiB
Python

"""Helpers for building vessel device rows from catalog selections."""
from __future__ import annotations
from app.core.crypto import decrypt_secret, encrypt_secret
from app.agent.asterisk_profiles import ASTERISK_CATALOG_KEYS
from app.inventory_catalog import CatalogEntry, catalog_by_key
from app.models.inventory import Device, Vessel
from app.schemas import DeviceOut, VesselDeviceSelection
from app.services.device_defaults import merge_selection_with_defaults
def device_to_out(d: Device) -> DeviceOut:
out = DeviceOut.model_validate(d)
out.has_secret = bool(d.secret_enc)
return out
def _resolve_address(vessel: Vessel, sel: VesselDeviceSelection) -> str:
if sel.address:
return sel.address
if vessel.public_ip:
return vessel.public_ip
raise ValueError(f"Device '{sel.catalog_key}' needs an address or vessel public IP")
def build_device(
vessel: Vessel,
sel: VesselDeviceSelection,
entry: CatalogEntry,
*,
existing_secret_plain: str | None = None,
) -> Device:
port, username, secret = merge_selection_with_defaults(
entry.key,
entry,
port=sel.port,
username=sel.username,
secret=sel.secret,
existing_secret=existing_secret_plain,
)
secret_enc = encrypt_secret(secret) if secret else None
return Device(
vessel_id=vessel.id,
catalog_key=entry.key,
name=f"{vessel.name}{entry.label}",
device_type=entry.device_type,
address=_resolve_address(vessel, sel),
port=port,
mcp_server=entry.mcp_server,
username=username,
secret_enc=secret_enc,
notes=sel.notes,
)
def validate_selections(selections: list[VesselDeviceSelection]) -> list[tuple[VesselDeviceSelection, CatalogEntry]]:
catalog = catalog_by_key()
enabled = [s for s in selections if s.enabled]
if not enabled:
raise ValueError("Select at least one device for this vessel")
out: list[tuple[VesselDeviceSelection, CatalogEntry]] = []
seen: set[str] = set()
asterisk_picked = False
for sel in enabled:
entry = catalog.get(sel.catalog_key)
if not entry:
raise ValueError(f"Unknown catalog key: {sel.catalog_key}")
if sel.catalog_key in seen:
raise ValueError(f"Duplicate device slot: {sel.catalog_key}")
if sel.catalog_key in ASTERISK_CATALOG_KEYS:
if asterisk_picked:
raise ValueError(
"Only one Asterisk slot per vessel — pick GeneseasX (docker voip) OR TMGeneseas/satbox (native)"
)
asterisk_picked = True
seen.add(sel.catalog_key)
out.append((sel, entry))
return out