Merge branch 'nightly' into beta
This commit is contained in:
@@ -12,7 +12,7 @@ alembic==1.13.0
|
||||
# Authentication & Security
|
||||
Flask-Login==0.6.3
|
||||
bcrypt==4.1.2
|
||||
cryptography==41.0.7
|
||||
cryptography>=46.0.0
|
||||
|
||||
# API & Serialization
|
||||
Flask-CORS==4.0.0
|
||||
@@ -34,4 +34,4 @@ python-dotenv==1.0.0
|
||||
|
||||
# Development & Testing
|
||||
pytest==7.4.3
|
||||
pytest-flask==1.3.0
|
||||
pytest-flask==1.3.0
|
||||
@@ -1,5 +1,5 @@
|
||||
PyYAML==6.0.1
|
||||
python-libnmap==0.7.3
|
||||
sslyze==6.0.0
|
||||
sslyze==6.2.0
|
||||
playwright==1.40.0
|
||||
Jinja2==3.1.2
|
||||
|
||||
@@ -1054,6 +1054,8 @@ class SneakyScanner:
|
||||
# Preserve directory structure in ZIP
|
||||
arcname = f"{screenshot_dir.name}/{screenshot_file.name}"
|
||||
zipf.write(screenshot_file, arcname)
|
||||
# Track screenshot directory for database storage
|
||||
output_paths['screenshots'] = screenshot_dir
|
||||
|
||||
output_paths['zip'] = zip_path
|
||||
print(f"ZIP archive saved to: {zip_path}", flush=True)
|
||||
|
||||
@@ -146,6 +146,47 @@ def acknowledge_alert(alert_id):
|
||||
}), 400
|
||||
|
||||
|
||||
@bp.route('/acknowledge-all', methods=['POST'])
|
||||
@api_auth_required
|
||||
def acknowledge_all_alerts():
|
||||
"""
|
||||
Acknowledge all unacknowledged alerts.
|
||||
|
||||
Returns:
|
||||
JSON response with count of acknowledged alerts
|
||||
"""
|
||||
acknowledged_by = request.json.get('acknowledged_by', 'api') if request.json else 'api'
|
||||
|
||||
try:
|
||||
# Get all unacknowledged alerts
|
||||
unacked_alerts = current_app.db_session.query(Alert).filter(
|
||||
Alert.acknowledged == False
|
||||
).all()
|
||||
|
||||
count = 0
|
||||
for alert in unacked_alerts:
|
||||
alert.acknowledged = True
|
||||
alert.acknowledged_at = datetime.now(timezone.utc)
|
||||
alert.acknowledged_by = acknowledged_by
|
||||
count += 1
|
||||
|
||||
current_app.db_session.commit()
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'message': f'Acknowledged {count} alerts',
|
||||
'count': count,
|
||||
'acknowledged_by': acknowledged_by
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.db_session.rollback()
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'Failed to acknowledge alerts: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@bp.route('/rules', methods=['GET'])
|
||||
@api_auth_required
|
||||
def list_alert_rules():
|
||||
|
||||
@@ -77,12 +77,12 @@ def execute_scan(scan_id: int, config_id: int, db_url: str = None):
|
||||
|
||||
# Generate output files (JSON, HTML, ZIP)
|
||||
logger.info(f"Scan {scan_id}: Generating output files...")
|
||||
scanner.generate_outputs(report, timestamp)
|
||||
output_paths = scanner.generate_outputs(report, timestamp)
|
||||
|
||||
# Save results to database
|
||||
logger.info(f"Scan {scan_id}: Saving results to database...")
|
||||
scan_service = ScanService(session)
|
||||
scan_service._save_scan_to_db(report, scan_id, status='completed')
|
||||
scan_service._save_scan_to_db(report, scan_id, status='completed', output_paths=output_paths)
|
||||
|
||||
# Evaluate alert rules
|
||||
logger.info(f"Scan {scan_id}: Evaluating alert rules...")
|
||||
|
||||
@@ -5,8 +5,9 @@ Provides dashboard and scan viewing pages.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from flask import Blueprint, current_app, redirect, render_template, url_for
|
||||
from flask import Blueprint, current_app, redirect, render_template, send_from_directory, url_for
|
||||
|
||||
from web.auth.decorators import login_required
|
||||
|
||||
@@ -244,3 +245,31 @@ def alert_rules():
|
||||
'alert_rules.html',
|
||||
rules=rules
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/help')
|
||||
@login_required
|
||||
def help():
|
||||
"""
|
||||
Help page - explains how to use the application.
|
||||
|
||||
Returns:
|
||||
Rendered help template
|
||||
"""
|
||||
return render_template('help.html')
|
||||
|
||||
|
||||
@bp.route('/output/<path:filename>')
|
||||
@login_required
|
||||
def serve_output_file(filename):
|
||||
"""
|
||||
Serve output files (JSON, HTML, ZIP) from the output directory.
|
||||
|
||||
Args:
|
||||
filename: Name of the file to serve
|
||||
|
||||
Returns:
|
||||
The requested file
|
||||
"""
|
||||
output_dir = os.environ.get('OUTPUT_DIR', '/app/output')
|
||||
return send_from_directory(output_dir, filename)
|
||||
|
||||
@@ -308,7 +308,7 @@ class ScanService:
|
||||
return count
|
||||
|
||||
def _save_scan_to_db(self, report: Dict[str, Any], scan_id: int,
|
||||
status: str = 'completed') -> None:
|
||||
status: str = 'completed', output_paths: Dict = None) -> None:
|
||||
"""
|
||||
Save scan results to database.
|
||||
|
||||
@@ -319,6 +319,7 @@ class ScanService:
|
||||
report: Scan report dictionary from scanner
|
||||
scan_id: Scan ID to update
|
||||
status: Final scan status (completed or failed)
|
||||
output_paths: Dictionary with paths to generated files {'json': Path, 'html': Path, 'zip': Path}
|
||||
"""
|
||||
scan = self.db.query(Scan).filter(Scan.id == scan_id).first()
|
||||
if not scan:
|
||||
@@ -329,6 +330,17 @@ class ScanService:
|
||||
scan.duration = report.get('scan_duration')
|
||||
scan.completed_at = datetime.utcnow()
|
||||
|
||||
# Save output file paths
|
||||
if output_paths:
|
||||
if 'json' in output_paths:
|
||||
scan.json_path = str(output_paths['json'])
|
||||
if 'html' in output_paths:
|
||||
scan.html_path = str(output_paths['html'])
|
||||
if 'zip' in output_paths:
|
||||
scan.zip_path = str(output_paths['zip'])
|
||||
if 'screenshots' in output_paths:
|
||||
scan.screenshot_dir = str(output_paths['screenshots'])
|
||||
|
||||
# Map report data to database models
|
||||
self._map_report_to_models(report, scan)
|
||||
|
||||
@@ -439,9 +451,10 @@ class ScanService:
|
||||
|
||||
# Process certificate and TLS info if present
|
||||
http_info = service_data.get('http_info', {})
|
||||
if http_info.get('certificate'):
|
||||
ssl_tls = http_info.get('ssl_tls', {})
|
||||
if ssl_tls.get('certificate'):
|
||||
self._process_certificate(
|
||||
http_info['certificate'],
|
||||
ssl_tls,
|
||||
scan_obj.id,
|
||||
service.id
|
||||
)
|
||||
@@ -479,16 +492,19 @@ class ScanService:
|
||||
return service
|
||||
return None
|
||||
|
||||
def _process_certificate(self, cert_data: Dict[str, Any], scan_id: int,
|
||||
def _process_certificate(self, ssl_tls_data: Dict[str, Any], scan_id: int,
|
||||
service_id: int) -> None:
|
||||
"""
|
||||
Process certificate and TLS version data.
|
||||
|
||||
Args:
|
||||
cert_data: Certificate data dictionary
|
||||
ssl_tls_data: SSL/TLS data dictionary containing 'certificate' and 'tls_versions'
|
||||
scan_id: Scan ID
|
||||
service_id: Service ID
|
||||
"""
|
||||
# Extract certificate data from ssl_tls structure
|
||||
cert_data = ssl_tls_data.get('certificate', {})
|
||||
|
||||
# Create ScanCertificate record
|
||||
cert = ScanCertificate(
|
||||
scan_id=scan_id,
|
||||
@@ -506,7 +522,7 @@ class ScanService:
|
||||
self.db.flush()
|
||||
|
||||
# Process TLS versions
|
||||
tls_versions = cert_data.get('tls_versions', {})
|
||||
tls_versions = ssl_tls_data.get('tls_versions', {})
|
||||
for version, version_data in tls_versions.items():
|
||||
tls = ScanTLSVersion(
|
||||
scan_id=scan_id,
|
||||
|
||||
@@ -6,9 +6,14 @@
|
||||
<div class="row mt-4">
|
||||
<div class="col-12 d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Alert History</h1>
|
||||
<a href="{{ url_for('main.alert_rules') }}" class="btn btn-primary">
|
||||
<i class="bi bi-gear"></i> Manage Alert Rules
|
||||
</a>
|
||||
<div>
|
||||
<button class="btn btn-success me-2" onclick="acknowledgeAllAlerts()">
|
||||
<i class="bi bi-check-all"></i> Ack All
|
||||
</button>
|
||||
<a href="{{ url_for('main.alert_rules') }}" class="btn btn-primary">
|
||||
<i class="bi bi-gear"></i> Manage Alert Rules
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -265,5 +270,34 @@ function acknowledgeAlert(alertId) {
|
||||
alert('Failed to acknowledge alert');
|
||||
});
|
||||
}
|
||||
|
||||
function acknowledgeAllAlerts() {
|
||||
if (!confirm('Acknowledge all unacknowledged alerts?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/alerts/acknowledge-all', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': localStorage.getItem('api_key') || ''
|
||||
},
|
||||
body: JSON.stringify({
|
||||
acknowledged_by: 'web_user'
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to acknowledge alerts: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Failed to acknowledge alerts');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -77,6 +77,12 @@
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'main.help' %}active{% endif %}"
|
||||
href="{{ url_for('main.help') }}">
|
||||
<i class="bi bi-question-circle"></i> Help
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.logout') }}">Logout</a>
|
||||
</li>
|
||||
@@ -108,7 +114,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Global notification container - always above modals -->
|
||||
<div id="notification-container" style="position: fixed; top: 20px; right: 20px; z-index: 1100; min-width: 300px;"></div>
|
||||
<div id="notification-container" style="position: fixed; top: 20px; right: 20px; z-index: 9999; min-width: 300px;"></div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
|
||||
375
app/web/templates/help.html
Normal file
375
app/web/templates/help.html
Normal file
@@ -0,0 +1,375 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Help - SneakyScanner{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h1 class="mb-4"><i class="bi bi-question-circle"></i> Help & Documentation</h1>
|
||||
<p class="text-muted">Learn how to use SneakyScanner to manage your network scanning operations.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Navigation -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-compass"></i> Quick Navigation</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-3 col-6">
|
||||
<a href="#getting-started" class="btn btn-outline-primary w-100">Getting Started</a>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<a href="#sites" class="btn btn-outline-primary w-100">Sites</a>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<a href="#scan-configs" class="btn btn-outline-primary w-100">Scan Configs</a>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<a href="#running-scans" class="btn btn-outline-primary w-100">Running Scans</a>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<a href="#scheduling" class="btn btn-outline-primary w-100">Scheduling</a>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<a href="#comparisons" class="btn btn-outline-primary w-100">Comparisons</a>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<a href="#alerts" class="btn btn-outline-primary w-100">Alerts</a>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<a href="#webhooks" class="btn btn-outline-primary w-100">Webhooks</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Getting Started -->
|
||||
<div class="row mb-4" id="getting-started">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-rocket-takeoff"></i> Getting Started</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>SneakyScanner helps you perform network vulnerability scans and track changes over time. Here's the typical workflow:</p>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Basic Workflow:</strong>
|
||||
<ol class="mb-0 mt-2">
|
||||
<li><strong>Create a Site</strong> - Define a logical grouping for your targets</li>
|
||||
<li><strong>Add IPs</strong> - Add IP addresses or ranges to your site</li>
|
||||
<li><strong>Create a Scan Config</strong> - Configure how scans should run using your site</li>
|
||||
<li><strong>Run a Scan</strong> - Execute scans manually or on a schedule</li>
|
||||
<li><strong>Review Results</strong> - Analyze findings and compare scans over time</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sites -->
|
||||
<div class="row mb-4" id="sites">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-globe"></i> Creating Sites & Adding IPs</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>What is a Site?</h6>
|
||||
<p>A Site is a logical grouping of IP addresses that you want to scan together. For example, you might create separate sites for "Production Servers", "Development Environment", or "Office Network".</p>
|
||||
|
||||
<h6>Creating a Site</h6>
|
||||
<ol>
|
||||
<li>Navigate to <strong>Configs → Sites</strong> in the navigation menu</li>
|
||||
<li>Click the <strong>Create Site</strong> button</li>
|
||||
<li>Enter a descriptive name for your site</li>
|
||||
<li>Optionally add a description to help identify the site's purpose</li>
|
||||
<li>Click <strong>Create</strong> to save the site</li>
|
||||
</ol>
|
||||
|
||||
<h6>Adding IP Addresses</h6>
|
||||
<p>After creating a site, you need to add the IP addresses you want to scan:</p>
|
||||
<ol>
|
||||
<li>Find your site in the Sites list</li>
|
||||
<li>Click the <strong>Manage IPs</strong> button (or the site name)</li>
|
||||
<li>Click <strong>Add IP</strong></li>
|
||||
<li>Enter the IP address or CIDR range (e.g., <code>192.168.1.1</code> or <code>192.168.1.0/24</code>)</li>
|
||||
<li>Click <strong>Add</strong> to save</li>
|
||||
</ol>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-exclamation-triangle"></i> <strong>Note:</strong> You can add individual IPs or CIDR notation ranges. Large ranges will result in longer scan times.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scan Configs -->
|
||||
<div class="row mb-4" id="scan-configs">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-gear"></i> Creating Scan Configurations</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>What is a Scan Config?</h6>
|
||||
<p>A Scan Configuration defines how a scan should be performed. It links to a Site and specifies scanning parameters like ports to scan, timing options, and other settings.</p>
|
||||
|
||||
<h6>Creating a Scan Config</h6>
|
||||
<ol>
|
||||
<li>Navigate to <strong>Configs → Scan Configs</strong> in the navigation menu</li>
|
||||
<li>Click the <strong>Create Config</strong> button</li>
|
||||
<li>Enter a name for the configuration</li>
|
||||
<li>Select the <strong>Site</strong> to associate with this config</li>
|
||||
<li>Configure scan parameters:
|
||||
<ul>
|
||||
<li><strong>Ports</strong> - Specify ports to scan (e.g., <code>22,80,443</code> or <code>1-1000</code>)</li>
|
||||
<li><strong>Timing</strong> - Set scan speed/aggressiveness</li>
|
||||
<li><strong>Additional Options</strong> - Configure other nmap parameters as needed</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Click <strong>Create</strong> to save the configuration</li>
|
||||
</ol>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> <strong>Tip:</strong> Create different configs for different purposes - a quick config for daily checks and a thorough config for weekly deep scans.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Running Scans -->
|
||||
<div class="row mb-4" id="running-scans">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-play-circle"></i> Running Scans</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>Starting a Manual Scan</h6>
|
||||
<ol>
|
||||
<li>Navigate to <strong>Scans</strong> in the navigation menu</li>
|
||||
<li>Click the <strong>New Scan</strong> button</li>
|
||||
<li>Select the <strong>Scan Config</strong> you want to use</li>
|
||||
<li>Click <strong>Start Scan</strong></li>
|
||||
</ol>
|
||||
|
||||
<h6>Monitoring Scan Progress</h6>
|
||||
<p>While a scan is running:</p>
|
||||
<ul>
|
||||
<li>The scan will appear in the Scans list with a <span class="badge badge-warning">Running</span> status</li>
|
||||
<li>You can view live progress by clicking on the scan</li>
|
||||
<li>The Dashboard also shows active scans</li>
|
||||
</ul>
|
||||
|
||||
<h6>Viewing Scan Results</h6>
|
||||
<ol>
|
||||
<li>Once complete, click on a scan in the Scans list</li>
|
||||
<li>View discovered hosts, open ports, and services</li>
|
||||
<li>Export results or compare with previous scans</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scheduling -->
|
||||
<div class="row mb-4" id="scheduling">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-calendar-check"></i> Scheduling Scans</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>Why Schedule Scans?</h6>
|
||||
<p>Scheduled scans allow you to automatically run scans at regular intervals, ensuring continuous monitoring of your network without manual intervention.</p>
|
||||
|
||||
<h6>Creating a Schedule</h6>
|
||||
<ol>
|
||||
<li>Navigate to <strong>Schedules</strong> in the navigation menu</li>
|
||||
<li>Click the <strong>Create Schedule</strong> button</li>
|
||||
<li>Enter a name for the schedule</li>
|
||||
<li>Select the <strong>Scan Config</strong> to use</li>
|
||||
<li>Configure the schedule:
|
||||
<ul>
|
||||
<li><strong>Frequency</strong> - How often to run (daily, weekly, monthly, custom cron)</li>
|
||||
<li><strong>Time</strong> - When to start the scan</li>
|
||||
<li><strong>Days</strong> - Which days to run (for weekly schedules)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Enable/disable the schedule as needed</li>
|
||||
<li>Click <strong>Create</strong> to save</li>
|
||||
</ol>
|
||||
|
||||
<h6>Managing Schedules</h6>
|
||||
<ul>
|
||||
<li><strong>Enable/Disable</strong> - Toggle schedules on or off without deleting them</li>
|
||||
<li><strong>Edit</strong> - Modify the schedule timing or associated config</li>
|
||||
<li><strong>Delete</strong> - Remove schedules you no longer need</li>
|
||||
<li><strong>View History</strong> - See past runs triggered by the schedule</li>
|
||||
</ul>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> <strong>Tip:</strong> Schedule comprehensive scans during off-peak hours to minimize network impact.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scan Comparisons -->
|
||||
<div class="row mb-4" id="comparisons">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-arrow-left-right"></i> Scan Comparisons</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>Why Compare Scans?</h6>
|
||||
<p>Comparing scans helps you identify changes in your network over time - new hosts, closed ports, new services, or potential security issues.</p>
|
||||
|
||||
<h6>Comparing Two Scans</h6>
|
||||
<ol>
|
||||
<li>Navigate to <strong>Scans</strong> in the navigation menu</li>
|
||||
<li>Find the scan you want to use as the baseline</li>
|
||||
<li>Click on the scan to view its details</li>
|
||||
<li>Click the <strong>Compare</strong> button</li>
|
||||
<li>Select another scan to compare against</li>
|
||||
<li>Review the comparison results</li>
|
||||
</ol>
|
||||
|
||||
<h6>Understanding Comparison Results</h6>
|
||||
<p>The comparison view shows:</p>
|
||||
<ul>
|
||||
<li><span class="badge badge-success">New</span> - Hosts or ports that appear in the newer scan but not the older one</li>
|
||||
<li><span class="badge badge-danger">Removed</span> - Hosts or ports that were in the older scan but not the newer one</li>
|
||||
<li><span class="badge badge-warning">Changed</span> - Services or states that differ between scans</li>
|
||||
<li><span class="badge badge-info">Unchanged</span> - Items that remain the same</li>
|
||||
</ul>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-exclamation-triangle"></i> <strong>Security Note:</strong> Pay close attention to unexpected new open ports or services - these could indicate unauthorized changes or potential compromises.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alerts -->
|
||||
<div class="row mb-4" id="alerts">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-bell"></i> Alerts & Alert Rules</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>Understanding Alerts</h6>
|
||||
<p>Alerts notify you when scan results match certain conditions you define. This helps you stay informed about important changes without manually reviewing every scan.</p>
|
||||
|
||||
<h6>Viewing Alert History</h6>
|
||||
<ol>
|
||||
<li>Navigate to <strong>Alerts → Alert History</strong></li>
|
||||
<li>View all triggered alerts with timestamps and details</li>
|
||||
<li>Filter alerts by severity, date, or type</li>
|
||||
<li>Click on an alert to see full details and the associated scan</li>
|
||||
</ol>
|
||||
|
||||
<h6>Creating Alert Rules</h6>
|
||||
<ol>
|
||||
<li>Navigate to <strong>Alerts → Alert Rules</strong></li>
|
||||
<li>Click <strong>Create Rule</strong></li>
|
||||
<li>Configure the rule:
|
||||
<ul>
|
||||
<li><strong>Name</strong> - A descriptive name for the rule</li>
|
||||
<li><strong>Condition</strong> - What triggers the alert (e.g., new open port, new host, specific service detected)</li>
|
||||
<li><strong>Severity</strong> - How critical is this alert (Info, Warning, Critical)</li>
|
||||
<li><strong>Scope</strong> - Which sites or configs this rule applies to</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Enable the rule</li>
|
||||
<li>Click <strong>Create</strong> to save</li>
|
||||
</ol>
|
||||
|
||||
<h6>Common Alert Rule Examples</h6>
|
||||
<ul>
|
||||
<li><strong>New Host Detected</strong> - Alert when a previously unknown host appears</li>
|
||||
<li><strong>New Open Port</strong> - Alert when a new port opens on any host</li>
|
||||
<li><strong>Critical Port Open</strong> - Alert for specific high-risk ports (e.g., 23/Telnet, 3389/RDP)</li>
|
||||
<li><strong>Service Change</strong> - Alert when a service version changes</li>
|
||||
<li><strong>Host Offline</strong> - Alert when an expected host stops responding</li>
|
||||
</ul>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> <strong>Tip:</strong> Start with a few important rules and refine them over time to avoid alert fatigue.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Webhooks -->
|
||||
<div class="row mb-4" id="webhooks">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-broadcast"></i> Webhooks</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>What are Webhooks?</h6>
|
||||
<p>Webhooks allow SneakyScanner to send notifications to external services when events occur, such as scan completion or alert triggers. This enables integration with tools like Slack, Discord, Microsoft Teams, or custom systems.</p>
|
||||
|
||||
<h6>Creating a Webhook</h6>
|
||||
<ol>
|
||||
<li>Navigate to <strong>Alerts → Webhooks</strong></li>
|
||||
<li>Click <strong>Create Webhook</strong></li>
|
||||
<li>Configure the webhook:
|
||||
<ul>
|
||||
<li><strong>Name</strong> - A descriptive name</li>
|
||||
<li><strong>URL</strong> - The endpoint to send notifications to</li>
|
||||
<li><strong>Events</strong> - Which events trigger this webhook</li>
|
||||
<li><strong>Secret</strong> - Optional secret for request signing</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Test the webhook to verify it works</li>
|
||||
<li>Click <strong>Create</strong> to save</li>
|
||||
</ol>
|
||||
|
||||
<h6>Webhook Events</h6>
|
||||
<ul>
|
||||
<li><strong>Scan Started</strong> - When a scan begins</li>
|
||||
<li><strong>Scan Completed</strong> - When a scan finishes</li>
|
||||
<li><strong>Scan Failed</strong> - When a scan encounters an error</li>
|
||||
<li><strong>Alert Triggered</strong> - When an alert rule matches</li>
|
||||
</ul>
|
||||
|
||||
<h6>Integration Examples</h6>
|
||||
<ul>
|
||||
<li><strong>Slack</strong> - Use a Slack Incoming Webhook URL</li>
|
||||
<li><strong>Discord</strong> - Use a Discord Webhook URL</li>
|
||||
<li><strong>Microsoft Teams</strong> - Use a Teams Incoming Webhook</li>
|
||||
<li><strong>Custom API</strong> - Send to your own endpoint for custom processing</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to Top -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 text-center">
|
||||
<a href="#" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-up"></i> Back to Top
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -154,6 +154,67 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Certificate Details Modal -->
|
||||
<div class="modal fade" id="certificateModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content" style="background-color: #1e293b; border: 1px solid #334155;">
|
||||
<div class="modal-header" style="border-bottom: 1px solid #334155;">
|
||||
<h5 class="modal-title" style="color: #60a5fa;">
|
||||
<i class="bi bi-shield-lock"></i> Certificate Details
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted">Subject</label>
|
||||
<div id="cert-subject" class="mono" style="word-break: break-all;">-</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted">Issuer</label>
|
||||
<div id="cert-issuer" class="mono" style="word-break: break-all;">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted">Valid From</label>
|
||||
<div id="cert-valid-from" class="mono">-</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted">Valid Until</label>
|
||||
<div id="cert-valid-until" class="mono">-</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted">Days Until Expiry</label>
|
||||
<div id="cert-days-expiry">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted">Serial Number</label>
|
||||
<div id="cert-serial" class="mono" style="word-break: break-all;">-</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted">Self-Signed</label>
|
||||
<div id="cert-self-signed">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted">Subject Alternative Names (SANs)</label>
|
||||
<div id="cert-sans">-</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted">TLS Version Support</label>
|
||||
<div id="cert-tls-versions">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" style="border-top: 1px solid #334155;">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
@@ -162,6 +223,25 @@
|
||||
let scanData = null;
|
||||
let historyChart = null; // Store chart instance to prevent duplicates
|
||||
|
||||
// Show alert notification
|
||||
function showAlert(type, message) {
|
||||
const container = document.getElementById('notification-container');
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `alert alert-${type} alert-dismissible fade show mb-2`;
|
||||
|
||||
notification.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
|
||||
container.appendChild(notification);
|
||||
|
||||
// Auto-dismiss after 5 seconds
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Load scan on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadScan().then(() => {
|
||||
@@ -218,7 +298,6 @@
|
||||
document.getElementById('scan-timestamp').textContent = new Date(scan.timestamp).toLocaleString();
|
||||
document.getElementById('scan-duration').textContent = scan.duration ? `${scan.duration.toFixed(1)}s` : '-';
|
||||
document.getElementById('scan-triggered-by').textContent = scan.triggered_by || 'manual';
|
||||
document.getElementById('scan-config-id').textContent = scan.config_id || '-';
|
||||
|
||||
// Status badge
|
||||
let statusBadge = '';
|
||||
@@ -313,6 +392,8 @@
|
||||
<th>Product</th>
|
||||
<th>Version</th>
|
||||
<th>Status</th>
|
||||
<th>Screenshot</th>
|
||||
<th>Certificate</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="site-${siteIdx}-ip-${ipIdx}-ports"></tbody>
|
||||
@@ -326,10 +407,12 @@
|
||||
const ports = ip.ports || [];
|
||||
|
||||
if (ports.length === 0) {
|
||||
portsContainer.innerHTML = '<tr class="scan-row"><td colspan="7" class="text-center text-muted">No ports found</td></tr>';
|
||||
portsContainer.innerHTML = '<tr class="scan-row"><td colspan="9" class="text-center text-muted">No ports found</td></tr>';
|
||||
} else {
|
||||
ports.forEach(port => {
|
||||
const service = port.services && port.services.length > 0 ? port.services[0] : null;
|
||||
const screenshotPath = service && service.screenshot_path ? service.screenshot_path : null;
|
||||
const certificate = service && service.certificates && service.certificates.length > 0 ? service.certificates[0] : null;
|
||||
|
||||
const row = document.createElement('tr');
|
||||
row.classList.add('scan-row'); // Fix white row bug
|
||||
@@ -341,6 +424,8 @@
|
||||
<td>${service ? service.product || '-' : '-'}</td>
|
||||
<td class="mono">${service ? service.version || '-' : '-'}</td>
|
||||
<td>${port.expected ? '<span class="badge badge-good">Expected</span>' : '<span class="badge badge-warning">Unexpected</span>'}</td>
|
||||
<td>${screenshotPath ? `<a href="/output/${screenshotPath.replace(/^\/?(?:app\/)?output\/?/, '')}" target="_blank" class="btn btn-sm btn-outline-primary" title="View Screenshot"><i class="bi bi-image"></i></a>` : '-'}</td>
|
||||
<td>${certificate ? `<button class="btn btn-sm btn-outline-info" onclick='showCertificateModal(${JSON.stringify(certificate).replace(/'/g, "'")})' title="View Certificate"><i class="bi bi-shield-lock"></i></button>` : '-'}</td>
|
||||
`;
|
||||
portsContainer.appendChild(row);
|
||||
});
|
||||
@@ -439,7 +524,7 @@
|
||||
window.location.href = '{{ url_for("main.scans") }}';
|
||||
} catch (error) {
|
||||
console.error('Error deleting scan:', error);
|
||||
alert(`Failed to delete scan: ${error.message}`);
|
||||
showAlert('danger', `Failed to delete scan: ${error.message}`);
|
||||
|
||||
// Re-enable button on error
|
||||
deleteBtn.disabled = false;
|
||||
@@ -593,5 +678,97 @@
|
||||
console.error('Error loading historical chart:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Show certificate details modal
|
||||
function showCertificateModal(cert) {
|
||||
// Populate modal fields
|
||||
document.getElementById('cert-subject').textContent = cert.subject || '-';
|
||||
document.getElementById('cert-issuer').textContent = cert.issuer || '-';
|
||||
document.getElementById('cert-serial').textContent = cert.serial_number || '-';
|
||||
|
||||
// Format dates
|
||||
document.getElementById('cert-valid-from').textContent = cert.not_valid_before
|
||||
? new Date(cert.not_valid_before).toLocaleString()
|
||||
: '-';
|
||||
document.getElementById('cert-valid-until').textContent = cert.not_valid_after
|
||||
? new Date(cert.not_valid_after).toLocaleString()
|
||||
: '-';
|
||||
|
||||
// Days until expiry with color coding
|
||||
if (cert.days_until_expiry !== null && cert.days_until_expiry !== undefined) {
|
||||
let badgeClass = 'badge-success';
|
||||
if (cert.days_until_expiry < 0) {
|
||||
badgeClass = 'badge-danger';
|
||||
} else if (cert.days_until_expiry < 30) {
|
||||
badgeClass = 'badge-warning';
|
||||
}
|
||||
document.getElementById('cert-days-expiry').innerHTML =
|
||||
`<span class="badge ${badgeClass}">${cert.days_until_expiry} days</span>`;
|
||||
} else {
|
||||
document.getElementById('cert-days-expiry').textContent = '-';
|
||||
}
|
||||
|
||||
// Self-signed indicator
|
||||
document.getElementById('cert-self-signed').innerHTML = cert.is_self_signed
|
||||
? '<span class="badge badge-warning">Yes</span>'
|
||||
: '<span class="badge badge-success">No</span>';
|
||||
|
||||
// SANs
|
||||
if (cert.sans && cert.sans.length > 0) {
|
||||
document.getElementById('cert-sans').innerHTML = cert.sans
|
||||
.map(san => `<span class="badge bg-secondary me-1 mb-1">${san}</span>`)
|
||||
.join('');
|
||||
} else {
|
||||
document.getElementById('cert-sans').textContent = 'None';
|
||||
}
|
||||
|
||||
// TLS versions
|
||||
if (cert.tls_versions && cert.tls_versions.length > 0) {
|
||||
let tlsHtml = '<div class="table-responsive"><table class="table table-sm mb-0">';
|
||||
tlsHtml += '<thead><tr><th>Version</th><th>Status</th><th>Cipher Suites</th></tr></thead><tbody>';
|
||||
|
||||
cert.tls_versions.forEach(tls => {
|
||||
const statusBadge = tls.supported
|
||||
? '<span class="badge badge-success">Supported</span>'
|
||||
: '<span class="badge badge-danger">Not Supported</span>';
|
||||
|
||||
let ciphers = '-';
|
||||
if (tls.cipher_suites && tls.cipher_suites.length > 0) {
|
||||
ciphers = `<small class="text-muted">${tls.cipher_suites.length} cipher(s)</small>
|
||||
<button class="btn btn-sm btn-link p-0 ms-1" onclick="toggleCiphers(this, '${tls.tls_version}')" data-ciphers='${JSON.stringify(tls.cipher_suites).replace(/'/g, "'")}'>
|
||||
<i class="bi bi-chevron-down"></i>
|
||||
</button>
|
||||
<div class="cipher-list" style="display:none; font-size: 0.75rem; max-height: 100px; overflow-y: auto;"></div>`;
|
||||
}
|
||||
|
||||
tlsHtml += `<tr class="scan-row"><td>${tls.tls_version}</td><td>${statusBadge}</td><td>${ciphers}</td></tr>`;
|
||||
});
|
||||
|
||||
tlsHtml += '</tbody></table></div>';
|
||||
document.getElementById('cert-tls-versions').innerHTML = tlsHtml;
|
||||
} else {
|
||||
document.getElementById('cert-tls-versions').textContent = 'No TLS information available';
|
||||
}
|
||||
|
||||
// Show modal
|
||||
const modal = new bootstrap.Modal(document.getElementById('certificateModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
// Toggle cipher suites display
|
||||
function toggleCiphers(btn, version) {
|
||||
const cipherList = btn.nextElementSibling;
|
||||
const icon = btn.querySelector('i');
|
||||
|
||||
if (cipherList.style.display === 'none') {
|
||||
const ciphers = JSON.parse(btn.dataset.ciphers);
|
||||
cipherList.innerHTML = ciphers.map(c => `<div class="mono">${c}</div>`).join('');
|
||||
cipherList.style.display = 'block';
|
||||
icon.className = 'bi bi-chevron-up';
|
||||
} else {
|
||||
cipherList.style.display = 'none';
|
||||
icon.className = 'bi bi-chevron-down';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -151,6 +151,25 @@
|
||||
let statusFilter = '';
|
||||
let totalCount = 0;
|
||||
|
||||
// Show alert notification
|
||||
function showAlert(type, message) {
|
||||
const container = document.getElementById('notification-container');
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `alert alert-${type} alert-dismissible fade show mb-2`;
|
||||
|
||||
notification.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
|
||||
container.appendChild(notification);
|
||||
|
||||
// Auto-dismiss after 5 seconds
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Load initial data when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadScans();
|
||||
@@ -456,15 +475,7 @@
|
||||
bootstrap.Modal.getInstance(document.getElementById('triggerScanModal')).hide();
|
||||
|
||||
// Show success message
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = 'alert alert-success alert-dismissible fade show mt-3';
|
||||
alertDiv.innerHTML = `
|
||||
Scan triggered successfully! (ID: ${data.scan_id})
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
// Insert at the beginning of container-fluid
|
||||
const container = document.querySelector('.container-fluid');
|
||||
container.insertBefore(alertDiv, container.firstChild);
|
||||
showAlert('success', `Scan triggered successfully! (ID: ${data.scan_id})`);
|
||||
|
||||
// Refresh scans
|
||||
loadScans();
|
||||
@@ -490,23 +501,18 @@
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete scan');
|
||||
const data = await response.json();
|
||||
throw new Error(data.message || 'Failed to delete scan');
|
||||
}
|
||||
|
||||
// Show success message
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = 'alert alert-success alert-dismissible fade show mt-3';
|
||||
alertDiv.innerHTML = `
|
||||
Scan ${scanId} deleted successfully.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
document.querySelector('.container-fluid').insertBefore(alertDiv, document.querySelector('.row'));
|
||||
showAlert('success', `Scan ${scanId} deleted successfully.`);
|
||||
|
||||
// Refresh scans
|
||||
loadScans();
|
||||
} catch (error) {
|
||||
console.error('Error deleting scan:', error);
|
||||
alert('Failed to delete scan. Please try again.');
|
||||
showAlert('danger', `Failed to delete scan: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -720,7 +720,7 @@ Retrieve a paginated list of scans with optional status filtering.
|
||||
"duration": 125.5,
|
||||
"status": "completed",
|
||||
"title": "Production Network Scan",
|
||||
"config_id": "/app/configs/production.yaml",
|
||||
"config_id": 1,
|
||||
"triggered_by": "manual",
|
||||
"started_at": "2025-11-14T10:30:00Z",
|
||||
"completed_at": "2025-11-14T10:32:05Z"
|
||||
@@ -731,7 +731,7 @@ Retrieve a paginated list of scans with optional status filtering.
|
||||
"duration": 98.2,
|
||||
"status": "completed",
|
||||
"title": "Development Network Scan",
|
||||
"config_id": "/app/configs/dev.yaml",
|
||||
"config_id": 2,
|
||||
"triggered_by": "scheduled",
|
||||
"started_at": "2025-11-13T15:00:00Z",
|
||||
"completed_at": "2025-11-13T15:01:38Z"
|
||||
@@ -793,7 +793,7 @@ Retrieve complete details for a specific scan, including all sites, IPs, ports,
|
||||
"duration": 125.5,
|
||||
"status": "completed",
|
||||
"title": "Production Network Scan",
|
||||
"config_id": "/app/configs/production.yaml",
|
||||
"config_id": 1,
|
||||
"json_path": "/app/output/scan_report_20251114_103000.json",
|
||||
"html_path": "/app/output/scan_report_20251114_103000.html",
|
||||
"zip_path": "/app/output/scan_report_20251114_103000.zip",
|
||||
@@ -968,7 +968,8 @@ Delete a scan and all associated files (JSON, HTML, ZIP, screenshots).
|
||||
**Success Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"message": "Scan 42 deleted successfully"
|
||||
"scan_id": 42,
|
||||
"message": "Scan deleted successfully"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1111,7 +1112,7 @@ Retrieve a list of all schedules with pagination and filtering.
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Daily Production Scan",
|
||||
"config_id": "/app/configs/prod-scan.yaml",
|
||||
"config_id": 1,
|
||||
"cron_expression": "0 2 * * *",
|
||||
"enabled": true,
|
||||
"created_at": "2025-11-01T10:00:00Z",
|
||||
@@ -1157,7 +1158,7 @@ Retrieve details for a specific schedule including execution history.
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Daily Production Scan",
|
||||
"config_id": "/app/configs/prod-scan.yaml",
|
||||
"config_id": 1,
|
||||
"cron_expression": "0 2 * * *",
|
||||
"enabled": true,
|
||||
"created_at": "2025-11-01T10:00:00Z",
|
||||
@@ -1201,7 +1202,7 @@ Create a new scheduled scan.
|
||||
```json
|
||||
{
|
||||
"name": "Daily Production Scan",
|
||||
"config_id": "/app/configs/prod-scan.yaml",
|
||||
"config_id": 1,
|
||||
"cron_expression": "0 2 * * *",
|
||||
"enabled": true
|
||||
}
|
||||
@@ -1215,7 +1216,7 @@ Create a new scheduled scan.
|
||||
"schedule": {
|
||||
"id": 1,
|
||||
"name": "Daily Production Scan",
|
||||
"config_id": "/app/configs/prod-scan.yaml",
|
||||
"config_id": 1,
|
||||
"cron_expression": "0 2 * * *",
|
||||
"enabled": true,
|
||||
"created_at": "2025-11-01T10:00:00Z"
|
||||
@@ -1236,7 +1237,7 @@ Create a new scheduled scan.
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/schedules \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"Daily Scan","config_id":"/app/configs/prod.yaml","cron_expression":"0 2 * * *"}' \
|
||||
-d '{"name":"Daily Scan","config_id":1,"cron_expression":"0 2 * * *"}' \
|
||||
-b cookies.txt
|
||||
```
|
||||
|
||||
@@ -1270,7 +1271,7 @@ Update an existing schedule.
|
||||
"schedule": {
|
||||
"id": 1,
|
||||
"name": "Updated Schedule Name",
|
||||
"config_id": "/app/configs/prod-scan.yaml",
|
||||
"config_id": 1,
|
||||
"cron_expression": "0 3 * * *",
|
||||
"enabled": false,
|
||||
"updated_at": "2025-11-15T10:00:00Z"
|
||||
@@ -1512,7 +1513,7 @@ Get historical trend data for scans with the same configuration.
|
||||
],
|
||||
"labels": ["2025-11-10 12:00", "2025-11-15 12:00"],
|
||||
"port_counts": [25, 26],
|
||||
"config_id": "/app/configs/prod-scan.yaml"
|
||||
"config_id": 1
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1632,7 +1633,8 @@ Retrieve a specific setting by key.
|
||||
{
|
||||
"status": "success",
|
||||
"key": "smtp_server",
|
||||
"value": "smtp.gmail.com"
|
||||
"value": "smtp.gmail.com",
|
||||
"read_only": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2342,6 +2344,9 @@ List all configured webhooks with pagination.
|
||||
"severity_filter": ["critical", "warning"],
|
||||
"timeout": 10,
|
||||
"retry_count": 3,
|
||||
"template": null,
|
||||
"template_format": "json",
|
||||
"content_type_override": null,
|
||||
"created_at": "2025-11-18T10:00:00Z",
|
||||
"updated_at": "2025-11-18T10:00:00Z"
|
||||
}
|
||||
@@ -2393,6 +2398,9 @@ Get details for a specific webhook.
|
||||
"severity_filter": ["critical"],
|
||||
"timeout": 10,
|
||||
"retry_count": 3,
|
||||
"template": null,
|
||||
"template_format": "json",
|
||||
"content_type_override": null,
|
||||
"created_at": "2025-11-18T10:00:00Z",
|
||||
"updated_at": "2025-11-18T10:00:00Z"
|
||||
}
|
||||
@@ -2475,6 +2483,9 @@ Create a new webhook configuration.
|
||||
"custom_headers": null,
|
||||
"timeout": 10,
|
||||
"retry_count": 3,
|
||||
"template": null,
|
||||
"template_format": "json",
|
||||
"content_type_override": null,
|
||||
"created_at": "2025-11-18T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -2577,6 +2588,9 @@ Update an existing webhook configuration.
|
||||
"custom_headers": null,
|
||||
"timeout": 15,
|
||||
"retry_count": 3,
|
||||
"template": null,
|
||||
"template_format": "json",
|
||||
"content_type_override": null,
|
||||
"updated_at": "2025-11-18T11:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -3310,9 +3324,9 @@ API versioning will be implemented in future releases. The API is considered sta
|
||||
- **Webhooks API** - Webhook management, delivery tracking, authentication support, retry logic
|
||||
|
||||
### Endpoint Count
|
||||
- Total endpoints: 80+
|
||||
- Authenticated endpoints: 75+
|
||||
- Public endpoints: 5 (login, setup, health checks)
|
||||
- Total endpoints: 65+
|
||||
- Authenticated endpoints: 60+
|
||||
- Public endpoints: 5 (login, setup, health checks for scans/schedules/settings/alerts/webhooks)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user