add ollama bagcround run and cache

This commit is contained in:
larssand
2026-06-18 23:13:16 +02:00
parent 6117013c6e
commit 5e257b7d47
7 changed files with 120 additions and 1 deletions

View File

@@ -192,6 +192,10 @@ def run_monitor(args: argparse.Namespace) -> int:
policy_path=args.policies,
interval=args.interval,
anomaly_limit=args.anomaly_limit,
llm=args.llm,
llm_interval=args.llm_interval,
llm_model=args.model,
llm_timeout=args.llm_timeout,
)
return 0
@@ -272,6 +276,10 @@ def build_parser() -> argparse.ArgumentParser:
monitor.add_argument("--policies", default=None, help="Optional FortiGate policy JSON/config file to audit continuously")
monitor.add_argument("--interval", type=int, default=10, help="Seconds between analysis runs")
monitor.add_argument("--anomaly-limit", type=int, default=20, help="Maximum anomaly findings to include")
monitor.add_argument("--llm", action="store_true", help="Generate cached Ollama analyst note for dashboard")
monitor.add_argument("--llm-interval", type=int, default=300, help="Seconds between Ollama dashboard assessments")
monitor.add_argument("--model", default=None, help="Ollama model name")
monitor.add_argument("--llm-timeout", type=int, default=None, help="Ollama request timeout in seconds")
monitor.set_defaults(func=run_monitor)
dashboard = subparsers.add_parser("dashboard", help="Serve local fgAI dashboard")

View File

@@ -46,6 +46,7 @@ HTML = """<!doctype html>
<section class="panel"><h2>Live Status</h2><div id="liveStatus" class="muted">Waiting for monitor data.</div></section>
</div>
</section>
<section class="panel"><h2>AI Assessment</h2><div id="llmAssessment" class="muted">LLM assessment disabled.</div></section>
<section class="panel"><h2>Anomalies</h2><div id="anomalies"></div></section>
<section class="panel"><h2>Recommendations</h2><div id="recommendations"></div></section>
<section class="panel"><h2>Block Candidates</h2><div id="blocks"></div></section>
@@ -84,6 +85,9 @@ async function refresh() {
`Critical anomalies: ${esc((a.critical || 0))}`,
`High anomalies: ${esc((a.high || 0))}`
].join('<br>');
const llm = data.llm_assessment || {};
const llmText = llm.text ? esc(llm.text).replace(/\\n/g, '<br>') : esc(llm.error || 'LLM assessment disabled or waiting for first run.');
document.getElementById('llmAssessment').innerHTML = `<div>Status: <code>${esc(llm.status || 'unknown')}</code></div><p>${llmText}</p>`;
document.getElementById('anomalies').innerHTML = table(data.anomalies || [], [
{label:'Source', key:'subject'},
{label:'Score', key:'score'},

View File

@@ -46,3 +46,28 @@ def ollama_summary(
with request.urlopen(req, timeout=selected_timeout) as response:
data = json.loads(response.read().decode("utf-8"))
return str(data.get("response", "")).strip()
def ollama_dashboard_assessment(analysis: dict[str, object], model: str | None = None, timeout: int | None = None) -> str:
compact = {
"summary": analysis.get("summary", {}),
"anomaly_summary": analysis.get("anomaly_summary", {}),
"top_anomalies": analysis.get("anomalies", [])[:5],
"top_recommendations": analysis.get("recommendations", [])[:5],
"block_candidates": analysis.get("block_candidates", [])[:5],
"policy_findings": analysis.get("policy_findings", [])[:5],
}
return ollama_summary(
[],
[],
model,
analysis={
"task": (
"Write a concise dashboard analyst note for a FortiGate admin. "
"Explain likely cause, whether this looks malicious or noisy, and the next action. "
"Mention policyid=0 as implicit deny/drop, not an editable policy."
),
"data": compact,
},
timeout=timeout,
)

View File

@@ -5,6 +5,7 @@ import time
from pathlib import Path
from .anomaly import anomaly_summary, detect_source_anomalies
from .llm import ollama_dashboard_assessment
from .logs import local_in_failures, read_events, summarize_events, top_field_values
from .mitigation import parse_allowlist, suggest_block_candidates
from .policies import audit_policies, read_policies
@@ -94,6 +95,24 @@ def build_status(
}
def add_llm_assessment(status: dict[str, object], *, previous: str | None = None, model: str | None = None, timeout: int | None = None) -> None:
try:
status["llm_assessment"] = {
"enabled": True,
"status": "ok",
"generated_at": int(time.time()),
"text": ollama_dashboard_assessment(status, model=model, timeout=timeout),
}
except Exception as exc:
status["llm_assessment"] = {
"enabled": True,
"status": "error",
"generated_at": int(time.time()),
"error": str(exc),
"text": previous or "",
}
def write_status(status: dict[str, object], output: str) -> None:
output_path = Path(output)
output_path.parent.mkdir(parents=True, exist_ok=True)
@@ -109,10 +128,33 @@ def monitor_loop(
policy_path: str | None = None,
interval: int = 10,
anomaly_limit: int = 20,
llm: bool = False,
llm_interval: int = 300,
llm_model: str | None = None,
llm_timeout: int | None = None,
) -> None:
print(f"Monitoring {log_path}")
print(f"Writing status to {output}")
last_llm_at = 0
last_llm_text: str | None = None
while True:
status = build_status(log_path, policy_path=policy_path, anomaly_limit=anomaly_limit)
if llm:
now = int(time.time())
if now - last_llm_at >= llm_interval:
add_llm_assessment(status, previous=last_llm_text, model=llm_model, timeout=llm_timeout)
assessment = status.get("llm_assessment", {})
if isinstance(assessment, dict):
last_llm_text = str(assessment.get("text", "") or last_llm_text or "")
last_llm_at = now
else:
status["llm_assessment"] = {
"enabled": True,
"status": "cached",
"generated_at": last_llm_at,
"text": last_llm_text or "",
}
else:
status["llm_assessment"] = {"enabled": False, "status": "disabled", "text": ""}
write_status(status, output)
time.sleep(interval)