# retoor import logging from typing import Any from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse, JSONResponse from devplacepy.templating import templates from devplacepy.utils import require_admin, not_found from devplacepy.services.manager import service_manager from devplacepy.seo import base_seo_context, site_url, website_schema from devplacepy.services.audit import record as audit logger = logging.getLogger(__name__) router = APIRouter() @router.get("", response_class=HTMLResponse) async def services_page(request: Request): admin = require_admin(request) services = service_manager.describe_all() base = site_url(request) seo_ctx = base_seo_context( request, title="Services - Admin", description="Monitor and configure background services on DevPlace.", robots="noindex,nofollow", breadcrumbs=[ {"name": "Home", "url": "/feed"}, {"name": "Admin", "url": "/admin"}, {"name": "Services", "url": "/admin/services"}, ], schemas=[website_schema(base)], ) return templates.TemplateResponse( request, "services.html", { **seo_ctx, "request": request, "user": admin, "services": services, "admin_section": "services", }, ) @router.get("/data") async def services_json(request: Request): require_admin(request) return JSONResponse({"services": service_manager.describe_all()}) @router.get("/{name}", response_class=HTMLResponse) async def service_detail(request: Request, name: str): admin = require_admin(request) svc = service_manager.get_service(name) if svc is None: raise not_found("Service not found") info = svc.describe() base = site_url(request) seo_ctx = base_seo_context( request, title=f"{info['title']} - Services", description=info["description"] or f"Configure the {info['title']} service.", robots="noindex,nofollow", breadcrumbs=[ {"name": "Home", "url": "/feed"}, {"name": "Admin", "url": "/admin"}, {"name": "Services", "url": "/admin/services"}, {"name": info["title"], "url": f"/admin/services/{name}"}, ], schemas=[website_schema(base)], ) return templates.TemplateResponse( request, "service_detail.html", { **seo_ctx, "request": request, "user": admin, "service": info, "admin_section": "services", }, ) @router.get("/{name}/data") async def service_detail_json(request: Request, name: str): require_admin(request) svc = service_manager.get_service(name) if svc is None: return JSONResponse({"error": "unknown service"}, status_code=404) return JSONResponse({"service": svc.describe()}) def _audit_service(request: Request, admin: dict, name: str, event_key: str, summary: str, **kwargs: Any) -> None: audit.record( request, event_key, user=admin, target_type="service", target_uid=name, target_label=name, summary=summary, links=[audit.service_link(name)], **kwargs, ) @router.post("/{name}/start") async def service_start(request: Request, name: str): admin = require_admin(request) if not service_manager.set_enabled(name, True): return JSONResponse({"ok": False, "error": "unknown service"}, status_code=404) _audit_service( request, admin, name, "service.start", f"admin {admin['username']} started service {name}", new_value="enabled", ) return JSONResponse({"ok": True}) @router.post("/{name}/stop") async def service_stop(request: Request, name: str): admin = require_admin(request) if not service_manager.set_enabled(name, False): return JSONResponse({"ok": False, "error": "unknown service"}, status_code=404) _audit_service( request, admin, name, "service.stop", f"admin {admin['username']} stopped service {name}", new_value="disabled", ) return JSONResponse({"ok": True}) @router.post("/{name}/run") async def service_run(request: Request, name: str): admin = require_admin(request) if not service_manager.send_command(name, "run"): return JSONResponse({"ok": False, "error": "unknown service"}, status_code=404) _audit_service( request, admin, name, "service.run_now", f"admin {admin['username']} triggered an immediate run of service {name}", ) return JSONResponse({"ok": True}) @router.post("/{name}/clear-logs") async def service_clear_logs(request: Request, name: str): admin = require_admin(request) if not service_manager.send_command(name, "clear"): return JSONResponse({"ok": False, "error": "unknown service"}, status_code=404) _audit_service( request, admin, name, "service.logs.clear", f"admin {admin['username']} cleared logs for service {name}", ) return JSONResponse({"ok": True}) @router.post("/{name}/config") async def service_config(request: Request, name: str): admin = require_admin(request) svc = service_manager.get_service(name) before = svc.get_config() if svc is not None else {} form = await request.form() result = service_manager.save_config(name, dict(form)) if result is None: return JSONResponse({"ok": False, "error": "unknown service"}, status_code=404) after = svc.get_config() if svc is not None else {} for key, new_value in after.items(): old_value = before.get(key) if old_value == new_value: continue audit.record( request, "service.config.update", user=admin, target_type="service", target_uid=name, target_label=name, old_value=old_value, new_value=new_value, summary=f"admin {admin['username']} updated {key} of service {name}", links=[audit.service_link(name), audit.setting(key)], ) if not result["ok"]: return JSONResponse(result, status_code=400) return JSONResponse(result)