416 lines
17 KiB
HTML
416 lines
17 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Create Configuration - SneakyScanner{% endblock %}
|
|
|
|
{% block extra_styles %}
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/config-manager.css') }}">
|
|
<style>
|
|
.file-info {
|
|
background-color: #1e293b;
|
|
border: 1px solid #334155;
|
|
padding: 10px 15px;
|
|
border-radius: 5px;
|
|
margin-top: 15px;
|
|
display: none;
|
|
}
|
|
|
|
.file-info-name {
|
|
color: #60a5fa;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.file-info-size {
|
|
color: #94a3b8;
|
|
font-size: 0.9em;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1 style="color: #60a5fa;">Create New Configuration</h1>
|
|
<a href="{{ url_for('main.configs') }}" class="btn btn-secondary">
|
|
<i class="bi bi-arrow-left"></i> Back to Configs
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Upload Tabs -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<ul class="nav nav-tabs mb-4" id="uploadTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="cidr-tab" data-bs-toggle="tab" data-bs-target="#cidr"
|
|
type="button" role="tab" style="color: #60a5fa;">
|
|
<i class="bi bi-diagram-3"></i> Create from CIDR
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="yaml-tab" data-bs-toggle="tab" data-bs-target="#yaml"
|
|
type="button" role="tab" style="color: #60a5fa;">
|
|
<i class="bi bi-filetype-yml"></i> Upload YAML
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content" id="uploadTabsContent">
|
|
<!-- CIDR Form Tab -->
|
|
<div class="tab-pane fade show active" id="cidr" role="tabpanel">
|
|
<div class="row">
|
|
<div class="col-lg-8 offset-lg-2">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0" style="color: #60a5fa;">
|
|
<i class="bi bi-diagram-3"></i> Create Configuration from CIDR Range
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="text-muted">
|
|
<i class="bi bi-info-circle"></i>
|
|
Specify a CIDR range to automatically generate a configuration for all IPs in that range.
|
|
You can edit the configuration afterwards to add expected ports and services.
|
|
</p>
|
|
|
|
<form id="cidr-form">
|
|
<div class="mb-3">
|
|
<label for="config-title" class="form-label" style="color: #94a3b8;">
|
|
Config Title <span class="text-danger">*</span>
|
|
</label>
|
|
<input type="text" class="form-control" id="config-title"
|
|
placeholder="e.g., Production Infrastructure Scan" required>
|
|
<div class="form-text">A descriptive title for your scan configuration</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="cidr-range" class="form-label" style="color: #94a3b8;">
|
|
CIDR Range <span class="text-danger">*</span>
|
|
</label>
|
|
<input type="text" class="form-control" id="cidr-range"
|
|
placeholder="e.g., 10.0.0.0/24 or 192.168.1.0/28" required>
|
|
<div class="form-text">
|
|
Enter a CIDR range (e.g., 10.0.0.0/24 for 254 hosts).
|
|
Maximum 10,000 addresses per range.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="site-name" class="form-label" style="color: #94a3b8;">
|
|
Site Name (optional)
|
|
</label>
|
|
<input type="text" class="form-control" id="site-name"
|
|
placeholder="e.g., Production Servers">
|
|
<div class="form-text">
|
|
Logical grouping name for these IPs (default: "Site 1")
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="ping-default">
|
|
<label class="form-check-label" for="ping-default" style="color: #94a3b8;">
|
|
Expect ping response by default
|
|
</label>
|
|
</div>
|
|
<div class="form-text">
|
|
Sets the default expectation for ICMP ping responses from these IPs
|
|
</div>
|
|
</div>
|
|
|
|
<div id="cidr-errors" class="alert alert-danger" style="display:none;">
|
|
<strong>Error:</strong> <span id="cidr-error-message"></span>
|
|
</div>
|
|
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" class="btn btn-primary btn-lg">
|
|
<i class="bi bi-plus-circle"></i> Create Configuration
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div id="cidr-success" class="alert alert-success mt-3" style="display:none;">
|
|
<i class="bi bi-check-circle-fill"></i>
|
|
<strong>Success!</strong> Configuration created: <span id="cidr-created-filename"></span>
|
|
<div class="mt-2">
|
|
<a href="#" id="edit-new-config-link" class="btn btn-sm btn-outline-success">
|
|
<i class="bi bi-pencil"></i> Edit Now
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- YAML Upload Tab -->
|
|
<div class="tab-pane fade" id="yaml" role="tabpanel">
|
|
<div class="row">
|
|
<div class="col-lg-8 offset-lg-2">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0" style="color: #60a5fa;">
|
|
<i class="bi bi-cloud-upload"></i> Upload YAML Configuration
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="text-muted">
|
|
<i class="bi bi-info-circle"></i>
|
|
For advanced users: upload a YAML config file directly.
|
|
</p>
|
|
|
|
<div id="yaml-dropzone" class="dropzone">
|
|
<i class="bi bi-cloud-upload"></i>
|
|
<p>Drag & drop YAML file here or click to browse</p>
|
|
<input type="file" id="yaml-file-input" accept=".yaml,.yml" hidden>
|
|
</div>
|
|
|
|
<div id="yaml-file-info" class="file-info">
|
|
<div class="file-info-name" id="yaml-filename"></div>
|
|
<div class="file-info-size" id="yaml-filesize"></div>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<label for="yaml-custom-filename" class="form-label" style="color: #94a3b8;">
|
|
Custom Filename (optional):
|
|
</label>
|
|
<input type="text" id="yaml-custom-filename" class="form-control"
|
|
placeholder="Leave empty to use uploaded filename">
|
|
</div>
|
|
|
|
<button id="upload-yaml-btn" class="btn btn-primary mt-3" disabled>
|
|
<i class="bi bi-upload"></i> Upload YAML
|
|
</button>
|
|
|
|
<div id="yaml-errors" class="alert alert-danger mt-3" style="display:none;">
|
|
<strong>Error:</strong> <span id="yaml-error-message"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Modal -->
|
|
<div class="modal fade" id="successModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<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: #10b981;">
|
|
<i class="bi bi-check-circle"></i> Success
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p style="color: #e2e8f0;">Configuration saved successfully!</p>
|
|
<p style="color: #60a5fa; font-weight: bold;" id="success-filename"></p>
|
|
</div>
|
|
<div class="modal-footer" style="border-top: 1px solid #334155;">
|
|
<a href="{{ url_for('main.configs') }}" class="btn btn-primary">
|
|
<i class="bi bi-list"></i> View All Configs
|
|
</a>
|
|
<button type="button" class="btn btn-success" onclick="location.reload()">
|
|
<i class="bi bi-plus-circle"></i> Create Another
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
// Global variables
|
|
let yamlFile = null;
|
|
|
|
// ============== CIDR Form Submission ==============
|
|
|
|
document.getElementById('cidr-form').addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
|
|
const title = document.getElementById('config-title').value.trim();
|
|
const cidr = document.getElementById('cidr-range').value.trim();
|
|
const siteName = document.getElementById('site-name').value.trim();
|
|
const pingDefault = document.getElementById('ping-default').checked;
|
|
|
|
// Validate inputs
|
|
if (!title) {
|
|
showError('cidr', 'Config title is required');
|
|
return;
|
|
}
|
|
|
|
if (!cidr) {
|
|
showError('cidr', 'CIDR range is required');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const submitBtn = e.target.querySelector('button[type="submit"]');
|
|
const originalText = submitBtn.innerHTML;
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Creating...';
|
|
|
|
try {
|
|
const response = await fetch('/api/configs/create-from-cidr', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
title: title,
|
|
cidr: cidr,
|
|
site_name: siteName || null,
|
|
ping_default: pingDefault
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.message || `HTTP ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// Hide error, show success
|
|
document.getElementById('cidr-errors').style.display = 'none';
|
|
document.getElementById('cidr-created-filename').textContent = data.filename;
|
|
|
|
// Set edit link
|
|
document.getElementById('edit-new-config-link').href = `/configs/edit/${data.filename}`;
|
|
|
|
document.getElementById('cidr-success').style.display = 'block';
|
|
|
|
// Reset form
|
|
e.target.reset();
|
|
|
|
// Show success modal
|
|
showSuccess(data.filename);
|
|
|
|
} catch (error) {
|
|
console.error('Error creating config from CIDR:', error);
|
|
showError('cidr', error.message);
|
|
} finally {
|
|
// Restore button state
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = originalText;
|
|
}
|
|
});
|
|
|
|
// ============== YAML Upload ==============
|
|
|
|
// Setup YAML dropzone
|
|
const yamlDropzone = document.getElementById('yaml-dropzone');
|
|
const yamlFileInput = document.getElementById('yaml-file-input');
|
|
|
|
yamlDropzone.addEventListener('click', () => yamlFileInput.click());
|
|
|
|
yamlDropzone.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
yamlDropzone.classList.add('dragover');
|
|
});
|
|
|
|
yamlDropzone.addEventListener('dragleave', () => {
|
|
yamlDropzone.classList.remove('dragover');
|
|
});
|
|
|
|
yamlDropzone.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
yamlDropzone.classList.remove('dragover');
|
|
const file = e.dataTransfer.files[0];
|
|
handleYAMLFile(file);
|
|
});
|
|
|
|
yamlFileInput.addEventListener('change', (e) => {
|
|
const file = e.target.files[0];
|
|
handleYAMLFile(file);
|
|
});
|
|
|
|
// Handle YAML file selection
|
|
function handleYAMLFile(file) {
|
|
if (!file) return;
|
|
|
|
// Check file extension
|
|
if (!file.name.endsWith('.yaml') && !file.name.endsWith('.yml')) {
|
|
showError('yaml', 'Please select a YAML file (.yaml or .yml)');
|
|
return;
|
|
}
|
|
|
|
yamlFile = file;
|
|
|
|
// Show file info
|
|
document.getElementById('yaml-filename').textContent = file.name;
|
|
document.getElementById('yaml-filesize').textContent = formatFileSize(file.size);
|
|
document.getElementById('yaml-file-info').style.display = 'block';
|
|
|
|
// Enable upload button
|
|
document.getElementById('upload-yaml-btn').disabled = false;
|
|
document.getElementById('yaml-errors').style.display = 'none';
|
|
}
|
|
|
|
// Upload YAML file
|
|
async function uploadYAMLFile() {
|
|
if (!yamlFile) return;
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('file', yamlFile);
|
|
|
|
const customFilename = document.getElementById('yaml-custom-filename').value.trim();
|
|
if (customFilename) {
|
|
formData.append('filename', customFilename);
|
|
}
|
|
|
|
const response = await fetch('/api/configs/upload-yaml', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.message || `HTTP ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
showSuccess(data.filename);
|
|
|
|
} catch (error) {
|
|
console.error('Error uploading YAML:', error);
|
|
showError('yaml', error.message);
|
|
}
|
|
}
|
|
|
|
document.getElementById('upload-yaml-btn').addEventListener('click', uploadYAMLFile);
|
|
|
|
// ============== Helper Functions ==============
|
|
|
|
// Format file size
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
}
|
|
|
|
// Show error
|
|
function showError(type, message) {
|
|
const errorDiv = document.getElementById(`${type}-errors`);
|
|
const errorMsg = document.getElementById(`${type}-error-message`);
|
|
errorMsg.textContent = message;
|
|
errorDiv.style.display = 'block';
|
|
}
|
|
|
|
// Show success
|
|
function showSuccess(filename) {
|
|
document.getElementById('success-filename').textContent = filename;
|
|
new bootstrap.Modal(document.getElementById('successModal')).show();
|
|
}
|
|
</script>
|
|
{% endblock %}
|