383 lines
15 KiB
HTML
383 lines
15 KiB
HTML
{{ define "webtemplates_content" }}
|
|
<div class="space-y-6">
|
|
<!-- Header -->
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-100">Web Templates</h1>
|
|
<p class="text-gray-400 mt-1">Manage HTML templates for web honeypot services</p>
|
|
</div>
|
|
<button id="btn-new-template" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium">
|
|
New Template
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Template List -->
|
|
<div class="bg-gray-800 rounded-lg border border-gray-700">
|
|
<div class="p-6">
|
|
<h2 class="text-lg font-semibold text-gray-100 mb-4">Available Templates</h2>
|
|
<div id="template-list" class="space-y-3">
|
|
<!-- Templates will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Template Editor Modal -->
|
|
<div id="template-editor-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50">
|
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
<div class="bg-gray-800 rounded-lg border border-gray-700 w-full max-w-4xl max-h-[90vh] flex flex-col">
|
|
<div class="p-6 border-b border-gray-700">
|
|
<div class="flex justify-between items-center">
|
|
<h3 id="editor-title" class="text-lg font-semibold text-gray-100">Edit Template</h3>
|
|
<button id="btn-close-editor" class="text-gray-400 hover:text-gray-200">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 p-6 overflow-hidden">
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 h-full">
|
|
<!-- Editor -->
|
|
<div class="flex flex-col">
|
|
<div class="flex justify-between items-center mb-3">
|
|
<label class="text-sm font-medium text-gray-300">Template Name</label>
|
|
<button id="btn-validate" class="text-sm bg-green-600 hover:bg-green-700 text-white px-3 py-1 rounded">
|
|
Validate
|
|
</button>
|
|
</div>
|
|
<input id="template-name" type="text" placeholder="template-name.html"
|
|
class="mb-3 bg-gray-900 border border-gray-600 rounded px-3 py-2 text-gray-100 text-sm">
|
|
|
|
<label class="text-sm font-medium text-gray-300 mb-2">HTML Content</label>
|
|
<textarea id="template-content"
|
|
class="flex-1 bg-gray-900 border border-gray-600 rounded px-3 py-2 text-gray-100 font-mono text-sm resize-none min-h-96"
|
|
placeholder="Enter your HTML template here..."
|
|
spellcheck="false"
|
|
style="tab-size: 2; white-space: pre; overflow-wrap: normal; overflow-x: auto;"></textarea>
|
|
|
|
<div id="validation-result" class="mt-2 text-sm hidden"></div>
|
|
</div>
|
|
|
|
<!-- Preview -->
|
|
<div class="flex flex-col">
|
|
<label class="text-sm font-medium text-gray-300 mb-2">Preview</label>
|
|
<div class="flex-1 border border-gray-600 rounded overflow-hidden">
|
|
<iframe id="template-preview" class="w-full h-full bg-white"></iframe>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6 border-t border-gray-700 flex justify-between">
|
|
<div class="space-x-2">
|
|
<button id="btn-preview" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded">
|
|
Update Preview
|
|
</button>
|
|
<button id="btn-create-default" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded">
|
|
Load Default Template
|
|
</button>
|
|
</div>
|
|
<div class="space-x-2">
|
|
<button id="btn-cancel-edit" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded">
|
|
Cancel
|
|
</button>
|
|
<button id="btn-save-template" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">
|
|
Save Template
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Messages -->
|
|
<div id="status-message" class="hidden fixed top-4 right-4 px-4 py-2 rounded-lg text-white z-50"></div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentTemplate = null;
|
|
let templates = [];
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadTemplates();
|
|
setupEventListeners();
|
|
});
|
|
|
|
function setupEventListeners() {
|
|
document.getElementById('btn-new-template').addEventListener('click', () => openEditor());
|
|
document.getElementById('btn-close-editor').addEventListener('click', closeEditor);
|
|
document.getElementById('btn-cancel-edit').addEventListener('click', closeEditor);
|
|
document.getElementById('btn-save-template').addEventListener('click', saveTemplate);
|
|
document.getElementById('btn-validate').addEventListener('click', validateTemplate);
|
|
document.getElementById('btn-preview').addEventListener('click', updatePreview);
|
|
document.getElementById('btn-create-default').addEventListener('click', loadDefaultTemplate);
|
|
}
|
|
|
|
async function loadTemplates() {
|
|
try {
|
|
const response = await fetch('/api/webtemplates');
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
templates = data.templates || [];
|
|
renderTemplateList();
|
|
} else {
|
|
showStatus('Failed to load templates: ' + data.error, 'error');
|
|
}
|
|
} catch (error) {
|
|
showStatus('Error loading templates: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
function renderTemplateList() {
|
|
const container = document.getElementById('template-list');
|
|
|
|
if (templates.length === 0) {
|
|
container.innerHTML = '<p class="text-gray-400 text-center py-8">No templates found. Create your first template!</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = templates.map(template => `
|
|
<div class="flex items-center justify-between p-4 bg-gray-900 rounded-lg border border-gray-600">
|
|
<div class="flex items-center space-x-3">
|
|
<svg class="w-5 h-5 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
</svg>
|
|
<span class="text-gray-100 font-medium">${template}</span>
|
|
</div>
|
|
<div class="space-x-2">
|
|
<button onclick="editTemplate('${template}')" class="text-blue-400 hover:text-blue-300 text-sm">
|
|
Edit
|
|
</button>
|
|
<button onclick="deleteTemplate('${template}')" class="text-red-400 hover:text-red-300 text-sm">
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function openEditor(templateName = null) {
|
|
currentTemplate = templateName;
|
|
|
|
if (templateName) {
|
|
document.getElementById('editor-title').textContent = 'Edit Template';
|
|
document.getElementById('template-name').value = templateName;
|
|
document.getElementById('template-name').disabled = true;
|
|
loadTemplateContent(templateName);
|
|
} else {
|
|
document.getElementById('editor-title').textContent = 'New Template';
|
|
document.getElementById('template-name').value = '';
|
|
document.getElementById('template-name').disabled = false;
|
|
document.getElementById('template-content').value = '';
|
|
}
|
|
|
|
document.getElementById('template-editor-modal').classList.remove('hidden');
|
|
}
|
|
|
|
function closeEditor() {
|
|
document.getElementById('template-editor-modal').classList.add('hidden');
|
|
currentTemplate = null;
|
|
}
|
|
|
|
async function loadTemplateContent(templateName) {
|
|
try {
|
|
const response = await fetch(`/api/webtemplates/${templateName}`);
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
document.getElementById('template-content').value = data.content;
|
|
updatePreview();
|
|
} else {
|
|
showStatus('Failed to load template: ' + data.error, 'error');
|
|
}
|
|
} catch (error) {
|
|
showStatus('Error loading template: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function saveTemplate() {
|
|
let name = document.getElementById('template-name').value.trim();
|
|
const content = document.getElementById('template-content').value;
|
|
|
|
if (!name) {
|
|
showStatus('Template name is required', 'error');
|
|
return;
|
|
}
|
|
|
|
// Automatically add .html extension if not present
|
|
if (!name.endsWith('.html')) {
|
|
name = name + '.html';
|
|
document.getElementById('template-name').value = name;
|
|
}
|
|
|
|
try {
|
|
const url = currentTemplate ? `/api/webtemplates/${currentTemplate}` : '/api/webtemplates';
|
|
const method = currentTemplate ? 'PUT' : 'POST';
|
|
|
|
const headers = { 'Content-Type': 'application/json' };
|
|
const csrfToken = '{{ .CSRFToken }}';
|
|
if (csrfToken) {
|
|
headers['X-CSRF-Token'] = csrfToken;
|
|
}
|
|
|
|
const body = currentTemplate ?
|
|
JSON.stringify({ content: content }) :
|
|
JSON.stringify({ name: name, content: content });
|
|
|
|
const response = await fetch(url, {
|
|
method: method,
|
|
headers: headers,
|
|
body: body
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
showStatus('Template saved successfully', 'success');
|
|
closeEditor();
|
|
loadTemplates();
|
|
} else {
|
|
showStatus('Failed to save template: ' + data.error, 'error');
|
|
}
|
|
} catch (error) {
|
|
showStatus('Error saving template: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function validateTemplate() {
|
|
const content = document.getElementById('template-content').value;
|
|
|
|
if (!content.trim()) {
|
|
showValidationResult(false, 'Template content is empty');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const headers = { 'Content-Type': 'application/json' };
|
|
const csrfToken = '{{ .CSRFToken }}';
|
|
if (csrfToken) {
|
|
headers['X-CSRF-Token'] = csrfToken;
|
|
}
|
|
|
|
const response = await fetch('/api/webtemplates/validate', {
|
|
method: 'POST',
|
|
headers: headers,
|
|
body: JSON.stringify({ content: content })
|
|
});
|
|
|
|
const data = await response.json();
|
|
showValidationResult(data.valid, data.error || 'Template is valid');
|
|
} catch (error) {
|
|
showValidationResult(false, 'Error validating template: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function showValidationResult(isValid, message) {
|
|
const resultDiv = document.getElementById('validation-result');
|
|
resultDiv.className = `mt-2 text-sm ${isValid ? 'text-green-400' : 'text-red-400'}`;
|
|
resultDiv.textContent = message;
|
|
resultDiv.classList.remove('hidden');
|
|
}
|
|
|
|
function updatePreview() {
|
|
const content = document.getElementById('template-content').value;
|
|
const preview = document.getElementById('template-preview');
|
|
|
|
// Create a blob URL for the preview
|
|
const blob = new Blob([content], { type: 'text/html' });
|
|
const url = URL.createObjectURL(blob);
|
|
preview.src = url;
|
|
}
|
|
|
|
function loadDefaultTemplate() {
|
|
const defaultTemplate = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Login - {{ .ServiceName }}</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; background: #f5f5f5; margin: 0; padding: 20px; }
|
|
.login-container { max-width: 400px; margin: 50px auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
|
.form-group { margin-bottom: 20px; }
|
|
label { display: block; margin-bottom: 5px; font-weight: bold; }
|
|
input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
|
|
button { width: 100%; padding: 12px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
|
|
button:hover { background: #0056b3; }
|
|
.error { color: red; margin-top: 10px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="login-container">
|
|
<h2>{{ .ServiceName }}</h2>
|
|
<form method="POST" action="{{ .LoginPath }}">
|
|
<div class="form-group">
|
|
<label for="username">Username</label>
|
|
<input type="text" id="username" name="username" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="password">Password</label>
|
|
<input type="password" id="password" name="password" required>
|
|
</div>
|
|
<button type="submit">Login</button>
|
|
</form>
|
|
</div>
|
|
</body>
|
|
</html>`;
|
|
|
|
document.getElementById('template-content').value = defaultTemplate;
|
|
updatePreview();
|
|
}
|
|
|
|
async function editTemplate(templateName) {
|
|
openEditor(templateName);
|
|
}
|
|
|
|
async function deleteTemplate(templateName) {
|
|
if (!confirm(`Are you sure you want to delete the template "${templateName}"?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const headers = {};
|
|
const csrfToken = '{{ .CSRFToken }}';
|
|
if (csrfToken) {
|
|
headers['X-CSRF-Token'] = csrfToken;
|
|
}
|
|
|
|
const response = await fetch(`/api/webtemplates/${templateName}`, {
|
|
method: 'DELETE',
|
|
headers: headers
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
showStatus('Template deleted successfully', 'success');
|
|
loadTemplates();
|
|
} else {
|
|
showStatus('Failed to delete template: ' + data.error, 'error');
|
|
}
|
|
} catch (error) {
|
|
showStatus('Error deleting template: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
function showStatus(message, type) {
|
|
const statusDiv = document.getElementById('status-message');
|
|
statusDiv.className = `fixed top-4 right-4 px-4 py-2 rounded-lg text-white z-50 ${
|
|
type === 'success' ? 'bg-green-600' : 'bg-red-600'
|
|
}`;
|
|
statusDiv.textContent = message;
|
|
statusDiv.classList.remove('hidden');
|
|
|
|
setTimeout(() => {
|
|
statusDiv.classList.add('hidden');
|
|
}, 5000);
|
|
}
|
|
</script>
|
|
{{ end }}
|