// Server management functions for Fail2ban UI "use strict"; function loadServers() { return fetch('/api/servers') .then(function(res) { return res.json(); }) .then(function(data) { serversCache = data.servers || []; var enabledServers = serversCache.filter(function(s) { return s.enabled; }); if (!enabledServers.length) { currentServerId = null; currentServer = null; } else { var desired = currentServerId; var selected = desired ? enabledServers.find(function(s) { return s.id === desired; }) : null; if (!selected) { var def = enabledServers.find(function(s) { return s.isDefault; }); selected = def || enabledServers[0]; } currentServer = selected; currentServerId = selected ? selected.id : null; } renderServerSelector(); renderServerSubtitle(); updateRestartBanner(); }) .catch(function(err) { console.error('Error loading servers:', err); serversCache = []; currentServerId = null; currentServer = null; renderServerSelector(); renderServerSubtitle(); updateRestartBanner(); }); } function renderServerSelector() { var container = document.getElementById('serverSelectorContainer'); if (!container) return; var enabledServers = serversCache.filter(function(s) { return s.enabled; }); if (!serversCache.length) { container.innerHTML = '
No servers configured
'; if (typeof updateTranslations === 'function') { updateTranslations(); } return; } if (!enabledServers.length) { container.innerHTML = '
No servers configured
'; if (typeof updateTranslations === 'function') { updateTranslations(); } return; } var options = enabledServers.map(function(server) { var label = escapeHtml(server.name || server.id); var type = server.type ? (' (' + server.type.toUpperCase() + ')') : ''; return ''; }).join(''); container.innerHTML = '' + '
' + ' ' + ' ' + '
'; var select = document.getElementById('serverSelect'); if (select) { select.value = currentServerId || ''; select.addEventListener('change', function(e) { setCurrentServer(e.target.value); }); } if (typeof updateTranslations === 'function') { updateTranslations(); } } function renderServerSubtitle() { var subtitle = document.getElementById('currentServerSubtitle'); if (!subtitle) return; if (!currentServer) { subtitle.textContent = t('servers.selector.none', 'No server configured. Please add a Fail2ban server.'); subtitle.classList.add('text-red-500'); return; } subtitle.classList.remove('text-red-500'); var parts = []; parts.push(currentServer.name || currentServer.id); parts.push(currentServer.type ? currentServer.type.toUpperCase() : 'LOCAL'); if (currentServer.host) { var host = currentServer.host; if (currentServer.port) { host += ':' + currentServer.port; } parts.push(host); } else if (currentServer.hostname) { parts.push(currentServer.hostname); } subtitle.textContent = parts.join(' • '); } function setCurrentServer(serverId) { if (!serverId) { currentServerId = null; currentServer = null; } else { var next = serversCache.find(function(s) { return s.id === serverId && s.enabled; }); currentServer = next || null; currentServerId = currentServer ? currentServer.id : null; } renderServerSelector(); renderServerSubtitle(); updateRestartBanner(); refreshData(); } function openServerManager(serverId) { showLoading(true); loadServers() .then(function() { if (serverId) { editServer(serverId); } else { resetServerForm(); } renderServerManagerList(); openModal('serverManagerModal'); }) .finally(function() { showLoading(false); }); } function renderServerManagerList() { var list = document.getElementById('serverManagerList'); var emptyState = document.getElementById('serverManagerListEmpty'); if (!list || !emptyState) return; if (!serversCache.length) { list.innerHTML = ''; emptyState.classList.remove('hidden'); if (typeof updateTranslations === 'function') updateTranslations(); return; } emptyState.classList.add('hidden'); var html = serversCache.map(function(server) { var statusBadge = server.enabled ? 'Enabled' : 'Disabled'; var defaultBadge = server.isDefault ? 'Default' : ''; var descriptor = []; if (server.type) { descriptor.push(server.type.toUpperCase()); } if (server.host) { var endpoint = server.host; if (server.port) { endpoint += ':' + server.port; } descriptor.push(endpoint); } else if (server.hostname) { descriptor.push(server.hostname); } var meta = descriptor.join(' • '); var tags = (server.tags || []).length ? '
' + escapeHtml(server.tags.join(', ')) + '
' : ''; return '' + '
' + '
' + '
' + '

