from datetime import UTC, datetime from typing import Any import uuid from fastapi import APIRouter, Depends from sqlmodel import Session from ..core.redaction import redact_message from ..db.session import get_session from ..services import ai_security_reviewer, knowledge_service router = APIRouter(prefix='/api/ai') _AI_TOOLS = [ { 'id': 'read_container_status', 'name': 'read_container_status', 'description': 'Leest containerstatus via de Portainer read facade.', 'type': 'read', 'permissionLevel': 1, 'requiresApproval': False, 'rateLimit': '60/min', 'tool': 'read_container_status', 'enabled': True, 'allowedByDefault': True, 'scope': 'read', }, { 'id': 'read_unraid_health', 'name': 'read_unraid_health', 'description': 'Leest Unraid array, cache, parity en docker health zonder mutaties.', 'type': 'read', 'permissionLevel': 1, 'requiresApproval': False, 'rateLimit': '30/min', 'tool': 'read_unraid_health', 'enabled': True, 'allowedByDefault': True, 'scope': 'read', }, { 'id': 'read_audit', 'name': 'read_audit', 'description': 'Leest geredigeerde audit events voor analyse en incidentcontext.', 'type': 'read', 'permissionLevel': 1, 'requiresApproval': False, 'rateLimit': '30/min', 'tool': 'read_audit', 'enabled': True, 'allowedByDefault': True, 'scope': 'read', }, { 'id': 'query_knowledge', 'name': 'query_knowledge', 'description': 'Zoekt lokale documentatie en actuele, geredigeerde AppOps-context met bronverwijzingen.', 'type': 'read', 'permissionLevel': 1, 'requiresApproval': False, 'rateLimit': '30/min', 'tool': 'query_knowledge', 'enabled': True, 'allowedByDefault': True, 'scope': 'read', }, { 'id': 'draft_playbook_run', 'name': 'draft_playbook_run', 'description': 'Maakt alleen een voorstel voor playbook-uitvoering; uitvoering loopt via policy en approval.', 'type': 'mutate', 'permissionLevel': 3, 'requiresApproval': True, 'rateLimit': '5/min', 'tool': 'draft_playbook_run', 'enabled': True, 'allowedByDefault': False, 'scope': 'mutate', }, ] def _redact_payload(value: Any) -> Any: if isinstance(value, str): return redact_message(value) if isinstance(value, list): return [_redact_payload(item) for item in value] if isinstance(value, dict): return {str(key): _redact_payload(item) for key, item in value.items()} return value @router.get('/status') def status(session: Session = Depends(get_session)): summary = ai_security_reviewer.get_summary(session) current_risks = int(summary.get('criticalFindings') or 0) + int(summary.get('highFindings') or 0) + int(summary.get('mediumFindings') or 0) recommended_actions = int(summary.get('recommendationsCount') or 0) data_completeness = summary.get('dataCompleteness') if isinstance(summary.get('dataCompleteness'), dict) else {} missing_sources = data_completeness.get('missingSources') or [] if missing_sources: storage_forecast = f"Coverage partial; ontbrekende bronnen: {', '.join(str(item) for item in missing_sources[:4])}." else: storage_forecast = 'Geen storage forecast beschikbaar in de advisory AI status.' return { 'dailySummary': redact_message( f"AI advisor draait read-only. Security status: {summary.get('status', 'unknown')}; " f"{current_risks} open risico(s), {recommended_actions} aanbeveling(en)." ), 'currentRisks': current_risks, 'recommendedActions': recommended_actions, 'storageForecast': redact_message(storage_forecast), 'securityScore': int(summary.get('score') or 0), 'autopilotStatus': 'off', } @router.post('/chat') def chat(payload: dict, session: Session = Depends(get_session)): content = redact_message(str(payload.get('content') or '')) try: grounded = knowledge_service.query(session, content, limit=5) except ValueError: grounded = { 'answer': 'Stel een concrete vraag over AppOps, policies, apps, audit of platformwerking.', 'citations': [], 'confidence': 'none', } citations = grounded.get('citations') or [] evidence = '; '.join( f"[{item.get('rank')}] {item.get('title')} — {item.get('uri')}" for item in citations[:5] if isinstance(item, dict) ) return { 'id': f"chat-{uuid.uuid4().hex[:12]}", 'role': 'assistant', 'content': grounded['answer'], 'timestamp': datetime.now(UTC).isoformat(), 'metadata': { 'evidence': evidence or 'Geen relevante bron gevonden; er is niet gegokt.', 'risk': 'low', 'policyResult': 'allowed', 'nextStep': 'RAG blijft read-only. Uitvoering loopt uitsluitend via Command Center, policy en approvals.', 'citations': citations, 'confidence': grounded.get('confidence', 'none'), }, } @router.get('/recommendations') def recommendations(session: Session = Depends(get_session)): results: list[dict[str, Any]] = [] for recommendation in ai_security_reviewer.get_recommendations(session): evidence = recommendation.get('evidence') or [] evidence_text = '; '.join( redact_message(f"{item.get('source', 'source')}:{item.get('label', 'evidence')}={item.get('value', item.get('observed', ''))}") for item in evidence[:3] if isinstance(item, dict) ) or redact_message(str(recommendation.get('description') or 'Security reviewer recommendation.')) status = recommendation.get('status') or 'open' if status == 'accepted': status = 'applied' confidence = recommendation.get('confidence') or 'medium' confidence_score = {'low': 0.45, 'medium': 0.7, 'high': 0.9}.get(str(confidence), 0.7) severity = str(recommendation.get('severity') or 'medium') if severity == 'info': severity = 'low' results.append({ 'id': recommendation.get('id'), 'title': redact_message(str(recommendation.get('title') or 'AI recommendation')), 'severity': severity, 'confidence': confidence_score, 'evidence': evidence_text, 'affectedApps': [str(item) for item in recommendation.get('affectedResources', [])], 'suggestedAction': redact_message(str(recommendation.get('suggestedAction') or 'Review in AI Security Reviewer')), 'status': status, }) return results @router.post('/analyze/incident/{incident_id}') def analyze_incident(incident_id: str): return {'incident': incident_id, 'analysis': 'No blocking issues.'} @router.post('/summarize/logs') def summarize_logs(payload: dict): return {'summary': 'No critical events.'} @router.get('/tools') def tools(): return _AI_TOOLS @router.get('/history') def history(): return [] @router.get('/memory') def memory(): return [] @router.post('/memory') def create_memory(payload: dict): return {'success': True, 'memory': _redact_payload(payload)} @router.patch('/memory/{memory_id}') def update_memory(memory_id: str, payload: dict): return {'success': True, 'id': memory_id, 'memory': _redact_payload(payload)} @router.delete('/memory/{memory_id}') def delete_memory(memory_id: str): return {'success': True, 'id': memory_id}