Migrate from file-based configs to database with per-IP site configuration
Major architectural changes: - Replace YAML config files with database-stored ScanConfig model - Remove CIDR block support in favor of individual IP addresses per site - Each IP now has its own expected_ping, expected_tcp_ports, expected_udp_ports - AlertRule now uses config_id FK instead of config_file string API changes: - POST /api/scans now requires config_id instead of config_file - Alert rules API uses config_id with validation - All config dropdowns fetch from /api/configs dynamically Template updates: - scans.html, dashboard.html, alert_rules.html load configs via API - Display format: Config Title (X sites) in dropdowns - Removed Jinja2 config_files loops Migrations: - 008: Expand CIDRs to individual IPs with per-IP port configs - 009: Remove CIDR-related columns - 010: Add config_id to alert_rules, remove config_file
This commit is contained in:
@@ -113,34 +113,28 @@
|
||||
<div class="modal-body">
|
||||
<form id="trigger-scan-form">
|
||||
<div class="mb-3">
|
||||
<label for="config-file" class="form-label">Config File</label>
|
||||
<select class="form-select" id="config-file" name="config_file" required {% if not config_files %}disabled{% endif %}>
|
||||
<option value="">Select a config file...</option>
|
||||
{% for config in config_files %}
|
||||
<option value="{{ config }}">{{ config }}</option>
|
||||
{% endfor %}
|
||||
<label for="config-select" class="form-label">Scan Configuration</label>
|
||||
<select class="form-select" id="config-select" name="config_id" required>
|
||||
<option value="">Loading configurations...</option>
|
||||
</select>
|
||||
{% if config_files %}
|
||||
<div class="form-text text-muted">
|
||||
Select a scan configuration file
|
||||
<div class="form-text text-muted" id="config-help-text">
|
||||
Select a scan configuration
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning mt-2 mb-0" role="alert">
|
||||
<div id="no-configs-warning" class="alert alert-warning mt-2 mb-0" role="alert" style="display: none;">
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
<strong>No configurations available</strong>
|
||||
<p class="mb-2 mt-2">You need to create a configuration file before you can trigger a scan.</p>
|
||||
<a href="{{ url_for('main.upload_config') }}" class="btn btn-sm btn-primary">
|
||||
<p class="mb-2 mt-2">You need to create a configuration before you can trigger a scan.</p>
|
||||
<a href="{{ url_for('main.configs') }}" class="btn btn-sm btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Create Configuration
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="trigger-error" class="alert alert-danger" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer" style="border-top: 1px solid #334155;">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="triggerScan()" {% if not config_files %}disabled{% endif %}>
|
||||
<button type="button" class="btn btn-primary" id="trigger-scan-btn" onclick="triggerScan()">
|
||||
<span id="modal-trigger-text">Trigger Scan</span>
|
||||
<span id="modal-trigger-spinner" class="spinner-border spinner-border-sm ms-2" style="display: none;"></span>
|
||||
</button>
|
||||
@@ -359,23 +353,75 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Load available configs
|
||||
async function loadConfigs() {
|
||||
const selectEl = document.getElementById('config-select');
|
||||
const helpTextEl = document.getElementById('config-help-text');
|
||||
const noConfigsWarning = document.getElementById('no-configs-warning');
|
||||
const triggerBtn = document.getElementById('trigger-scan-btn');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/configs');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load configurations');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const configs = data.configs || [];
|
||||
|
||||
// Clear existing options
|
||||
selectEl.innerHTML = '';
|
||||
|
||||
if (configs.length === 0) {
|
||||
selectEl.innerHTML = '<option value="">No configurations available</option>';
|
||||
selectEl.disabled = true;
|
||||
triggerBtn.disabled = true;
|
||||
helpTextEl.style.display = 'none';
|
||||
noConfigsWarning.style.display = 'block';
|
||||
} else {
|
||||
selectEl.innerHTML = '<option value="">Select a configuration...</option>';
|
||||
configs.forEach(config => {
|
||||
const option = document.createElement('option');
|
||||
option.value = config.id;
|
||||
const siteText = config.site_count === 1 ? 'site' : 'sites';
|
||||
option.textContent = `${config.title} (${config.site_count} ${siteText})`;
|
||||
selectEl.appendChild(option);
|
||||
});
|
||||
selectEl.disabled = false;
|
||||
triggerBtn.disabled = false;
|
||||
helpTextEl.style.display = 'block';
|
||||
noConfigsWarning.style.display = 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading configs:', error);
|
||||
selectEl.innerHTML = '<option value="">Error loading configurations</option>';
|
||||
selectEl.disabled = true;
|
||||
triggerBtn.disabled = true;
|
||||
helpTextEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Show trigger scan modal
|
||||
function showTriggerScanModal() {
|
||||
const modal = new bootstrap.Modal(document.getElementById('triggerScanModal'));
|
||||
document.getElementById('trigger-error').style.display = 'none';
|
||||
document.getElementById('trigger-scan-form').reset();
|
||||
|
||||
// Load configs when modal is shown
|
||||
loadConfigs();
|
||||
|
||||
modal.show();
|
||||
}
|
||||
|
||||
// Trigger scan
|
||||
async function triggerScan() {
|
||||
const configFile = document.getElementById('config-file').value;
|
||||
const configId = document.getElementById('config-select').value;
|
||||
const errorEl = document.getElementById('trigger-error');
|
||||
const btnText = document.getElementById('modal-trigger-text');
|
||||
const btnSpinner = document.getElementById('modal-trigger-spinner');
|
||||
|
||||
if (!configFile) {
|
||||
errorEl.textContent = 'Please enter a config file path.';
|
||||
if (!configId) {
|
||||
errorEl.textContent = 'Please select a configuration.';
|
||||
errorEl.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
@@ -392,13 +438,13 @@
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
config_file: configFile
|
||||
config_id: parseInt(configId)
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.error || 'Failed to trigger scan');
|
||||
throw new Error(data.message || data.error || 'Failed to trigger scan');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
Reference in New Issue
Block a user