diff --git a/backend/routers/coverage.py b/backend/routers/coverage.py index db69729..29766bd 100644 --- a/backend/routers/coverage.py +++ b/backend/routers/coverage.py @@ -1339,22 +1339,41 @@ def get_onboarding_status(db: Session = Depends(get_db)): rules_for_src = rule_by_source.get(src.source_name, []) rules_firing = any(firing_cache.get(r, 0) > 0 for r in rules_for_src) - stages = [ + has_detection_rules = len(rules_for_src) > 0 + + # Core stages (apply to every source) + core_stages = [ {"stage": "Data Received", "done": (src.event_count or 0) > 0}, {"stage": "Parser File Exists", "done": parser_info is not None}, {"stage": "Parser Active", "done": parser_active}, {"stage": "Source Labeled", "done": has_ds_name and parser_active}, - {"stage": "Detection Rules", "done": len(rules_for_src) > 0}, - {"stage": "Rules Firing", "done": rules_firing}, ] - completed = sum(1 for s in stages if s["done"]) + # Detection stages (only meaningful when detection rules exist) + detection_stages = [ + {"stage": "Detection Rules", "done": has_detection_rules, "na": False}, + {"stage": "Rules Firing", "done": rules_firing, "na": False}, + ] + + if has_detection_rules: + stages = core_stages + detection_stages + total = 6 + else: + # Mark detection stages as N/A — don't count against progress + stages = core_stages + [ + {"stage": "Detection Rules", "done": False, "na": True}, + {"stage": "Rules Firing", "done": False, "na": True}, + ] + total = 4 # progress calculated over core stages only + + completed = sum(1 for s in stages if s.get("done") and not s.get("na")) out.append({ "source": src.source_name, "event_count": src.event_count, "stages": stages, "completed": completed, - "total": len(stages), - "pct": round(completed / len(stages) * 100), + "total": total, + "has_detection_rules": has_detection_rules, + "pct": round(completed / total * 100) if total else 0, }) # Sort: incomplete first, then by event volume diff --git a/frontend/index.html b/frontend/index.html index 23f88b4..f454702 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -973,7 +973,6 @@ function obCopy() { // ── Onboarding Pipeline ─────────────────────────────────────────────────── -let _obShowCompleted = false let _obPipelineData = null @@ -992,13 +991,17 @@ async function loadOnboardingPipeline() { ○ Not Started: ${data.not_started}` const STAGE_ICONS = ['📥','📄','⚙️','🏷️','🔍','🔔'] - const incomplete = sources.filter(s => s.completed < s.total) - const complete = sources.filter(s => s.completed === s.total) + const STAGE_LABELS = ['Data Received','Parser File','Parser Active','Source Labeled','Detection Rules','Rules Firing'] + + // Split into two groups + const withRules = sources.filter(s => s.has_detection_rules) + const withoutRules = sources.filter(s => !s.has_detection_rules) function renderRow(s, i) { - const stageDots = s.stages.map((st, si) => - `${STAGE_ICONS[si] || '●'}` - ).join('') + const stageDots = s.stages.map((st, si) => { + if (st.na) return `—` + return `${STAGE_ICONS[si] || '●'}` + }).join('') const pct = s.pct const barColor = pct === 100 ? 'bg-emerald-500' : pct >= 50 ? 'bg-amber-500' : 'bg-red-500' return `
| Source | +Pipeline Stages | +Progress | +Events | +
|---|
| Source | -Pipeline Stages | -Progress | -Events | -
|---|
No active sources found — sync sources on the Coverage Map first.
' : ''}` } catch(e) { if (tableEl) tableEl.innerHTML = `${esc(e.message)}
` @@ -1061,14 +1074,14 @@ function obTogglePipeline() { if (btn) btn.textContent = _obPipelineVisible ? 'Hide Pipeline' : 'Show Pipeline' } -function obToggleCompleted() { - _obShowCompleted = !_obShowCompleted - const rows = document.getElementById('ob-complete-rows') - const label = document.getElementById('ob-complete-toggle-label') - if (rows) rows.classList.toggle('hidden', !_obShowCompleted) +function obToggleSection(idPrefix) { + const rows = document.getElementById(`${idPrefix}-complete-rows`) + const label = document.getElementById(`${idPrefix}-toggle-label`) + if (!rows) return + const hidden = rows.classList.toggle('hidden') if (label) { - const count = rows?.querySelectorAll('tr').length || 0 - label.textContent = (_obShowCompleted ? 'Hide' : 'Show') + ` completed (${count})` + const count = rows.querySelectorAll('tr').length + label.textContent = (hidden ? 'Show' : 'Hide') + ` completed (${count})` } }