import gradio as gr import pandas as pd import plotly.express as px import plotly.graph_objects as go from datasets import load_dataset # --------------------------------------------------------------------------- # Data loading # --------------------------------------------------------------------------- def load_data(lang="en"): repo = "AYI-NEDJIMI/soc-analyst-fr" if lang == "fr" else "AYI-NEDJIMI/soc-analyst-en" try: ds = load_dataset(repo, split="train") df = ds.to_pandas() except Exception as e: print(f"Error loading {repo}: {e}") df = pd.DataFrame() return df DF_EN = load_data("en") DF_FR = load_data("fr") def get_df(lang): return DF_FR.copy() if lang == "fr" else DF_EN.copy() def safe_col(df, col): """Return column values or empty series.""" if col in df.columns: return df[col] return pd.Series(dtype=str) def unique_vals(df, col): if col not in df.columns: return [] vals = df[col].dropna().unique().tolist() return sorted([str(v) for v in vals if str(v).strip()]) # --------------------------------------------------------------------------- # Labels (i18n) # --------------------------------------------------------------------------- LABELS = { "en": { "title": "🛡️ SOC Analyst & SOC 2 Explorer", "subtitle": "Explore 147 entries: playbooks, SIEM use cases, triage procedures, SOC 2 controls, tools & Q&A", "lang_label": "Language", "tab_playbooks": "SOC Playbooks", "tab_siem": "SIEM Use Cases", "tab_triage": "Triage Procedures", "tab_soc2": "SOC 2 Controls", "tab_tools": "Tools", "tab_qa": "Q&A", "tab_stats": "Statistics", "filter_alert_type": "Alert Type", "filter_severity": "Severity", "filter_siem": "SIEM Platform", "filter_alert_cat": "Alert Category", "filter_trust": "Trust Criteria", "filter_tool_cat": "Category", "filter_difficulty": "Difficulty", "all": "All", "no_results": "No results found.", "search": "Search keyword", }, "fr": { "title": "🛡️ SOC Analyst & SOC 2 Explorer", "subtitle": "Explorez 147 entrées : playbooks, cas d'usage SIEM, procédures de triage, contrôles SOC 2, outils & Q&R", "lang_label": "Langue", "tab_playbooks": "Playbooks SOC", "tab_siem": "Cas d'usage SIEM", "tab_triage": "Procédures de triage", "tab_soc2": "Contrôles SOC 2", "tab_tools": "Outils", "tab_qa": "Q&R", "tab_stats": "Statistiques", "filter_alert_type": "Type d'alerte", "filter_severity": "Sévérité", "filter_siem": "Plateforme SIEM", "filter_alert_cat": "Catégorie d'alerte", "filter_trust": "Critère de confiance", "filter_tool_cat": "Catégorie", "filter_difficulty": "Difficulté", "all": "Tous", "no_results": "Aucun résultat.", "search": "Mot-clé de recherche", }, } def L(key, lang): return LABELS.get(lang, LABELS["en"]).get(key, key) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def fmt_list(val): """Format a value that might be a list, string, or None into readable text.""" if val is None or (isinstance(val, float) and pd.isna(val)): return "" if isinstance(val, list): return "\n".join(f"• {v}" for v in val) s = str(val).strip() if not s or s.lower() == "nan": return "" return s def fmt_code(val): if val is None or (isinstance(val, float) and pd.isna(val)): return "" s = str(val).strip() if not s or s.lower() == "nan": return "" return f"```\n{s}\n```" def filter_df(df, type_col, type_val, filters: dict): sub = df[df[type_col] == type_val].copy() if type_col and type_val else df.copy() for col, val in filters.items(): if val and val not in ("All", "Tous", "") and col in sub.columns: sub = sub[sub[col].astype(str) == val] return sub # --------------------------------------------------------------------------- # Tab renderers # --------------------------------------------------------------------------- def render_playbooks(lang, alert_type, severity): df = get_df(lang) sub = filter_df(df, "type", "playbook", {}) if alert_type and alert_type not in ("All", "Tous"): sub = sub[safe_col(sub, "alert_type").astype(str) == alert_type] if severity and severity not in ("All", "Tous"): sub = sub[safe_col(sub, "severity").astype(str) == severity] if sub.empty: return L("no_results", lang) blocks = [] for _, row in sub.iterrows(): title = fmt_list(row.get("alert_type", "Playbook")) sev = fmt_list(row.get("severity", "")) blocks.append(f"## {title} \n**Severity:** {sev}\n") for field, label in [("triage_steps", "Triage Steps"), ("investigation_steps", "Investigation Steps"), ("containment_actions", "Containment Actions"), ("mitre_techniques", "MITRE Techniques"), ("tools_required", "Tools Required"), ("sla_minutes", "SLA (minutes)")]: v = fmt_list(row.get(field)) if v: blocks.append(f"### {label}\n{v}\n") blocks.append("---\n") return "\n".join(blocks) def render_siem(lang, platform): df = get_df(lang) sub = filter_df(df, "type", "siem_usecase", {}) if platform and platform not in ("All", "Tous"): sub = sub[safe_col(sub, "siem_platform").astype(str) == platform] if sub.empty: return L("no_results", lang) blocks = [] for _, row in sub.iterrows(): name = fmt_list(row.get("use_case_name", "")) plat = fmt_list(row.get("siem_platform", "")) blocks.append(f"## {name} \n**Platform:** {plat}\n") for field, label in [("detection_logic", "Detection Logic"), ("data_sources", "Data Sources")]: v = fmt_list(row.get(field)) if v: blocks.append(f"### {label}\n{v}\n") q = fmt_code(row.get("query_example")) if q: blocks.append(f"### Query Example\n{q}\n") blocks.append("---\n") return "\n".join(blocks) def render_triage(lang, alert_cat): df = get_df(lang) sub = filter_df(df, "type", "triage", {}) if alert_cat and alert_cat not in ("All", "Tous"): sub = sub[safe_col(sub, "alert_category").astype(str) == alert_cat] if sub.empty: return L("no_results", lang) blocks = [] for _, row in sub.iterrows(): cat = fmt_list(row.get("alert_category", "")) blocks.append(f"## {cat}\n") for field, label in [("priority_matrix", "Priority Matrix"), ("escalation_path", "Escalation Path"), ("sla_minutes", "SLA (minutes)"), ("triage_steps", "Triage Steps")]: v = fmt_list(row.get(field)) if v: blocks.append(f"### {label}\n{v}\n") blocks.append("---\n") return "\n".join(blocks) def render_soc2(lang, trust): df = get_df(lang) sub = filter_df(df, "type", "soc2_control", {}) if trust and trust not in ("All", "Tous"): sub = sub[safe_col(sub, "trust_criteria").astype(str) == trust] if sub.empty: return L("no_results", lang) blocks = [] for _, row in sub.iterrows(): cid = fmt_list(row.get("control_id", "")) cname = fmt_list(row.get("control_name", "")) blocks.append(f"## {cid} — {cname}\n") tc = fmt_list(row.get("trust_criteria", "")) if tc: blocks.append(f"**Trust Criteria:** {tc}\n") ev = fmt_list(row.get("evidence_required", "")) if ev: blocks.append(f"### Evidence Required\n{ev}\n") blocks.append("---\n") return "\n".join(blocks) def render_tools(lang, category): df = get_df(lang) sub = filter_df(df, "type", "tool", {}) if category and category not in ("All", "Tous"): sub = sub[safe_col(sub, "category").astype(str) == category] if sub.empty: return L("no_results", lang) blocks = [] for _, row in sub.iterrows(): name = fmt_list(row.get("name", "")) vendor = fmt_list(row.get("vendor", "")) blocks.append(f"## {name} \n**Vendor:** {vendor}\n") for field, label in [("features", "Features"), ("strengths", "Strengths"), ("weaknesses", "Weaknesses"), ("category", "Category")]: v = fmt_list(row.get(field)) if v: blocks.append(f"### {label}\n{v}\n") blocks.append("---\n") return "\n".join(blocks) def render_qa(lang, difficulty, keyword): df = get_df(lang) sub = filter_df(df, "type", "qa", {}) if difficulty and difficulty not in ("All", "Tous"): sub = sub[safe_col(sub, "difficulty").astype(str) == difficulty] if keyword and keyword.strip(): kw = keyword.strip().lower() mask = sub.apply(lambda r: kw in str(r.get("question", "")).lower() or kw in str(r.get("answer", "")).lower(), axis=1) sub = sub[mask] if sub.empty: return L("no_results", lang) blocks = [] for _, row in sub.iterrows(): q = fmt_list(row.get("question", "")) a = fmt_list(row.get("answer", "")) d = fmt_list(row.get("difficulty", "")) blocks.append(f"### Q: {q}\n**Difficulty:** {d}\n\n**A:** {a}\n\n---\n") return "\n".join(blocks) # --------------------------------------------------------------------------- # Statistics # --------------------------------------------------------------------------- def render_stats(lang): df = get_df(lang) figs = [] # Severity distribution (playbooks + triage) if "severity" in df.columns: sev = df["severity"].dropna() if not sev.empty: counts = sev.value_counts().reset_index() counts.columns = ["Severity", "Count"] fig1 = px.bar(counts, x="Severity", y="Count", title="Distribution by Severity", color="Severity", color_discrete_sequence=px.colors.qualitative.Set2) figs.append(fig1) # SIEM platform distribution if "siem_platform" in df.columns: sp = df["siem_platform"].dropna() if not sp.empty: counts = sp.value_counts().reset_index() counts.columns = ["Platform", "Count"] fig2 = px.pie(counts, names="Platform", values="Count", title="SIEM Platform Distribution", color_discrete_sequence=px.colors.qualitative.Pastel) figs.append(fig2) # Trust criteria distribution if "trust_criteria" in df.columns: tc = df["trust_criteria"].dropna() if not tc.empty: counts = tc.value_counts().reset_index() counts.columns = ["Trust Criteria", "Count"] fig3 = px.bar(counts, x="Trust Criteria", y="Count", title="SOC 2 Trust Criteria Distribution", color="Trust Criteria", color_discrete_sequence=px.colors.qualitative.Bold) figs.append(fig3) # Entry type distribution if "type" in df.columns: tp = df["type"].dropna() if not tp.empty: counts = tp.value_counts().reset_index() counts.columns = ["Type", "Count"] fig4 = px.pie(counts, names="Type", values="Count", title="Entry Type Distribution", color_discrete_sequence=px.colors.qualitative.Vivid) figs.append(fig4) return figs # --------------------------------------------------------------------------- # Dropdown choice helpers # --------------------------------------------------------------------------- def choices_for(col, type_val, lang, include_all=True): df = get_df(lang) sub = df[df["type"] == type_val] if "type" in df.columns and type_val else df vals = unique_vals(sub, col) if include_all: vals = [L("all", lang)] + vals return vals # --------------------------------------------------------------------------- # Footer # --------------------------------------------------------------------------- FOOTER = """
SOC Analyst & SOC 2 Explorer — Built by AYI-NEDJIMI Consultants