Files
Unraid-AppOps-Gateway-Public/backend/app/api/routes_ai.py
T

211 lines
7.6 KiB
Python

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}