Spaces:
No application file
No application file
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>OSINT Early Warning System</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: #1a1a1a; | |
| color: #e0e0e0; | |
| line-height: 1.6; | |
| min-height: 100vh; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 40px; | |
| border-bottom: 2px solid #333; | |
| padding-bottom: 20px; | |
| } | |
| .header h1 { | |
| color: #006aff; | |
| font-size: 2.5rem; | |
| font-weight: 300; | |
| margin-bottom: 10px; | |
| } | |
| .header p { | |
| color: #888; | |
| font-size: 1.1rem; | |
| } | |
| .search-section { | |
| background: #2a2a2a; | |
| border-radius: 8px; | |
| padding: 30px; | |
| margin-bottom: 30px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); | |
| } | |
| .search-form { | |
| display: flex; | |
| gap: 15px; | |
| margin-bottom: 20px; | |
| } | |
| .search-input { | |
| flex: 1; | |
| padding: 15px; | |
| background: #1a1a1a; | |
| border: 2px solid #444; | |
| border-radius: 6px; | |
| color: #e0e0e0; | |
| font-size: 1rem; | |
| transition: border-color 0.3s ease; | |
| } | |
| .search-input:focus { | |
| outline: none; | |
| border-color: #00ff88; | |
| } | |
| .search-input::placeholder { | |
| color: #666; | |
| } | |
| .search-btn { | |
| padding: 15px 30px; | |
| background: #00ff88; | |
| color: #1a1a1a; | |
| border: none; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| transition: all 0.3s ease; | |
| } | |
| .search-btn:hover:not(:disabled) { | |
| background: #00cc6a; | |
| transform: translateY(-2px); | |
| } | |
| .search-btn:disabled { | |
| background: #555; | |
| color: #888; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .status-section { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 15px; | |
| margin-bottom: 30px; | |
| } | |
| .status-card { | |
| background: #2a2a2a; | |
| border-radius: 6px; | |
| padding: 20px; | |
| text-align: center; | |
| border-left: 4px solid #00ff88; | |
| } | |
| .status-card h3 { | |
| color: #00ff88; | |
| font-size: 0.9rem; | |
| margin-bottom: 5px; | |
| font-weight: 500; | |
| } | |
| .status-card p { | |
| font-size: 1.2rem; | |
| font-weight: 600; | |
| } | |
| .loading { | |
| display: none; | |
| text-align: center; | |
| padding: 40px; | |
| color: #00ff88; | |
| } | |
| .loading.active { | |
| display: block; | |
| } | |
| .spinner { | |
| border: 4px solid #333; | |
| border-top: 4px solid #00ff88; | |
| border-radius: 50%; | |
| width: 40px; | |
| height: 40px; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto 20px; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .results-section { | |
| background: #2a2a2a; | |
| border-radius: 8px; | |
| padding: 30px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); | |
| display: none; | |
| } | |
| .results-section.active { | |
| display: block; | |
| } | |
| .results-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 25px; | |
| padding-bottom: 15px; | |
| border-bottom: 1px solid #444; | |
| } | |
| .results-title { | |
| color: #00ff88; | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| } | |
| .risk-badge { | |
| padding: 8px 16px; | |
| border-radius: 20px; | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| } | |
| .risk-low { background: #28a745; color: white; } | |
| .risk-medium { background: #ffc107; color: black; } | |
| .risk-high { background: #fd7e14; color: white; } | |
| .risk-critical { background: #dc3545; color: white; } | |
| .risk-unknown { background: #6c757d; color: white; } | |
| .results-content { | |
| white-space: pre-wrap; | |
| line-height: 1.8; | |
| font-size: 1rem; | |
| } | |
| .results-meta { | |
| margin-top: 25px; | |
| padding-top: 20px; | |
| border-top: 1px solid #444; | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 15px; | |
| font-size: 0.9rem; | |
| color: #888; | |
| } | |
| .meta-item { | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| .meta-label { | |
| font-weight: 600; | |
| } | |
| .error { | |
| background: #dc3545; | |
| color: white; | |
| padding: 15px; | |
| border-radius: 6px; | |
| margin: 20px 0; | |
| display: none; | |
| } | |
| .error.active { | |
| display: block; | |
| } | |
| .examples { | |
| margin-top: 15px; | |
| color: #666; | |
| font-size: 0.9rem; | |
| } | |
| .examples strong { | |
| color: #888; | |
| } | |
| .example-btn { | |
| background: none; | |
| border: 1px solid #444; | |
| color: #888; | |
| padding: 5px 10px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| margin: 2px; | |
| font-size: 0.8rem; | |
| transition: all 0.3s ease; | |
| } | |
| .example-btn:hover { | |
| border-color: #00ff88; | |
| color: #00ff88; | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 15px; | |
| } | |
| .search-form { | |
| flex-direction: column; | |
| } | |
| .header h1 { | |
| font-size: 2rem; | |
| } | |
| .status-section { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header class="header"> | |
| <h1>OSINT Early Warning</h1> | |
| <p>Comprehensive threat intelligence analysis</p> | |
| </header> | |
| <section class="search-section"> | |
| <form class="search-form" id="searchForm"> | |
| <input | |
| type="text" | |
| class="search-input" | |
| id="entityInput" | |
| placeholder="Enter IP, domain, hash, CVE, or other entity..." | |
| required | |
| > | |
| <button type="submit" class="search-btn" id="searchBtn"> | |
| Analyze | |
| </button> | |
| </form> | |
| <div class="examples"> | |
| <strong>Examples:</strong> | |
| <button class="example-btn" onclick="setExample('8.8.8.8')">IP Address</button> | |
| <button class="example-btn" onclick="setExample('google.com')">Domain</button> | |
| <button class="example-btn" onclick="setExample('CVE-2024-0001')">CVE</button> | |
| <button class="example-btn" onclick="setExample('44d88612fea8a8f36de82e1278abb02f')">Hash</button> | |
| </div> | |
| </section> | |
| <section class="status-section" id="statusSection"> | |
| <div class="status-card"> | |
| <h3>System Status</h3> | |
| <p id="systemStatus">Ready</p> | |
| </div> | |
| <div class="status-card"> | |
| <h3>Active Sources</h3> | |
| <p id="activeSources">Loading...</p> | |
| </div> | |
| <div class="status-card"> | |
| <h3>Last Analysis</h3> | |
| <p id="lastAnalysis">None</p> | |
| </div> | |
| <div class="status-card"> | |
| <h3>Response Time</h3> | |
| <p id="responseTime">-</p> | |
| </div> | |
| </section> | |
| <div class="loading" id="loadingSection"> | |
| <div class="spinner"></div> | |
| <p>Analyzing entity across multiple OSINT sources...</p> | |
| <p><small>This may take 30-60 seconds</small></p> | |
| </div> | |
| <div class="error" id="errorSection"></div> | |
| <section class="results-section" id="resultsSection"> | |
| <div class="results-header"> | |
| <h2 class="results-title" id="resultsTitle">Analysis Results</h2> | |
| <span class="risk-badge" id="riskBadge">Unknown</span> | |
| </div> | |
| <div class="results-content" id="resultsContent"></div> | |
| <div class="results-meta" id="resultsMeta"></div> | |
| </section> | |
| </div> | |
| <script> | |
| let analysisStartTime; | |
| // Initialize the application | |
| document.addEventListener('DOMContentLoaded', function() { | |
| loadSourcesStatus(); | |
| setupEventListeners(); | |
| }); | |
| function setupEventListeners() { | |
| const form = document.getElementById('searchForm'); | |
| form.addEventListener('submit', handleSearch); | |
| } | |
| async function handleSearch(e) { | |
| e.preventDefault(); | |
| const entity = document.getElementById('entityInput').value.trim(); | |
| if (!entity) return; | |
| showLoading(); | |
| hideError(); | |
| hideResults(); | |
| analysisStartTime = Date.now(); | |
| updateSystemStatus('Analyzing...'); | |
| try { | |
| const response = await fetch(`/api/osint?entity=${encodeURIComponent(entity)}`); | |
| const data = await response.json(); | |
| if (!response.ok) { | |
| throw new Error(data.error || 'Analysis failed'); | |
| } | |
| displayResults(entity, data); | |
| updateSystemStatus('Complete'); | |
| updateLastAnalysis(entity); | |
| updateResponseTime(); | |
| } catch (error) { | |
| showError(error.message); | |
| updateSystemStatus('Error'); | |
| console.error('Analysis error:', error); | |
| } finally { | |
| hideLoading(); | |
| } | |
| } | |
| function showLoading() { | |
| document.getElementById('loadingSection').classList.add('active'); | |
| document.getElementById('searchBtn').disabled = true; | |
| document.getElementById('searchBtn').textContent = 'Analyzing...'; | |
| } | |
| function hideLoading() { | |
| document.getElementById('loadingSection').classList.remove('active'); | |
| document.getElementById('searchBtn').disabled = false; | |
| document.getElementById('searchBtn').textContent = 'Analyze'; | |
| } | |
| function showError(message) { | |
| const errorSection = document.getElementById('errorSection'); | |
| errorSection.textContent = message; | |
| errorSection.classList.add('active'); | |
| } | |
| function hideError() { | |
| document.getElementById('errorSection').classList.remove('active'); | |
| } | |
| function hideResults() { | |
| document.getElementById('resultsSection').classList.remove('active'); | |
| } | |
| function displayResults(entity, data) { | |
| const resultsSection = document.getElementById('resultsSection'); | |
| const resultsTitle = document.getElementById('resultsTitle'); | |
| const riskBadge = document.getElementById('riskBadge'); | |
| const resultsContent = document.getElementById('resultsContent'); | |
| const resultsMeta = document.getElementById('resultsMeta'); | |
| // Set title and risk badge | |
| resultsTitle.textContent = `Analysis: ${entity}`; | |
| riskBadge.textContent = data.risk_level || 'Unknown'; | |
| riskBadge.className = `risk-badge risk-${(data.risk_level || 'unknown').toLowerCase()}`; | |
| // Set content | |
| resultsContent.textContent = data.summary || 'No analysis available'; | |
| // Set metadata | |
| resultsMeta.innerHTML = ` | |
| <div class="meta-item"> | |
| <span class="meta-label">Confidence:</span> | |
| <span>${(data.confidence * 100).toFixed(1)}%</span> | |
| </div> | |
| <div class="meta-item"> | |
| <span class="meta-label">Sources:</span> | |
| <span>${data.sources_used ? data.sources_used.join(', ') : 'None'}</span> | |
| </div> | |
| <div class="meta-item"> | |
| <span class="meta-label">Analysis Time:</span> | |
| <span>${data.timestamp ? new Date(data.timestamp).toLocaleString() : 'Unknown'}</span> | |
| </div> | |
| <div class="meta-item"> | |
| <span class="meta-label">Response Time:</span> | |
| <span>${((Date.now() - analysisStartTime) / 1000).toFixed(1)}s</span> | |
| </div> | |
| `; | |
| resultsSection.classList.add('active'); | |
| } | |
| async function loadSourcesStatus() { | |
| try { | |
| const response = await fetch('/api/sources'); | |
| const data = await response.json(); | |
| const activeSources = data.sources.filter(s => s.status === 'active').length; | |
| document.getElementById('activeSources').textContent = activeSources; | |
| } catch (error) { | |
| document.getElementById('activeSources').textContent = 'Error'; | |
| console.error('Failed to load sources:', error); | |
| } | |
| } | |
| function updateSystemStatus(status) { | |
| document.getElementById('systemStatus').textContent = status; | |
| } | |
| function updateLastAnalysis(entity) { | |
| const now = new Date(); | |
| const timeStr = now.toLocaleTimeString(); | |
| document.getElementById('lastAnalysis').textContent = `${entity} (${timeStr})`; | |
| } | |
| function updateResponseTime() { | |
| if (analysisStartTime) { | |
| const responseTime = ((Date.now() - analysisStartTime) / 1000).toFixed(1); | |
| document.getElementById('responseTime').textContent = `${responseTime}s`; | |
| } | |
| } | |
| function setExample(value) { | |
| document.getElementById('entityInput').value = value; | |
| document.getElementById('entityInput').focus(); | |
| } | |
| // Health check | |
| setInterval(async () => { | |
| try { | |
| const response = await fetch('/api/health'); | |
| if (response.ok) { | |
| updateSystemStatus('Ready'); | |
| } else { | |
| updateSystemStatus('Warning'); | |
| } | |
| } catch (error) { | |
| updateSystemStatus('Offline'); | |
| } | |
| }, 30000); // Check every 30 seconds | |
| </script> | |
| </body> | |
| </html> |