Files
honeydany/app/templates/webtemplates.html
T
2025-09-28 21:28:39 +01:00

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 }}