| import gradio as gr |
| import pandas as pd |
| import plotly.express as px |
| import plotly.graph_objects as go |
| from datasets import load_dataset |
|
|
| |
| |
| |
|
|
| 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 = { |
| "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) |
|
|
| |
| |
| |
|
|
| 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 |
|
|
| |
| |
| |
|
|
| 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) |
|
|
|
|
| |
| |
| |
|
|
| def render_stats(lang): |
| df = get_df(lang) |
| figs = [] |
|
|
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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 |
|
|
|
|
| |
| |
| |
|
|
| 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 = """ |
| <div style="text-align:center; padding:20px; margin-top:30px; border-top:1px solid #444; font-size:0.9em; color:#aaa;"> |
| <p><strong>SOC Analyst & SOC 2 Explorer</strong> — Built by <a href="https://ayinedjimi-consultants.fr" target="_blank">AYI-NEDJIMI Consultants</a></p> |
| <p> |
| <a href="https://ayinedjimi-consultants.fr" target="_blank">🌐 Website</a> | |
| <a href="https://www.linkedin.com/in/ayinedjimi/" target="_blank">💼 LinkedIn</a> | |
| <a href="https://github.com/AYI-NEDJIMI" target="_blank">🐙 GitHub</a> | |
| <a href="https://x.com/AYI_NEDJIMI" target="_blank">🐦 X / Twitter</a> |
| </p> |
| </div> |
| """ |
|
|
| |
| |
| |
|
|
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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) |
| |
| while len(figs) < 4: |
| figs.append(go.Figure()) |
| return figs[:4] |
|
|
| stats_btn.click(update_stats, [lang], stats_plots) |
|
|
| |
| demo.load(update_stats, [lang], stats_plots) |
|
|
| |
| def on_lang_change(lang_val): |
| return ( |
| |
| 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], |
| ) |
|
|
| |
| gr.HTML(FOOTER) |
|
|
| return demo |
|
|
|
|
| if __name__ == "__main__": |
| demo = build_app() |
| demo.launch() |
|
|