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

🌐 Website  |  💼 LinkedIn  |  🐙 GitHub  |  🐦 X / Twitter

""" # --------------------------------------------------------------------------- # Build the Gradio App # --------------------------------------------------------------------------- def build_app(): with gr.Blocks( title="SOC Analyst & SOC 2 Explorer", theme=gr.themes.Soft(primary_hue="blue", secondary_hue="cyan"), ) as demo: gr.Markdown("# 🛡️ SOC Analyst & SOC 2 Explorer") gr.Markdown("Explore **147 entries**: playbooks, SIEM use cases, triage procedures, SOC 2 controls, tools & Q&A. \nDatasets: [`soc-analyst-en`](https://huggingface.co/datasets/AYI-NEDJIMI/soc-analyst-en) | [`soc-analyst-fr`](https://huggingface.co/datasets/AYI-NEDJIMI/soc-analyst-fr)") lang = gr.Radio(choices=["en", "fr"], value="en", label="Language / Langue", interactive=True) # ---- TAB 1: Playbooks ---- with gr.Tab("SOC Playbooks"): with gr.Row(): pb_alert = gr.Dropdown(choices=choices_for("alert_type", "playbook", "en"), label="Alert Type", value="All", interactive=True) pb_sev = gr.Dropdown(choices=choices_for("severity", "playbook", "en"), label="Severity", value="All", interactive=True) pb_out = gr.Markdown(value=render_playbooks("en", "All", "All")) pb_alert.change(render_playbooks, [lang, pb_alert, pb_sev], pb_out) pb_sev.change(render_playbooks, [lang, pb_alert, pb_sev], pb_out) # ---- TAB 2: SIEM Use Cases ---- with gr.Tab("SIEM Use Cases"): siem_plat = gr.Dropdown(choices=choices_for("siem_platform", "siem_usecase", "en"), label="SIEM Platform", value="All", interactive=True) siem_out = gr.Markdown(value=render_siem("en", "All")) siem_plat.change(render_siem, [lang, siem_plat], siem_out) # ---- TAB 3: Triage Procedures ---- with gr.Tab("Triage Procedures"): tri_cat = gr.Dropdown(choices=choices_for("alert_category", "triage", "en"), label="Alert Category", value="All", interactive=True) tri_out = gr.Markdown(value=render_triage("en", "All")) tri_cat.change(render_triage, [lang, tri_cat], tri_out) # ---- TAB 4: SOC 2 Controls ---- with gr.Tab("SOC 2 Controls"): soc2_trust = gr.Dropdown(choices=choices_for("trust_criteria", "soc2_control", "en"), label="Trust Criteria", value="All", interactive=True) soc2_out = gr.Markdown(value=render_soc2("en", "All")) soc2_trust.change(render_soc2, [lang, soc2_trust], soc2_out) # ---- TAB 5: Tools ---- with gr.Tab("Tools"): tool_cat = gr.Dropdown(choices=choices_for("category", "tool", "en"), label="Category", value="All", interactive=True) tool_out = gr.Markdown(value=render_tools("en", "All")) tool_cat.change(render_tools, [lang, tool_cat], tool_out) # ---- TAB 6: Q&A ---- with gr.Tab("Q&A"): with gr.Row(): qa_diff = gr.Dropdown(choices=choices_for("difficulty", "qa", "en"), label="Difficulty", value="All", interactive=True) qa_kw = gr.Textbox(label="Search keyword", value="", interactive=True) qa_out = gr.Markdown(value=render_qa("en", "All", "")) qa_diff.change(render_qa, [lang, qa_diff, qa_kw], qa_out) qa_kw.submit(render_qa, [lang, qa_diff, qa_kw], qa_out) # ---- TAB 7: Statistics ---- with gr.Tab("Statistics"): stats_btn = gr.Button("Refresh Statistics") stats_plots = [gr.Plot() for _ in range(4)] def update_stats(lang_val): figs = render_stats(lang_val) # Pad to 4 outputs while len(figs) < 4: figs.append(go.Figure()) return figs[:4] stats_btn.click(update_stats, [lang], stats_plots) # Auto-load on start demo.load(update_stats, [lang], stats_plots) # ---- Language toggle: refresh all dropdowns & content ---- def on_lang_change(lang_val): return ( # Dropdown choices gr.update(choices=choices_for("alert_type", "playbook", lang_val), value=L("all", lang_val)), gr.update(choices=choices_for("severity", "playbook", lang_val), value=L("all", lang_val)), render_playbooks(lang_val, L("all", lang_val), L("all", lang_val)), gr.update(choices=choices_for("siem_platform", "siem_usecase", lang_val), value=L("all", lang_val)), render_siem(lang_val, L("all", lang_val)), gr.update(choices=choices_for("alert_category", "triage", lang_val), value=L("all", lang_val)), render_triage(lang_val, L("all", lang_val)), gr.update(choices=choices_for("trust_criteria", "soc2_control", lang_val), value=L("all", lang_val)), render_soc2(lang_val, L("all", lang_val)), gr.update(choices=choices_for("category", "tool", lang_val), value=L("all", lang_val)), render_tools(lang_val, L("all", lang_val)), gr.update(choices=choices_for("difficulty", "qa", lang_val), value=L("all", lang_val)), render_qa(lang_val, L("all", lang_val), ""), ) lang.change( on_lang_change, [lang], [pb_alert, pb_sev, pb_out, siem_plat, siem_out, tri_cat, tri_out, soc2_trust, soc2_out, tool_cat, tool_out, qa_diff, qa_out], ) # Footer gr.HTML(FOOTER) return demo if __name__ == "__main__": demo = build_app() demo.launch()