// modals.js import { setInconsistencyData, inconsistencyData } from './global.js'; import { loadAllData } from './data_loader.js'; // Will be imported later import { configStore, API_BASE_URL } from './global.js'; // ========================================================================= // MAIN CONFIGURATION MODAL HANDLERS // ========================================================================= /** * Switches between the JSON and YAML tabs in a given modal content area. * This function MUST be exported as it's used directly in HTML/inline handlers. * @param {HTMLElement} modalContent - The parent container (modal-content) for tabs. * @param {string} tabName - 'json' or 'yaml'. */ export function switchTab(modalContent, tabName) { // Deactivate all buttons and hide all content modalContent.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active')); modalContent.querySelectorAll('.code-block').forEach(content => content.style.display = 'none'); // Activate the selected button and show the corresponding content const activeBtn = modalContent.querySelector(`.tab-button[data-tab="${tabName}"]`); const activeContent = document.getElementById(`modal-${tabName}-content`); if (activeBtn) activeBtn.classList.add('active'); if (activeContent) activeContent.style.display = 'block'; } /** * Displays the main configuration modal. * @param {string} title - The modal title. * @param {object} jsonData - The configuration data object (for JSON tab). * @param {string} yamlData - The configuration data as a YAML string. * @param {string} [defaultTab='yaml'] - The tab to show by default. */ export function showModal(title, jsonData, yamlData, defaultTab = 'yaml') { document.getElementById('modal-title').textContent = title; // Populate JSON content document.getElementById('modal-json-content').textContent = JSON.stringify(jsonData, null, 2); // Populate YAML content document.getElementById('modal-yaml-content').textContent = yamlData; // Default to the specified tab const modalContent = document.getElementById('configModal')?.querySelector('.modal-content'); if (modalContent) { switchTab(modalContent, defaultTab); } document.getElementById('configModal').style.display = 'block'; } export function hideModal() { document.getElementById('configModal').style.display = 'none'; } /** * Sets up click handlers for the tab buttons in the main modal. */ export function setupModalTabs() { const modalContent = document.getElementById('configModal')?.querySelector('.modal-content'); if (!modalContent) return; modalContent.querySelectorAll('.tab-button').forEach(button => { button.addEventListener('click', (event) => { const tabName = event.target.getAttribute('data-tab'); switchTab(modalContent, tabName); }); }); } // ========================================================================= // ADD FILTER CHAIN MODAL HANDLERS // ========================================================================= /** * Shows the modal for adding a new filter chain to a listener. * @param {string} listenerName - The name of the listener to modify. */ export function showAddFilterChainModal(listenerName) { // 1. Set the listener name in the hidden input for form submission document.getElementById('add-fc-listener-name').value = listenerName; // 2. Set the title document.getElementById('add-fc-modal-title').textContent = `Add New Filter Chain to: ${listenerName}`; // 3. Clear any previous YAML content const yamlInput = document.getElementById('add-fc-yaml-input'); yamlInput.value = ''; // 4. Show the modal document.getElementById('addFilterChainModal').style.display = 'block'; // 5. Provide a template to guide the user (optional) yamlInput.placeholder = `# Paste your new Filter Chain YAML here. # NOTE: The root key should be the filter chain object itself. filter_chain_match: server_names: ["new.example.com"] ...`; } /** * Closes the Add Filter Chain modal. */ export function hideAddFilterChainModal() { document.getElementById('addFilterChainModal').style.display = 'none'; } // ========================================================================= // CONSISTENCY MODAL HANDLERS // ========================================================================= export function showConsistencyModal() { if (!inconsistencyData || inconsistencyData.inconsistent === false) return; // Populate modal content const cacheOnly = inconsistencyData['cache-only'] || {}; const dbOnly = inconsistencyData['db-only'] || {}; document.getElementById('cache-only-count').textContent = Object.keys(cacheOnly).length; document.getElementById('cache-only-data').textContent = JSON.stringify(cacheOnly, null, 2); document.getElementById('db-only-count').textContent = Object.keys(dbOnly).length; document.getElementById('db-only-data').textContent = JSON.stringify(dbOnly, null, 2); document.getElementById('consistencyModal').style.display = 'block'; } export function hideConsistencyModal() { document.getElementById('consistencyModal').style.display = 'none'; } // ========================================================================= // WINDOW EVENT LISTENERS // ========================================================================= window.addEventListener('keydown', (event) => { // Check for Escape key to close all modals if (event.key === 'Escape') { hideModal(); hideAddFilterChainModal(); hideConsistencyModal(); } }); // Close modal when clicking outside of the content (on the backdrop) window.addEventListener('click', (event) => { const modal = document.getElementById('configModal'); const addModal = document.getElementById('addFilterChainModal'); const consistencyModal = document.getElementById('consistencyModal'); if (event.target === modal) { hideModal(); } if (event.target === addModal) { hideAddFilterChainModal(); } if (event.target === consistencyModal) { hideConsistencyModal(); } }); // ========================================================================= // UTILITY HANDLERS (Download) // ========================================================================= export function downloadYaml() { const yamlContent = document.getElementById('modal-yaml-content').textContent; if (!yamlContent || yamlContent.trim() === '') { alert("No YAML content available to download."); return; } // Use modal title as filename fallback const title = document.getElementById('modal-title').textContent .replace(/\s+/g, '_') .replace(/[^\w\-]/g, ''); const blob = new Blob([yamlContent], { type: 'text/yaml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = title ? `${title}.yaml` : 'config.yaml'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } export async function showListenerConfigModal(listenerName) { const config = configStore.listeners[listenerName]; if (!config) { showModal(`🚨 Error: Listener Not Found`, { name: listenerName, error: 'Configuration data missing from memory.' }, 'Error: Listener not in memory.'); return; } let yamlData = configStore.listeners[listenerName]?.yaml || 'Loading YAML...'; if (yamlData === 'Loading YAML...') { try { const response = await fetch(`${API_BASE_URL}/get-listener?name=${listenerName}&format=yaml`); if (!response.ok) { yamlData = `Error fetching YAML: ${response.status} ${response.statusText}`; } else { yamlData = await response.text(); configStore.listeners[listenerName].yaml = yamlData; // Store YAML } } catch (error) { console.error("Failed to fetch YAML listener config:", error); yamlData = `Network Error fetching YAML: ${error.message}`; } } showModal(`Full Config for Listener: ${listenerName}`, config, yamlData); }