155 lines
5.6 KiB
Python
155 lines
5.6 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
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,
|
|
}
|