This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

1462 lines
38 KiB
Python

"""
BreakPilot Studio - Security Dashboard Module
DevSecOps Dashboard fuer Entwickler, Security-Experten und Ops:
Features fuer Developer:
- Scan-Ergebnisse auf einen Blick
- Pre-commit Hook Status
- Code Quality Metriken
- Quick-Fix Suggestions
Features fuer Security:
- Vulnerability Severity Distribution
- CVE-Tracking und Trends
- SBOM-Viewer
- Compliance-Status (OWASP Top 10)
- Secrets Detection History
Features fuer Ops:
- Container Image Scan Results
- Dependency Update Status
- Security Scan Scheduling
- CI/CD Pipeline Integration Status
- Runtime Security Alerts (Falco)
"""
class SecurityModule:
"""DevSecOps Security Dashboard Modul."""
@staticmethod
def get_css() -> str:
"""CSS fuer das Security Dashboard."""
return """
/* =============================================
SECURITY DASHBOARD MODULE - DevSecOps
============================================= */
.panel-security {
display: none;
flex-direction: column;
height: 100%;
background: var(--bp-bg);
overflow-y: auto;
}
.panel-security.active {
display: flex;
}
/* Security Header */
.security-header {
padding: 24px 32px;
background: var(--bp-surface);
border-bottom: 1px solid var(--bp-border);
display: flex;
justify-content: space-between;
align-items: center;
}
.security-header h2 {
font-size: 24px;
font-weight: 600;
color: var(--bp-text);
margin: 0;
display: flex;
align-items: center;
gap: 12px;
}
.security-header-icon {
font-size: 28px;
}
.security-header-actions {
display: flex;
gap: 12px;
align-items: center;
}
.security-status-badge {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.security-status-badge.secure {
background: rgba(34, 197, 94, 0.15);
color: #22c55e;
}
.security-status-badge.warning {
background: rgba(245, 158, 11, 0.15);
color: #f59e0b;
}
.security-status-badge.critical {
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
}
/* Security Tabs */
.security-tabs {
display: flex;
gap: 4px;
padding: 16px 32px;
background: var(--bp-surface);
border-bottom: 1px solid var(--bp-border);
flex-wrap: wrap;
}
.security-tab {
padding: 10px 20px;
border: none;
background: transparent;
color: var(--bp-text-muted);
font-size: 14px;
font-weight: 500;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 8px;
}
.security-tab:hover {
background: var(--bp-bg);
color: var(--bp-text);
}
.security-tab.active {
background: var(--bp-primary);
color: white;
}
.security-tab-badge {
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
font-weight: 600;
background: rgba(255, 255, 255, 0.2);
}
.security-tab.active .security-tab-badge {
background: rgba(255, 255, 255, 0.3);
}
/* Security Content */
.security-content {
padding: 24px 32px;
flex: 1;
}
/* Security Summary Cards */
.security-summary-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.security-summary-card {
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 12px;
padding: 20px;
transition: all 0.2s;
}
.security-summary-card:hover {
border-color: var(--bp-primary);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.security-summary-card.critical {
border-left: 4px solid #ef4444;
}
.security-summary-card.high {
border-left: 4px solid #f97316;
}
.security-summary-card.medium {
border-left: 4px solid #f59e0b;
}
.security-summary-card.low {
border-left: 4px solid #22c55e;
}
.security-summary-card.info {
border-left: 4px solid #3b82f6;
}
.security-summary-icon {
font-size: 24px;
margin-bottom: 8px;
}
.security-summary-count {
font-size: 36px;
font-weight: 700;
margin-bottom: 4px;
}
.security-summary-card.critical .security-summary-count { color: #ef4444; }
.security-summary-card.high .security-summary-count { color: #f97316; }
.security-summary-card.medium .security-summary-count { color: #f59e0b; }
.security-summary-card.low .security-summary-count { color: #22c55e; }
.security-summary-card.info .security-summary-count { color: #3b82f6; }
.security-summary-label {
font-size: 13px;
font-weight: 500;
color: var(--bp-text);
}
.security-summary-sublabel {
font-size: 11px;
color: var(--bp-text-muted);
margin-top: 4px;
}
/* Tool Status Section */
.security-tools-section {
margin-bottom: 24px;
}
.security-section-title {
font-size: 16px;
font-weight: 600;
color: var(--bp-text);
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.security-tools-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.security-tool-card {
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 12px;
padding: 20px;
}
.security-tool-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.security-tool-name {
font-size: 16px;
font-weight: 600;
color: var(--bp-text);
display: flex;
align-items: center;
gap: 8px;
}
.security-tool-status {
padding: 4px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
}
.security-tool-status.installed {
background: rgba(34, 197, 94, 0.15);
color: #22c55e;
}
.security-tool-status.not-installed {
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
}
.security-tool-status.running {
background: rgba(59, 130, 246, 0.15);
color: #3b82f6;
}
.security-tool-description {
font-size: 13px;
color: var(--bp-text-muted);
margin-bottom: 12px;
line-height: 1.5;
}
.security-tool-meta {
display: flex;
gap: 16px;
font-size: 12px;
color: var(--bp-text-muted);
}
.security-tool-meta-item {
display: flex;
align-items: center;
gap: 4px;
}
.security-tool-actions {
margin-top: 12px;
display: flex;
gap: 8px;
}
.security-tool-btn {
padding: 8px 16px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
border: none;
cursor: pointer;
transition: all 0.2s;
}
.security-tool-btn.primary {
background: var(--bp-primary);
color: white;
}
.security-tool-btn.primary:hover {
background: var(--bp-primary-hover);
}
.security-tool-btn.secondary {
background: var(--bp-surface-elevated);
color: var(--bp-text);
border: 1px solid var(--bp-border);
}
.security-tool-btn.secondary:hover {
background: var(--bp-bg);
}
/* Findings Table */
.security-findings-container {
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 12px;
overflow: hidden;
}
.security-findings-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--bp-border);
}
.security-findings-title {
font-size: 16px;
font-weight: 600;
color: var(--bp-text);
}
.security-findings-filter {
display: flex;
gap: 8px;
}
.security-filter-btn {
padding: 6px 12px;
border-radius: 6px;
font-size: 12px;
border: 1px solid var(--bp-border);
background: transparent;
color: var(--bp-text-muted);
cursor: pointer;
transition: all 0.2s;
}
.security-filter-btn:hover,
.security-filter-btn.active {
background: var(--bp-primary);
color: white;
border-color: var(--bp-primary);
}
.security-findings-table {
width: 100%;
border-collapse: collapse;
}
.security-findings-table th,
.security-findings-table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid var(--bp-border);
}
.security-findings-table th {
background: var(--bp-surface-elevated);
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
color: var(--bp-text-muted);
}
.security-findings-table td {
font-size: 13px;
color: var(--bp-text);
}
.security-findings-table tr:hover {
background: var(--bp-bg);
}
.severity-badge {
padding: 4px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
}
.severity-badge.critical {
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
}
.severity-badge.high {
background: rgba(249, 115, 22, 0.15);
color: #f97316;
}
.severity-badge.medium {
background: rgba(245, 158, 11, 0.15);
color: #f59e0b;
}
.severity-badge.low {
background: rgba(34, 197, 94, 0.15);
color: #22c55e;
}
.severity-badge.info {
background: rgba(59, 130, 246, 0.15);
color: #3b82f6;
}
/* SBOM Viewer */
.sbom-container {
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 12px;
overflow: hidden;
}
.sbom-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--bp-border);
}
.sbom-search {
padding: 8px 16px;
border-radius: 8px;
border: 1px solid var(--bp-border);
background: var(--bp-surface-elevated);
color: var(--bp-text);
font-size: 13px;
width: 300px;
}
.sbom-stats {
display: flex;
gap: 24px;
padding: 16px 20px;
background: var(--bp-bg);
border-bottom: 1px solid var(--bp-border);
}
.sbom-stat {
text-align: center;
}
.sbom-stat-value {
font-size: 24px;
font-weight: 700;
color: var(--bp-primary);
}
.sbom-stat-label {
font-size: 12px;
color: var(--bp-text-muted);
}
/* Timeline / History */
.security-timeline {
padding: 20px;
}
.security-timeline-item {
display: flex;
gap: 16px;
padding-bottom: 20px;
border-left: 2px solid var(--bp-border);
margin-left: 8px;
padding-left: 20px;
position: relative;
}
.security-timeline-item::before {
content: '';
position: absolute;
left: -6px;
top: 0;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--bp-primary);
}
.security-timeline-item.success::before {
background: #22c55e;
}
.security-timeline-item.warning::before {
background: #f59e0b;
}
.security-timeline-item.error::before {
background: #ef4444;
}
.security-timeline-time {
font-size: 12px;
color: var(--bp-text-muted);
min-width: 100px;
}
.security-timeline-content {
flex: 1;
}
.security-timeline-title {
font-size: 14px;
font-weight: 500;
color: var(--bp-text);
}
.security-timeline-desc {
font-size: 13px;
color: var(--bp-text-muted);
margin-top: 4px;
}
/* Loading Spinner */
.security-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px;
color: var(--bp-text-muted);
}
.security-spinner {
width: 40px;
height: 40px;
border: 3px solid var(--bp-border);
border-top-color: var(--bp-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Empty State */
.security-empty {
text-align: center;
padding: 60px 20px;
color: var(--bp-text-muted);
}
.security-empty-icon {
font-size: 48px;
margin-bottom: 16px;
}
.security-empty-title {
font-size: 18px;
font-weight: 600;
color: var(--bp-text);
margin-bottom: 8px;
}
.security-empty-desc {
font-size: 14px;
max-width: 400px;
margin: 0 auto;
}
/* Scan Progress */
.security-scan-progress {
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
}
.security-scan-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.security-scan-title {
font-size: 14px;
font-weight: 600;
color: var(--bp-text);
}
.security-scan-percentage {
font-size: 14px;
font-weight: 600;
color: var(--bp-primary);
}
.security-progress-bar {
height: 8px;
background: var(--bp-bg);
border-radius: 4px;
overflow: hidden;
}
.security-progress-fill {
height: 100%;
background: var(--bp-primary);
border-radius: 4px;
transition: width 0.3s ease;
}
.security-scan-steps {
display: flex;
justify-content: space-between;
margin-top: 12px;
font-size: 12px;
color: var(--bp-text-muted);
}
.security-scan-step {
display: flex;
align-items: center;
gap: 4px;
}
.security-scan-step.completed {
color: #22c55e;
}
.security-scan-step.active {
color: var(--bp-primary);
}
/* Auto-refresh indicator */
.security-auto-refresh {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--bp-text-muted);
}
.security-auto-refresh-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #22c55e;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Responsive */
@media (max-width: 768px) {
.security-header {
flex-direction: column;
gap: 16px;
align-items: flex-start;
}
.security-summary-grid {
grid-template-columns: repeat(2, 1fr);
}
.security-tools-grid {
grid-template-columns: 1fr;
}
.security-tabs {
padding: 12px 16px;
}
.security-tab {
padding: 8px 12px;
font-size: 12px;
}
}
"""
@staticmethod
def get_html() -> str:
"""HTML-Struktur fuer das Security Dashboard."""
return """
<!-- SECURITY DASHBOARD PANEL -->
<div class="panel-security" id="panel-security">
<!-- Header -->
<div class="security-header">
<h2>
<span class="security-header-icon">&#128737;</span>
Security Dashboard
</h2>
<div class="security-header-actions">
<div class="security-auto-refresh" id="security-auto-refresh">
<span class="security-auto-refresh-dot"></span>
<span>Auto-Refresh aktiv</span>
</div>
<span class="security-status-badge secure" id="security-overall-status">
<span>&#10003;</span> Sicher
</span>
<button class="btn btn-primary" onclick="SecurityDashboard.runFullScan()">
<span>&#9654;</span> Scan starten
</button>
</div>
</div>
<!-- Tabs -->
<div class="security-tabs">
<button class="security-tab active" data-tab="overview" onclick="SecurityDashboard.switchTab('overview')">
<span>&#128200;</span> Uebersicht
</button>
<button class="security-tab" data-tab="secrets" onclick="SecurityDashboard.switchTab('secrets')">
<span>&#128273;</span> Secrets
<span class="security-tab-badge" id="secrets-count">0</span>
</button>
<button class="security-tab" data-tab="vulnerabilities" onclick="SecurityDashboard.switchTab('vulnerabilities')">
<span>&#128030;</span> Schwachstellen
<span class="security-tab-badge" id="vuln-count">0</span>
</button>
<button class="security-tab" data-tab="sbom" onclick="SecurityDashboard.switchTab('sbom')">
<span>&#128230;</span> SBOM
</button>
<button class="security-tab" data-tab="containers" onclick="SecurityDashboard.switchTab('containers')">
<span>&#128230;</span> Container
</button>
<button class="security-tab" data-tab="compliance" onclick="SecurityDashboard.switchTab('compliance')">
<span>&#9989;</span> Compliance
</button>
<button class="security-tab" data-tab="history" onclick="SecurityDashboard.switchTab('history')">
<span>&#128197;</span> Historie
</button>
</div>
<!-- Content -->
<div class="security-content">
<!-- Scan Progress (hidden by default) -->
<div class="security-scan-progress hidden" id="security-scan-progress">
<div class="security-scan-header">
<span class="security-scan-title">Security Scan laeuft...</span>
<span class="security-scan-percentage" id="scan-percentage">0%</span>
</div>
<div class="security-progress-bar">
<div class="security-progress-fill" id="scan-progress-fill" style="width: 0%"></div>
</div>
<div class="security-scan-steps">
<span class="security-scan-step" id="step-secrets">&#128273; Secrets</span>
<span class="security-scan-step" id="step-sast">&#128270; SAST</span>
<span class="security-scan-step" id="step-deps">&#128230; Dependencies</span>
<span class="security-scan-step" id="step-containers">&#128736; Container</span>
<span class="security-scan-step" id="step-sbom">&#128203; SBOM</span>
</div>
</div>
<!-- Tab: Overview -->
<div class="security-tab-content" id="tab-overview">
<!-- Summary Cards -->
<div class="security-summary-grid" id="security-summary">
<div class="security-summary-card critical">
<div class="security-summary-icon">&#128680;</div>
<div class="security-summary-count" id="critical-count">0</div>
<div class="security-summary-label">Critical</div>
<div class="security-summary-sublabel">Sofort beheben</div>
</div>
<div class="security-summary-card high">
<div class="security-summary-icon">&#9888;</div>
<div class="security-summary-count" id="high-count">0</div>
<div class="security-summary-label">High</div>
<div class="security-summary-sublabel">Hohe Prioritaet</div>
</div>
<div class="security-summary-card medium">
<div class="security-summary-icon">&#128993;</div>
<div class="security-summary-count" id="medium-count">0</div>
<div class="security-summary-label">Medium</div>
<div class="security-summary-sublabel">Bald beheben</div>
</div>
<div class="security-summary-card low">
<div class="security-summary-icon">&#128994;</div>
<div class="security-summary-count" id="low-count">0</div>
<div class="security-summary-label">Low</div>
<div class="security-summary-sublabel">Bei Gelegenheit</div>
</div>
<div class="security-summary-card info">
<div class="security-summary-icon">&#128712;</div>
<div class="security-summary-count" id="info-count">0</div>
<div class="security-summary-label">Info</div>
<div class="security-summary-sublabel">Zur Kenntnis</div>
</div>
</div>
<!-- Tools Status -->
<div class="security-tools-section">
<div class="security-section-title">
<span>&#128295;</span> DevSecOps Tools
</div>
<div class="security-tools-grid" id="security-tools-grid">
<!-- Wird dynamisch gefuellt -->
</div>
</div>
<!-- Recent Findings -->
<div class="security-section-title">
<span>&#128269;</span> Aktuelle Findings
</div>
<div class="security-findings-container">
<div class="security-findings-header">
<span class="security-findings-title">Letzte Scan-Ergebnisse</span>
<div class="security-findings-filter">
<button class="security-filter-btn active" data-filter="all">Alle</button>
<button class="security-filter-btn" data-filter="critical">Critical</button>
<button class="security-filter-btn" data-filter="high">High</button>
<button class="security-filter-btn" data-filter="new">Neu</button>
</div>
</div>
<table class="security-findings-table">
<thead>
<tr>
<th>Severity</th>
<th>Tool</th>
<th>Finding</th>
<th>Datei</th>
<th>Gefunden</th>
</tr>
</thead>
<tbody id="security-findings-body">
<!-- Wird dynamisch gefuellt -->
</tbody>
</table>
</div>
</div>
<!-- Tab: Secrets -->
<div class="security-tab-content hidden" id="tab-secrets">
<div class="security-section-title">
<span>&#128273;</span> Secrets Detection (Gitleaks)
</div>
<div class="security-findings-container">
<div class="security-findings-header">
<span class="security-findings-title">Erkannte Secrets</span>
<button class="btn btn-sm btn-primary" onclick="SecurityDashboard.runSecretsScan()">
Secrets-Scan starten
</button>
</div>
<div id="secrets-content">
<div class="security-empty">
<div class="security-empty-icon">&#128274;</div>
<div class="security-empty-title">Keine Secrets gefunden</div>
<div class="security-empty-desc">
Gitleaks hat keine hartkodierten Secrets im Codebase gefunden.
</div>
</div>
</div>
</div>
</div>
<!-- Tab: Vulnerabilities -->
<div class="security-tab-content hidden" id="tab-vulnerabilities">
<div class="security-section-title">
<span>&#128030;</span> Schwachstellen (Trivy / Grype)
</div>
<div class="security-findings-container">
<div class="security-findings-header">
<span class="security-findings-title">CVE-Uebersicht</span>
<div class="security-findings-filter">
<button class="security-filter-btn active" data-filter="all">Alle</button>
<button class="security-filter-btn" data-filter="fixable">Behebbar</button>
<button class="security-filter-btn" data-filter="no-fix">Kein Fix</button>
</div>
</div>
<table class="security-findings-table">
<thead>
<tr>
<th>CVE-ID</th>
<th>Severity</th>
<th>Package</th>
<th>Version</th>
<th>Fix verfuegbar</th>
</tr>
</thead>
<tbody id="vuln-findings-body">
<!-- Wird dynamisch gefuellt -->
</tbody>
</table>
</div>
</div>
<!-- Tab: SBOM -->
<div class="security-tab-content hidden" id="tab-sbom">
<div class="security-section-title">
<span>&#128230;</span> Software Bill of Materials (Syft)
</div>
<div class="sbom-container">
<div class="sbom-header">
<span class="security-findings-title">SBOM Viewer</span>
<input type="text" class="sbom-search" placeholder="Package suchen..." id="sbom-search" onkeyup="SecurityDashboard.filterSBOM()">
</div>
<div class="sbom-stats">
<div class="sbom-stat">
<div class="sbom-stat-value" id="sbom-total">0</div>
<div class="sbom-stat-label">Packages gesamt</div>
</div>
<div class="sbom-stat">
<div class="sbom-stat-value" id="sbom-python">0</div>
<div class="sbom-stat-label">Python</div>
</div>
<div class="sbom-stat">
<div class="sbom-stat-value" id="sbom-go">0</div>
<div class="sbom-stat-label">Go</div>
</div>
<div class="sbom-stat">
<div class="sbom-stat-value" id="sbom-npm">0</div>
<div class="sbom-stat-label">NPM</div>
</div>
</div>
<table class="security-findings-table">
<thead>
<tr>
<th>Package</th>
<th>Version</th>
<th>Typ</th>
<th>Lizenz</th>
</tr>
</thead>
<tbody id="sbom-body">
<!-- Wird dynamisch gefuellt -->
</tbody>
</table>
</div>
</div>
<!-- Tab: Containers -->
<div class="security-tab-content hidden" id="tab-containers">
<div class="security-section-title">
<span>&#128736;</span> Container Image Security (Trivy)
</div>
<div class="security-tools-grid" id="container-images-grid">
<!-- Wird dynamisch gefuellt -->
</div>
</div>
<!-- Tab: Compliance -->
<div class="security-tab-content hidden" id="tab-compliance">
<div class="security-section-title">
<span>&#9989;</span> Compliance Status
</div>
<div class="security-tools-grid">
<div class="security-tool-card">
<div class="security-tool-header">
<span class="security-tool-name">&#128737; OWASP Top 10</span>
<span class="security-tool-status installed">8/10 geprueft</span>
</div>
<div class="security-tool-description">
Web Application Security Risks
</div>
<div id="owasp-checklist"></div>
</div>
<div class="security-tool-card">
<div class="security-tool-header">
<span class="security-tool-name">&#128220; DSGVO / GDPR</span>
<span class="security-tool-status installed">Konform</span>
</div>
<div class="security-tool-description">
Datenschutz-Grundverordnung Compliance
</div>
</div>
<div class="security-tool-card">
<div class="security-tool-header">
<span class="security-tool-name">&#128230; Supply Chain</span>
<span class="security-tool-status installed">SBOM aktuell</span>
</div>
<div class="security-tool-description">
Software Supply Chain Security mit SBOM
</div>
</div>
</div>
</div>
<!-- Tab: History -->
<div class="security-tab-content hidden" id="tab-history">
<div class="security-section-title">
<span>&#128197;</span> Scan Historie
</div>
<div class="security-findings-container">
<div class="security-timeline" id="security-timeline">
<!-- Wird dynamisch gefuellt -->
</div>
</div>
</div>
</div>
</div>
"""
@staticmethod
def get_js() -> str:
"""JavaScript fuer das Security Dashboard."""
return """
// ==========================================
// SECURITY DASHBOARD MODULE
// ==========================================
const SecurityDashboard = {
autoRefreshInterval: null,
scanPollingInterval: null,
currentTab: 'overview',
findings: [],
sbomData: [],
tools: [],
// Initialize
init() {
console.log('SecurityDashboard initialized');
this.loadDashboard();
this.startAutoRefresh();
},
// Load all dashboard data
async loadDashboard() {
try {
await Promise.all([
this.loadToolStatus(),
this.loadFindings(),
this.loadSummary(),
this.loadSBOM(),
this.loadHistory()
]);
} catch (error) {
console.error('Error loading security dashboard:', error);
}
},
// Switch tabs
switchTab(tabName) {
this.currentTab = tabName;
// Update tab buttons
document.querySelectorAll('.security-tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.tab === tabName);
});
// Update tab content
document.querySelectorAll('.security-tab-content').forEach(content => {
content.classList.add('hidden');
});
const tabContent = document.getElementById('tab-' + tabName);
if (tabContent) {
tabContent.classList.remove('hidden');
}
},
// Load tool status
async loadToolStatus() {
try {
const response = await fetch('/api/v1/security/tools');
if (response.ok) {
this.tools = await response.json();
this.renderToolStatus();
}
} catch (error) {
console.error('Error loading tool status:', error);
this.renderDefaultToolStatus();
}
},
// Render tool status
renderToolStatus() {
const grid = document.getElementById('security-tools-grid');
if (!grid) return;
const defaultTools = [
{ name: 'Gitleaks', icon: '&#128273;', desc: 'Secrets Detection in Git History', license: 'MIT', version: '8.18.x' },
{ name: 'Semgrep', icon: '&#128270;', desc: 'Static Application Security Testing (SAST)', license: 'LGPL-2.1', version: '1.52.x' },
{ name: 'Bandit', icon: '&#128013;', desc: 'Python Security Linter', license: 'Apache-2.0', version: '1.7.x' },
{ name: 'Trivy', icon: '&#128736;', desc: 'Container & Filesystem Vulnerability Scanner', license: 'Apache-2.0', version: '0.48.x' },
{ name: 'Grype', icon: '&#128030;', desc: 'Vulnerability Scanner', license: 'Apache-2.0', version: '0.74.x' },
{ name: 'Syft', icon: '&#128230;', desc: 'SBOM Generator (CycloneDX/SPDX)', license: 'Apache-2.0', version: '0.100.x' }
];
grid.innerHTML = defaultTools.map(tool => {
const serverTool = this.tools.find(t => t.name === tool.name) || {};
const isInstalled = serverTool.installed !== false;
const lastRun = serverTool.last_run || 'Nie';
return `
<div class="security-tool-card">
<div class="security-tool-header">
<span class="security-tool-name">${tool.icon} ${tool.name}</span>
<span class="security-tool-status ${isInstalled ? 'installed' : 'not-installed'}">
${isInstalled ? 'Installiert' : 'Nicht installiert'}
</span>
</div>
<div class="security-tool-description">${tool.desc}</div>
<div class="security-tool-meta">
<span class="security-tool-meta-item">&#128196; ${tool.license}</span>
<span class="security-tool-meta-item">&#128200; v${tool.version}</span>
<span class="security-tool-meta-item">&#128337; ${lastRun}</span>
</div>
<div class="security-tool-actions">
<button class="security-tool-btn primary" onclick="SecurityDashboard.runToolScan('${tool.name.toLowerCase()}')">
Scan starten
</button>
<button class="security-tool-btn secondary" onclick="SecurityDashboard.viewToolReport('${tool.name.toLowerCase()}')">
Report anzeigen
</button>
</div>
</div>
`;
}).join('');
},
renderDefaultToolStatus() {
this.tools = [];
this.renderToolStatus();
},
// Load findings
async loadFindings() {
try {
const response = await fetch('/api/v1/security/findings');
if (response.ok) {
this.findings = await response.json();
this.renderFindings();
}
} catch (error) {
console.error('Error loading findings:', error);
this.renderEmptyFindings();
}
},
// Render findings table
renderFindings() {
const tbody = document.getElementById('security-findings-body');
if (!tbody) return;
if (!this.findings || this.findings.length === 0) {
this.renderEmptyFindings();
return;
}
tbody.innerHTML = this.findings.slice(0, 20).map(finding => `
<tr>
<td><span class="severity-badge ${finding.severity.toLowerCase()}">${finding.severity}</span></td>
<td>${finding.tool || 'Unknown'}</td>
<td>${finding.title || finding.message || 'No description'}</td>
<td>${finding.file || '-'}</td>
<td>${finding.found_at ? new Date(finding.found_at).toLocaleString('de-DE') : '-'}</td>
</tr>
`).join('');
// Update counts
document.getElementById('secrets-count').textContent = this.findings.filter(f => f.tool === 'gitleaks').length;
document.getElementById('vuln-count').textContent = this.findings.filter(f => f.tool === 'trivy' || f.tool === 'grype').length;
},
renderEmptyFindings() {
const tbody = document.getElementById('security-findings-body');
if (!tbody) return;
tbody.innerHTML = `
<tr>
<td colspan="5" style="text-align: center; padding: 40px; color: var(--bp-text-muted);">
&#128274; Keine Findings gefunden. Das ist gut!
</td>
</tr>
`;
},
// Load summary
async loadSummary() {
try {
const response = await fetch('/api/v1/security/summary');
if (response.ok) {
const summary = await response.json();
this.renderSummary(summary);
}
} catch (error) {
console.error('Error loading summary:', error);
this.renderSummary({ critical: 0, high: 0, medium: 0, low: 0, info: 0 });
}
},
renderSummary(summary) {
document.getElementById('critical-count').textContent = summary.critical || 0;
document.getElementById('high-count').textContent = summary.high || 0;
document.getElementById('medium-count').textContent = summary.medium || 0;
document.getElementById('low-count').textContent = summary.low || 0;
document.getElementById('info-count').textContent = summary.info || 0;
// Update overall status
const statusBadge = document.getElementById('security-overall-status');
if (summary.critical > 0) {
statusBadge.className = 'security-status-badge critical';
statusBadge.innerHTML = '<span>&#10060;</span> Critical Issues';
} else if (summary.high > 0) {
statusBadge.className = 'security-status-badge warning';
statusBadge.innerHTML = '<span>&#9888;</span> Warnings';
} else {
statusBadge.className = 'security-status-badge secure';
statusBadge.innerHTML = '<span>&#10003;</span> Sicher';
}
},
// Load SBOM
async loadSBOM() {
try {
const response = await fetch('/api/v1/security/sbom');
if (response.ok) {
this.sbomData = await response.json();
this.renderSBOM();
}
} catch (error) {
console.error('Error loading SBOM:', error);
}
},
renderSBOM() {
if (!this.sbomData || !this.sbomData.components) return;
const components = this.sbomData.components || [];
// Update stats
document.getElementById('sbom-total').textContent = components.length;
document.getElementById('sbom-python').textContent = components.filter(c => c.type === 'python' || c.purl?.startsWith('pkg:pypi')).length;
document.getElementById('sbom-go').textContent = components.filter(c => c.type === 'go' || c.purl?.startsWith('pkg:golang')).length;
document.getElementById('sbom-npm').textContent = components.filter(c => c.type === 'npm' || c.purl?.startsWith('pkg:npm')).length;
// Render table
const tbody = document.getElementById('sbom-body');
if (!tbody) return;
tbody.innerHTML = components.slice(0, 50).map(comp => `
<tr>
<td>${comp.name || 'Unknown'}</td>
<td>${comp.version || '-'}</td>
<td>${comp.type || this.getTypeFromPurl(comp.purl)}</td>
<td>${comp.licenses?.[0]?.license?.id || comp.licenses?.[0]?.license?.name || '-'}</td>
</tr>
`).join('');
},
getTypeFromPurl(purl) {
if (!purl) return 'unknown';
if (purl.startsWith('pkg:pypi')) return 'Python';
if (purl.startsWith('pkg:golang')) return 'Go';
if (purl.startsWith('pkg:npm')) return 'NPM';
return 'other';
},
filterSBOM() {
const search = document.getElementById('sbom-search').value.toLowerCase();
const tbody = document.getElementById('sbom-body');
if (!tbody || !this.sbomData?.components) return;
const filtered = this.sbomData.components.filter(c =>
c.name?.toLowerCase().includes(search) ||
c.version?.toLowerCase().includes(search)
);
tbody.innerHTML = filtered.slice(0, 50).map(comp => `
<tr>
<td>${comp.name || 'Unknown'}</td>
<td>${comp.version || '-'}</td>
<td>${comp.type || this.getTypeFromPurl(comp.purl)}</td>
<td>${comp.licenses?.[0]?.license?.id || '-'}</td>
</tr>
`).join('');
},
// Load history
async loadHistory() {
try {
const response = await fetch('/api/v1/security/history');
if (response.ok) {
const history = await response.json();
this.renderHistory(history);
}
} catch (error) {
console.error('Error loading history:', error);
this.renderDefaultHistory();
}
},
renderHistory(history) {
const timeline = document.getElementById('security-timeline');
if (!timeline) return;
if (!history || history.length === 0) {
this.renderDefaultHistory();
return;
}
timeline.innerHTML = history.map(item => `
<div class="security-timeline-item ${item.status || 'success'}">
<div class="security-timeline-time">${new Date(item.timestamp).toLocaleString('de-DE')}</div>
<div class="security-timeline-content">
<div class="security-timeline-title">${item.title}</div>
<div class="security-timeline-desc">${item.description || ''}</div>
</div>
</div>
`).join('');
},
renderDefaultHistory() {
const timeline = document.getElementById('security-timeline');
if (!timeline) return;
timeline.innerHTML = `
<div class="security-timeline-item success">
<div class="security-timeline-time">${new Date().toLocaleString('de-DE')}</div>
<div class="security-timeline-content">
<div class="security-timeline-title">Security Dashboard initialisiert</div>
<div class="security-timeline-desc">DevSecOps Pipeline bereit</div>
</div>
</div>
`;
},
// Run full scan
async runFullScan() {
const progressEl = document.getElementById('security-scan-progress');
progressEl.classList.remove('hidden');
const steps = ['secrets', 'sast', 'deps', 'containers', 'sbom'];
let progress = 0;
for (const step of steps) {
document.getElementById('step-' + step).classList.add('active');
try {
await fetch('/api/v1/security/scan/' + step, { method: 'POST' });
} catch (error) {
console.error('Scan step failed:', step, error);
}
progress += 20;
document.getElementById('scan-progress-fill').style.width = progress + '%';
document.getElementById('scan-percentage').textContent = progress + '%';
document.getElementById('step-' + step).classList.remove('active');
document.getElementById('step-' + step).classList.add('completed');
await new Promise(r => setTimeout(r, 500));
}
// Reload dashboard data
await this.loadDashboard();
// Hide progress after delay
setTimeout(() => {
progressEl.classList.add('hidden');
// Reset steps
steps.forEach(step => {
document.getElementById('step-' + step).classList.remove('completed', 'active');
});
document.getElementById('scan-progress-fill').style.width = '0%';
document.getElementById('scan-percentage').textContent = '0%';
}, 2000);
},
// Run individual tool scan
async runToolScan(tool) {
try {
const response = await fetch('/api/v1/security/scan/' + tool, { method: 'POST' });
if (response.ok) {
await this.loadDashboard();
console.log('Scan completed:', tool);
}
} catch (error) {
console.error('Scan failed:', tool, error);
}
},
// Run secrets scan
async runSecretsScan() {
await this.runToolScan('secrets');
},
// View tool report
async viewToolReport(tool) {
try {
const response = await fetch('/api/v1/security/reports/' + tool);
if (response.ok) {
const report = await response.json();
console.log('Report:', tool, report);
// Could open a modal with full report
}
} catch (error) {
console.error('Error loading report:', tool, error);
}
},
// Auto-refresh
startAutoRefresh() {
this.autoRefreshInterval = setInterval(() => {
this.loadSummary();
}, 30000); // Every 30 seconds
},
stopAutoRefresh() {
if (this.autoRefreshInterval) {
clearInterval(this.autoRefreshInterval);
this.autoRefreshInterval = null;
}
}
};
// Initialize when security panel is shown
function initSecurityModule() {
SecurityDashboard.init();
}
// Clean up when leaving
function cleanupSecurityModule() {
SecurityDashboard.stopAutoRefresh();
}
"""