| | import gradio as gr |
| | import pandas as pd |
| | import plotly.express as px |
| | import plotly.graph_objects as go |
| | from datasets import load_dataset |
| |
|
| | |
| | |
| | |
| |
|
| | DATASETS = { |
| | "FR": "AYI-NEDJIMI/devsecops-pipeline-fr", |
| | "EN": "AYI-NEDJIMI/devsecops-pipeline-en", |
| | } |
| |
|
| | _cache = {} |
| |
|
| |
|
| | def _load(lang: str) -> pd.DataFrame: |
| | if lang not in _cache: |
| | try: |
| | ds = load_dataset(DATASETS[lang], split="train") |
| | _cache[lang] = ds.to_pandas() |
| | except Exception as e: |
| | print(f"Error loading {lang} dataset: {e}") |
| | _cache[lang] = pd.DataFrame() |
| | return _cache[lang] |
| |
|
| |
|
| | def _safe(df: pd.DataFrame, col: str): |
| | """Return column values if it exists, else empty Series.""" |
| | if col in df.columns: |
| | return df[col].fillna("") |
| | return pd.Series([""] * len(df), index=df.index) |
| |
|
| |
|
| | def _filter_type(lang, t): |
| | df = _load(lang) |
| | if "type" in df.columns: |
| | return df[df["type"] == t].reset_index(drop=True) |
| | return df |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def _display(df, cols): |
| | present = [c for c in cols if c in df.columns] |
| | out = df[present].copy() if present else pd.DataFrame() |
| | return out |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def tab_practices(lang, maturity_filter): |
| | df = _filter_type(lang, "practice") |
| | if maturity_filter and maturity_filter != "All": |
| | df = df[_safe(df, "maturity_level") == maturity_filter] |
| | cols = ["practice_name", "description", "benefits", "tools", "metrics"] |
| | return _display(df, cols) |
| |
|
| |
|
| | def tab_sast(lang): |
| | df = _filter_type(lang, "sast_tool") |
| | cols = ["name", "vendor", "supported_languages", "ci_integration", "strengths", "weaknesses"] |
| | return _display(df, cols) |
| |
|
| |
|
| | def tab_dast(lang): |
| | df = _filter_type(lang, "dast_tool") |
| | cols = ["name", "vendor", "scan_types", "api_support", "strengths", "weaknesses"] |
| | return _display(df, cols) |
| |
|
| |
|
| | def tab_sca(lang): |
| | df = _filter_type(lang, "sca_tool") |
| | cols = ["name", "vendor", "package_managers_supported", "vulnerability_db", "strengths", "weaknesses"] |
| | return _display(df, cols) |
| |
|
| |
|
| | def tab_pipeline(lang, ci_filter): |
| | df = _filter_type(lang, "pipeline_template") |
| | if ci_filter and ci_filter != "All": |
| | df = df[_safe(df, "ci_platform") == ci_filter] |
| | cols = ["pipeline_name", "stages", "yaml_example", "security_gates"] |
| | return _display(df, cols) |
| |
|
| |
|
| | def tab_container(lang): |
| | df = _filter_type(lang, "container_security") |
| | cols = ["category", "name", "implementation", "best_practices", "common_misconfigurations"] |
| | return _display(df, cols) |
| |
|
| |
|
| | def tab_secret(lang): |
| | df = _filter_type(lang, "secret_management") |
| | cols = ["name", "vendor", "features", "integration", "strengths", "weaknesses"] |
| | return _display(df, cols) |
| |
|
| |
|
| | def tab_qa(lang): |
| | df = _filter_type(lang, "qa") |
| | cols = ["question", "answer", "difficulty"] |
| | return _display(df, cols) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def pipeline_detail(lang, ci_filter): |
| | df = _filter_type(lang, "pipeline_template") |
| | if ci_filter and ci_filter != "All": |
| | df = df[_safe(df, "ci_platform") == ci_filter] |
| | parts = [] |
| | for _, row in df.iterrows(): |
| | name = row.get("pipeline_name", "N/A") |
| | stages = row.get("stages", "N/A") |
| | gates = row.get("security_gates", "N/A") |
| | yaml_ex = row.get("yaml_example", "") |
| | parts.append(f"### {name}\n\n**Stages:** {stages}\n\n**Security Gates:** {gates}\n\n```yaml\n{yaml_ex}\n```\n\n---\n") |
| | return "\n".join(parts) if parts else "No pipeline templates found." |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def stats_type_chart(lang): |
| | df = _load(lang) |
| | if "type" not in df.columns: |
| | return go.Figure() |
| | counts = df["type"].value_counts().reset_index() |
| | counts.columns = ["type", "count"] |
| | fig = px.bar(counts, x="type", y="count", title="Entries by Type", |
| | color="type", text_auto=True) |
| | fig.update_layout(showlegend=False) |
| | return fig |
| |
|
| |
|
| | def stats_ci_chart(lang): |
| | df = _load(lang) |
| | if "ci_platform" not in df.columns: |
| | return go.Figure() |
| | sub = df[df["ci_platform"].notna() & (df["ci_platform"] != "")] |
| | if sub.empty: |
| | return go.Figure() |
| | counts = sub["ci_platform"].value_counts().reset_index() |
| | counts.columns = ["ci_platform", "count"] |
| | fig = px.pie(counts, names="ci_platform", values="count", title="Pipeline Templates by CI Platform") |
| | return fig |
| |
|
| |
|
| | def stats_maturity_chart(lang): |
| | df = _load(lang) |
| | if "maturity_level" not in df.columns: |
| | return go.Figure() |
| | sub = df[df["maturity_level"].notna() & (df["maturity_level"] != "")] |
| | if sub.empty: |
| | return go.Figure() |
| | counts = sub["maturity_level"].value_counts().reset_index() |
| | counts.columns = ["maturity_level", "count"] |
| | fig = px.bar(counts, x="maturity_level", y="count", title="Practices by Maturity Level", |
| | color="maturity_level", text_auto=True) |
| | fig.update_layout(showlegend=False) |
| | return fig |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def _maturity_choices(lang): |
| | df = _filter_type(lang, "practice") |
| | if "maturity_level" in df.columns: |
| | vals = sorted(df["maturity_level"].dropna().unique().tolist()) |
| | else: |
| | vals = [] |
| | return ["All"] + vals |
| |
|
| |
|
| | def _ci_choices(lang): |
| | df = _filter_type(lang, "pipeline_template") |
| | if "ci_platform" in df.columns: |
| | vals = sorted(df["ci_platform"].dropna().unique().tolist()) |
| | else: |
| | vals = [] |
| | return ["All"] + vals |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | FOOTER_HTML = """ |
| | <div style="text-align:center; padding:20px; margin-top:30px; border-top:1px solid #444; font-size:0.9em; color:#aaa;"> |
| | <p><strong>DevSecOps Pipeline Explorer</strong> โ Built by AYI-NEDJIMI Consultants</p> |
| | <p> |
| | <a href="https://ayinedjimi-consultants.fr" target="_blank" style="margin:0 8px;">๐ ayinedjimi-consultants.fr</a> | |
| | <a href="https://www.linkedin.com/company/ayi-nedjimi" target="_blank" style="margin:0 8px;">LinkedIn</a> | |
| | <a href="https://github.com/AYI-NEDJIMI" target="_blank" style="margin:0 8px;">GitHub</a> | |
| | <a href="https://x.com/AYI_NEDJIMI" target="_blank" style="margin:0 8px;">X / Twitter</a> |
| | </p> |
| | </div> |
| | """ |
| |
|
| | |
| | |
| | |
| |
|
| | def build_app(): |
| | with gr.Blocks( |
| | title="DevSecOps Pipeline Explorer", |
| | theme=gr.themes.Soft(), |
| | ) as demo: |
| | gr.Markdown("# DevSecOps Pipeline Explorer\nExplore 130 DevSecOps practices, tools, pipeline templates, and more.") |
| |
|
| | lang = gr.Radio(["EN", "FR"], value="EN", label="Language / Langue", interactive=True) |
| |
|
| | with gr.Tabs(): |
| | |
| | with gr.Tab("DevSecOps Practices"): |
| | maturity_dd = gr.Dropdown(choices=_maturity_choices("EN"), value="All", label="Filter by Maturity Level") |
| | practices_table = gr.Dataframe(value=tab_practices("EN", "All"), label="Practices", wrap=True) |
| | btn_practices = gr.Button("Refresh") |
| |
|
| | def _refresh_practices(l, m): |
| | return tab_practices(l, m) |
| |
|
| | btn_practices.click(_refresh_practices, [lang, maturity_dd], practices_table) |
| | lang.change(lambda l: gr.update(choices=_maturity_choices(l), value="All"), lang, maturity_dd) |
| | lang.change(lambda l: tab_practices(l, "All"), lang, practices_table) |
| | maturity_dd.change(_refresh_practices, [lang, maturity_dd], practices_table) |
| |
|
| | |
| | with gr.Tab("SAST Tools"): |
| | sast_table = gr.Dataframe(value=tab_sast("EN"), label="SAST Tools", wrap=True) |
| | lang.change(lambda l: tab_sast(l), lang, sast_table) |
| |
|
| | |
| | with gr.Tab("DAST Tools"): |
| | dast_table = gr.Dataframe(value=tab_dast("EN"), label="DAST Tools", wrap=True) |
| | lang.change(lambda l: tab_dast(l), lang, dast_table) |
| |
|
| | |
| | with gr.Tab("SCA Tools"): |
| | sca_table = gr.Dataframe(value=tab_sca("EN"), label="SCA Tools", wrap=True) |
| | lang.change(lambda l: tab_sca(l), lang, sca_table) |
| |
|
| | |
| | with gr.Tab("Pipeline Templates"): |
| | ci_dd = gr.Dropdown(choices=_ci_choices("EN"), value="All", label="Filter by CI Platform") |
| | pipeline_table = gr.Dataframe(value=tab_pipeline("EN", "All"), label="Pipeline Templates", wrap=True) |
| | pipeline_md = gr.Markdown(value=pipeline_detail("EN", "All"), label="YAML Details") |
| | btn_pipeline = gr.Button("Refresh") |
| |
|
| | def _refresh_pipeline(l, c): |
| | return tab_pipeline(l, c), pipeline_detail(l, c) |
| |
|
| | btn_pipeline.click(_refresh_pipeline, [lang, ci_dd], [pipeline_table, pipeline_md]) |
| | lang.change(lambda l: gr.update(choices=_ci_choices(l), value="All"), lang, ci_dd) |
| | lang.change(lambda l: (tab_pipeline(l, "All"), pipeline_detail(l, "All")), lang, [pipeline_table, pipeline_md]) |
| | ci_dd.change(lambda l, c: (tab_pipeline(l, c), pipeline_detail(l, c)), [lang, ci_dd], [pipeline_table, pipeline_md]) |
| |
|
| | |
| | with gr.Tab("Container Security"): |
| | container_table = gr.Dataframe(value=tab_container("EN"), label="Container Security", wrap=True) |
| | lang.change(lambda l: tab_container(l), lang, container_table) |
| |
|
| | |
| | with gr.Tab("Secret Management"): |
| | secret_table = gr.Dataframe(value=tab_secret("EN"), label="Secret Management", wrap=True) |
| | lang.change(lambda l: tab_secret(l), lang, secret_table) |
| |
|
| | |
| | with gr.Tab("Q&A"): |
| | qa_table = gr.Dataframe(value=tab_qa("EN"), label="Q&A", wrap=True) |
| | lang.change(lambda l: tab_qa(l), lang, qa_table) |
| |
|
| | |
| | with gr.Tab("Statistics"): |
| | chart_type = gr.Plot(value=stats_type_chart("EN"), label="By Type") |
| | chart_ci = gr.Plot(value=stats_ci_chart("EN"), label="By CI Platform") |
| | chart_maturity = gr.Plot(value=stats_maturity_chart("EN"), label="By Maturity Level") |
| | lang.change(lambda l: stats_type_chart(l), lang, chart_type) |
| | lang.change(lambda l: stats_ci_chart(l), lang, chart_ci) |
| | lang.change(lambda l: stats_maturity_chart(l), lang, chart_maturity) |
| |
|
| | gr.HTML(FOOTER_HTML) |
| |
|
| | return demo |
| |
|
| |
|
| | if __name__ == "__main__": |
| | demo = build_app() |
| | demo.launch() |
| |
|