Spaces:
Running
Running
MugdhaV
Initial deployment: Gradio frontend with Modal backend - Multi-language security scanner with parallel processing
e1e9580 | """ | |
| UI Components for Security Auditor Gradio Interface | |
| Generates HTML for badges, cards, and sections matching design mockups | |
| """ | |
| from typing import Dict, List | |
| def create_severity_badge(severity: str, count: int = 0) -> str: | |
| """ | |
| Create severity badge as a visual indicator. | |
| Args: | |
| severity: CRITICAL, HIGH, MEDIUM, LOW, INFO | |
| count: Number of findings | |
| Returns: | |
| HTML string for severity badge | |
| """ | |
| colors = { | |
| "CRITICAL": {"bg": "#fef2f2", "text": "#dc2626", "border": "#fca5a5"}, | |
| "HIGH": {"bg": "#fff7ed", "text": "#ea580c", "border": "#fdba74"}, | |
| "MEDIUM": {"bg": "#fffbeb", "text": "#d97706", "border": "#fcd34d"}, | |
| "LOW": {"bg": "#f0fdfa", "text": "#0d9488", "border": "#5eead4"}, | |
| "INFO": {"bg": "#f9fafb", "text": "#6b7280", "border": "#d1d5db"} | |
| } | |
| color = colors.get(severity, colors["INFO"]) | |
| return f""" | |
| <div | |
| class="severity-badge" | |
| data-severity="{severity}" | |
| style=" | |
| display: inline-flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 16px 24px; | |
| background: {color['bg']}; | |
| border: 2px solid {color['border']}; | |
| border-radius: 6px; | |
| min-width: 100px; | |
| " | |
| > | |
| <div style=" | |
| font-size: 11px; | |
| font-weight: 600; | |
| color: {color['text']}; | |
| text-transform: capitalize; | |
| letter-spacing: 0.05em; | |
| margin-bottom: 8px; | |
| ">{severity.capitalize()}</div> | |
| <div style=" | |
| font-size: 32px; | |
| font-weight: 700; | |
| color: {color['text']}; | |
| ">{count}</div> | |
| </div> | |
| """ | |
| def create_finding_card(vulnerability: Dict) -> str: | |
| """ | |
| Create HTML for vulnerability finding card. | |
| Args: | |
| vulnerability: Dict with name, severity, file_path, line_number, | |
| description, cwe_id, cve_ids, remediation | |
| Returns: | |
| HTML string for finding card | |
| """ | |
| severity_colors = { | |
| "CRITICAL": "#dc2626", | |
| "HIGH": "#ea580c", | |
| "MEDIUM": "#d97706", | |
| "LOW": "#0d9488", | |
| "INFO": "#6b7280" | |
| } | |
| severity_bg = { | |
| "CRITICAL": "#fef2f2", | |
| "HIGH": "#fff7ed", | |
| "MEDIUM": "#fffbeb", | |
| "LOW": "#f0fdfa", | |
| "INFO": "#f9fafb" | |
| } | |
| severity = vulnerability.get('risk_level', 'INFO') | |
| color = severity_colors.get(severity, severity_colors['INFO']) | |
| bg = severity_bg.get(severity, severity_bg['INFO']) | |
| # Build CWE/CVE tags | |
| tags_html = "" | |
| if vulnerability.get('cwe_id'): | |
| tags_html += f""" | |
| <span style=" | |
| display: inline-block; | |
| padding: 4px 12px; | |
| background: #f3f4f6; | |
| color: #4b5563; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| font-weight: 500; | |
| margin-right: 8px; | |
| ">{vulnerability['cwe_id']}</span> | |
| """ | |
| for cve in vulnerability.get('cve_ids', [])[:3]: # Show max 3 CVEs | |
| tags_html += f""" | |
| <span style=" | |
| display: inline-block; | |
| padding: 4px 12px; | |
| background: #fef2f2; | |
| color: #dc2626; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| font-weight: 500; | |
| margin-right: 8px; | |
| ">{cve}</span> | |
| """ | |
| # Build remediation section | |
| remediation_html = "" | |
| if vulnerability.get('remediation'): | |
| # Truncate very long remediation text | |
| remediation_text = vulnerability['remediation'] | |
| if len(remediation_text) > 500: | |
| remediation_text = remediation_text[:500] + "..." | |
| remediation_html = f""" | |
| <details style="margin-top: 16px;" class="remediation-details"> | |
| <summary style=" | |
| cursor: pointer; | |
| padding: 12px 16px; | |
| background: #f9fafb; | |
| border-radius: 8px; | |
| font-weight: 600; | |
| color: #374151; | |
| user-select: none; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 8px; | |
| transition: background 0.2s ease; | |
| " onmouseover="this.style.background='#f3f4f6'" onmouseout="this.style.background='#f9fafb'"> | |
| <div style="display: flex; align-items: center; gap: 8px;"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" | |
| fill="none" stroke="#d97757" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path> | |
| </svg> | |
| <span>Remediation Guidance</span> | |
| </div> | |
| <svg class="chevron-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" | |
| fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" | |
| style="transition: transform 0.2s ease;"> | |
| <polyline points="6 9 12 15 18 9"></polyline> | |
| </svg> | |
| </summary> | |
| <style> | |
| details[open] .chevron-icon {{ | |
| transform: rotate(180deg); | |
| }} | |
| </style> | |
| <div style=" | |
| padding: 16px; | |
| margin-top: 8px; | |
| background: #f0fdf4; | |
| border-left: 4px solid #10b981; | |
| border-radius: 8px; | |
| "> | |
| <pre style=" | |
| background: white; | |
| padding: 12px; | |
| border-radius: 6px; | |
| overflow-x: auto; | |
| font-size: 13px; | |
| line-height: 1.5; | |
| color: #1f2937; | |
| white-space: pre-wrap; | |
| word-wrap: break-word; | |
| ">{remediation_text}</pre> | |
| </div> | |
| </details> | |
| """ | |
| return f""" | |
| <div class="finding-card" data-severity="{severity}" style=" | |
| background: white; | |
| border: 1px solid #e5e7eb; | |
| border-radius: 12px; | |
| padding: 20px; | |
| margin-bottom: 16px; | |
| box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); | |
| "> | |
| <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;"> | |
| <h3 style=" | |
| margin: 0; | |
| font-size: 18px; | |
| font-weight: 600; | |
| color: #111827; | |
| ">{vulnerability.get('name', 'Unknown Vulnerability')}</h3> | |
| <span style=" | |
| padding: 6px 12px; | |
| background: {bg}; | |
| color: {color}; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| ">{severity}</span> | |
| </div> | |
| <div style="margin-bottom: 12px;"> | |
| {tags_html} | |
| </div> | |
| <div style=" | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| margin-bottom: 12px; | |
| color: #6b7280; | |
| font-size: 14px; | |
| "> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" | |
| fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" | |
| style="flex-shrink: 0;"> | |
| <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path> | |
| <polyline points="14 2 14 8 20 8"></polyline> | |
| </svg> | |
| <span style="font-family: 'Fira Code', monospace; color: #374151;">{vulnerability.get('file_path', 'N/A')}:{vulnerability.get('line_number', 'N/A')}</span> | |
| </div> | |
| <p style=" | |
| margin: 0 0 16px 0; | |
| color: #4b5563; | |
| line-height: 1.6; | |
| font-size: 14px; | |
| ">{vulnerability.get('description', '')}</p> | |
| {remediation_html} | |
| </div> | |
| """ | |
| def create_summary_section(scan_result: Dict) -> str: | |
| """ | |
| Create Analysis Summary section with proper alignment and sentence capitalization. | |
| Args: | |
| scan_result: Dict with target, files_scanned, scan_type, summary data | |
| Returns: | |
| HTML string for summary section | |
| """ | |
| summary = scan_result.get('summary', {}) | |
| # Metadata row - Fixed alignment with grid layout | |
| metadata_html = f""" | |
| <div style=" | |
| display: grid; | |
| grid-template-columns: repeat(4, minmax(0, 1fr)); | |
| gap: 24px; | |
| margin-bottom: 24px; | |
| padding: 16px; | |
| background: #ffffff; | |
| border: 1px solid #e5e7eb; | |
| border-radius: 6px; | |
| "> | |
| <div style="display: flex; flex-direction: column; justify-content: center;"> | |
| <div style=" | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: #6b7280; | |
| margin-bottom: 8px; | |
| line-height: 1.2; | |
| ">Target</div> | |
| <div style=" | |
| font-size: 14px; | |
| font-weight: 600; | |
| color: #131314; | |
| font-family: ui-monospace, monospace; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| line-height: 1.4; | |
| " title="{scan_result.get('target', 'N/A')}">{scan_result.get('target', 'N/A')}</div> | |
| </div> | |
| <div style="display: flex; flex-direction: column; justify-content: center;"> | |
| <div style=" | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: #6b7280; | |
| margin-bottom: 8px; | |
| line-height: 1.2; | |
| ">Files analyzed</div> | |
| <div style=" | |
| font-size: 24px; | |
| font-weight: 700; | |
| color: #131314; | |
| line-height: 1.2; | |
| ">{scan_result.get('files_scanned', 0)}</div> | |
| </div> | |
| <div style="display: flex; flex-direction: column; justify-content: center;"> | |
| <div style=" | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: #6b7280; | |
| margin-bottom: 8px; | |
| line-height: 1.2; | |
| ">Total findings</div> | |
| <div style=" | |
| font-size: 28px; | |
| font-weight: 700; | |
| color: #dc2626; | |
| line-height: 1.2; | |
| ">{summary.get('total_vulnerabilities', 0)}</div> | |
| </div> | |
| <div style="display: flex; flex-direction: column; justify-content: center;"> | |
| <div style=" | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: #6b7280; | |
| margin-bottom: 8px; | |
| line-height: 1.2; | |
| ">Analysis type</div> | |
| <div style=" | |
| font-size: 14px; | |
| font-weight: 700; | |
| color: #131314; | |
| text-transform: capitalize; | |
| line-height: 1.2; | |
| ">{scan_result.get('scan_type', 'local')}</div> | |
| </div> | |
| </div> | |
| """ | |
| # Severity badges row | |
| by_severity = summary.get('by_severity', {}) | |
| badges_html = f""" | |
| <div style=" | |
| display: flex; | |
| gap: 16px; | |
| flex-wrap: wrap; | |
| "> | |
| {create_severity_badge('CRITICAL', by_severity.get('CRITICAL', 0))} | |
| {create_severity_badge('HIGH', by_severity.get('HIGH', 0))} | |
| {create_severity_badge('MEDIUM', by_severity.get('MEDIUM', 0))} | |
| {create_severity_badge('LOW', by_severity.get('LOW', 0))} | |
| {create_severity_badge('INFO', by_severity.get('INFO', 0))} | |
| </div> | |
| """ | |
| return f""" | |
| <div style=" | |
| background: white; | |
| border: 1px solid #e5e7eb; | |
| border-radius: 12px; | |
| padding: 24px; | |
| margin-bottom: 12px; | |
| box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); | |
| "> | |
| <h2 style=" | |
| margin: 0 0 20px 0; | |
| font-size: 20px; | |
| font-weight: 700; | |
| color: #131314; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| "> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" | |
| fill="none" stroke="#d97757" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M9 11l3 3L22 4"></path> | |
| <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path> | |
| </svg> | |
| Analysis Summary | |
| </h2> | |
| {metadata_html} | |
| {badges_html} | |
| </div> | |
| """ | |
| def create_empty_state() -> str: | |
| """ | |
| Create HTML for empty state (no results yet). | |
| Returns: | |
| HTML string for empty state | |
| """ | |
| return """ | |
| <div style=" | |
| background: white; | |
| border: 2px dashed #e5e7eb; | |
| border-radius: 12px; | |
| padding: 60px 40px; | |
| text-align: center; | |
| margin: 40px 0; | |
| "> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" | |
| fill="none" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" | |
| style="margin: 0 auto 16px; display: block;"> | |
| <circle cx="11" cy="11" r="8"></circle> | |
| <path d="m21 21-4.35-4.35"></path> | |
| </svg> | |
| <h3 style=" | |
| margin: 0 0 8px 0; | |
| font-size: 20px; | |
| font-weight: 600; | |
| color: #131314; | |
| ">Ready to Scan</h3> | |
| <p style=" | |
| margin: 0; | |
| color: #6b7280; | |
| font-size: 14px; | |
| ">Upload files or enter a URL to begin security analysis</p> | |
| </div> | |
| """ | |
| def create_loading_state(message: str = "Scanning...") -> str: | |
| """ | |
| Create HTML for loading state. | |
| Args: | |
| message: Loading message to display | |
| Returns: | |
| HTML string for loading state | |
| """ | |
| return f""" | |
| <div style=" | |
| background: white; | |
| border: 1px solid #e5e7eb; | |
| border-radius: 12px; | |
| padding: 40px; | |
| text-align: center; | |
| margin: 40px 0; | |
| "> | |
| <div style=" | |
| display: inline-block; | |
| width: 40px; | |
| height: 40px; | |
| border: 4px solid #f3f4f6; | |
| border-top-color: #f59e0b; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin-bottom: 16px; | |
| "></div> | |
| <style> | |
| @keyframes spin {{ | |
| 0% {{ transform: rotate(0deg); }} | |
| 100% {{ transform: rotate(360deg); }} | |
| }} | |
| </style> | |
| <h3 style=" | |
| margin: 0 0 8px 0; | |
| font-size: 18px; | |
| font-weight: 600; | |
| color: #111827; | |
| ">{message}</h3> | |
| <p style=" | |
| margin: 0; | |
| color: #6b7280; | |
| font-size: 14px; | |
| ">This may take a few moments...</p> | |
| </div> | |
| """ | |