535 lines
24 KiB
HTML
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 }}
|