264 lines
8.0 KiB
HTML
264 lines
8.0 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Edit Config - SneakyScanner{% endblock %}
|
|
|
|
{% block extra_styles %}
|
|
<!-- CodeMirror CSS -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/dracula.min.css">
|
|
<style>
|
|
.config-editor-container {
|
|
background: #1e293b;
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
.editor-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.CodeMirror {
|
|
height: 600px;
|
|
border: 1px solid #334155;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
background: #0f172a;
|
|
}
|
|
|
|
.editor-actions {
|
|
margin-top: 1.5rem;
|
|
display: flex;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.validation-feedback {
|
|
margin-top: 1rem;
|
|
padding: 1rem;
|
|
border-radius: 4px;
|
|
display: none;
|
|
}
|
|
|
|
.validation-feedback.success {
|
|
background: #065f46;
|
|
border: 1px solid #10b981;
|
|
color: #d1fae5;
|
|
}
|
|
|
|
.validation-feedback.error {
|
|
background: #7f1d1d;
|
|
border: 1px solid #ef4444;
|
|
color: #fee2e2;
|
|
}
|
|
|
|
.back-link {
|
|
color: #94a3b8;
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.back-link:hover {
|
|
color: #cbd5e1;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-lg mt-4">
|
|
<a href="{{ url_for('main.configs') }}" class="back-link">
|
|
<i class="bi bi-arrow-left"></i> Back to Configs
|
|
</a>
|
|
|
|
<h2>Edit Configuration</h2>
|
|
<p class="text-muted">Edit the YAML configuration for <strong>{{ filename }}</strong></p>
|
|
|
|
<div class="config-editor-container">
|
|
<div class="editor-header">
|
|
<h5 class="mb-0">
|
|
<i class="bi bi-file-earmark-code"></i> YAML Editor
|
|
</h5>
|
|
<button type="button" class="btn btn-sm btn-outline-primary" onclick="validateConfig()">
|
|
<i class="bi bi-check-circle"></i> Validate
|
|
</button>
|
|
</div>
|
|
|
|
<textarea id="yaml-editor">{{ content }}</textarea>
|
|
|
|
<div class="validation-feedback" id="validation-feedback"></div>
|
|
|
|
<div class="editor-actions">
|
|
<button type="button" class="btn btn-primary" onclick="saveConfig()">
|
|
<i class="bi bi-save"></i> Save Changes
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" onclick="resetEditor()">
|
|
<i class="bi bi-arrow-counterclockwise"></i> Reset
|
|
</button>
|
|
<a href="{{ url_for('main.configs') }}" class="btn btn-outline-secondary">
|
|
<i class="bi bi-x-circle"></i> Cancel
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Modal -->
|
|
<div class="modal fade" id="successModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-success text-white">
|
|
<h5 class="modal-title">
|
|
<i class="bi bi-check-circle-fill"></i> Success
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
Configuration updated successfully!
|
|
</div>
|
|
<div class="modal-footer">
|
|
<a href="{{ url_for('main.configs') }}" class="btn btn-success">
|
|
Back to Configs
|
|
</a>
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
Continue Editing
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<!-- CodeMirror JS -->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/yaml/yaml.min.js"></script>
|
|
|
|
<script>
|
|
// Initialize CodeMirror editor
|
|
const editor = CodeMirror.fromTextArea(document.getElementById('yaml-editor'), {
|
|
mode: 'yaml',
|
|
theme: 'dracula',
|
|
lineNumbers: true,
|
|
lineWrapping: true,
|
|
indentUnit: 2,
|
|
tabSize: 2,
|
|
indentWithTabs: false,
|
|
extraKeys: {
|
|
"Tab": function(cm) {
|
|
cm.replaceSelection(" ", "end");
|
|
}
|
|
}
|
|
});
|
|
|
|
// Store original content for reset
|
|
const originalContent = editor.getValue();
|
|
|
|
// Validation function
|
|
async function validateConfig() {
|
|
const feedback = document.getElementById('validation-feedback');
|
|
const content = editor.getValue();
|
|
|
|
try {
|
|
// Basic YAML syntax check (client-side)
|
|
// Just check for common YAML issues
|
|
if (content.trim() === '') {
|
|
showFeedback('error', 'Configuration cannot be empty');
|
|
return false;
|
|
}
|
|
|
|
// Check for basic structure
|
|
if (!content.includes('title:')) {
|
|
showFeedback('error', 'Missing required field: title');
|
|
return false;
|
|
}
|
|
|
|
if (!content.includes('sites:')) {
|
|
showFeedback('error', 'Missing required field: sites');
|
|
return false;
|
|
}
|
|
|
|
showFeedback('success', 'Configuration appears valid. Click "Save Changes" to save.');
|
|
return true;
|
|
} catch (error) {
|
|
showFeedback('error', 'Validation error: ' + error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Save configuration
|
|
async function saveConfig() {
|
|
const content = editor.getValue();
|
|
const filename = '{{ filename }}';
|
|
|
|
// Show loading state
|
|
const saveBtn = event.target;
|
|
const originalText = saveBtn.innerHTML;
|
|
saveBtn.disabled = true;
|
|
saveBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Saving...';
|
|
|
|
try {
|
|
const response = await fetch(`/api/configs/${filename}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ content: content })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
// Show success modal
|
|
const modal = new bootstrap.Modal(document.getElementById('successModal'));
|
|
modal.show();
|
|
} else {
|
|
// Show error feedback
|
|
showFeedback('error', data.message || 'Failed to save configuration');
|
|
}
|
|
} catch (error) {
|
|
showFeedback('error', 'Network error: ' + error.message);
|
|
} finally {
|
|
// Restore button state
|
|
saveBtn.disabled = false;
|
|
saveBtn.innerHTML = originalText;
|
|
}
|
|
}
|
|
|
|
// Reset editor to original content
|
|
function resetEditor() {
|
|
if (confirm('Are you sure you want to reset all changes?')) {
|
|
editor.setValue(originalContent);
|
|
hideFeedback();
|
|
}
|
|
}
|
|
|
|
// Show validation feedback
|
|
function showFeedback(type, message) {
|
|
const feedback = document.getElementById('validation-feedback');
|
|
feedback.className = `validation-feedback ${type}`;
|
|
feedback.innerHTML = `
|
|
<i class="bi bi-${type === 'success' ? 'check-circle-fill' : 'exclamation-triangle-fill'}"></i>
|
|
${message}
|
|
`;
|
|
feedback.style.display = 'block';
|
|
}
|
|
|
|
// Hide validation feedback
|
|
function hideFeedback() {
|
|
const feedback = document.getElementById('validation-feedback');
|
|
feedback.style.display = 'none';
|
|
}
|
|
|
|
// Auto-validate on content change (debounced)
|
|
let validationTimeout;
|
|
editor.on('change', function() {
|
|
clearTimeout(validationTimeout);
|
|
hideFeedback();
|
|
});
|
|
</script>
|
|
{% endblock %}
|