"""Tool-Einstufung (Plattform-Admin) und Verschärfung je Workspace (Workspace-Admin).""" from typing import Annotated from urllib.parse import urlencode from fastapi import APIRouter, Form, Request from fastapi.responses import HTMLResponse, Response from sqlalchemy import select from app import web from app.core.errors import Invalid from app.identity.admin import require as require_platform_admin from app.mcp import service as mcp from app.tools import service as tools from app.tools.models import DECISIONS, RISK_CLASSES from app.workspaces.models import Workspace Text = Annotated[str, Form()] def router() -> APIRouter: r = APIRouter() def page(request: Request, principal, error: str = "", notice: str = "", status: int = 200) -> HTMLResponse: require_platform_admin(principal) with web.db(request).session() as session: origins = mcp.origins(session) workspace_names = dict(session.execute(select(Workspace.id, Workspace.name)).all()) # Tools der Datenbanken stuft man am Treiber ein definitions = [d for d in tools.sync(session) if not (d.kind in origins and origins[d.kind].template_id)] for server in origins.values(): session.expunge(server) return web.render(request, "admin_tools.html", {"definitions": definitions, "classes": RISK_CLASSES, "origins": origins, "workspace_names": workspace_names, "decisions": DECISIONS, "defaults": tools.DEFAULT_DECISION, "error": error, "notice": notice}, status) def act(request: Request, principal, action, notice) -> Response: try: with web.db(request).session() as session: result = action(session) except Invalid as e: return page(request, principal, error=str(e), status=400) return page(request, principal, notice=notice(result) if callable(notice) else notice) @r.get("/admin/tools") def overview(request: Request, principal: web.User) -> HTMLResponse: return page(request, principal) @r.post("/admin/tools/suggested") def suggested(request: Request, principal: web.Checked) -> Response: return act(request, principal, lambda s: tools.classify_suggested(s, principal), lambda count: f"{count} Tools nach Vorschlag eingestuft.") @r.post("/admin/tools/{name}") def classify(request: Request, principal: web.Checked, name: str, risk_class: Text, decision: Text, approval_level: Text = "workspace_admin", four_eyes: Annotated[bool, Form()] = False) -> Response: return act(request, principal, lambda s: tools.classify(s, principal, name, risk_class, decision, approval_level, four_eyes), f"{name} eingestuft.") @r.post("/workspaces/{workspace_id}/tools/{name}") def workspace_policy(request: Request, principal: web.Checked, workspace_id: int, name: str, decision: Text = "", four_eyes: Annotated[bool, Form()] = False) -> Response: with web.db(request).session() as session: tools.set_workspace_policy(session, principal, workspace_id, name, decision, four_eyes) return web.redirect(f"/workspaces/{workspace_id}/settings?{urlencode({'tab': 'tools', 'tool': name})}") @r.post("/workspaces/{workspace_id}/source-rules") def source_rule(request: Request, principal: web.Checked, workspace_id: int, rule: Text, decision: Text = "", four_eyes: Annotated[bool, Form()] = False) -> Response: """rule = :; leere Entscheidung ohne Vier-Augen-Prinzip entfernt die Regel.""" source_id, _, name = rule.partition(":") if not source_id.isdigit() or not name: raise Invalid("Bitte Quelle und Tool wählen.") with web.db(request).session() as session: tools.set_source_policy(session, principal, workspace_id, int(source_id), name, decision, four_eyes) return web.redirect(f"/workspaces/{workspace_id}/settings?tab=tools&saved=1#source-rules") return r