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

535 lines
24 KiB
HTML

{{ define "settings_title" }}Settings{{ end }}
{{ define "settings_content" }}
<h1 class="text-2xl font-semibold text-white mb-6">Settings</h1>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="bg-gray-800 border border-gray-700 rounded-lg p-6">
<h2 class="text-lg font-semibold text-white mb-4">Services</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
{{/* Toggle component */}}
{{ $svc := .Cfg.Services }}
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">HTTP</span>
<input id="svc-http" type="checkbox" class="h-5 w-5" {{ if $svc.HTTP }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">HTTPS</span>
<input id="svc-https" type="checkbox" class="h-5 w-5" {{ if $svc.HTTPS }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">SSH</span>
<input id="svc-ssh" type="checkbox" class="h-5 w-5" {{ if $svc.SSH }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">FTP</span>
<input id="svc-ftp" type="checkbox" class="h-5 w-5" {{ if $svc.FTP }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">SMTP</span>
<input id="svc-smtp" type="checkbox" class="h-5 w-5" {{ if $svc.SMTP }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">IMAP</span>
<input id="svc-imap" type="checkbox" class="h-5 w-5" {{ if $svc.IMAP }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">Telnet</span>
<input id="svc-telnet" type="checkbox" class="h-5 w-5" {{ if $svc.Telnet }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">MySQL</span>
<input id="svc-mysql" type="checkbox" class="h-5 w-5" {{ if $svc.MySQL }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">PostgreSQL</span>
<input id="svc-postgresql" type="checkbox" class="h-5 w-5" {{ if $svc.PostgreSQL }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">MongoDB</span>
<input id="svc-mongodb" type="checkbox" class="h-5 w-5" {{ if $svc.MongoDB }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">RDP</span>
<input id="svc-rdp" type="checkbox" class="h-5 w-5" {{ if $svc.RDP }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">SMB</span>
<input id="svc-smb" type="checkbox" class="h-5 w-5" {{ if $svc.SMB }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">SIP</span>
<input id="svc-sip" type="checkbox" class="h-5 w-5" {{ if $svc.SIP }}checked{{ end }}>
</label>
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
<span class="text-gray-200">VNC</span>
<input id="svc-vnc" type="checkbox" class="h-5 w-5" {{ if $svc.VNC }}checked{{ end }}>
</label>
</div>
</div>
<div class="bg-gray-800 border border-gray-700 rounded-lg p-6">
<h2 class="text-lg font-semibold text-white mb-4">Ports</h2>
{{ $p := .Cfg.Ports }}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<label class="block">
<span class="text-gray-300">HTTP</span>
<input id="port-http" type="number" value="{{ $p.HTTP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">HTTPS</span>
<input id="port-https" type="number" value="{{ $p.HTTPS }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">SSH</span>
<input id="port-ssh" type="number" value="{{ $p.SSH }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">FTP</span>
<input id="port-ftp" type="number" value="{{ $p.FTP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">SMTP</span>
<input id="port-smtp" type="number" value="{{ $p.SMTP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">IMAP</span>
<input id="port-imap" type="number" value="{{ $p.IMAP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">Telnet</span>
<input id="port-telnet" type="number" value="{{ $p.Telnet }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">MySQL</span>
<input id="port-mysql" type="number" value="{{ $p.MySQL }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">PostgreSQL</span>
<input id="port-postgresql" type="number" value="{{ $p.PostgreSQL }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">MongoDB</span>
<input id="port-mongodb" type="number" value="{{ $p.MongoDB }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">RDP</span>
<input id="port-rdp" type="number" value="{{ $p.RDP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">SMB</span>
<input id="port-smb" type="number" value="{{ $p.SMB }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">SIP</span>
<input id="port-sip" type="number" value="{{ $p.SIP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
<label class="block">
<span class="text-gray-300">VNC</span>
<input id="port-vnc" type="number" value="{{ $p.VNC }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
</label>
</div>
</div>
<!-- HTTP/HTTPS Templates Section -->
<div class="bg-gray-800 border border-gray-700 rounded-lg p-6">
<h2 class="text-lg font-semibold text-white mb-4">HTTP/HTTPS Templates</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<label class="block">
<span class="text-gray-300">HTTP Template</span>
<select id="http-template" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" data-current-value="{{ .Cfg.Web.HTTPTemplateName }}">
<option value="">None (Default Welcome)</option>
<!-- Templates will be loaded here -->
</select>
</label>
<label class="block">
<span class="text-gray-300">HTTPS Template</span>
<select id="https-template" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" data-current-value="{{ .Cfg.Web.HTTPSTemplateName }}">
<option value="">None (Default Welcome)</option>
<!-- Templates will be loaded here -->
</select>
</label>
</div>
</div>
</div>
<!-- Web Services Section -->
<div class="mt-6 bg-gray-800 border border-gray-700 rounded-lg p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-semibold text-white">Web Services</h2>
<button id="btn-add-webservice" class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded text-sm">
Add Service
</button>
</div>
<div id="webservices-list" class="space-y-4">
<!-- Web services will be loaded here -->
</div>
</div>
<!-- Web Templates Section -->
<div class="mt-6 bg-gray-800 border border-gray-700 rounded-lg p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-semibold text-white">Web Templates</h2>
<button id="btn-manage-templates" class="bg-green-600 hover:bg-green-700 text-white px-3 py-1 rounded text-sm">
Manage Templates
</button>
</div>
<div id="templates-list" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
<!-- Templates will be loaded here -->
</div>
</div>
<div class="mt-6 flex items-center gap-3">
<button id="btn-save" class="px-4 py-2 bg-primary-600 hover:bg-primary-500 rounded text-white">Save Settings</button>
<button id="btn-restart" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded text-white">Restart App</button>
<span id="save-status" class="text-sm text-gray-400"></span>
</div>
<script>
let currentCSRFToken = '{{ .CSRFToken }}';
async function saveSettings() {
const payload = {
services: {
http: document.getElementById('svc-http').checked,
https: document.getElementById('svc-https').checked,
ssh: document.getElementById('svc-ssh').checked,
ftp: document.getElementById('svc-ftp').checked,
smtp: document.getElementById('svc-smtp').checked,
imap: document.getElementById('svc-imap').checked,
telnet: document.getElementById('svc-telnet').checked,
mysql: document.getElementById('svc-mysql').checked,
postgresql: document.getElementById('svc-postgresql').checked,
mongodb: document.getElementById('svc-mongodb').checked,
rdp: document.getElementById('svc-rdp').checked,
smb: document.getElementById('svc-smb').checked,
sip: document.getElementById('svc-sip').checked,
vnc: document.getElementById('svc-vnc').checked,
},
ports: {
http: parseInt(document.getElementById('port-http').value, 10),
https: parseInt(document.getElementById('port-https').value, 10),
ssh: parseInt(document.getElementById('port-ssh').value, 10),
ftp: parseInt(document.getElementById('port-ftp').value, 10),
smtp: parseInt(document.getElementById('port-smtp').value, 10),
imap: parseInt(document.getElementById('port-imap').value, 10),
telnet: parseInt(document.getElementById('port-telnet').value, 10),
mysql: parseInt(document.getElementById('port-mysql').value, 10),
postgresql: parseInt(document.getElementById('port-postgresql').value, 10),
mongodb: parseInt(document.getElementById('port-mongodb').value, 10),
rdp: parseInt(document.getElementById('port-rdp').value, 10),
smb: parseInt(document.getElementById('port-smb').value, 10),
sip: parseInt(document.getElementById('port-sip').value, 10),
vnc: parseInt(document.getElementById('port-vnc').value, 10),
},
web: {
http_template_name: document.getElementById('http-template').value,
https_template_name: document.getElementById('https-template').value,
}
};
const headers = { 'Content-Type': 'application/json' };
if (currentCSRFToken) {
headers['X-CSRF-Token'] = currentCSRFToken;
}
const res = await fetch('/api/settings', { method: 'POST', headers: headers, body: JSON.stringify(payload) });
const out = await res.json().catch(() => ({}));
const el = document.getElementById('save-status');
if (res.ok) {
// Update CSRF token for subsequent requests
if (out.csrf_token) {
currentCSRFToken = out.csrf_token;
}
el.textContent = 'Saved. You may need to restart to apply port changes.';
el.className = 'text-sm text-green-400';
} else {
el.textContent = out.error || 'Save failed';
el.className = 'text-sm text-red-400';
}
}
async function restartApp() {
const headers = {};
if (currentCSRFToken) {
headers['X-CSRF-Token'] = currentCSRFToken;
}
const res = await fetch('/api/restart', { method: 'POST', headers: headers });
const out = await res.json().catch(() => ({}));
if (res.ok) {
// Update CSRF token for subsequent requests
if (out.csrf_token) {
currentCSRFToken = out.csrf_token;
}
document.getElementById('save-status').textContent = 'Restarting...';
setTimeout(() => location.reload(), 1200);
} else {
document.getElementById('save-status').textContent = 'Restart failed';
document.getElementById('save-status').className = 'text-sm text-red-400';
}
}
document.getElementById('btn-save').addEventListener('click', saveSettings);
document.getElementById('btn-restart').addEventListener('click', restartApp);
// Web Templates Management
document.getElementById('btn-manage-templates').addEventListener('click', () => {
openTemplateManager();
});
// Web Services Management
document.getElementById('btn-add-webservice').addEventListener('click', () => {
addWebService();
});
// Load initial data
loadWebServices();
loadTemplates();
async function loadWebServices() {
try {
console.log('Loading web services from server...');
const response = await fetch('/api/webservices');
const data = await response.json();
console.log('Loaded web services data:', data);
webServices = data.services || [];
console.log('webServices array after load:', webServices);
renderWebServices(webServices);
} catch (error) {
console.error('Failed to load web services:', error);
webServices = [];
}
}
async function loadTemplates() {
try {
const response = await fetch('/api/webtemplates');
const data = await response.json();
renderTemplates(data.templates || []);
} catch (error) {
console.error('Failed to load templates:', error);
}
}
function renderWebServices(services) {
const container = document.getElementById('webservices-list');
if (services.length === 0) {
container.innerHTML = '<p class="text-gray-400 text-center py-4">No web services configured</p>';
return;
}
// Update the global webServices array to keep it in sync
webServices = [...services];
container.innerHTML = services.map((service, index) => `
<div class="bg-gray-900 border border-gray-600 rounded p-4">
<div class="grid grid-cols-1 md:grid-cols-5 gap-4 items-center">
<div>
<label class="block text-sm text-gray-300 mb-1">Name</label>
<input type="text" value="${service.name}" class="w-full bg-gray-800 border border-gray-600 rounded px-2 py-1 text-gray-100 text-sm"
onchange="updateWebService(${index}, 'name', this.value)">
</div>
<div>
<label class="block text-sm text-gray-300 mb-1">Port</label>
<input type="number" value="${service.port}" class="w-full bg-gray-800 border border-gray-600 rounded px-2 py-1 text-gray-100 text-sm"
onchange="updateWebService(${index}, 'port', parseInt(this.value))">
</div>
<div>
<label class="block text-sm text-gray-300 mb-1">Path</label>
<input type="text" value="${service.path}" class="w-full bg-gray-800 border border-gray-600 rounded px-2 py-1 text-gray-100 text-sm"
onchange="updateWebService(${index}, 'path', this.value)">
</div>
<div>
<label class="block text-sm text-gray-300 mb-1">Template</label>
<select class="w-full bg-gray-800 border border-gray-600 rounded px-2 py-1 text-gray-100 text-sm"
onchange="updateWebService(${index}, 'template_name', this.value)">
<option value="">None (200 OK)</option>
${getTemplateOptions(service.template_name)}
</select>
</div>
<div class="flex items-center gap-2">
<label class="flex items-center">
<input type="checkbox" ${service.enabled ? 'checked' : ''} class="mr-1"
onchange="updateWebService(${index}, 'enabled', this.checked)">
<span class="text-sm text-gray-300">Enabled</span>
</label>
<button onclick="removeWebService(${index})" class="text-red-400 hover:text-red-300 text-sm">Remove</button>
</div>
</div>
</div>
`).join('');
}
function getTemplateOptions(selectedTemplate) {
if (!window.availableTemplates || window.availableTemplates.length === 0) {
return '<option value="" disabled>Loading templates...</option>';
}
return window.availableTemplates.map(t =>
`<option value="${t}" ${selectedTemplate === t ? 'selected' : ''}>${t}</option>`
).join('');
}
function renderTemplates(templates) {
window.availableTemplates = templates;
const container = document.getElementById('templates-list');
if (templates.length === 0) {
container.innerHTML = '<p class="text-gray-400 text-center py-4 col-span-full">No templates found</p>';
} else {
container.innerHTML = templates.map(template => `
<div class="bg-gray-900 border border-gray-600 rounded p-3">
<div class="flex items-center justify-between">
<span class="text-gray-100 text-sm font-medium">${template}</span>
<button onclick="editTemplate('${template}')" class="text-blue-400 hover:text-blue-300 text-xs">Edit</button>
</div>
</div>
`).join('');
}
// Update HTTP/HTTPS template dropdowns
updateTemplateDropdowns(templates);
// Re-render web services to update their template dropdowns
if (webServices && webServices.length > 0) {
renderWebServices(webServices);
}
}
function updateTemplateDropdowns(templates) {
const httpSelect = document.getElementById('http-template');
const httpsSelect = document.getElementById('https-template');
// Get current values from data attributes or current selection
const currentHttpTemplate = httpSelect.dataset.currentValue || httpSelect.value;
const currentHttpsTemplate = httpsSelect.dataset.currentValue || httpsSelect.value;
// Clear and repopulate options
[httpSelect, httpsSelect].forEach(select => {
// Keep the "None" option
select.innerHTML = '<option value="">None (Default Welcome)</option>';
templates.forEach(template => {
const option = document.createElement('option');
option.value = template;
option.textContent = template;
select.appendChild(option);
});
});
// Set current values
httpSelect.value = currentHttpTemplate;
httpsSelect.value = currentHttpsTemplate;
// Clear data attributes after first use
delete httpSelect.dataset.currentValue;
delete httpsSelect.dataset.currentValue;
}
let webServices = [];
function addWebService() {
const newService = {
enabled: false,
port: 9000 + webServices.length,
name: `service-${webServices.length + 1}`,
path: '/login',
template_name: '',
use_https: false
};
webServices.push(newService);
renderWebServices(webServices);
}
async function updateWebService(index, field, value) {
if (webServices[index]) {
webServices[index][field] = value;
// Auto-save web services when changed
await saveWebServices();
}
}
async function saveWebServices() {
try {
console.log('Saving web services:', webServices);
const headers = { 'Content-Type': 'application/json' };
if (currentCSRFToken) {
headers['X-CSRF-Token'] = currentCSRFToken;
}
const payload = { services: webServices };
console.log('Payload being sent:', payload);
const response = await fetch('/api/webservices', {
method: 'POST',
headers: headers,
body: JSON.stringify(payload)
});
const result = await response.json();
console.log('Server response:', result);
if (response.ok) {
// Update CSRF token if provided
if (result.csrf_token) {
currentCSRFToken = result.csrf_token;
}
console.log('Web services saved successfully');
// Restart app so changes (like template swap) take effect on running web services
try {
const hdrs = {};
if (currentCSRFToken) hdrs['X-CSRF-Token'] = currentCSRFToken;
const rres = await fetch('/api/restart', { method: 'POST', headers: hdrs });
if (rres.ok) {
console.log('Restart triggered');
} else {
console.warn('Restart request failed');
}
} catch (e) {
console.warn('Error triggering restart', e);
}
} else {
console.error('Failed to save web services:', result.error);
}
} catch (error) {
console.error('Error saving web services:', error);
}
}
async function removeWebService(index) {
webServices.splice(index, 1);
console.log('After removal, webServices:', webServices);
// Handle empty services array
if (webServices.length === 0) {
document.getElementById('webservices-list').innerHTML = '<p class="text-gray-400 text-center py-4">No web services configured</p>';
} else {
renderWebServices(webServices);
}
// Auto-save after removal
await saveWebServices();
}
function openTemplateManager() {
// Open template manager modal (we'll embed the template manager here)
showTemplateManagerModal();
}
function editTemplate(templateName) {
showTemplateManagerModal(templateName);
}
function showTemplateManagerModal(templateName = null) {
// Create and show the template manager modal
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4';
modal.innerHTML = `
<div class="bg-gray-800 rounded-lg border border-gray-700 w-full max-w-6xl h-[90vh] flex flex-col">
<div class="p-4 border-b border-gray-700 flex justify-between items-center shrink-0">
<h3 class="text-lg font-semibold text-gray-100">Template Manager</h3>
<button onclick="this.closest('.fixed').remove()" 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 class="flex-1 p-4 overflow-hidden">
<iframe src="/webtemplates" class="w-full h-full min-h-0 border-0 rounded"></iframe>
</div>
</div>
`;
document.body.appendChild(modal);
}
</script>
{{ end }}