' + escapeHtml(server.name || server.id) + defaultBadge + statusBadge + '

' + '

' + escapeHtml(meta || server.id) + '

' + tags + '
' + '
' + ' ' + (server.isDefault ? '' : '') + ' ' + (server.enabled ? '' : '') + ' ' + ' ' + '
' + '
' + '
'; }).join(''); list.innerHTML = html; if (typeof updateTranslations === 'function') updateTranslations(); } function resetServerForm() { document.getElementById('serverId').value = ''; document.getElementById('serverName').value = ''; document.getElementById('serverType').value = 'local'; document.getElementById('serverHost').value = ''; document.getElementById('serverPort').value = ''; document.getElementById('serverSocket').value = '/var/run/fail2ban/fail2ban.sock'; document.getElementById('serverLogPath').value = '/var/log/fail2ban.log'; document.getElementById('serverHostname').value = ''; document.getElementById('serverSSHUser').value = ''; document.getElementById('serverSSHKey').value = ''; document.getElementById('serverAgentUrl').value = ''; document.getElementById('serverAgentSecret').value = ''; document.getElementById('serverTags').value = ''; document.getElementById('serverDefault').checked = false; document.getElementById('serverEnabled').checked = false; populateSSHKeySelect(sshKeysCache || [], ''); onServerTypeChange('local'); } function editServer(serverId) { var server = serversCache.find(function(s) { return s.id === serverId; }); if (!server) return; document.getElementById('serverId').value = server.id || ''; document.getElementById('serverName').value = server.name || ''; document.getElementById('serverType').value = server.type || 'local'; document.getElementById('serverHost').value = server.host || ''; document.getElementById('serverPort').value = server.port || ''; document.getElementById('serverSocket').value = server.socketPath || '/var/run/fail2ban/fail2ban.sock'; document.getElementById('serverLogPath').value = server.logPath || '/var/log/fail2ban.log'; document.getElementById('serverHostname').value = server.hostname || ''; document.getElementById('serverSSHUser').value = server.sshUser || ''; document.getElementById('serverSSHKey').value = server.sshKeyPath || ''; document.getElementById('serverAgentUrl').value = server.agentUrl || ''; document.getElementById('serverAgentSecret').value = server.agentSecret || ''; document.getElementById('serverTags').value = (server.tags || []).join(','); document.getElementById('serverDefault').checked = !!server.isDefault; document.getElementById('serverEnabled').checked = !!server.enabled; onServerTypeChange(server.type || 'local'); if ((server.type || 'local') === 'ssh') { loadSSHKeys().then(function(keys) { populateSSHKeySelect(keys, server.sshKeyPath || ''); }); } } function onServerTypeChange(type) { document.querySelectorAll('[data-server-fields]').forEach(function(el) { var values = (el.getAttribute('data-server-fields') || '').split(/\s+/); if (values.indexOf(type) !== -1) { el.classList.remove('hidden'); } else { el.classList.add('hidden'); } }); var enabledToggle = document.getElementById('serverEnabled'); if (!enabledToggle) return; var isEditing = !!document.getElementById('serverId').value; if (isEditing) { return; } if (type === 'local') { enabledToggle.checked = false; } else { enabledToggle.checked = true; } if (type === 'ssh') { loadSSHKeys().then(function(keys) { if (!isEditing) { populateSSHKeySelect(keys, ''); } }); } else { populateSSHKeySelect([], ''); } } function submitServerForm(event) { event.preventDefault(); showLoading(true); var payload = { id: document.getElementById('serverId').value || undefined, name: document.getElementById('serverName').value.trim(), type: document.getElementById('serverType').value, host: document.getElementById('serverHost').value.trim(), port: document.getElementById('serverPort').value ? parseInt(document.getElementById('serverPort').value, 10) : undefined, socketPath: document.getElementById('serverSocket').value.trim(), logPath: document.getElementById('serverLogPath').value.trim(), hostname: document.getElementById('serverHostname').value.trim(), sshUser: document.getElementById('serverSSHUser').value.trim(), sshKeyPath: document.getElementById('serverSSHKey').value.trim(), agentUrl: document.getElementById('serverAgentUrl').value.trim(), agentSecret: document.getElementById('serverAgentSecret').value.trim(), tags: document.getElementById('serverTags').value ? document.getElementById('serverTags').value.split(',').map(function(tag) { return tag.trim(); }).filter(Boolean) : [], enabled: document.getElementById('serverEnabled').checked }; if (!payload.socketPath) delete payload.socketPath; if (!payload.logPath) delete payload.logPath; if (!payload.hostname) delete payload.hostname; if (!payload.agentUrl) delete payload.agentUrl; if (!payload.agentSecret) delete payload.agentSecret; if (!payload.sshUser) delete payload.sshUser; if (!payload.sshKeyPath) delete payload.sshKeyPath; if (document.getElementById('serverDefault').checked) { payload.isDefault = true; } if (payload.type !== 'local' && payload.type !== 'ssh') { delete payload.socketPath; } if (payload.type !== 'local') { delete payload.logPath; } if (payload.type !== 'ssh') { delete payload.sshUser; delete payload.sshKeyPath; } if (payload.type !== 'agent') { delete payload.agentUrl; delete payload.agentSecret; } fetch('/api/servers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(function(res) { return res.json(); }) .then(function(data) { if (data.error) { showToast('Error saving server: ' + (data.error || 'Unknown error'), 'error'); return; } showToast(t('servers.form.success', 'Server saved successfully.'), 'success'); var saved = data.server || {}; currentServerId = saved.id || currentServerId; return loadServers().then(function() { renderServerManagerList(); renderServerSelector(); renderServerSubtitle(); if (currentServerId) { currentServer = serversCache.find(function(s) { return s.id === currentServerId; }) || currentServer; } return refreshData({ silent: true }); }); }) .catch(function(err) { showToast('Error saving server: ' + err, 'error'); }) .finally(function() { showLoading(false); }); } function populateSSHKeySelect(keys, selected) { var select = document.getElementById('serverSSHKeySelect'); if (!select) return; var options = ''; var selectedInList = false; if (keys && keys.length) { keys.forEach(function(key) { var safe = escapeHtml(key); if (selected && key === selected) { selectedInList = true; } options += ''; }); } else { options += ''; } if (selected && !selectedInList) { var safeSelected = escapeHtml(selected); options += ''; } select.innerHTML = options; if (selected) { select.value = selected; } else { select.value = ''; } if (typeof updateTranslations === 'function') { updateTranslations(); } } function loadSSHKeys() { if (sshKeysCache !== null) { populateSSHKeySelect(sshKeysCache, document.getElementById('serverSSHKey').value); return Promise.resolve(sshKeysCache); } return fetch('/api/ssh/keys') .then(function(res) { return res.json(); }) .then(function(data) { sshKeysCache = data.keys || []; populateSSHKeySelect(sshKeysCache, document.getElementById('serverSSHKey').value); return sshKeysCache; }) .catch(function(err) { console.error('Error loading SSH keys:', err); sshKeysCache = []; populateSSHKeySelect(sshKeysCache, document.getElementById('serverSSHKey').value); return sshKeysCache; }); } function setServerEnabled(serverId, enabled) { var server = serversCache.find(function(s) { return s.id === serverId; }); if (!server) { return; } var payload = Object.assign({}, server, { enabled: enabled }); showLoading(true); fetch('/api/servers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(function(res) { return res.json(); }) .then(function(data) { if (data.error) { showToast('Error saving server: ' + (data.error || 'Unknown error'), 'error'); return; } if (!enabled && currentServerId === serverId) { currentServerId = null; currentServer = null; } return loadServers().then(function() { renderServerManagerList(); renderServerSelector(); renderServerSubtitle(); return refreshData({ silent: true }); }); }) .catch(function(err) { showToast('Error saving server: ' + err, 'error'); }) .finally(function() { showLoading(false); }); } function testServerConnection(serverId) { if (!serverId) return; showLoading(true); fetch('/api/servers/' + encodeURIComponent(serverId) + '/test', { method: 'POST' }) .then(function(res) { return res.json(); }) .then(function(data) { if (data.error) { showToast(t(data.messageKey || 'servers.actions.test_failure', data.error), 'error'); return; } showToast(t(data.messageKey || 'servers.actions.test_success', data.message || 'Connection successful'), 'success'); }) .catch(function(err) { showToast(t('servers.actions.test_failure', 'Connection failed') + ': ' + err, 'error'); }) .finally(function() { showLoading(false); }); } function deleteServer(serverId) { if (!confirm(t('servers.actions.delete_confirm', 'Delete this server entry?'))) return; showLoading(true); fetch('/api/servers/' + encodeURIComponent(serverId), { method: 'DELETE' }) .then(function(res) { return res.json(); }) .then(function(data) { if (data.error) { showToast('Error deleting server: ' + (data.error || 'Unknown error'), 'error'); return; } if (currentServerId === serverId) { currentServerId = null; currentServer = null; } return loadServers().then(function() { renderServerManagerList(); renderServerSelector(); renderServerSubtitle(); return refreshData({ silent: true }); }).then(function() { showToast(t('servers.actions.delete_success', 'Server removed'), 'success'); }); }) .catch(function(err) { showToast('Error deleting server: ' + err, 'error'); }) .finally(function() { showLoading(false); }); } function makeDefaultServer(serverId) { showLoading(true); fetch('/api/servers/' + encodeURIComponent(serverId) + '/default', { method: 'POST' }) .then(function(res) { return res.json(); }) .then(function(data) { if (data.error) { showToast('Error setting default server: ' + (data.error || 'Unknown error'), 'error'); return; } currentServerId = data.server ? data.server.id : serverId; return loadServers().then(function() { renderServerManagerList(); renderServerSelector(); renderServerSubtitle(); return refreshData({ silent: true }); }).then(function() { showToast(t('servers.actions.set_default_success', 'Server set as default'), 'success'); }); }) .catch(function(err) { showToast('Error setting default server: ' + err, 'error'); }) .finally(function() { showLoading(false); }); } function restartFail2banServer(serverId) { if (!serverId) { showToast("No server selected", 'error'); return; } if (!confirm("Keep in mind that while fail2ban is restarting, logs are not being parsed and no IP addresses are blocked. Restart fail2ban on this server now? This will take some time.")) return; showLoading(true); fetch('/api/fail2ban/restart?serverId=' + encodeURIComponent(serverId), { method: 'POST', headers: { 'Content-Type': 'application/json' } }) .then(function(res) { return res.json(); }) .then(function(data) { if (data.error) { showToast("Failed to restart Fail2ban: " + data.error, 'error'); return; } return loadServers().then(function() { updateRestartBanner(); showToast(t('restart_banner.success', 'Fail2ban restart triggered'), 'success'); return refreshData({ silent: true }); }); }) .catch(function(err) { showToast("Failed to restart Fail2ban: " + err, 'error'); }) .finally(function() { showLoading(false); }); } function restartFail2ban() { if (!confirm("Keep in mind that while fail2ban is restarting, logs are not being parsed and no IP addresses are blocked. Restart fail2ban now? This will take some time.")) return; restartFail2banServer(currentServerId); }