# retoor <retoor@molodetz.nl>
from __future__ import annotations
import logging
import re
import time
from typing import Optional
from devplacepy.services.base import BaseService
from devplacepy.services.manager import service_manager
from devplacepy.services.pubsub import publish as pubsub_publish
from devplacepy.services.pubsub.hub import pubsub
logger = logging.getLogger(__name__)
_SEGMENT = r"[A-Za-z0-9_-]+"
LOG_TAIL = 400
def _instance_project(inst: dict) -> dict:
from devplacepy.database import get_table
return get_table("projects").find_one(uid=inst["project_uid"]) or {}
def _instance_is_broadcastable(inst: dict) -> bool:
project = _instance_project(inst)
return bool(project) and not project.get("is_private")
async def _container_list(_match: re.Match) -> dict:
from devplacepy.routers.admin.containers import _decorate
from devplacepy.services.containers import store
return {"instances": _decorate(store.all_instances()), "partial": True}
async def _project_containers(match: re.Match) -> Optional[dict]:
from devplacepy.database import get_table, resolve_by_slug
from devplacepy.services.containers import store
project = resolve_by_slug(get_table("projects"), match.group("slug"))
if not project or project.get("is_private"):
return None
return {"instances": store.list_instances(project["uid"])}
async def _container_detail(match: re.Match) -> Optional[dict]:
from devplacepy.services.containers import api, store
uid = match.group("uid")
inst = store.get_instance(uid)
if not inst or not _instance_is_broadcastable(inst):
return None
return {
"instance": inst,
"events": store.list_events(uid),
"schedules": store.list_schedules(uid),
"stats": api.instance_stats(uid),
"runtime": api.instance_runtime(inst),
}
async def _container_logs(match: re.Match) -> Optional[dict]:
from devplacepy.services.containers import store
from devplacepy.services.containers.runtime import get_backend
uid = match.group("uid")
inst = store.get_instance(uid)
if not inst or not _instance_is_broadcastable(inst):
return None
if not inst.get("container_id"):
return {"logs": ""}
lines: list = []
async def collect(line: str) -> None:
lines.append(line)
await get_backend().logs(
inst["container_id"], follow=False, tail=LOG_TAIL, on_log=collect
)
return {"logs": "\n".join(lines)}
async def _bots(_match: re.Match) -> dict:
from devplacepy.routers.admin.bots import _frames_payload
return _frames_payload()
async def _services(_match: re.Match) -> dict:
return {"services": service_manager.describe_all()}
async def _service_detail(match: re.Match) -> Optional[dict]:
svc = service_manager.get_service(match.group("name"))
if svc is None:
return None
return {"service": svc.describe()}
async def _ai_usage(match: re.Match) -> dict:
from devplacepy.services.openai_gateway.analytics import build_analytics
from devplacepy.services.openai_gateway.usage import pricing_from_cfg
hours = int(match.group("hours"))
svc = service_manager.get_service("openai")
pricing = pricing_from_cfg(svc.get_config()) if svc is not None else None
return build_analytics(hours, top_n=10, pricing=pricing)
async def _backups(_match: re.Match) -> dict:
from devplacepy.routers.admin.backups import _dashboard
return _dashboard(can_download=False)
VIEWS = [
(re.compile(r"^container\.list$"), _container_list, 4.0),
(re.compile(rf"^project\.(?P<slug>{_SEGMENT})\.containers$"), _project_containers, 3.0),
(re.compile(rf"^container\.(?P<uid>{_SEGMENT})\.detail$"), _container_detail, 4.0),
(re.compile(rf"^container\.(?P<uid>{_SEGMENT})\.logs$"), _container_logs, 3.0),
(re.compile(r"^fleet\.bots$"), _bots, 2.0),
(re.compile(r"^admin\.services$"), _services, 5.0),
(re.compile(rf"^admin\.services\.(?P<name>{_SEGMENT})$"), _service_detail, 5.0),
(re.compile(r"^admin\.ai-usage\.(?P<hours>\d+)$"), _ai_usage, 15.0),
(re.compile(r"^admin\.backups$"), _backups, 8.0),
]
class LiveViewRelayService(BaseService):
title = "Live view relay"
description = (
"Pushes admin live-view snapshots (container list and instances, bot fleet, "
"background services, AI usage, backups) onto the pub/sub bus, computed only for topics "
"that currently have subscribers. Runs on the service lock owner where pub/sub "
"subscribers converge, replacing per-client HTTP polling with server push."
)
default_enabled = True
def __init__(self):
super().__init__(name="live_view_relay", interval_seconds=1)
self._last: dict[str, float] = {}
def _handler_for(self, topic: str):
for pattern, compute, interval in VIEWS:
match = pattern.match(topic)
if match is not None:
return compute, interval, match
return None
async def run_once(self) -> None:
now = time.monotonic()
active: set[str] = set()
published = 0
for entry in pubsub.topics():
topic = entry["topic"]
if not entry["subscribers"] or "*" in topic:
continue
handler = self._handler_for(topic)
if handler is None:
continue
active.add(topic)
compute, interval, match = handler
if now - self._last.get(topic, 0.0) < interval:
continue
self._last[topic] = now
try:
payload = await compute(match)
except Exception:
logger.exception("live view relay failed for %s", topic)
continue
if payload is None:
continue
published += await pubsub_publish(topic, payload)
self._last = {topic: ts for topic, ts in self._last.items() if topic in active}
if published:
self.log(f"pushed {published} live-view frame(s)")
def collect_metrics(self) -> dict:
return {"tracked_topics": len(self._last)}