# retoor from ._shared import SERVICE_ACTIONS, endpoint, field from .negotiation import _classify def _service_control_endpoints(): endpoints = [] for action, summary in SERVICE_ACTIONS: endpoints.append( endpoint( id=f"services-{action.replace('-', '')}", method="POST", path=f"/admin/services/{{name}}/{action}", title=f"Service: {action}", summary=summary + ".", auth="admin", interactive=True, destructive=True, params=[ field( "name", "path", required=True, example="news", description="Registered service name.", ) ], sample_response={"ok": True}, ) ) return endpoints def _field_to_param(spec): description = spec["label"] if spec.get("help"): description = f"{spec['label']} - {spec['help']}" value = "" if spec.get("secret") else str(spec.get("value", "")) if spec.get("secret"): description += " Leave blank to keep the current value." if spec["type"] == "bool": return field( spec["key"], "form", "enum", False, value or "0", description, ["1", "0"] ) if spec["type"] == "select": options = [option["value"] for option in spec.get("options") or []] return field( spec["key"], "form", "enum", False, value or (options[0] if options else ""), description, options, ) if spec["type"] in ("int", "float"): return field(spec["key"], "form", "int", False, value, description) return field(spec["key"], "form", "string", False, value, description) def _service_section(service): enabled = "yes" if service.get("enabled") else "no" groups = ", ".join(group["name"] for group in service.get("field_groups", [])) lines = [ f"## {service.get('title') or service['name']}", "", service.get("description", ""), "", f"- Service name: `{service['name']}`", f"- Enabled: {enabled}", f"- Run interval: {service.get('interval_seconds', 0)}s", ] if groups: lines.append(f"- Configuration groups: {groups}") return "\n".join(lines) def build_services_group(services, base): intro_parts = [ "# Background Services", "", "DevPlace runs background services supervised by a single manager. Each service has its " "own enable flag, run interval, live status, metrics, and a rolling log buffer. Manage " "them from the admin panel at `/admin/services` or with the endpoints below.", "", "Control a service by its `name` (shown per service): start or stop it, trigger a single " "run, clear its logs, or save its configuration. `GET /admin/services/data` returns the " "live status, metrics, and log tail for every service.", "", "See the [Admin API](/docs/admin.html) for the rest of administration; the Devii assistant " "is itself a service, configured in [Configuration and CLI](/docs/devii-config.html).", ] config_endpoints = [] for service in services: intro_parts.append("") intro_parts.append(_service_section(service)) params = [ _field_to_param(spec) for spec in service.get("fields", []) if not spec["key"].endswith("_enabled") ] config_endpoints.append( endpoint( id=f"services-config-{service['name']}", method="POST", path=f"/admin/services/{service['name']}/config", title=f"Configure {service.get('title') or service['name']}", summary=f"Save configuration for the {service['name']} service. Empty values keep the current setting.", auth="admin", encoding="form", destructive=True, params=params, notes=[ "Use start/stop to enable or disable the service; this saves the remaining settings." ], ) ) control_endpoints = [ endpoint( id="services-data", method="GET", path="/admin/services/data", title="Service status", summary="Live status, metrics, and log tail for every background service.", auth="admin", sample_response={ "services": [{"name": "news", "status": "running", "enabled": True}] }, ), endpoint( id="services-detail-data", method="GET", path="/admin/services/{name}/data", title="One service status", summary="Live status, metrics, and log tail for a single background service.", auth="admin", params=[field("name", "path", "string", True, "news", "Service name.")], sample_response={ "service": {"name": "news", "status": "running", "enabled": True} }, ), *_service_control_endpoints(), ] endpoints = control_endpoints + config_endpoints for ep in endpoints: ep["negotiation"] = _classify(ep) return { "slug": "services", "title": "Background Services", "admin": True, "intro": "\n".join(intro_parts), "endpoints": endpoints, }