diff --git a/src/fgai/dashboard.py b/src/fgai/dashboard.py
index f2ccd92..aa9f307 100644
--- a/src/fgai/dashboard.py
+++ b/src/fgai/dashboard.py
@@ -163,6 +163,29 @@ function compactRelatedActivity(rows) {
}
return [...groups.values()].sort((left,right) => (right.security-left.security) || (right.count-left.count)).slice(0,25);
}
+function inferredProfileType(row) {
+ const explicit = row.detected_log_type || (row.discovery || {}).log_type;
+ if (explicit) return explicit;
+ const haystack = `${row.stream_name || ''} ${(row.common_fields || []).map(item => item.field || '').join(' ')} ${(row.categorical_fields || []).join(' ')}`.toLowerCase();
+ if (/(windows|winlog|sysmon|powershell|security event)/.test(haystack)) return 'windows';
+ if (/(fortigate|firewall|policyid|dstport|srcport)/.test(haystack)) return 'firewall';
+ if (/(dns|adguard|query_domain|dns_query|\\bqh\\b)/.test(haystack)) return 'dns';
+ if (/(nginx|apache|iis|squid|proxy|url|request_uri)/.test(haystack)) return 'web_proxy';
+ if (/(audit|app|application|serverlog|prod)/.test(haystack)) return 'application';
+ return 'unknown';
+}
+function profileDiscoveryDetails(row) {
+ const d = row.discovery || {};
+ 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 ` This row was produced before profile discovery metadata was added. Wait for the next monitor cycle after updating, or re-apply/edit the profile. Observed common fields Selected Top fields Rejected ${esc(reasons || '')} Selected Top fields Rejected ${esc(reasons || '')}waiting for discovery data
${esc(common || '-')}${esc(d.field_count)} fields analyzed
Entity: ${esc((selected.entity || []).join(', ') || '-')}
Time: ${esc((selected.time || []).join(', ') || '-')}
Categorical: ${esc((selected.categorical || []).join(', ') || '-')}
Numeric: ${esc((selected.numeric || []).join(', ') || '-')}
${esc(top || '-')}
${esc(rejected || '-')}${esc(d.field_count || 0)} fields analyzed
Entity: ${esc((selected.entity || []).join(', ') || '-')}
Time: ${esc((selected.time || []).join(', ') || '-')}
Categorical: ${esc((selected.categorical || []).join(', ') || '-')}
Numeric: ${esc((selected.numeric || []).join(', ') || '-')}
${esc(top || '-')}
${esc(rejected || '-')}
No missing stream profiles. Enable "show existing profiles" to inspect already configured profiles.
'; const llmText = llm.text ? esc(llm.text).replace(/\\n/g, '