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>
1462 lines
38 KiB
Python
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">🛡</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>✓</span> Sicher
|
|
</span>
|
|
<button class="btn btn-primary" onclick="SecurityDashboard.runFullScan()">
|
|
<span>▶</span> Scan starten
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div class="security-tabs">
|
|
<button class="security-tab active" data-tab="overview" onclick="SecurityDashboard.switchTab('overview')">
|
|
<span>📈</span> Uebersicht
|
|
</button>
|
|
<button class="security-tab" data-tab="secrets" onclick="SecurityDashboard.switchTab('secrets')">
|
|
<span>🔑</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>🐞</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>📦</span> SBOM
|
|
</button>
|
|
<button class="security-tab" data-tab="containers" onclick="SecurityDashboard.switchTab('containers')">
|
|
<span>📦</span> Container
|
|
</button>
|
|
<button class="security-tab" data-tab="compliance" onclick="SecurityDashboard.switchTab('compliance')">
|
|
<span>✅</span> Compliance
|
|
</button>
|
|
<button class="security-tab" data-tab="history" onclick="SecurityDashboard.switchTab('history')">
|
|
<span>📅</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">🔑 Secrets</span>
|
|
<span class="security-scan-step" id="step-sast">🔎 SAST</span>
|
|
<span class="security-scan-step" id="step-deps">📦 Dependencies</span>
|
|
<span class="security-scan-step" id="step-containers">🛠 Container</span>
|
|
<span class="security-scan-step" id="step-sbom">📋 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">🚨</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">⚠</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">🟡</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">🟢</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">🛈</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>🔧</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>🔍</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>🔑</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">🔒</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>🐞</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>📦</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>🛠</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>✅</span> Compliance Status
|
|
</div>
|
|
<div class="security-tools-grid">
|
|
<div class="security-tool-card">
|
|
<div class="security-tool-header">
|
|
<span class="security-tool-name">🛡 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">📜 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">📦 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>📅</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: '🔑', desc: 'Secrets Detection in Git History', license: 'MIT', version: '8.18.x' },
|
|
{ name: 'Semgrep', icon: '🔎', desc: 'Static Application Security Testing (SAST)', license: 'LGPL-2.1', version: '1.52.x' },
|
|
{ name: 'Bandit', icon: '🐍', desc: 'Python Security Linter', license: 'Apache-2.0', version: '1.7.x' },
|
|
{ name: 'Trivy', icon: '🛠', desc: 'Container & Filesystem Vulnerability Scanner', license: 'Apache-2.0', version: '0.48.x' },
|
|
{ name: 'Grype', icon: '🐞', desc: 'Vulnerability Scanner', license: 'Apache-2.0', version: '0.74.x' },
|
|
{ name: 'Syft', icon: '📦', 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">📄 ${tool.license}</span>
|
|
<span class="security-tool-meta-item">📈 v${tool.version}</span>
|
|
<span class="security-tool-meta-item">🕑 ${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);">
|
|
🔒 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>❌</span> Critical Issues';
|
|
} else if (summary.high > 0) {
|
|
statusBadge.className = 'security-status-badge warning';
|
|
statusBadge.innerHTML = '<span>⚠</span> Warnings';
|
|
} else {
|
|
statusBadge.className = 'security-status-badge secure';
|
|
statusBadge.innerHTML = '<span>✓</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();
|
|
}
|
|
"""
|