fix advisor and drop down refresh
This commit is contained in:
@@ -176,16 +176,24 @@ function inferredProfileType(row) {
|
||||
}
|
||||
function profileDiscoveryDetails(row) {
|
||||
const d = row.discovery || {};
|
||||
const detailId = `profile-discovery:${row.stream_id || row.stream_name || ''}:${d.field_count || 0}`;
|
||||
if (!d.field_count) {
|
||||
const common = (row.common_fields || []).slice(0,8).map(item => `${item.field} ${(Number(item.coverage || 0)*100).toFixed(0)}%/${item.unique_values}`).join(', ');
|
||||
return `<details><summary>waiting for discovery data</summary><p>This row was produced before profile discovery metadata was added. Wait for the next monitor cycle after updating, or re-apply/edit the profile.</p><p><b>Observed common fields</b><br>${esc(common || '-')}</p></details>`;
|
||||
return `<details data-detail-id="${esc(detailId)}"><summary>waiting for discovery data</summary><p>This row was produced before profile discovery metadata was added. Wait for the next monitor cycle after updating, or re-apply/edit the profile.</p><p><b>Observed common fields</b><br>${esc(common || '-')}</p></details>`;
|
||||
}
|
||||
const selected = d.selected_fields || {};
|
||||
const top = (d.top_fields || []).slice(0,8).map(item => `${item.field} ${(Number(item.coverage || 0)*100).toFixed(0)}%/${item.unique_values}`).join(', ');
|
||||
const shared = (d.shared_fields || row.shared_fields || []).slice(0,8).map(item => `${item.field} (${item.streams} streams)`).join(', ');
|
||||
const rejected = (d.rejected_fields || []).slice(0,8).map(item => `${item.field}: ${item.reason}`).join('; ');
|
||||
const reasons = (d.reasons || []).join('; ');
|
||||
return `<details><summary>${esc(d.field_count)} fields analyzed</summary><p><b>Selected</b><br>Entity: ${esc((selected.entity || []).join(', ') || '-')}<br>Time: ${esc((selected.time || []).join(', ') || '-')}<br>Categorical: ${esc((selected.categorical || []).join(', ') || '-')}<br>Numeric: ${esc((selected.numeric || []).join(', ') || '-')}</p><p><b>Shared across streams</b><br>${esc(shared || '-')}</p><p><b>Top fields</b><br>${esc(top || '-')}</p><p><b>Rejected</b><br>${esc(rejected || '-')}</p><p>${esc(reasons || '')}</p></details>`;
|
||||
return `<details data-detail-id="${esc(detailId)}"><summary>${esc(d.field_count)} fields analyzed</summary><p><b>Selected</b><br>Entity: ${esc((selected.entity || []).join(', ') || '-')}<br>Time: ${esc((selected.time || []).join(', ') || '-')}<br>Categorical: ${esc((selected.categorical || []).join(', ') || '-')}<br>Numeric: ${esc((selected.numeric || []).join(', ') || '-')}</p><p><b>Shared across streams</b><br>${esc(shared || '-')}</p><p><b>Top fields</b><br>${esc(top || '-')}</p><p><b>Rejected</b><br>${esc(rejected || '-')}</p><p>${esc(reasons || '')}</p></details>`;
|
||||
}
|
||||
function profileAdvisorLabel(row, advisor) {
|
||||
const rowAdvisor = row.profile_advisor || {};
|
||||
if (rowAdvisor.status === 'heuristic' && rowAdvisor.error) return 'heuristic (advisor error)';
|
||||
if (rowAdvisor.status) return rowAdvisor.status;
|
||||
if (advisor.enabled && advisor.status === 'error') return 'heuristic (advisor error)';
|
||||
return advisor.enabled ? (advisor.status || 'checking') : 'heuristic';
|
||||
}
|
||||
function drawTrend(history) {
|
||||
const canvas=document.getElementById('trendChart'), ctx=canvas.getContext('2d'), ratio=window.devicePixelRatio||1, cw=canvas.clientWidth, ch=canvas.clientHeight;
|
||||
@@ -308,7 +316,8 @@ async function refresh() {
|
||||
`MCP aggregate events: ${esc(mcp.aggregate_events || 0)}`,
|
||||
`MCP raw sample events: ${esc(mcp.raw_events_fetched || mcp.events_fetched || 0)}`,
|
||||
`MCP coverage: ${esc(mcp.coverage_status || 'unknown')}${mcp.partial_streams ? ` (${esc(mcp.partial_streams)} partial)` : ''}${mcp.truncated_streams ? ` (${esc(mcp.truncated_streams)} truncated)` : ''}${mcp.sample_limited_streams ? ` (${esc(mcp.sample_limited_streams)} sample capped)` : ''}`,
|
||||
mcp.coverage_warning ? `<span class="sev-high">${esc(mcp.coverage_warning)}</span>` : ''
|
||||
mcp.coverage_warning ? `<span class="sev-high">${esc(mcp.coverage_warning)}</span>` : '',
|
||||
advisor.status === 'error' ? `<span class="sev-high">Profile advisor error: ${esc(advisor.error || 'unknown error')}</span>` : ''
|
||||
].filter(Boolean).join('<br>');
|
||||
document.getElementById('health').innerHTML = [
|
||||
metric('Enabled streams', enabledStreams.length), metric('Streams missing profile', streamsMissingProfile), metric('MCP aggregate events', mcp.aggregate_events || 0), metric('MCP raw sample', mcp.raw_events_fetched || mcp.events_fetched || 0), metric('Partial streams', mcp.partial_streams || 0), metric('Sample capped', mcp.sample_limited_streams || 0), metric('Truncated streams', mcp.truncated_streams || 0), metric('Correlated entities', correlations.length)
|
||||
@@ -324,7 +333,7 @@ async function refresh() {
|
||||
{label:'Events', key:'events'},
|
||||
{label:'Type', render:r => esc(inferredProfileType(r))},
|
||||
{label:'Confidence', key:'confidence'},
|
||||
{label:'Advisor', render:r => esc((r.profile_advisor || {}).status || (advisor.enabled ? advisor.status : 'heuristic'))},
|
||||
{label:'Advisor', render:r => esc(profileAdvisorLabel(r, advisor))},
|
||||
{label:'Profile', render:r => r.profile_exists ? 'exists' : 'new'},
|
||||
{label:'Entity fields', render:r => esc((r.entity_fields || []).join(', ') || '-')},
|
||||
{label:'Time', key:'timestamp_field'},
|
||||
|
||||
@@ -7,6 +7,31 @@ from urllib import request
|
||||
from .models import BlockCandidate, Finding
|
||||
|
||||
|
||||
def _json_object_from_text(text: str) -> dict[str, object]:
|
||||
text = text.strip()
|
||||
if text.startswith("```"):
|
||||
lines = text.splitlines()
|
||||
if lines and lines[0].startswith("```"):
|
||||
lines = lines[1:]
|
||||
if lines and lines[-1].strip() == "```":
|
||||
lines = lines[:-1]
|
||||
text = "\n".join(lines).strip()
|
||||
try:
|
||||
payload = json.loads(text)
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
start = text.find("{")
|
||||
end = text.rfind("}")
|
||||
if start == -1 or end == -1 or end <= start:
|
||||
return {}
|
||||
try:
|
||||
payload = json.loads(text[start:end + 1])
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
|
||||
|
||||
def ollama_summary(
|
||||
findings: list[Finding],
|
||||
candidates: list[BlockCandidate],
|
||||
@@ -121,6 +146,6 @@ def ollama_profile_advice(suggestions: list[dict[str, object]], model: str | Non
|
||||
req = request.Request(f"{host}/api/generate", data=body, method="POST", headers={"Content-Type": "application/json"})
|
||||
with request.urlopen(req, timeout=selected_timeout) as response:
|
||||
data = json.loads(response.read().decode("utf-8"))
|
||||
payload = json.loads(str(data.get("response", "{}")))
|
||||
payload = _json_object_from_text(str(data.get("response", "{}")))
|
||||
profiles = payload.get("profiles", [])
|
||||
return profiles if isinstance(profiles, list) else []
|
||||
|
||||
@@ -264,6 +264,8 @@ def build_status(
|
||||
profile_advisor_status = {"enabled": True, "status": "ok", "model": str(runtime_values.get("profile_advisor_model", "") or "qwen3:8b")}
|
||||
except Exception as exc:
|
||||
profile_advisor_status = {"enabled": True, "status": "error", "error": str(exc), "model": str(runtime_values.get("profile_advisor_model", "") or "qwen3:8b")}
|
||||
for suggestion in profile_suggestions:
|
||||
suggestion.setdefault("profile_advisor", {"status": "heuristic", "error": str(exc)})
|
||||
intel_ips = sorted(
|
||||
{
|
||||
ip
|
||||
|
||||
Reference in New Issue
Block a user