// listeners.js import { API_BASE_URL, configStore } from './global.js'; import { showListenerConfigModal, showAddFilterChainModal, showModal } from './modals.js'; import { showDomainConfig } from './data_fetchers.js'; // ========================================================================= // LISTENER UTILITIES // ========================================================================= function getDomainRouteTable(filterChains, listenerName) { if (!filterChains || filterChains.length === 0) return 'N/A'; // Store filter chains in memory for robust retrieval in showDomainConfig configStore.listeners[listenerName].filterChains = filterChains; const domainConfigs = filterChains.map((chain, index) => { const domains = chain.filter_chain_match?.server_names || ["(default)"]; const filters = chain.filters?.map(f => f.name.replace(/^envoy\.filters\./, '')) || []; const routeType = filters.some(f => f.includes('http_connection_manager')) ? 'HTTP' : 'TCP'; const primaryDomainName = domains[0]; const allDomainsTitle = domains.join(', '); const modalTitle = `Filter Chain for Domains: ${allDomainsTitle} (${listenerName})`; // Use 'window.showDomainConfig' for inline HTML handler return ` <div class="domain-config-item"> <div class="domain-header"> <div class="domain-name-link" data-title="${modalTitle}" data-listener-name="${listenerName}" data-chain-index="${index}" data-domain-name="${primaryDomainName}" onclick="window.showDomainConfig(this)"> ${allDomainsTitle} </div> <div class="filter-list"> ${filters.map(f => `<span class="filter-badge">${f}</span>`).join('')} </div> </div> <div class="route-type-display">${routeType} Route</div> </div> `; }); return `<div class="domain-config-list">${domainConfigs.join('')}</div>`; } function getListenerRowData(listener) { const socketAddr = listener.address?.Address?.SocketAddress; const address = socketAddr?.address || 'N/A'; const port = socketAddr?.PortSpecifier?.PortValue || 'N/A'; const addressString = `${address}:${port}`; const isTlsInspector = listener.listener_filters?.some(f => f.name.includes('tls_inspector')); const isTlsInChain = listener.filter_chains?.some(c => c.transport_socket); const tlsIndicator = (isTlsInspector || isTlsInChain) ? '<span class="tls-badge">TLS</span>' : ''; return { name: listener.name, address: `${addressString} ${tlsIndicator}`, domains: getDomainRouteTable(listener.filter_chains, listener.name), status: listener.status || 'Enabled', rawData: listener }; } // ========================================================================= // LISTENER LISTING // ========================================================================= /** * Fetches and lists all listeners, populating the DOM table. */ export async function listListeners() { const tableBody = document.getElementById('listener-table-body'); if (!tableBody) { console.error("Could not find element with ID 'listener-table-body'."); return; } tableBody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 20px;">Loading...</td></tr>'; try { const response = await fetch(`${API_BASE_URL}/list-listeners`); if (!response.ok) throw new Error(response.statusText); const listenerResponse = await response.json(); const allListeners = [ ...(listenerResponse.enabled || []).map(l => ({ ...l, status: 'Enabled', configData: l })), ...(listenerResponse.disabled || []).map(l => ({ ...l, status: 'Disabled', configData: l })) ]; if (!allListeners.length) { tableBody.innerHTML = '<tr><td colspan="5" style="text-align: center; color: var(--secondary-color);">No listeners found.</td></tr>'; configStore.listeners = {}; return; } // Store full configs in memory by name configStore.listeners = allListeners.reduce((acc, l) => { const existing = acc[l.name] || {}; acc[l.name] = { ...l.configData, yaml: existing.yaml, filterChains: existing.filterChains }; return acc; }, configStore.listeners); tableBody.innerHTML = ''; allListeners.forEach(listener => { const rowData = getListenerRowData(listener); const row = tableBody.insertRow(); if (rowData.status === 'Disabled') row.classList.add('disabled-row'); let actionButtons = ''; // NEW: Add Filter Chain button actionButtons += `<button class="action-button add" onclick="window.showAddFilterChainModal('${listener.name}')">Add Chain</button>`; // Existing logic for disable/enable/remove if (rowData.status === 'Enabled') { actionButtons += `<button class="action-button disable" onclick="window.disableListener('${listener.name}', event)">Disable</button>`; } else { actionButtons += ` <button class="action-button enable" onclick="window.enableListener('${listener.name}', event)">Enable</button> <button class="action-button remove" onclick="window.removeListener('${listener.name}', event)">Remove</button> `; } const listenerNameCell = row.insertCell(); listenerNameCell.innerHTML = `<a href="#" onclick="event.preventDefault(); window.showListenerConfigModal('${listener.name}')"><span class="listener-name">${rowData.name}</span></a>`; row.insertCell().textContent = rowData.status; row.insertCell().innerHTML = rowData.address; row.insertCell().innerHTML = rowData.domains; row.insertCell().innerHTML = actionButtons; }); } catch (error) { tableBody.innerHTML = `<tr><td colspan="5" class="error" style="text-align: center;">🚨 Listener Error: ${error.message}</td></tr>`; console.error("Listener Fetch/Parse Error:", error); } } // ========================================================================= // LISTENER ENABLE/DISABLE/REMOVE LOGIC // ========================================================================= async function toggleListenerStatus(listenerName, action) { let url = ''; if (action === 'remove') { url = `${API_BASE_URL}/remove-listener`; } else { url = `${API_BASE_URL}/${action}-listener`; } const payload = { name: listenerName }; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`HTTP Error ${response.status}: ${errorBody}`); } console.log(`Listener '${listenerName}' successfully ${action}d.`); listListeners(); } catch (error) { console.error(`Failed to ${action} listener '${listenerName}':`, error); alert(`Failed to ${action} listener '${listenerName}'. Check console for details.`); } } // Exported functions must be attached to 'window' if called from inline HTML attributes export function disableListener(listenerName, event) { event.stopPropagation(); if (confirm(`Are you sure you want to DISABLE listener: ${listenerName}?`)) { toggleListenerStatus(listenerName, 'disable'); } } export function enableListener(listenerName, event) { event.stopPropagation(); if (confirm(`Are you sure you want to ENABLE listener: ${listenerName}?`)) { toggleListenerStatus(listenerName, 'enable'); } } export function removeListener(listenerName, event) { event.stopPropagation(); if (confirm(`⚠️ WARNING: Are you absolutely sure you want to PERMANENTLY REMOVE listener: ${listenerName}? This action cannot be undone.`)) { toggleListenerStatus(listenerName, 'remove'); } }