|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>AI SDL Digital Twin Security Dashboard</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<style> |
|
@keyframes pulse { |
|
0%, 100% { opacity: 1; } |
|
50% { opacity: 0.7; } |
|
} |
|
@keyframes float { |
|
0%, 100% { transform: translateY(0); } |
|
50% { transform: translateY(-10px); } |
|
} |
|
@keyframes scanning { |
|
0% { background-position: 0% 50%; } |
|
50% { background-position: 100% 50%; } |
|
100% { background-position: 0% 50%; } |
|
} |
|
@keyframes progress { |
|
0% { width: 0%; } |
|
100% { width: 100%; } |
|
} |
|
@keyframes highlight { |
|
0%, 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } |
|
50% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0.3); } |
|
} |
|
.scanning-animation { |
|
background: linear-gradient(90deg, #3b82f6, #8b5cf6, #3b82f6); |
|
background-size: 200% 200%; |
|
animation: scanning 3s ease infinite; |
|
} |
|
.risk-pulse { |
|
animation: pulse 2s infinite; |
|
} |
|
.avatar-float { |
|
animation: float 6s ease-in-out infinite; |
|
} |
|
.progress-animation { |
|
animation: progress 5s linear infinite; |
|
} |
|
.highlight-animation { |
|
animation: highlight 2s ease infinite; |
|
} |
|
.fade-in { |
|
animation: fadeIn 0.5s ease-in; |
|
} |
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(10px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
.terminal-font { |
|
font-family: 'Courier New', monospace; |
|
} |
|
.phase-active { |
|
border-left: 4px solid #3b82f6; |
|
background: rgba(59, 130, 246, 0.1); |
|
} |
|
.chat-message-enter { |
|
animation: chatMessageEnter 0.3s ease-out; |
|
} |
|
@keyframes chatMessageEnter { |
|
from { opacity: 0; transform: translateY(10px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
.risk-tag { |
|
transition: all 0.2s ease; |
|
} |
|
.risk-tag:hover { |
|
transform: scale(1.05); |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-900 text-gray-100 min-h-screen"> |
|
<div id="dashboard" class="container mx-auto px-4 py-8"> |
|
|
|
<header class="flex justify-between items-center mb-8"> |
|
<div class="flex items-center space-x-4"> |
|
<div class="w-16 h-16 bg-blue-600 rounded-full flex items-center justify-center avatar-float highlight-animation"> |
|
<i class="fas fa-robot text-3xl text-white"></i> |
|
</div> |
|
<div> |
|
<h1 class="text-3xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent">AI SDL Security Twin</h1> |
|
<p class="text-gray-400">Continuous Security Analysis Dashboard</p> |
|
</div> |
|
</div> |
|
<div class="flex items-center space-x-4"> |
|
<div class="relative"> |
|
<div class="w-3 h-3 bg-green-500 rounded-full absolute -top-1 -right-1 animate-ping"></div> |
|
<span class="px-4 py-2 bg-gray-800 rounded-lg flex items-center"> |
|
<span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span> |
|
<span>Active Scanning</span> |
|
</span> |
|
</div> |
|
<button id="simulateBtn" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition"> |
|
<i class="fas fa-play mr-2"></i>Start Simulation |
|
</button> |
|
</div> |
|
</header> |
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
|
|
|
<div class="lg:col-span-1 space-y-6"> |
|
|
|
<div class="bg-gray-800 rounded-xl p-6 shadow-lg"> |
|
<div class="flex flex-col items-center"> |
|
<div class="relative mb-4"> |
|
<div class="w-32 h-32 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center avatar-float"> |
|
<i class="fas fa-robot text-6xl text-white"></i> |
|
</div> |
|
<div class="absolute -bottom-2 -right-2 bg-blue-600 rounded-full p-2"> |
|
<i class="fas fa-shield-alt text-white"></i> |
|
</div> |
|
</div> |
|
<h3 class="text-xl font-semibold">SDL Security Twin</h3> |
|
<p class="text-gray-400 text-sm mb-4">Real-time Security Analysis Engine</p> |
|
<div class="w-full bg-gray-700 rounded-full h-2 mb-2"> |
|
<div id="overallProgress" class="bg-blue-500 h-2 rounded-full progress-animation" style="width: 0%"></div> |
|
</div> |
|
<p class="text-xs text-gray-400">System Security Coverage</p> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-gray-800 rounded-xl p-6 shadow-lg h-full"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-lg font-semibold">Security Operations</h3> |
|
<span class="text-xs bg-blue-900 text-blue-300 px-2 py-1 rounded">Live Feed</span> |
|
</div> |
|
<div class="space-y-4 h-96 overflow-y-auto pr-2" id="activityLog"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="lg:col-span-1"> |
|
<div class="bg-gray-800 rounded-xl p-6 shadow-lg h-full"> |
|
<h2 class="text-xl font-bold mb-6 text-center bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent">SDL Security Pipeline</h2> |
|
|
|
<div class="relative h-full"> |
|
|
|
<div class="space-y-8"> |
|
|
|
<div class="phase-card" id="requirementDesign"> |
|
<div class="phase-icon"> |
|
<div class="w-10 h-10 rounded-full bg-blue-900 flex items-center justify-center"> |
|
<i class="fas fa-clipboard-list text-white"></i> |
|
</div> |
|
</div> |
|
<div class="phase-content"> |
|
<h3 class="font-semibold">Requirement Design</h3> |
|
<p class="text-sm text-gray-400 mb-2">Analyzing security requirements and threat models</p> |
|
<div class="flex items-center text-xs"> |
|
<span class="mr-2">Status:</span> |
|
<span class="px-2 py-1 bg-gray-700 text-gray-300 rounded">Waiting</span> |
|
</div> |
|
<div class="mt-2" id="requirementStats"></div> |
|
</div> |
|
<div class="phase-progress"> |
|
<div class="w-full bg-gray-700 rounded-full h-1.5"> |
|
<div class="phase-progress-bar bg-blue-500 h-1.5 rounded-full" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="phase-card" id="codeChanges"> |
|
<div class="phase-icon"> |
|
<div class="w-10 h-10 rounded-full bg-blue-900 flex items-center justify-center"> |
|
<i class="fas fa-code text-white"></i> |
|
</div> |
|
</div> |
|
<div class="phase-content"> |
|
<h3 class="font-semibold">Code Changes</h3> |
|
<p class="text-sm text-gray-400 mb-2">Reviewing code for vulnerabilities and best practices</p> |
|
<div class="flex items-center text-xs"> |
|
<span class="mr-2">Status:</span> |
|
<span class="px-2 py-1 bg-gray-700 text-gray-300 rounded">Waiting</span> |
|
</div> |
|
<div class="mt-2" id="codeStats"></div> |
|
</div> |
|
<div class="phase-progress"> |
|
<div class="w-full bg-gray-700 rounded-full h-1.5"> |
|
<div class="phase-progress-bar bg-blue-500 h-1.5 rounded-full" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="phase-card" id="securityTesting"> |
|
<div class="phase-icon"> |
|
<div class="w-10 h-10 rounded-full bg-blue-900 flex items-center justify-center"> |
|
<i class="fas fa-shield-alt text-white"></i> |
|
</div> |
|
</div> |
|
<div class="phase-content"> |
|
<h3 class="font-semibold">Security Testing</h3> |
|
<p class="text-sm text-gray-400 mb-2">Executing automated security tests on interfaces</p> |
|
<div class="flex items-center text-xs"> |
|
<span class="mr-2">Status:</span> |
|
<span class="px-2 py-1 bg-gray-700 text-gray-300 rounded">Waiting</span> |
|
</div> |
|
<div class="mt-2" id="testingStats"></div> |
|
</div> |
|
<div class="phase-progress"> |
|
<div class="w-full bg-gray-700 rounded-full h-1.5"> |
|
<div class="phase-progress-bar bg-blue-500 h-1.5 rounded-full" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="phase-card" id="release"> |
|
<div class="phase-icon"> |
|
<div class="w-10 h-10 rounded-full bg-blue-900 flex items-center justify-center"> |
|
<i class="fas fa-rocket text-white"></i> |
|
</div> |
|
</div> |
|
<div class="phase-content"> |
|
<h3 class="font-semibold">Release</h3> |
|
<p class="text-sm text-gray-400 mb-2">Verifying security compliance before deployment</p> |
|
<div class="flex items-center text-xs"> |
|
<span class="mr-2">Status:</span> |
|
<span class="px-2 py-1 bg-gray-700 text-gray-300 rounded">Waiting</span> |
|
</div> |
|
<div class="mt-2" id="releaseStats"></div> |
|
</div> |
|
<div class="phase-progress"> |
|
<div class="w-full bg-gray-700 rounded-full h-1.5"> |
|
<div class="phase-progress-bar bg-blue-500 h-1.5 rounded-full" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="phase-card" id="onlineOperation"> |
|
<div class="phase-icon"> |
|
<div class="w-10 h-10 rounded-full bg-blue-900 flex items-center justify-center"> |
|
<i class="fas fa-globe text-white"></i> |
|
</div> |
|
</div> |
|
<div class="phase-content"> |
|
<h3 class="font-semibold">Online Operation</h3> |
|
<p class="text-sm text-gray-400 mb-2">Monitoring for vulnerabilities and attacks</p> |
|
<div class="flex items-center text-xs"> |
|
<span class="mr-2">Status:</span> |
|
<span class="px-2 py-1 bg-gray-700 text-gray-300 rounded">Waiting</span> |
|
</div> |
|
<div class="mt-2" id="operationStats"></div> |
|
</div> |
|
<div class="phase-progress"> |
|
<div class="w-full bg-gray-700 rounded-full h-1.5"> |
|
<div class="phase-progress-bar bg-blue-500 h-1.5 rounded-full" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="lg:col-span-1 space-y-6"> |
|
|
|
<div class="bg-gray-800 rounded-xl p-6 shadow-lg"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-lg font-semibold">Aggregated Risk Alerts</h3> |
|
<div id="alertCounter" class="text-xs bg-red-900 text-red-300 px-2 py-1 rounded flex items-center"> |
|
<span class="w-2 h-2 bg-red-500 rounded-full mr-1 animate-pulse"></span> |
|
<span>0 New</span> |
|
</div> |
|
</div> |
|
<div class="space-y-4 h-64 overflow-y-auto" id="riskAlerts"> |
|
<div class="text-center py-8 text-gray-500"> |
|
<i class="fas fa-check-circle text-3xl mb-2"></i> |
|
<p>No active risk alerts</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-gray-800 rounded-xl p-6 shadow-lg"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-lg font-semibold">Project Risk Overview</h3> |
|
<span class="text-xs bg-blue-900 text-blue-300 px-2 py-1 rounded">Live</span> |
|
</div> |
|
<div class="space-y-4 h-64 overflow-y-auto" id="projectRisks"> |
|
<div class="text-center py-8 text-gray-500"> |
|
<i class="fas fa-lock text-3xl mb-2"></i> |
|
<p>No projects with risks detected</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="projectDetail" class="container mx-auto px-4 py-8 hidden"> |
|
<div class="flex justify-between items-center mb-6"> |
|
<button id="backToDashboard" class="flex items-center text-blue-400 hover:text-blue-300 transition"> |
|
<i class="fas fa-arrow-left mr-2"></i> Back to Dashboard |
|
</button> |
|
<div class="flex items-center space-x-4"> |
|
<span class="px-3 py-1 bg-gray-700 rounded-full text-sm">Project Detail</span> |
|
<div id="projectStatus" class="px-3 py-1 bg-red-900 text-red-300 rounded-full text-sm flex items-center"> |
|
<span class="w-2 h-2 bg-red-500 rounded-full mr-2 animate-pulse"></span> |
|
<span>Security Risk</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-gray-800 rounded-xl p-6 mb-6"> |
|
<div class="flex justify-between items-start"> |
|
<div> |
|
<h2 id="projectName" class="text-2xl font-bold mb-1">Project Name</h2> |
|
<p id="projectTeam" class="text-gray-400 mb-4">Team Name</p> |
|
<div class="flex flex-wrap gap-2 mb-4" id="projectTags"> |
|
|
|
</div> |
|
</div> |
|
<div class="flex items-center space-x-4"> |
|
<div class="text-center"> |
|
<div class="text-3xl font-bold text-red-400" id="riskCount">0</div> |
|
<div class="text-xs text-gray-400">Security Risks</div> |
|
</div> |
|
<div class="text-center"> |
|
<div class="text-3xl font-bold text-blue-400" id="phaseCount">0</div> |
|
<div class="text-xs text-gray-400">Affected Phases</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="mt-4"> |
|
<h3 class="font-semibold mb-2">Security Risk Summary</h3> |
|
<div class="flex flex-wrap gap-2" id="riskSummary"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="mb-6"> |
|
<div class="flex overflow-x-auto border-b border-gray-700"> |
|
<button class="phase-tab active" data-phase="requirements">Requirements</button> |
|
<button class="phase-tab" data-phase="code">Code</button> |
|
<button class="phase-tab" data-phase="testing">Testing</button> |
|
<button class="phase-tab" data-phase="release">Release</button> |
|
<button class="phase-tab" data-phase="operation">Operation</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
|
|
|
<div class="lg:col-span-2 space-y-6"> |
|
|
|
<div class="phase-content active" id="requirementsContent"> |
|
<div class="bg-gray-800 rounded-xl p-6"> |
|
<h3 class="text-lg font-semibold mb-4 flex items-center"> |
|
<i class="fas fa-clipboard-list text-blue-400 mr-2"></i> |
|
Requirements Analysis |
|
</h3> |
|
<div class="mb-6"> |
|
<h4 class="font-medium mb-2">Project Requirements</h4> |
|
<div class="bg-gray-700 p-4 rounded-lg mb-4"> |
|
<p id="requirementsText" class="text-gray-300">Loading requirements...</p> |
|
</div> |
|
</div> |
|
<div> |
|
<h4 class="font-medium mb-2">Security Analysis Results</h4> |
|
<div id="requirementsRisks" class="space-y-3"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="phase-content hidden" id="codeContent"> |
|
<div class="bg-gray-800 rounded-xl p-6"> |
|
<h3 class="text-lg font-semibold mb-4 flex items-center"> |
|
<i class="fas fa-code text-blue-400 mr-2"></i> |
|
Code Analysis |
|
</h3> |
|
<div class="mb-6"> |
|
<h4 class="font-medium mb-2">Code Changes</h4> |
|
<div class="bg-gray-700 p-4 rounded-lg mb-4"> |
|
<div class="terminal-font text-sm text-gray-300 overflow-x-auto" id="codeSnippet"> |
|
<pre>Loading code changes...</pre> |
|
</div> |
|
</div> |
|
</div> |
|
<div> |
|
<h4 class="font-medium mb-2">Security Analysis Results</h4> |
|
<div id="codeRisks" class="space-y-3"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="phase-content hidden" id="testingContent"> |
|
<div class="bg-gray-800 rounded-xl p-6"> |
|
<h3 class="text-lg font-semibold mb-4 flex items-center"> |
|
<i class="fas fa-shield-alt text-blue-400 mr-2"></i> |
|
Security Testing |
|
</h3> |
|
<div class="mb-6"> |
|
<h4 class="font-medium mb-2">Tested Interfaces</h4> |
|
<div class="bg-gray-700 p-4 rounded-lg mb-4"> |
|
<div class="terminal-font text-sm text-gray-300" id="testedInterfaces"> |
|
Loading tested interfaces... |
|
</div> |
|
</div> |
|
</div> |
|
<div> |
|
<h4 class="font-medium mb-2">Security Analysis Results</h4> |
|
<div id="testingRisks" class="space-y-3"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="phase-content hidden" id="releaseContent"> |
|
<div class="bg-gray-800 rounded-xl p-6"> |
|
<h3 class="text-lg font-semibold mb-4 flex items-center"> |
|
<i class="fas fa-rocket text-blue-400 mr-2"></i> |
|
Release Analysis |
|
</h3> |
|
<div class="mb-6"> |
|
<h4 class="font-medium mb-2">Release Artifacts</h4> |
|
<div class="bg-gray-700 p-4 rounded-lg mb-4"> |
|
<div class="terminal-font text-sm text-gray-300" id="releaseArtifacts"> |
|
Loading release artifacts... |
|
</div> |
|
</div> |
|
</div> |
|
<div> |
|
<h4 class="font-medium mb-2">Security Analysis Results</h4> |
|
<div id="releaseRisks" class="space-y-3"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="phase-content hidden" id="operationContent"> |
|
<div class="bg-gray-800 rounded-xl p-6"> |
|
<h3 class="text-lg font-semibold mb-4 flex items-center"> |
|
<i class="fas fa-globe text-blue-400 mr-2"></i> |
|
Operation Monitoring |
|
</h3> |
|
<div class="mb-6"> |
|
<h4 class="font-medium mb-2">Runtime Metrics</h4> |
|
<div class="bg-gray-700 p-4 rounded-lg mb-4"> |
|
<div class="terminal-font text-sm text-gray-300" id="runtimeMetrics"> |
|
Loading runtime metrics... |
|
</div> |
|
</div> |
|
</div> |
|
<div> |
|
<h4 class="font-medium mb-2">Security Analysis Results</h4> |
|
<div id="operationRisks" class="space-y-3"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="lg:col-span-1 space-y-6"> |
|
|
|
<div class="bg-gray-800 rounded-xl p-6"> |
|
<h3 class="text-lg font-semibold mb-4 flex items-center"> |
|
<i class="fas fa-tasks text-blue-400 mr-2"></i> |
|
Repair Progress |
|
</h3> |
|
<div class="space-y-4"> |
|
<div> |
|
<div class="flex justify-between text-sm mb-1"> |
|
<span>Requirements</span> |
|
<span id="reqRepairPercent">0%</span> |
|
</div> |
|
<div class="w-full bg-gray-700 rounded-full h-2"> |
|
<div id="reqRepairBar" class="bg-blue-500 h-2 rounded-full" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
<div> |
|
<div class="flex justify-between text-sm mb-1"> |
|
<span>Code</span> |
|
<span id="codeRepairPercent">0%</span> |
|
</div> |
|
<div class="w-full bg-gray-700 rounded-full h-2"> |
|
<div id="codeRepairBar" class="bg-blue-500 h-2 rounded-full" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
<div> |
|
<div class="flex justify-between text-sm mb-1"> |
|
<span>Testing</span> |
|
<span id="testRepairPercent">0%</span> |
|
</div> |
|
<div class="w-full bg-gray-700 rounded-full h-2"> |
|
<div id="testRepairBar" class="bg-blue-500 h-2 rounded-full" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
<div> |
|
<div class="flex justify-between text-sm mb-1"> |
|
<span>Release</span> |
|
<span id="releaseRepairPercent">0%</span> |
|
</div> |
|
<div class="w-full bg-gray-700 rounded-full h-2"> |
|
<div id="releaseRepairBar" class="bg-blue-500 h-2 rounded-full" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
<div> |
|
<div class="flex justify-between text-sm mb-1"> |
|
<span>Operation</span> |
|
<span id="opRepairPercent">0%</span> |
|
</div> |
|
<div class="w-full bg-gray-700 rounded-full h-2"> |
|
<div id="opRepairBar" class="bg-blue-500 h-2 rounded-full" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-gray-800 rounded-xl p-6 h-full"> |
|
<h3 class="text-lg font-semibold mb-4 flex items-center"> |
|
<i class="fas fa-robot text-blue-400 mr-2"></i> |
|
Security Assistant |
|
</h3> |
|
<div class="chat-container h-96 flex flex-col"> |
|
<div class="chat-messages flex-1 overflow-y-auto mb-4 space-y-3" id="chatMessages"> |
|
<div class="chat-message chat-message-enter bg-gray-700 p-3 rounded-lg"> |
|
<div class="flex items-start"> |
|
<div class="mr-2 mt-1"> |
|
<div class="w-6 h-6 rounded-full bg-blue-600 flex items-center justify-center"> |
|
<i class="fas fa-robot text-xs text-white"></i> |
|
</div> |
|
</div> |
|
<div> |
|
<p class="text-sm">Hello! I'm your AI SDL Security Assistant. How can I help you with this project's security issues?</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="chat-input flex"> |
|
<input type="text" placeholder="Ask about security risks..." class="flex-1 bg-gray-700 text-white px-4 py-2 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
<button class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-r-lg transition"> |
|
<i class="fas fa-paper-plane"></i> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
const projects = [ |
|
{ |
|
id: 1, |
|
name: "支付宝国补项目", |
|
team: "Payment Team", |
|
tags: ["Payment", "High Priority", "Government"], |
|
requirements: "1. 用户补贴申请功能\n2. 补贴金额计算逻辑\n3. 多级审批流程\n4. 数据导出功能", |
|
code: "// 补贴计算逻辑\nfunction calculateSubsidy(user) {\n let amount = 0;\n // 敏感信息硬编码\n const subsidyRate = 0.3; \n \n // SQL拼接风险\n const sql = `UPDATE users SET subsidy = ${amount} WHERE id = ${user.id}`;\n \n // 越权检查缺失\n return amount * subsidyRate;\n}", |
|
interfaces: [ |
|
"POST /api/subsidy/apply", |
|
"GET /api/subsidy/status/{id}", |
|
"POST /api/subsidy/approve", |
|
"GET /api/subsidy/export" |
|
], |
|
release: "Version 1.2.3\n- 包含已知漏洞的旧版依赖\n- 生产环境使用测试密钥\n- 日志配置暴露敏感信息", |
|
operation: "CPU: 45%\nMemory: 1.2GB/2GB\nRequests: 1200/min\nSecurity Events:\n- 多次越权访问尝试\n- SQL注入攻击检测\n- 异常数据导出请求" |
|
}, |
|
{ |
|
id: 2, |
|
name: "微信小程序升级", |
|
team: "WeChat Team", |
|
tags: ["Mobile", "User Facing", "Feature Update"], |
|
requirements: "1. 新用户注册流程\n2. 第三方登录集成\n3. 用户数据同步\n4. 支付功能增强", |
|
code: "// 第三方登录处理\nfunction handleOAuth(provider) {\n // CSRF令牌缺失\n const user = getOAuthUser(provider);\n \n // XSS风险\n document.getElementById('welcome').innerHTML = `Welcome, ${user.name}!`;\n \n // 不安全的反序列化\n localStorage.setItem('user', JSON.stringify(user));\n}", |
|
interfaces: [ |
|
"POST /api/auth/oauth/{provider}", |
|
"GET /api/user/profile", |
|
"POST /api/payment/create", |
|
"GET /api/data/sync" |
|
], |
|
release: "Version 2.1.0\n- 未经安全测试的紧急修复\n- 错误的CORS配置\n- 调试模式在生产环境启用", |
|
operation: "CPU: 32%\nMemory: 890MB/2GB\nRequests: 980/min\nSecurity Events:\n- 多次OAuth滥用尝试\n- XSS攻击检测\n- 异常支付请求" |
|
}, |
|
{ |
|
id: 3, |
|
name: "云存储优化", |
|
team: "Cloud Team", |
|
tags: ["Infrastructure", "Performance", "Backend"], |
|
requirements: "1. 文件上传性能优化\n2. 存储成本降低方案\n3. 跨区域复制功能\n4. 访问控制增强", |
|
code: "// 文件上传处理\nasync function uploadFile(file) {\n // 不充分的文件类型检查\n if (!file.type) return false;\n \n // 权限检查缺失\n const path = `user_uploads/${user.id}/${file.name}`;\n \n // 路径遍历风险\n await s3.putObject({\n Bucket: 'my-bucket',\n Key: path,\n Body: file.data\n });\n}", |
|
interfaces: [ |
|
"POST /api/storage/upload", |
|
"GET /api/storage/download/{id}", |
|
"POST /api/storage/copy", |
|
"GET /api/storage/list" |
|
], |
|
release: "Version 1.5.2\n- 过期的SSL证书\n- 存储桶公开访问配置\n- 缺少必要的IAM策略", |
|
operation: "CPU: 28%\nMemory: 1.5GB/4GB\nRequests: 750/min\nSecurity Events:\n- 多次未授权访问尝试\n- 恶意文件上传检测\n- 异常数据复制请求" |
|
} |
|
]; |
|
|
|
const riskTypes = { |
|
"需求设计": [ |
|
{ name: "越权访问风险", severity: "high", description: "需求中缺少必要的权限控制描述,可能导致越权访问漏洞。" }, |
|
{ name: "数据隐私问题", severity: "critical", description: "需求中对敏感数据处理方式描述不足,可能导致数据泄露风险。" }, |
|
{ name: "日志记录不足", severity: "medium", description: "需求中未明确安全事件日志要求,影响安全审计能力。" }, |
|
{ name: "加密方案缺陷", severity: "high", description: "需求中指定的加密方案强度不足或实现方式不安全。" } |
|
], |
|
"代码变更": [ |
|
{ name: "SQL注入漏洞", severity: "critical", description: "代码中存在SQL拼接操作,可能导致SQL注入攻击。" }, |
|
{ name: "XSS漏洞", severity: "high", description: "代码中直接使用用户输入构建HTML,可能导致跨站脚本攻击。" }, |
|
{ name: "CSRF防护缺失", severity: "high", description: "代码中缺少CSRF防护措施,可能导致跨站请求伪造攻击。" }, |
|
{ name: "硬编码凭证", severity: "critical", description: "代码中包含硬编码的敏感凭证信息。" }, |
|
{ name: "缓冲区溢出", severity: "critical", description: "代码中存在不安全的缓冲区操作,可能导致内存破坏漏洞。" } |
|
], |
|
"安全测试": [ |
|
{ name: "JWT实现缺陷", severity: "high", description: "接口JWT实现存在安全缺陷,可能导致认证绕过。" }, |
|
{ name: "敏感信息泄露", severity: "critical", description: "接口响应中包含敏感信息,可能导致数据泄露。" }, |
|
{ name: "接口未授权访问", severity: "critical", description: "接口缺少必要的授权检查,可能导致未授权访问。" }, |
|
{ name: "速率限制缺失", severity: "medium", description: "接口缺少请求速率限制,可能导致暴力破解攻击。" } |
|
], |
|
"发布": [ |
|
{ name: "未修复漏洞发布", severity: "critical", description: "发布包中包含已知但未修复的安全漏洞。" }, |
|
{ name: "配置错误", severity: "high", description: "生产环境配置存在安全缺陷,如调试模式启用等。" }, |
|
{ name: "密钥泄露", severity: "critical", description: "发布包中包含敏感密钥或凭证信息。" }, |
|
{ name: "依赖漏洞", severity: "high", description: "项目依赖的第三方库包含已知安全漏洞。" } |
|
], |
|
"线上运行": [ |
|
{ name: "未修复漏洞", severity: "critical", description: "线上系统存在已知但未修复的安全漏洞。" }, |
|
{ name: "异常流量", severity: "high", description: "检测到异常访问模式,可能正在进行攻击尝试。" }, |
|
{ name: "入侵尝试", severity: "critical", description: "检测到明确的入侵尝试行为。" }, |
|
{ name: "数据泄露", severity: "critical", description: "检测到敏感数据异常访问或泄露迹象。" } |
|
] |
|
}; |
|
|
|
const phases = [ |
|
{ id: "requirementDesign", name: "需求设计", icon: "clipboard-list" }, |
|
{ id: "codeChanges", name: "代码变更", icon: "code" }, |
|
{ id: "securityTesting", name: "安全测试", icon: "shield-alt" }, |
|
{ id: "release", name: "发布", icon: "rocket" }, |
|
{ id: "onlineOperation", name: "线上运行", icon: "globe" } |
|
]; |
|
|
|
let activities = []; |
|
let detectedRisks = []; |
|
let projectRiskMap = {}; |
|
let simulationInterval; |
|
let isSimulating = false; |
|
let currentProjectView = null; |
|
|
|
|
|
function updateActivityLog() { |
|
const activityLog = document.getElementById('activityLog'); |
|
activityLog.innerHTML = ''; |
|
|
|
if (activities.length === 0) { |
|
activityLog.innerHTML = ` |
|
<div class="text-center py-8 text-gray-500"> |
|
<i class="fas fa-info-circle text-3xl mb-2"></i> |
|
<p>Waiting for security analysis to begin</p> |
|
</div> |
|
`; |
|
return; |
|
} |
|
|
|
activities.slice(0, 10).forEach(activity => { |
|
const activityItem = document.createElement('div'); |
|
activityItem.className = 'bg-gray-700 p-3 rounded-lg fade-in'; |
|
activityItem.innerHTML = ` |
|
<div class="flex justify-between items-start"> |
|
<div class="flex items-start"> |
|
<div class="mr-3 mt-1"> |
|
<div class="w-6 h-6 rounded-full ${activity.risk ? 'bg-red-600' : 'bg-blue-600'} flex items-center justify-center"> |
|
<i class="fas fa-${getActivityIcon(activity.action)} text-xs text-white"></i> |
|
</div> |
|
</div> |
|
<div> |
|
<p class="text-sm font-medium">${activity.action} <span class="${activity.risk ? 'text-red-400' : 'text-blue-400'}">${activity.project}</span></p> |
|
<p class="text-xs text-gray-400">${activity.phase} • ${activity.time}</p> |
|
${activity.risk ? `<p class="text-xs mt-1 text-red-300">Risk: ${activity.risk}</p>` : ''} |
|
</div> |
|
</div> |
|
</div> |
|
`; |
|
activityLog.appendChild(activityItem); |
|
}); |
|
} |
|
|
|
|
|
function updateRiskAlerts() { |
|
const riskAlerts = document.getElementById('riskAlerts'); |
|
const alertCounter = document.getElementById('alertCounter'); |
|
|
|
if (detectedRisks.length === 0) { |
|
riskAlerts.innerHTML = ` |
|
<div class="text-center py-8 text-gray-500"> |
|
<i class="fas fa-check-circle text-3xl mb-2"></i> |
|
<p>No active risk alerts</p> |
|
</div> |
|
`; |
|
alertCounter.innerHTML = ` |
|
<span class="w-2 h-2 bg-gray-500 rounded-full mr-1"></span> |
|
<span>0 New</span> |
|
`; |
|
return; |
|
} |
|
|
|
riskAlerts.innerHTML = ''; |
|
const newAlerts = detectedRisks.filter(r => r.new).length; |
|
|
|
alertCounter.innerHTML = ` |
|
<span class="w-2 h-2 bg-red-500 rounded-full mr-1 animate-pulse"></span> |
|
<span>${newAlerts} New</span> |
|
`; |
|
|
|
detectedRisks.slice(0, 5).forEach(risk => { |
|
const severityClass = risk.severity === 'critical' ? 'bg-red-900 text-red-300' : |
|
risk.severity === 'high' ? 'bg-orange-900 text-orange-300' : 'bg-yellow-900 text-yellow-300'; |
|
|
|
const alertItem = document.createElement('div'); |
|
alertItem.className = `bg-gray-700 p-4 rounded-lg border-l-4 ${risk.new ? 'border-red-500' : 'border-gray-600'} fade-in`; |
|
alertItem.innerHTML = ` |
|
<div class="flex justify-between items-start"> |
|
<div> |
|
<h4 class="font-semibold">${risk.project}</h4> |
|
<p class="text-sm text-gray-400">${risk.phase}环节</p> |
|
</div> |
|
<span class="text-xs px-2 py-1 ${severityClass} rounded">${risk.severity === 'critical' ? '严重' : risk.severity === 'high' ? '高危' : '中危'}</span> |
|
</div> |
|
<p class="mt-2 text-sm">${risk.risk}</p> |
|
<div class="flex justify-between items-center mt-3"> |
|
<span class="text-xs text-gray-400">${risk.time}</span> |
|
<button class="text-xs bg-blue-600 hover:bg-blue-700 px-3 py-1 rounded transition view-detail" data-project-id="${risk.projectId}">详情</button> |
|
</div> |
|
`; |
|
riskAlerts.appendChild(alertItem); |
|
}); |
|
|
|
|
|
document.querySelectorAll('.view-detail').forEach(button => { |
|
button.addEventListener('click', function() { |
|
const projectId = this.getAttribute('data-project-id'); |
|
showProjectDetail(projectId); |
|
}); |
|
}); |
|
} |
|
|
|
|
|
function updateProjectRiskOverview() { |
|
const projectRisks = document.getElementById('projectRisks'); |
|
|
|
if (Object.keys(projectRiskMap).length === 0) { |
|
projectRisks.innerHTML = ` |
|
<div class="text-center py-8 text-gray-500"> |
|
<i class="fas fa-lock text-3xl mb-2"></i> |
|
<p>No projects with risks detected</p> |
|
</div> |
|
`; |
|
return; |
|
} |
|
|
|
projectRisks.innerHTML = ''; |
|
|
|
Object.entries(projectRiskMap).forEach(([projectId, risks]) => { |
|
const project = projects.find(p => p.id == projectId); |
|
const projectItem = document.createElement('div'); |
|
projectItem.className = 'bg-gray-700 p-4 rounded-lg fade-in'; |
|
|
|
const riskPhases = [...new Set(risks.map(r => r.phase))]; |
|
const riskCount = risks.length; |
|
|
|
projectItem.innerHTML = ` |
|
<div class="flex justify-between items-start"> |
|
<div> |
|
<h4 class="font-semibold">${project.name}</h4> |
|
<p class="text-xs text-gray-400">${project.team}</p> |
|
</div> |
|
<span class="text-xs px-2 py-1 ${riskCount > 2 ? 'bg-red-900 text-red-300' : 'bg-yellow-900 text-yellow-300'} rounded"> |
|
${riskCount} 风险 |
|
</span> |
|
</div> |
|
<div class="mt-3"> |
|
<p class="text-xs text-gray-400 mb-1">影响环节:</p> |
|
<div class="flex flex-wrap gap-1"> |
|
${riskPhases.map(phase => ` |
|
<span class="text-xs px-2 py-1 bg-gray-600 rounded">${phase}</span> |
|
`).join('')} |
|
</div> |
|
</div> |
|
<div class="mt-3 flex justify-between items-center"> |
|
<button class="text-xs bg-blue-600 hover:bg-blue-700 px-3 py-1 rounded transition view-detail" data-project-id="${projectId}">详情</button> |
|
<span class="text-xs text-gray-400">${risks[0].time}</span> |
|
</div> |
|
`; |
|
projectRisks.appendChild(projectItem); |
|
}); |
|
|
|
|
|
document.querySelectorAll('.view-detail').forEach(button => { |
|
button.addEventListener('click', function() { |
|
const projectId = this.getAttribute('data-project-id'); |
|
showProjectDetail(projectId); |
|
}); |
|
}); |
|
} |
|
|
|
|
|
function updatePhaseStatus(phaseId, status, progress) { |
|
const phaseElement = document.getElementById(phaseId); |
|
const statusElement = phaseElement.querySelector('.phase-content div:nth-child(3) span:last-child'); |
|
const progressBar = phaseElement.querySelector('.phase-progress-bar'); |
|
|
|
|
|
if (status === 'scanning') { |
|
phaseElement.classList.add('phase-active'); |
|
statusElement.className = 'px-2 py-1 bg-blue-900 text-blue-300 rounded'; |
|
statusElement.textContent = 'Scanning'; |
|
progressBar.style.width = `${progress}%`; |
|
} else if (status === 'risk-detected') { |
|
phaseElement.classList.add('phase-active'); |
|
statusElement.className = 'px-2 py-1 bg-red-900 text-red-300 rounded'; |
|
statusElement.textContent = 'Risk Detected'; |
|
progressBar.style.width = '100%'; |
|
} else if (status === 'completed') { |
|
phaseElement.classList.remove('phase-active'); |
|
statusElement.className = 'px-2 py-1 bg-green-900 text-green-300 rounded'; |
|
statusElement.textContent = 'Completed'; |
|
progressBar.style.width = '100%'; |
|
} else { |
|
phaseElement.classList.remove('phase-active'); |
|
statusElement.className = 'px-2 py-1 bg-gray-700 text-gray-300 rounded'; |
|
statusElement.textContent = 'Waiting'; |
|
progressBar.style.width = '0%'; |
|
} |
|
} |
|
|
|
|
|
function simulatePhaseScanning(phase) { |
|
return new Promise(resolve => { |
|
let progress = 0; |
|
updatePhaseStatus(phase.id, 'scanning', progress); |
|
|
|
const project = projects[Math.floor(Math.random() * projects.length)]; |
|
const hasRisk = Math.random() > 0.6; |
|
let detectedRisk = null; |
|
|
|
const interval = setInterval(() => { |
|
progress += 10; |
|
updatePhaseStatus(phase.id, 'scanning', progress); |
|
|
|
|
|
if (hasRisk && progress > 30 && progress < 80 && !detectedRisk) { |
|
if (Math.random() > 0.7) { |
|
const riskType = riskTypes[phase.name][Math.floor(Math.random() * riskTypes[phase.name].length)]; |
|
detectedRisk = { |
|
project: project.name, |
|
projectId: project.id, |
|
phase: phase.name, |
|
risk: riskType.name, |
|
severity: riskType.severity, |
|
description: riskType.description, |
|
time: getRelativeTime(), |
|
new: true |
|
}; |
|
|
|
|
|
detectedRisks.unshift(detectedRisk); |
|
|
|
|
|
if (!projectRiskMap[project.id]) { |
|
projectRiskMap[project.id] = []; |
|
} |
|
projectRiskMap[project.id].push(detectedRisk); |
|
|
|
|
|
activities.unshift({ |
|
action: "检测到风险", |
|
phase: phase.name, |
|
project: project.name, |
|
risk: detectedRisk.risk, |
|
time: getRelativeTime() |
|
}); |
|
|
|
updatePhaseStatus(phase.id, 'risk-detected', 100); |
|
updateActivityLog(); |
|
updateRiskAlerts(); |
|
updateProjectRiskOverview(); |
|
} |
|
} |
|
|
|
if (progress >= 100) { |
|
clearInterval(interval); |
|
|
|
|
|
if (!hasRisk || !detectedRisk) { |
|
activities.unshift({ |
|
action: "完成扫描", |
|
phase: phase.name, |
|
project: project.name, |
|
time: getRelativeTime() |
|
}); |
|
updateActivityLog(); |
|
} |
|
|
|
updatePhaseStatus(phase.id, 'completed', 100); |
|
resolve(); |
|
} |
|
}, 300); |
|
}); |
|
} |
|
|
|
|
|
async function simulateSDLCycle() { |
|
for (const phase of phases) { |
|
await simulatePhaseScanning(phase); |
|
await new Promise(resolve => setTimeout(resolve, 1000)); |
|
} |
|
|
|
|
|
detectedRisks.forEach(r => r.new = false); |
|
updateRiskAlerts(); |
|
} |
|
|
|
|
|
function getRelativeTime() { |
|
const minutesAgo = Math.floor(Math.random() * 10); |
|
return minutesAgo === 0 ? '刚刚' : `${minutesAgo}分钟前`; |
|
} |
|
|
|
|
|
function getActivityIcon(action) { |
|
if (action.includes('分析') || action.includes('扫描')) return 'search'; |
|
if (action.includes('检测')) return 'exclamation-triangle'; |
|
if (action.includes('完成')) return 'check-circle'; |
|
if (action.includes('报告')) return 'file-alt'; |
|
if (action.includes('监控')) return 'eye'; |
|
if (action.includes('验证')) return 'check-double'; |
|
if (action.includes('计划')) return 'calendar-alt'; |
|
return 'info-circle'; |
|
} |
|
|
|
|
|
function showProjectDetail(projectId) { |
|
const project = projects.find(p => p.id == projectId); |
|
if (!project) return; |
|
|
|
currentProjectView = project; |
|
|
|
|
|
document.getElementById('dashboard').classList.add('hidden'); |
|
document.getElementById('projectDetail').classList.remove('hidden'); |
|
|
|
|
|
document.getElementById('projectName').textContent = project.name; |
|
document.getElementById('projectTeam').textContent = project.team; |
|
|
|
|
|
const projectTags = document.getElementById('projectTags'); |
|
projectTags.innerHTML = ''; |
|
project.tags.forEach(tag => { |
|
const tagElement = document.createElement('span'); |
|
tagElement.className = 'text-xs px-2 py-1 bg-gray-700 rounded-full'; |
|
tagElement.textContent = tag; |
|
projectTags.appendChild(tagElement); |
|
}); |
|
|
|
|
|
const projectRisks = projectRiskMap[projectId] || []; |
|
|
|
|
|
document.getElementById('riskCount').textContent = projectRisks.length; |
|
const riskPhases = [...new Set(projectRisks.map(r => r.phase))]; |
|
document.getElementById('phaseCount').textContent = riskPhases.length; |
|
|
|
|
|
const riskSummary = document.getElementById('riskSummary'); |
|
riskSummary.innerHTML = ''; |
|
|
|
const uniqueRisks = {}; |
|
projectRisks.forEach(risk => { |
|
if (!uniqueRisks[risk.risk]) { |
|
uniqueRisks[risk.risk] = risk; |
|
} |
|
}); |
|
|
|
Object.values(uniqueRisks).forEach(risk => { |
|
const severityClass = risk.severity === 'critical' ? 'bg-red-900 text-red-300' : |
|
risk.severity === 'high' ? 'bg-orange-900 text-orange-300' : 'bg-yellow-900 text-yellow-300'; |
|
|
|
const riskTag = document.createElement('span'); |
|
riskTag.className = `text-xs px-2 py-1 ${severityClass} rounded-full risk-tag`; |
|
riskTag.textContent = risk.risk; |
|
riskTag.title = risk.description; |
|
riskSummary.appendChild(riskTag); |
|
}); |
|
|
|
|
|
updatePhaseContent('requirements', project); |
|
updatePhaseContent('code', project); |
|
updatePhaseContent('testing', project); |
|
updatePhaseContent('release', project); |
|
updatePhaseContent('operation', project); |
|
|
|
|
|
document.getElementById('reqRepairPercent').textContent = `${Math.floor(Math.random() * 30)}%`; |
|
document.getElementById('reqRepairBar').style.width = `${Math.floor(Math.random() * 30)}%`; |
|
|
|
document.getElementById('codeRepairPercent').textContent = `${Math.floor(Math.random() * 50)}%`; |
|
document.getElementById('codeRepairBar').style.width = `${Math.floor(Math.random() * 50)}%`; |
|
|
|
document.getElementById('testRepairPercent').textContent = `${Math.floor(Math.random() * 70)}%`; |
|
document.getElementById('testRepairBar').style.width = `${Math.floor(Math.random() * 70)}%`; |
|
|
|
document.getElementById('releaseRepairPercent').textContent = `${Math.floor(Math.random() * 40)}%`; |
|
document.getElementById('releaseRepairBar').style.width = `${Math.floor(Math.random() * 40)}%`; |
|
|
|
document.getElementById('opRepairPercent').textContent = `${Math.floor(Math.random() * 20)}%`; |
|
document.getElementById('opRepairBar').style.width = `${Math.floor(Math.random() * 20)}%`; |
|
} |
|
|
|
|
|
function updatePhaseContent(phase, project) { |
|
const risks = (projectRiskMap[project.id] || []).filter(r => { |
|
if (phase === 'requirements') return r.phase === '需求设计'; |
|
if (phase === 'code') return r.phase === '代码变更'; |
|
if (phase === 'testing') return r.phase === '安全测试'; |
|
if (phase === 'release') return r.phase === '发布'; |
|
if (phase === 'operation') return r.phase === '线上运行'; |
|
return false; |
|
}); |
|
|
|
|
|
if (phase === 'requirements') { |
|
document.getElementById('requirementsText').textContent = project.requirements; |
|
|
|
const risksContainer = document.getElementById('requirementsRisks'); |
|
risksContainer.innerHTML = risks.length > 0 ? '' : '<p class="text-gray-500 text-sm">No security risks detected in this phase</p>'; |
|
|
|
risks.forEach(risk => { |
|
const severityClass = risk.severity === 'critical' ? 'border-red-500 bg-red-900/20' : |
|
risk.severity === 'high' ? 'border-orange-500 bg-orange-900/20' : 'border-yellow-500 bg-yellow-900/20'; |
|
|
|
const riskElement = document.createElement('div'); |
|
riskElement.className = `border-l-4 p-3 ${severityClass}`; |
|
riskElement.innerHTML = ` |
|
<div class="flex justify-between items-start"> |
|
<h5 class="font-medium">${risk.risk}</h5> |
|
<span class="text-xs px-2 py-1 ${severityClass.replace('border-l-4', '')} rounded"> |
|
${risk.severity === 'critical' ? '严重' : risk.severity === 'high' ? '高危' : '中危'} |
|
</span> |
|
</div> |
|
<p class="text-sm mt-1 text-gray-300">${risk.description}</p> |
|
<div class="mt-2 flex justify-between items-center"> |
|
<span class="text-xs text-gray-400">${risk.time}</span> |
|
<button class="text-xs bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded transition">标记为修复</button> |
|
</div> |
|
`; |
|
risksContainer.appendChild(riskElement); |
|
}); |
|
} |
|
else if (phase === 'code') { |
|
document.getElementById('codeSnippet').innerHTML = `<pre>${project.code}</pre>`; |
|
|
|
const risksContainer = document.getElementById('codeRisks'); |
|
risksContainer.innerHTML = risks.length > 0 ? '' : '<p class="text-gray-500 text-sm">No security risks detected in this phase</p>'; |
|
|
|
risks.forEach(risk => { |
|
const severityClass = risk.severity === 'critical' ? 'border-red-500 bg-red-900/20' : |
|
risk.severity === 'high' ? 'border-orange-500 bg-orange-900/20' : 'border-yellow-500 bg-yellow-900/20'; |
|
|
|
const riskElement = document.createElement('div'); |
|
riskElement.className = `border-l-4 p-3 ${severityClass}`; |
|
riskElement.innerHTML = ` |
|
<div class="flex justify-between items-start"> |
|
<h5 class="font-medium">${risk.risk}</h5> |
|
<span class="text-xs px-2 py-1 ${severityClass.replace('border-l-4', '')} rounded"> |
|
${risk.severity === 'critical' ? '严重' : risk.severity === 'high' ? '高危' : '中危'} |
|
</span> |
|
</div> |
|
<p class="text-sm mt-1 text-gray-300">${risk.description}</p> |
|
<div class="mt-2 flex justify-between items-center"> |
|
<span class="text-xs text-gray-400">${risk.time}</span> |
|
<button class="text-xs bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded transition">标记为修复</button> |
|
</div> |
|
`; |
|
risksContainer.appendChild(riskElement); |
|
}); |
|
} |
|
else if (phase === 'testing') { |
|
document.getElementById('testedInterfaces').innerHTML = project.interfaces.map(i => `<div class="mb-1">${i}</div>`).join(''); |
|
|
|
const risksContainer = document.getElementById('testingRisks'); |
|
risksContainer.innerHTML = risks.length > 0 ? '' : '<p class="text-gray-500 text-sm">No security risks detected in this phase</p>'; |
|
|
|
risks.forEach(risk => { |
|
const severityClass = risk.severity === 'critical' ? 'border-red-500 bg-red-900/20' : |
|
risk.severity === 'high' ? 'border-orange-500 bg-orange-900/20' : 'border-yellow-500 bg-yellow-900/20'; |
|
|
|
const riskElement = document.createElement('div'); |
|
riskElement.className = `border-l-4 p-3 ${severityClass}`; |
|
riskElement.innerHTML = ` |
|
<div class="flex justify-between items-start"> |
|
<h5 class="font-medium">${risk.risk}</h5> |
|
<span class="text-xs px-2 py-1 ${severityClass.replace('border-l-4', '')} rounded"> |
|
${risk.severity === 'critical' ? '严重' : risk.severity === 'high' ? '高危' : '中危'} |
|
</span> |
|
</div> |
|
<p class="text-sm mt-1 text-gray-300">${risk.description}</p> |
|
<div class="mt-2 flex justify-between items-center"> |
|
<span class="text-xs text-gray-400">${risk.time}</span> |
|
<button class="text-xs bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded transition">标记为修复</button> |
|
</div> |
|
`; |
|
risksContainer.appendChild(riskElement); |
|
}); |
|
} |
|
else if (phase === 'release') { |
|
document.getElementById('releaseArtifacts').textContent = project.release; |
|
|
|
const risksContainer = document.getElementById('releaseRisks'); |
|
risksContainer.innerHTML = risks.length > 0 ? '' : '<p class="text-gray-500 text-sm">No security risks detected in this phase</p>'; |
|
|
|
risks.forEach(risk => { |
|
const severityClass = risk.severity === 'critical' ? 'border-red-500 bg-red-900/20' : |
|
risk.severity === 'high' ? 'border-orange-500 bg-orange-900/20' : 'border-yellow-500 bg-yellow-900/20'; |
|
|
|
const riskElement = document.createElement('div'); |
|
riskElement.className = `border-l-4 p-3 ${severityClass}`; |
|
riskElement.innerHTML = ` |
|
<div class="flex justify-between items-start"> |
|
<h5 class="font-medium">${risk.risk}</h5> |
|
<span class="text-xs px-2 py-1 ${severityClass.replace('border-l-4', '')} rounded"> |
|
${risk.severity === 'critical' ? '严重' : risk.severity === 'high' ? '高危' : '中危'} |
|
</span> |
|
</div> |
|
<p class="text-sm mt-1 text-gray-300">${risk.description}</p> |
|
<div class="mt-2 flex justify-between items-center"> |
|
<span class="text-xs text-gray-400">${risk.time}</span> |
|
<button class="text-xs bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded transition">标记为修复</button> |
|
</div> |
|
`; |
|
risksContainer.appendChild(riskElement); |
|
}); |
|
} |
|
else if (phase === 'operation') { |
|
document.getElementById('runtimeMetrics').textContent = project.operation; |
|
|
|
const risksContainer = document.getElementById('operationRisks'); |
|
risksContainer.innerHTML = risks.length > 0 ? '' : '<p class="text-gray-500 text-sm">No security risks detected in this phase</p>'; |
|
|
|
risks.forEach(risk => { |
|
const severityClass = risk.severity === 'critical' ? 'border-red-500 bg-red-900/20' : |
|
risk.severity === 'high' ? 'border-orange-500 bg-orange-900/20' : 'border-yellow-500 bg-yellow-900/20'; |
|
|
|
const riskElement = document.createElement('div'); |
|
riskElement.className = `border-l-4 p-3 ${severityClass}`; |
|
riskElement.innerHTML = ` |
|
<div class="flex justify-between items-start"> |
|
<h5 class="font-medium">${risk.risk}</h5> |
|
<span class="text-xs px-2 py-1 ${severityClass.replace('border-l-4', '')} rounded"> |
|
${risk.severity === 'critical' ? '严重' : risk.severity === 'high' ? '高危' : '中危'} |
|
</span> |
|
</div> |
|
<p class="text-sm mt-1 text-gray-300">${risk.description}</p> |
|
<div class="mt-2 flex justify-between items-center"> |
|
<span class="text-xs text-gray-400">${risk.time}</span> |
|
<button class="text-xs bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded transition">标记为修复</button> |
|
</div> |
|
`; |
|
risksContainer.appendChild(riskElement); |
|
}); |
|
} |
|
} |
|
|
|
|
|
document.getElementById('simulateBtn').addEventListener('click', function() { |
|
const btn = this; |
|
|
|
if (isSimulating) { |
|
clearInterval(simulationInterval); |
|
btn.innerHTML = '<i class="fas fa-play mr-2"></i>Start Simulation'; |
|
isSimulating = false; |
|
|
|
|
|
phases.forEach(phase => { |
|
updatePhaseStatus(phase.id, 'waiting', 0); |
|
}); |
|
} else { |
|
btn.innerHTML = '<i class="fas fa-stop mr-2"></i>Stop Simulation'; |
|
isSimulating = true; |
|
|
|
|
|
simulateSDLCycle(); |
|
simulationInterval = setInterval(simulateSDLCycle, 25000); |
|
} |
|
}); |
|
|
|
|
|
document.getElementById('backToDashboard').addEventListener('click', function() { |
|
document.getElementById('dashboard').classList.remove('hidden'); |
|
document.getElementById('projectDetail').classList.add('hidden'); |
|
}); |
|
|
|
|
|
document.querySelectorAll('.phase-tab').forEach(tab => { |
|
tab.addEventListener('click', function() { |
|
|
|
document.querySelectorAll('.phase-tab').forEach(t => t.classList.remove('active')); |
|
this.classList.add('active'); |
|
|
|
|
|
const phase = this.getAttribute('data-phase'); |
|
document.querySelectorAll('.phase-content').forEach(c => c.classList.add('hidden')); |
|
document.getElementById(`${phase}Content`).classList.remove('hidden'); |
|
}); |
|
}); |
|
|
|
|
|
document.querySelector('.chat-input button').addEventListener('click', function() { |
|
const input = document.querySelector('.chat-input input'); |
|
const message = input.value.trim(); |
|
if (message === '') return; |
|
|
|
|
|
const userMessage = document.createElement('div'); |
|
userMessage.className = 'chat-message chat-message-enter bg-blue-900/30 p-3 rounded-lg mb-3'; |
|
userMessage.innerHTML = ` |
|
<div class="flex items-start"> |
|
<div class="mr-2 mt-1"> |
|
<div class="w-6 h-6 rounded-full bg-blue-600 flex items-center justify-center"> |
|
<i class="fas fa-user text-xs text-white"></i> |
|
</div> |
|
</div> |
|
<div> |
|
<p class="text-sm">${message}</p> |
|
</div> |
|
</div> |
|
`; |
|
document.getElementById('chatMessages').appendChild(userMessage); |
|
|
|
|
|
input.value = ''; |
|
|
|
|
|
setTimeout(() => { |
|
const responses = [ |
|
`关于${currentProjectView.name}项目的${message}问题,检测到的主要风险包括...`, |
|
`针对${message},建议采取以下修复措施...`, |
|
`在${currentProjectView.name}项目中,${message}相关的安全分析结果显示...`, |
|
`关于${message}的安全建议:1. 更新依赖库 2. 加强输入验证 3. 实施更严格的访问控制`, |
|
`项目${currentProjectView.name}中${message}问题的根本原因是...` |
|
]; |
|
|
|
const aiMessage = document.createElement('div'); |
|
aiMessage.className = 'chat-message chat-message-enter bg-gray-700 p-3 rounded-lg'; |
|
aiMessage.innerHTML = ` |
|
<div class="flex items-start"> |
|
<div class="mr-2 mt-1"> |
|
<div class="w-6 h-6 rounded-full bg-blue-600 flex items-center justify-center"> |
|
<i class="fas fa-robot text-xs text-white"></i> |
|
</div> |
|
</div> |
|
<div> |
|
<p class="text-sm">${responses[Math.floor(Math.random() * responses.length)]}</p> |
|
</div> |
|
</div> |
|
`; |
|
document.getElementById('chatMessages').appendChild(aiMessage); |
|
|
|
|
|
document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight; |
|
}, 1000); |
|
}); |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
updateActivityLog(); |
|
updateRiskAlerts(); |
|
updateProjectRiskOverview(); |
|
|
|
|
|
phases.forEach(phase => { |
|
updatePhaseStatus(phase.id, 'waiting', 0); |
|
}); |
|
}); |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=hackaigc/ai-sdl1-0" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |