Files
SneakyScan/app/web/templates/ip_search_results.html
Phillip Tarrant 4c6b4bf35d Add IP address search feature with global search box
- Add API endpoint GET /api/scans/by-ip/{ip_address} to retrieve
  last 10 scans containing a specific IP
- Add ScanService.get_scans_by_ip() method with ScanIP join query
- Add search box to global navigation header
- Create dedicated search results page at /search/ip
- Update API documentation with new endpoint
2025-11-21 11:29:03 -06:00

176 lines
6.5 KiB
HTML

{% extends "base.html" %}
{% block title %}Search Results for {{ ip_address }} - SneakyScanner{% endblock %}
{% block content %}
<div class="row mt-4">
<div class="col-12 d-flex justify-content-between align-items-center mb-4">
<h1>
<i class="bi bi-search"></i>
Search Results
{% if ip_address %}
<small class="text-muted">for {{ ip_address }}</small>
{% endif %}
</h1>
<a href="{{ url_for('main.scans') }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Back to Scans
</a>
</div>
</div>
{% if not ip_address %}
<!-- No IP provided -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body text-center py-5">
<i class="bi bi-exclamation-circle text-warning" style="font-size: 3rem;"></i>
<h4 class="mt-3">No IP Address Provided</h4>
<p class="text-muted">Please enter an IP address in the search box to find related scans.</p>
</div>
</div>
</div>
</div>
{% else %}
<!-- Results Table -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Last 10 Scans Containing {{ ip_address }}</h5>
</div>
<div class="card-body">
<div id="results-loading" class="text-center py-5">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-muted">Searching for scans...</p>
</div>
<div id="results-error" class="alert alert-danger" style="display: none;"></div>
<div id="results-empty" class="text-center py-5 text-muted" style="display: none;">
<i class="bi bi-search" style="font-size: 3rem;"></i>
<h5 class="mt-3">No Scans Found</h5>
<p>No completed scans contain the IP address <strong>{{ ip_address }}</strong>.</p>
</div>
<div id="results-table-container" style="display: none;">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th style="width: 80px;">ID</th>
<th>Title</th>
<th style="width: 200px;">Timestamp</th>
<th style="width: 100px;">Duration</th>
<th style="width: 120px;">Status</th>
<th style="width: 100px;">Actions</th>
</tr>
</thead>
<tbody id="results-tbody">
</tbody>
</table>
</div>
<div class="text-muted mt-3">
Found <span id="result-count">0</span> scan(s) containing this IP address.
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
<script>
const ipAddress = "{{ ip_address | e }}";
// Load results when page loads
document.addEventListener('DOMContentLoaded', function() {
if (ipAddress) {
loadResults();
}
});
// Load search results from API
async function loadResults() {
const loadingEl = document.getElementById('results-loading');
const errorEl = document.getElementById('results-error');
const emptyEl = document.getElementById('results-empty');
const tableEl = document.getElementById('results-table-container');
// Show loading state
loadingEl.style.display = 'block';
errorEl.style.display = 'none';
emptyEl.style.display = 'none';
tableEl.style.display = 'none';
try {
const response = await fetch(`/api/scans/by-ip/${encodeURIComponent(ipAddress)}`);
if (!response.ok) {
throw new Error('Failed to search for scans');
}
const data = await response.json();
const scans = data.scans || [];
loadingEl.style.display = 'none';
if (scans.length === 0) {
emptyEl.style.display = 'block';
} else {
tableEl.style.display = 'block';
renderResultsTable(scans);
document.getElementById('result-count').textContent = data.count;
}
} catch (error) {
console.error('Error searching for scans:', error);
loadingEl.style.display = 'none';
errorEl.textContent = 'Failed to search for scans. Please try again.';
errorEl.style.display = 'block';
}
}
// Render results table
function renderResultsTable(scans) {
const tbody = document.getElementById('results-tbody');
tbody.innerHTML = '';
scans.forEach(scan => {
const row = document.createElement('tr');
row.classList.add('scan-row');
// Format timestamp
const timestamp = new Date(scan.timestamp).toLocaleString();
// Format duration
const duration = scan.duration ? `${scan.duration.toFixed(1)}s` : '-';
// Status badge
let statusBadge = '';
if (scan.status === 'completed') {
statusBadge = '<span class="badge badge-success">Completed</span>';
} else if (scan.status === 'running') {
statusBadge = '<span class="badge badge-info">Running</span>';
} else if (scan.status === 'failed') {
statusBadge = '<span class="badge badge-danger">Failed</span>';
} else {
statusBadge = `<span class="badge badge-info">${scan.status}</span>`;
}
row.innerHTML = `
<td class="mono">${scan.id}</td>
<td>${scan.title || 'Untitled Scan'}</td>
<td class="text-muted">${timestamp}</td>
<td class="mono">${duration}</td>
<td>${statusBadge}</td>
<td>
<a href="/scans/${scan.id}" class="btn btn-sm btn-secondary">View</a>
</td>
`;
tbody.appendChild(row);
});
}
</script>
{% endblock %}