// 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'; // ========================================================================= // GENERIC MODAL HANDLERS // ========================================================================= /** * Shows any modal element by its ID. (NEW GENERIC FUNCTION) * @param {string} modalId - The ID of the modal element to display. */ export function showModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.style.display = 'block'; } } /** * Hides any modal element by its ID. (NEW GENERIC FUNCTION) * @param {string} modalId - The ID of the modal element to hide. Defaults to 'configModal'. */ export function hideModal(modalId = 'configModal') { const modal = document.getElementById(modalId); if (modal) { modal.style.display = 'none'; } } // ========================================================================= // CONFIGURATION DISPLAY MODAL HANDLERS (JSON/YAML tabs) // ========================================================================= /** * Switches between the JSON and YAML tabs in the main config modal. * 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 (with JSON/YAML tabs). (RENAMED FROM showModal) * @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 showConfigModal(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); } // Use generic showModal showModal('configModal'); } /** * Hides the main configuration modal (configModal). (Uses generic hideModal) */ export function hideConfigModal() { hideModal('configModal'); } /** * 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 (using generic showModal) showModal('addFilterChainModal'); // 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. (Uses generic hideModal) */ export function hideAddFilterChainModal() { hideModal('addFilterChainModal'); } // ========================================================================= // 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); // Use generic showModal showModal('consistencyModal'); } export function hideConsistencyModal() { hideModal('consistencyModal'); } // ========================================================================= // ADD CLUSTER MODAL HANDLERS // ========================================================================= // /** // * Shows the modal for adding a new cluster. // */ // export function showAddClusterModal() { // showModal('addClusterModal'); // } // /** // * Hides the Add Cluster modal. // */ // function hideAddClusterModal() { // hideModal('addClusterModal'); // } // ========================================================================= // WINDOW EVENT LISTENERS // ========================================================================= window.addEventListener('keydown', (event) => { // Check for Escape key to close all modals if (event.key === 'Escape') { hideConfigModal(); hideAddFilterChainModal(); hideConsistencyModal(); hideAddClusterModal(); // Assume hideAddListenerModal is also attached to window/global scope if not in modals.js // If it is in listeners.js and attached to window: window.hideAddListenerModal(); } }); // Close modal when clicking outside of the content (on the backdrop) window.addEventListener('click', (event) => { const modal = document.getElementById('configModal'); const addFCModal = document.getElementById('addFilterChainModal'); const consistencyModal = document.getElementById('consistencyModal'); const addListenerModal = document.getElementById('addListenerModal'); // NEW const addClusterModal = document.getElementById('addClusterModal'); if (event.target === modal) { hideConfigModal(); } if (event.target === addFCModal) { hideAddFilterChainModal(); } if (event.target === consistencyModal) { hideConsistencyModal(); } // Added for the new listener modal if (event.target === addListenerModal) { // If hideAddListenerModal is in listeners.js, you must call it from window // window.hideAddListenerModal(); // OR, if you decide to move it here: hideModal('addListenerModal'); } if (event.target === addClusterModal) { hideAddClusterModal(); } }); // ========================================================================= // 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); } /** * Fetches and displays the full configuration for a listener in the tabbed modal. */ export async function showListenerConfigModal(listenerName) { const config = configStore.listeners[listenerName]; if (!config) { showConfigModal(`🚨 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}`; } } // Use the renamed function showConfigModal(`Full Config for Listener: ${listenerName}`, config, yamlData); } /** * Displays a DEDICATED modal for showing secret or privileged configurations. * It expects the modal to have the ID 'secretConfigModal' and content IDs * 'secret-modal-title', 'secret-modal-json-content', 'secret-modal-yaml-content'. * * @param {string} title - The modal title. * @param {string} secretName - The name of the secret to fetch the YAML for. * @param {object} jsonData - The configuration data object (used for JSON tab). * @param {string} defaultTab - The tab to show by default ('json' or 'yaml'). */ export async function showSecretConfigModal(secretName) { const config = configStore.secrets[secretName]; if (!config) { showConfigModal(`🚨 Error: Secret Not Found`, { name: secretName, error: 'Configuration data missing from memory.' }, 'Error: Secret not in memory.'); return; } let yamlData = configStore.secrets[secretName]?.yaml || 'Loading YAML...'; if (yamlData === 'Loading YAML...') { try { const response = await fetch(`${API_BASE_URL}/get-secret?name=${secretName}&format=yaml`); if (!response.ok) { yamlData = `Error fetching YAML: ${response.status} ${response.statusText}`; } else { yamlData = await response.text(); configStore.secrets[secretName].yaml = yamlData; // Store YAML } } catch (error) { console.error("Failed to fetch YAML listener config:", error); yamlData = `Network Error fetching YAML: ${error.message}`; } } // Use the renamed function showConfigModal(`Full Config for Secret: ${secretName}`, config, yamlData); } export async function showClusterConfigModal(clusterName) { const config = configStore.clusters[clusterName]; if (!config) { showConfigModal(`🚨 Error: Cluster Not Found`, { name: clusterName, error: 'Configuration data missing from memory.' }, 'Error: Cluster not in memory.'); return; } let yamlData = configStore.clusters[clusterName]?.yaml || 'Loading YAML...'; if (yamlData === 'Loading YAML...') { try { const response = await fetch(`${API_BASE_URL}/get-cluster?name=${clusterName}&format=yaml`); if (!response.ok) { yamlData = `Error fetching YAML: ${response.status} ${response.statusText}`; } else { yamlData = await response.text(); configStore.clusters[clusterName].yaml = yamlData; // Store YAML } } catch (error) { console.error("Failed to fetch YAML cluster config:", error); yamlData = `Network Error fetching YAML: ${error.message}`; } } showConfigModal(`Full Config for Cluster: ${clusterName}`, config, yamlData); }