added webhooks, moved app name and verison to simple config file
This commit is contained in:
369
app/web/templates/webhooks/form.html
Normal file
369
app/web/templates/webhooks/form.html
Normal file
@@ -0,0 +1,369 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ 'Edit' if mode == 'edit' else 'New' }} Webhook - SneakyScanner{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12 mb-4">
|
||||
<h1 style="color: #60a5fa;">{{ 'Edit' if mode == 'edit' else 'Create' }} Webhook</h1>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ url_for('webhooks.list_webhooks') }}">Webhooks</a></li>
|
||||
<li class="breadcrumb-item active">{{ 'Edit' if mode == 'edit' else 'New' }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form id="webhook-form">
|
||||
<!-- Basic Information -->
|
||||
<h5 class="card-title mb-3">Basic Information</h5>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Webhook Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="name" name="name" required
|
||||
placeholder="e.g., Slack Notifications">
|
||||
<div class="form-text">A descriptive name for this webhook</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="url" class="form-label">Webhook URL <span class="text-danger">*</span></label>
|
||||
<input type="url" class="form-control" id="url" name="url" required
|
||||
placeholder="https://hooks.example.com/webhook">
|
||||
<div class="form-text">The endpoint where alerts will be sent</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="enabled" name="enabled" checked>
|
||||
<label class="form-check-label" for="enabled">Enabled</label>
|
||||
</div>
|
||||
<div class="form-text">Disabled webhooks will not receive notifications</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<!-- Authentication -->
|
||||
<h5 class="card-title mb-3">Authentication</h5>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="auth_type" class="form-label">Authentication Type</label>
|
||||
<select class="form-select" id="auth_type" name="auth_type">
|
||||
<option value="none">None</option>
|
||||
<option value="bearer">Bearer Token</option>
|
||||
<option value="basic">Basic Auth (username:password)</option>
|
||||
<option value="custom">Custom Headers</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="auth_token_field" style="display: none;">
|
||||
<label for="auth_token" class="form-label">Authentication Token</label>
|
||||
<input type="password" class="form-control" id="auth_token" name="auth_token"
|
||||
placeholder="Enter token or username:password">
|
||||
<div class="form-text" id="auth_token_help">Will be encrypted when stored</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="custom_headers_field" style="display: none;">
|
||||
<label for="custom_headers" class="form-label">Custom Headers (JSON)</label>
|
||||
<textarea class="form-control font-monospace" id="custom_headers" name="custom_headers" rows="4"
|
||||
placeholder='{"X-API-Key": "your-key", "X-Custom-Header": "value"}'></textarea>
|
||||
<div class="form-text">JSON object with custom HTTP headers</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<!-- Filters -->
|
||||
<h5 class="card-title mb-3">Alert Filters</h5>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Alert Types</label>
|
||||
<div class="form-text mb-2">Select which alert types trigger this webhook (leave all unchecked for all types)</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input alert-type-check" type="checkbox" value="unexpected_port" id="type_unexpected">
|
||||
<label class="form-check-label" for="type_unexpected">Unexpected Port</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input alert-type-check" type="checkbox" value="drift_detection" id="type_drift">
|
||||
<label class="form-check-label" for="type_drift">Drift Detection</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input alert-type-check" type="checkbox" value="cert_expiry" id="type_cert">
|
||||
<label class="form-check-label" for="type_cert">Certificate Expiry</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input alert-type-check" type="checkbox" value="weak_tls" id="type_tls">
|
||||
<label class="form-check-label" for="type_tls">Weak TLS</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input alert-type-check" type="checkbox" value="ping_failed" id="type_ping">
|
||||
<label class="form-check-label" for="type_ping">Ping Failed</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Severity Filter</label>
|
||||
<div class="form-text mb-2">Select which severities trigger this webhook (leave all unchecked for all severities)</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input severity-check" type="checkbox" value="critical" id="severity_critical">
|
||||
<label class="form-check-label" for="severity_critical">Critical</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input severity-check" type="checkbox" value="warning" id="severity_warning">
|
||||
<label class="form-check-label" for="severity_warning">Warning</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input severity-check" type="checkbox" value="info" id="severity_info">
|
||||
<label class="form-check-label" for="severity_info">Info</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<!-- Advanced Settings -->
|
||||
<h5 class="card-title mb-3">Advanced Settings</h5>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="timeout" class="form-label">Timeout (seconds)</label>
|
||||
<input type="number" class="form-control" id="timeout" name="timeout" min="1" max="60" value="10">
|
||||
<div class="form-text">Maximum time to wait for response</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="retry_count" class="form-label">Retry Count</label>
|
||||
<input type="number" class="form-control" id="retry_count" name="retry_count" min="0" max="5" value="3">
|
||||
<div class="form-text">Number of retry attempts on failure</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<!-- Submit Buttons -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{{ url_for('webhooks.list_webhooks') }}" class="btn btn-secondary">Cancel</a>
|
||||
<div>
|
||||
<button type="button" class="btn btn-outline-primary me-2" id="test-btn">
|
||||
<i class="bi bi-send"></i> Test Webhook
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-circle"></i> Save Webhook
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bi bi-info-circle"></i> Help</h5>
|
||||
|
||||
<h6 class="mt-3">Payload Format</h6>
|
||||
<p class="small text-muted">Webhooks receive JSON payloads with alert details:</p>
|
||||
<pre class="small bg-dark text-light p-2 rounded"><code>{
|
||||
"event": "alert.created",
|
||||
"alert": {
|
||||
"id": 123,
|
||||
"type": "cert_expiry",
|
||||
"severity": "warning",
|
||||
"message": "...",
|
||||
"ip_address": "192.168.1.10",
|
||||
"port": 443
|
||||
},
|
||||
"scan": {...},
|
||||
"rule": {...}
|
||||
}</code></pre>
|
||||
|
||||
<h6 class="mt-3">Authentication Types</h6>
|
||||
<ul class="small">
|
||||
<li><strong>None:</strong> No authentication</li>
|
||||
<li><strong>Bearer:</strong> Add Authorization header with token</li>
|
||||
<li><strong>Basic:</strong> Use username:password format</li>
|
||||
<li><strong>Custom:</strong> Define custom HTTP headers</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="mt-3">Retry Logic</h6>
|
||||
<p class="small text-muted">Failed webhooks are retried with exponential backoff (2^attempt seconds, max 60s).</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const webhookId = {{ webhook.id if webhook else 'null' }};
|
||||
const mode = '{{ mode }}';
|
||||
|
||||
// Show/hide auth fields based on type
|
||||
document.getElementById('auth_type').addEventListener('change', function() {
|
||||
const authType = this.value;
|
||||
const tokenField = document.getElementById('auth_token_field');
|
||||
const headersField = document.getElementById('custom_headers_field');
|
||||
const tokenHelp = document.getElementById('auth_token_help');
|
||||
|
||||
tokenField.style.display = 'none';
|
||||
headersField.style.display = 'none';
|
||||
|
||||
if (authType === 'bearer') {
|
||||
tokenField.style.display = 'block';
|
||||
document.getElementById('auth_token').placeholder = 'Enter bearer token';
|
||||
tokenHelp.textContent = 'Bearer token for Authorization header (encrypted when stored)';
|
||||
} else if (authType === 'basic') {
|
||||
tokenField.style.display = 'block';
|
||||
document.getElementById('auth_token').placeholder = 'username:password';
|
||||
tokenHelp.textContent = 'Format: username:password (encrypted when stored)';
|
||||
} else if (authType === 'custom') {
|
||||
headersField.style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
||||
// Load existing webhook data if editing
|
||||
if (mode === 'edit' && webhookId) {
|
||||
loadWebhookData(webhookId);
|
||||
}
|
||||
|
||||
async function loadWebhookData(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/webhooks/${id}`);
|
||||
const data = await response.json();
|
||||
const webhook = data.webhook;
|
||||
|
||||
// Populate form fields
|
||||
document.getElementById('name').value = webhook.name || '';
|
||||
document.getElementById('url').value = webhook.url || '';
|
||||
document.getElementById('enabled').checked = webhook.enabled;
|
||||
document.getElementById('auth_type').value = webhook.auth_type || 'none';
|
||||
document.getElementById('timeout').value = webhook.timeout || 10;
|
||||
document.getElementById('retry_count').value = webhook.retry_count || 3;
|
||||
|
||||
// Trigger auth type change to show relevant fields
|
||||
document.getElementById('auth_type').dispatchEvent(new Event('change'));
|
||||
|
||||
// Don't populate auth_token (it's encrypted)
|
||||
if (webhook.custom_headers) {
|
||||
document.getElementById('custom_headers').value = JSON.stringify(webhook.custom_headers, null, 2);
|
||||
}
|
||||
|
||||
// Check alert types
|
||||
if (webhook.alert_types && webhook.alert_types.length > 0) {
|
||||
webhook.alert_types.forEach(type => {
|
||||
const checkbox = document.querySelector(`.alert-type-check[value="${type}"]`);
|
||||
if (checkbox) checkbox.checked = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Check severities
|
||||
if (webhook.severity_filter && webhook.severity_filter.length > 0) {
|
||||
webhook.severity_filter.forEach(sev => {
|
||||
const checkbox = document.querySelector(`.severity-check[value="${sev}"]`);
|
||||
if (checkbox) checkbox.checked = true;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading webhook:', error);
|
||||
alert('Failed to load webhook data');
|
||||
}
|
||||
}
|
||||
|
||||
// Form submission
|
||||
document.getElementById('webhook-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = {
|
||||
name: document.getElementById('name').value,
|
||||
url: document.getElementById('url').value,
|
||||
enabled: document.getElementById('enabled').checked,
|
||||
auth_type: document.getElementById('auth_type').value,
|
||||
timeout: parseInt(document.getElementById('timeout').value),
|
||||
retry_count: parseInt(document.getElementById('retry_count').value)
|
||||
};
|
||||
|
||||
// Add auth token if provided
|
||||
const authToken = document.getElementById('auth_token').value;
|
||||
if (authToken) {
|
||||
formData.auth_token = authToken;
|
||||
}
|
||||
|
||||
// Add custom headers if provided
|
||||
const customHeaders = document.getElementById('custom_headers').value;
|
||||
if (customHeaders.trim()) {
|
||||
try {
|
||||
formData.custom_headers = JSON.parse(customHeaders);
|
||||
} catch (e) {
|
||||
alert('Invalid JSON in custom headers');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect selected alert types
|
||||
const alertTypes = Array.from(document.querySelectorAll('.alert-type-check:checked'))
|
||||
.map(cb => cb.value);
|
||||
if (alertTypes.length > 0) {
|
||||
formData.alert_types = alertTypes;
|
||||
}
|
||||
|
||||
// Collect selected severities
|
||||
const severities = Array.from(document.querySelectorAll('.severity-check:checked'))
|
||||
.map(cb => cb.value);
|
||||
if (severities.length > 0) {
|
||||
formData.severity_filter = severities;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = mode === 'edit' ? `/api/webhooks/${webhookId}` : '/api/webhooks';
|
||||
const method = mode === 'edit' ? 'PUT' : 'POST';
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
alert('Webhook saved successfully!');
|
||||
window.location.href = '{{ url_for("webhooks.list_webhooks") }}';
|
||||
} else {
|
||||
alert(`Failed to save webhook: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving webhook:', error);
|
||||
alert('Failed to save webhook');
|
||||
}
|
||||
});
|
||||
|
||||
// Test webhook button
|
||||
document.getElementById('test-btn').addEventListener('click', async function() {
|
||||
if (mode !== 'edit' || !webhookId) {
|
||||
alert('Please save the webhook first before testing');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Send a test payload to this webhook?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/webhooks/${webhookId}/test`, { method: 'POST' });
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
alert(`Test successful!\nHTTP ${result.status_code}\n${result.message}`);
|
||||
} else {
|
||||
alert(`Test failed:\n${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error testing webhook:', error);
|
||||
alert('Failed to test webhook');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user