import { h, html, PageHeader, Badge, Empty, Table, renderGuard, renderGuardMulti, ConfirmDelete, esc, enc, $val, apiFetch, toast, definePage, getModel, modelFetch, ActionButton, ActionGroup } from '/static/hoover/index.js'; import { openModal, closeModal, isModalProcessing, setModalProcessing, refreshModals } from '/static/hoover/components/modal.js'; import { _deleting } from '/static/hoover/components/data.js'; // --------------------------------------------------------------------------- // Build a datalist element from DHCP leases // --------------------------------------------------------------------------- function _buildHostDatalist(leases, uniqueId) { const datalist = document.createElement('datalist'); datalist.id = 'hosts-' + uniqueId; for (const lease of leases) { const option = document.createElement('option'); option.value = lease.ip; if (lease.hostname) { option.setAttribute('label', lease.hostname); const displayIp = esc(lease.ip); const displayHost = esc(lease.hostname); option.appendChild(document.createTextNode(displayIp + ' (' + displayHost + ')')); } else { option.appendChild(document.createTextNode(esc(lease.ip))); } datalist.appendChild(option); } return datalist; } // --------------------------------------------------------------------------- // Inject discovered hosts hint section into modal // --------------------------------------------------------------------------- function _injectDiscoveredHosts(modalContent, leases, datalistId) { const hostsHint = document.createElement('div'); hostsHint.className = 'form-group'; const details = document.createElement('details'); const summary = document.createElement('summary'); summary.textContent = leases.length + ' host' + (leases.length !== 1 ? 's' : '') + ' discovered'; details.appendChild(summary); for (const lease of leases) { const item = document.createElement('div'); item.style.cssText = 'cursor:pointer;padding:2px 4px;margin:2px 0;border-radius:4px;font-size:0.875rem;'; const hostname = lease.hostname ? lease.hostname + ' → ' : ''; item.textContent = hostname + lease.ip; item.style.color = '#0d6efd'; item.addEventListener('click', () => { const hostInput = document.querySelector('#' + datalistId + '-host-input'); if (hostInput) { hostInput.value = lease.ip; } details.removeAttribute('open'); }); item.addEventListener('mouseenter', () => { item.style.background = '#e9ecef'; }); item.addEventListener('mouseleave', () => { item.style.background = ''; }); details.appendChild(item); } hostsHint.appendChild(details); const pathsGroup = modalContent.querySelector('#paths-' + datalistId.split('-')[1])?.parentElement; if (pathsGroup) { pathsGroup.parentElement.insertBefore(hostsHint, pathsGroup); } } // --------------------------------------------------------------------------- // Add a path row element to the paths container // --------------------------------------------------------------------------- function _addPathRow(container, data, datalistId, isFirst) { const row = document.createElement('div'); row.className = 'path-row-row'; const hostListAttr = datalistId ? ' list="' + datalistId + '"' : ''; const hostInputId = isFirst ? ' id="' + datalistId + '-host-input"' : ''; row.innerHTML = ` `; row.querySelector('.path-remove').addEventListener('click', () => { if (container.querySelectorAll('.path-row-row').length > 1) { row.remove(); } else { toast('At least one path required', 'warning'); } }); if (datalistId) { const picker = row.querySelector('.host-picker'); const hostInput = row.querySelector('input[placeholder="Host"]'); picker.addEventListener('click', () => { if (hostInput.showPicker) { hostInput.showPicker(); } else { hostInput.focus(); } }); } container.appendChild(row); return row; } // --------------------------------------------------------------------------- // Collect paths from the paths container // --------------------------------------------------------------------------- function _collectPaths(container) { const result = {}; const seen = new Set(); container.querySelectorAll('.path-row-row').forEach(row => { const pathField = row.querySelector('input[placeholder="Path"]'); const hostField = row.querySelector('input[placeholder="Host"]'); const portField = row.querySelector('input[placeholder="Port"]'); const protoSelect = row.querySelector('select'); const path = (pathField?.value || '').trim() || '/'; const host = (hostField?.value || '').trim(); const port = parseInt(portField?.value || ''); const proto = protoSelect?.value || 'http'; if (!host || !port) return; if (seen.has(path)) { toast('Duplicate path ' + path, 'warning'); return; } seen.add(path); const entry = { backend: { host, port, proto } }; if (row.querySelector('.path-ws').checked) entry.is_websocket = true; if (row.querySelector('.path-mgmt').checked) entry.is_management = true; result[path] = entry; }); return result; } // --------------------------------------------------------------------------- // Open backend form modal // --------------------------------------------------------------------------- export function openBackendModal(state, backend) { const isEdit = !!backend; const title = isEdit ? ('Edit Backend: ' + esc(backend.name)) : 'Add Backend'; // Extract DHCP leases const leases = state.dnsmasq?.data?.leases || []; openModal((modalContent) => { const uniqueId = Date.now(); const datalistId = 'hosts-' + uniqueId; const pathsId = 'paths-' + uniqueId; // Build form HTML const authVal = isEdit && backend.data.has_auth ? 'htpasswd' : 'none'; modalContent.innerHTML = `